斯泊润瑟柯思

IOC和DI解决什么问题?

OCP 开闭原则

OCP 开闭原则是软件七大开发原则当中最基本的一个原则

OCP 开闭原则:

  • 在软件开发过程中应当对扩展开放,对修改关闭。
  • 在软件开发过程中,通过添加额外的类对原先程序已有的功能进行扩展是没问题的,但是如果因为要进行功能扩展而修改之前运行正常的程序,这是不被允许的。
  • 因为一旦修改之前运行正常的程序,那么项目整体就要进行重新测试,这会导致额外的开销和资源消耗,并且测试过程相当麻烦。

OCP 开闭原则是最核心的、最基本的,其他的六个开发原则都是为这个原则服务的。

是否违反 OCP 开闭原则的判断:

  • 只要你在扩展系统功能的时候,没有修改以前写好的代码,那么你就是符合 OCP 原则的。
  • 反之,如果在扩展系统功能的时候,你修改了之前的代码,那么这个设计是失败的,违背了OCP原则。

DIP依赖倒置原则

依赖倒置原则,就是不再显示的建立类之间的关联关系,而是面向接口编程,面向抽象编程,不要面向具体编程,从而降低程序的耦合度,提高程序的扩展力,增强代码复用性

依赖倒置原则的目的:降低程序的耦合度,提高程序的扩展力,增强代码复用性

是否符合依赖倒置原则:

  • 上 不依赖 下,就是符合,即类的实例对象之间没有明确固定在代码中的依赖关系
  • 上 依赖 下,就是违背。只要“下”一改动,“上”就受到牵连。

符合依赖倒置原则的编程思想:控制反转

Spring

Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。

  • Spring实现了控制反转思想,Spring框架可以帮你维护对象和对象之间的关系
  • Spring实现了面向切面编程
  • 由于我们可以将创建对象交给Spring负责,所以Spring也是一个存放对象的容器

IoC控制反转

IoC是面向对象编程中的一种设计思想,可以用来降低代码之间的耦合度,当程序的设计既违背OCP又违背DIP,可以采用IoC这种编程思想来解决这个问题,符合DIP。

控制反转的核心思想:

  • 将对象的创建权交出去
  • 将对象和对象之间关系的管理权交出去
  • 对象的创建和对象之间关系的维护都由第三方容器来负责

控制反转中的反转是两件事:

  • 第一件事:不在程序中采用硬编码的方式来new对象了,将new对象的权利交出去
  • 第二件事:不在程序中采用硬编码的方式来维护对象的关系了,对象之间关系的维护权交出去了

控制反转思想的实现:依赖注入

DI依赖注入

控制反转的实现方式有多种,其中比较重要的叫做:依赖注入
依赖注入,用于实现对象之间关系的建立
控制反转是思想。依赖注入是这种思想的具体实现。

依赖注入DI,又包括常见的两种方式:

  • 第一种:set注入(执行set方法给属性赋值)
  • 第二种:构造方法注入(执行构造方法给属性赋值)

依赖注入 中 “依赖”是什么意思? “注入”是什么意思?

  • 依赖:A对象和B对象的关系。
  • 注入:是一种手段,通过这种手段,可以让A对象和B对象产生关系。

依赖注入:对象A和对象B之间的关系,靠注入的手段来维护。

名词总结

OCP:开闭原则(开发原则)
DIP:依赖倒置原则(开发原则)
IoC:控制反转(一种思想,一种新型的设计模式)
DI:依赖注入(控制反转思想的具体实现方式)

Spring入门程序

定义普通 Java 类

在开发中,普通Java对象,称为 pojo(Plain Old Java Object),或者称为 java bean(咖啡豆),也叫 domain(领域模型)

public class User {
    private String name;
    private Integer age;

    public User() {
    }

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

编写 Spring 配置文件

Spring 配置文件放在类根路径下,即 resource 目录下

pring的配置文件的命名任意




    
    
    
    

编写测试类

public class Test {
    @org.junit.Test
    public void tset1() {
        // 第一步:获取Spring容器对象。
        // ApplicationContext 翻译为:应用上下文。其实就是Spring容器。
        // ApplicationContext 是一个接口。
        // ApplicationContext 接口下有很多实现类。其中有一个实现类叫做:ClassPathXmlApplicationContext
        // ClassPathXmlApplicationContext 专门用于从类路径当中加载spring配置文件的一个Spring上下文对象。
        // 这行代码只要执行,就相当于启动了Spring容器,同时会解析spring.xml(Spring配置文件名)文件,
        // 并且实例化在Spring配置文件中配置的所有的bean对象,创建完成对象后会将其放到spring容器当中。
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");

        // 第二步:根据bean的id从Spring容器中获取这个对象。(id Bean的唯一标识)
        Object userBean = applicationContext.getBean("user");
        System.out.println(userBean);
    }
}

程序分析

  • 在spring的配置文件中,每个bean的id是不能重名,因为id是每个bean的唯一表示,用于通过容器对象获取相应的bean


    
    

报错

  • spring是通过反射机制调用类的无参数构造方法来创建对象的,所以要想让spring给你创建对象,必须保证无参数构造方法是存在的
public class User {
    private String name;
    private Integer age;

    public User() {
        System.out.println("User 无参构造方法执行...");
    }
    ...
}

  • spring把创建好的对象存储到一个Map集合当中

bean对象最终存储在spring容器中,在spring源码底层就是一个map集合,存储bean的map在DefaultListableBeanFactory类中
Spring容器加载到Bean类时 , 会把这个类的描述信息, 以包名加类名的方式存到beanDefinitionMap 中,
Map , 其中:String是Key , 默认是类名首字母小写,bean的id。BeanDefinition , 存的是类的定义(描述信息) , 我们通常叫BeanDefinition接口为 : bean的定义对象。

public class DefaultListableBeanFactory
    extends AbstractAutowireCapableBeanFactory
    implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
        ...
        private final Map beanDefinitionMap;
        ...
}
  • spring的配置文件可以有多个,在ClassPathXmlApplicationContext构造方法的参数上传递文件路径即可。且配置文件的文件名、文件路径都是任意的
//ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
//ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml", "beans.xml");
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml", "beans.xml", "xml/beans.xml");

// 源码:
public ClassPathXmlApplicationContext(String... configLocations) throws BeansException {
    this(configLocations, true, (ApplicationContext)null);
}
  • 在spring配置文件中配置的bean可以是任意类,只要这个类不是抽象的,并且提供了无参数构造方法。
  • getBean()方法返回的类型是Object,如果访问子类的特有属性和方法时,还需要向下转型,有其它办法可以解决这个问题吗?
//Object nowTime = applicationContext.getBean("nowTime");
//Date nowTime = (Date) applicationContext.getBean("nowTime");
// 不想强制类型转换,可以使用以下代码(通过第二个参数来指定返回的bean的类型。)
Date nowTime = applicationContext.getBean("nowTime", Date.class);
  • ApplicationContext的超级父接口BeanFactory。

    BeanFactory是Spring容器的超级接口。
    ApplicationContext是BeanFactory的子接口。
//ApplicationContext接口的超级父接口是:BeanFactory
//BeanFactory翻译为Bean工厂,就是能够生产Bean对象的一个工厂对象
//BeanFactory是IoC容器的顶级接口。
//Spring的IoC容器底层实际上使用了:工厂模式。
//Spring底层的IoC是怎么实现的?XML解析 + 工厂模式 + 反射机制

//ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring.xml");
User user = applicationContext.getBean("userBean", User.class);
System.out.println(user);
  • Spring底层的IoC容器是通过:XML解析+工厂模式+反射机制实现的。
  • spring不是在调用getBean()方法的时候创建对象,执行new ClassPathXmlApplicationContext("spring.xml");加载配置文件的时候,就会实例化对象。

Spring对IoC的实现

IoC

控制反转是一种思想。
控制反转是为了降低程序耦合度,提高程序扩展力,达到OCP原则,达到DIP原则。
控制反转,反转的是什么?

  • 将对象的创建权利交出去,交给第三方容器负责。
  • 将对象和对象之间关系的维护权交出去,交给第三方容器负责。

控制反转思想的实现:DI(Dependency Injection,依赖注入)

DI

依赖注入实现了控制反转的思想。
Spring通过依赖注入的方式来完成Bean管理的
Bean管理:Bean对象的创建,以及Bean对象中属性的赋值(或者叫做Bean对象之间关系的维护)。
依赖注入:

  • 依赖指的是对象和对象之间的关联关系。
  • 注入指的是一种数据传递行为,通过注入行为来让对象和对象产生关系。

依赖注入常见的实现方式包括两种:

  • 第一种:set注入
  • 第二种:构造注入

set 注入

  • set注入基于set方法实现,底层会通过反射机制调用属性对应的set方法然后给属性赋值。
  • 这种方式要求属性必须对外提供set方法。

set 注入的使用

public class UserDao {
    public void insert(){
        log.info("数据库保存用户信息。");
    }
}
public class VipDao {
    public void insert(){
        log.info("数据库保存vip用户信息。");
    }
}
public class UserService {
    private UserDao userDao;
    private VipDao vipDao;

    public void setAbc(VipDao vipDao){
        this.vipDao = vipDao;
    }

    // set注入,必须提供一个set方法。
    // Spring容器会调用这个set方法,来给userDao属性赋值!!!
    // 我自己写一个set方法,不使用IDEA工具生成的。不符合javabean规范。
    // 至少这个方法是以set单词开始的。前三个字母不能随便写,必须是“set"
    /*public void setMySQLUserDao(UserDao xyz){
        this.userDao = xyz;
    }*/


    // 这个set方法是IDEA工具生成的,符合javabean规范。
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void saveUser(){
        // 保存用户信息到数据库
        userDao.insert();
        vipDao.insert();
    }
}



    
    
    

    
    
        
        
        
        

        
        
        
        
        

        
        
    

set 注入分析

set 注入的实现原理:

通过property标签获取到属性名:userDao
通过属性名推断出与该属性对应的set方法的方法名:setUserDao
通过反射机制调用setUserDao()方法给属性赋值

一般情况下,set方法的命名符合JavaBean规范,property标签的name属性的属性值就是属性名。
property标签的ref属性的属性值是要注入的bean对象的id,通过ref属性来完成bean的装配,ref作为set方法的参数

总结:set注入的核心实现原理:通过反射机制调用set方法来给属性赋值,让两个对象之间产生关系。

构造注入

通过调用构造方法来给属性赋值。
与set注入相比,构造注入是在创建对象的同时进行注入,进行属性的赋值,而set注入是在对象创建之后。

public class CustomerService {
    private UserDao userDao;
    private VipDao vipDao;

    public CustomerService(UserDao userDao, VipDao vipDao) {
        this.userDao = userDao;
        this.vipDao = vipDao;
    }

    public void saveUser(){
        userDao.insert();
        vipDao.insert();
    }
}



    
    
    

    
    
        
        
        
        
        
        
        
    

    
        
        
        
        
        
    

    
        
        
        
        
    

通过构造方法进行注入有三种形式:
1.可以通过下标
2.可以通过参数名
3.也可以不指定下标和参数名,可以类型自动推断。

注入

注入外部Bean

外部Bean:要进行注入的bean定义到需要被注入的Bean的外面
对于外部Bean,在property标签中使用ref属性进行注入,或者使用ref标签进行注入,其中通过ref属性进行注入是常用。





    
    

注入内部Bean

内部Bean:在bean标签中嵌套bean标签,即需要进行注入的Bean通过Bean标签声明在需要被注入的Bean中,然后直接注入到需要被注入的Bean中。


    
        
        
    

注入简单类型

给简单类型赋值,即给属性注入简单类型,使用value标签,而不是ref。

public class User {
    private String username; // String是简单类型
    private String password;
    private int age; // int是简单类型

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public void setAge(int age) {
        this.age = age;
    }
}


    
    
    
    

Spring 中的简单类型

通过BeanUtils类的源码分析得知,简单类型包括:

  • 基本数据类型
  • 基本数据类型对应的包装类
  • String或其他的CharSequence子类
  • Number子类
  • Date子类,java.util.Date是简单类型
  • Enum子类
  • URI
  • URL
  • Temporal子类,Temporal是Java8提供的时间和时区类型
  • Locale,Locale是语言类,也是简单类型。
  • Class
  • 另外还包括以上简单值类型对应的数组类型。

级联属性赋值

// 班级类
public class Clazz {
    // 班级名称
    private String name;

    public void setName(String name) {
        this.name = name;
    }
}
public class Student {
    private String name;

    // 学生属于哪个班级
    private Clazz clazz;

    public void setClazz(Clazz clazz) {
        this.clazz = clazz;
    }

    // 使用级联属性赋值,这个需要这个get方法。
    // 因为级联给学生所在的班级属性赋值会调用学生的getClazz()方法
    public Clazz getClazz() {
        return clazz;
    }

    public void setName(String name) {
        this.name = name;
    }
}

原先的注入方法


    
    
    
    



    

级联注入:
注意:级联属性赋值,对于对其他对象引用的成员变量,一定要提供get方法,因为进行级联属性赋值时,需要先调用get方法获取其他对象引用的成员变量,然后再调用该成员变量属性的set方法进行属性赋值



    
    
    
        
    
    
    
    




注入数组

数组中的元素是简单类型

public class QianDaYe {
    private String[] aiHaos;

    public void setAiHaos(String[] aiHaos) {
        this.aiHaos = aiHaos;
    }
}


    
    
        
            抽烟
            喝酒
            烫头
        
    


数组中的元素是非简单类型

public class Woman {
    private String name;

    public void setName(String name) {
        this.name = name;
    }
}
public class QianDaYe {
    private String[] aiHaos;

    // 多个女性朋友
    private Woman[] womens;

    public void setWomens(Woman[] womens) {
        this.womens = womens;
    }

    public void setAiHaos(String[] aiHaos) {
        this.aiHaos = aiHaos;
    }
}

    



    



    




    
    
        
            抽烟
            喝酒
            烫头
        
    

    
    
        
            
            
            
        
    


注入数组小结
如果数组中是简单类型,使用value标签。
如果数组中是非简单类型,使用ref标签。

注入List集合与Set集合

List集合:有序可重复
注入List集合的时候使用标签,如果List集合中是简单类型使用value标签,反之使用ref标签。

Set集合:无序不可重复
注入set集合的时候使用标签,如果set集合中元素是简单类型的使用value标签,反之使用ref标签。

public class Person {

    // 注入List集合
    private List names;

    // 注入Set集合
    private Set addrs;

    public void setNames(List names) {
        this.names = names;
    }

    public void setAddrs(Set addrs) {
        this.addrs = addrs;
    }
}


    
        
        
            张三
            李四
            王五
            张三
            张三
            张三
            张三
        
    

    
        
        
            北京大兴区
            北京大兴区
            北京海淀区
            北京海淀区
            北京大兴区
        
    

注入Map集合与注入Properties

Map集合:
使用标签
如果key是简单类型,使用 key 属性,反之使用 key-ref 属性。
如果value是简单类型,使用 value 属性,反之使用 value-ref 属性。

java.util.Properties继承java.util.Hashtable,所以Properties也是一个Map集合。
Properties使用标签嵌套标签完成。
在Properties中,key和value的类型都是String类型

public class Person {

    // 注入Map集合
    // 多个电话
    private Map phones;

    // 注入属性类对象
    // Properties本质上也是一个Map集合。
    // Properties的父类Hashtable,Hashtable实现了Map接口。
    // 虽然这个也是一个Map集合,但是和Map的注入方式有点像,但是不同。
    // Properties的key和value只能是String类型。
    private Properties properties;

    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    public void setPhones(Map phones) {
        this.phones = phones;
    }
}

    
    
        
        
            com.mysql.cj.jdbc.Driver
            jdbc:mysql://localhost:3306/spring6
            root
            123456
        
    

    
        
        
            
            
            
            
            
            
        
    

注入null和空字符串

注入空字符串使用: 或者 value=""
注入null使用: 或者不为该属性赋值

public class Cat {
    private String name;
    private int age;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

    
    
    
    
    
    

    
    
    
    
        
    

    

注入的值中含有特殊符号

XML中有5个特殊字符,分别是:<、>、'、"、&
以上5个特殊符号在XML中会被特殊对待,会被当做XML语法的一部分进行解析,如果这些特殊符号直接出现在注入的字符串当中,会报错。

解决方案包括两种:

  • 第一种:特殊符号使用转义字符代替。
    5个特殊字符对应的转义字符分别是:
  • 第二种:将含有特殊符号的字符串放到: 当中。放在CDATA区中的数据不会被XML文件解析器解析。使用CDATA时,不能使用value属性,只能使用value标签。
public class MathBean {
    private String result;

    public void setResult(String result) {
        this.result = result;
    }
}
    
        
        

        
        
            
            
        

    

命名空间注入

p命名空间注入

目的:简化配置。p命名空间实际上是对set注入的简化。
使用p命名空间注入的前提条件包括两个:

  • 第一:在XML头部信息中添加p命名空间的配置信息:xmlns:p="http://www.springframework.org/schema/p"
  • 第二:p命名空间注入是基于setter方法的,所以需要对应的属性提供setter方法。
public class Dog {
    // 简单类型
    private String name;
    private int age;
    // 非简单类型
    private Date birth;

    // p命名空间注入底层还是set注入,只不过p命名空间注入可以让spring配置变的更加简单。
    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setBirth(Date birth) {
        this.birth = birth;
    }
}



    
    

    
    


c命名空间注入

c命名空间是简化构造方法注入的。
使用c命名空间的两个前提条件:

  • 第一:需要在xml配置文件头部添加c命名空间的配置信息:xmlns:c="http://www.springframework.org/schema/c"
  • 第二:需要提供构造方法。

注意:不管是p命名空间还是c命名空间,注入的时候都可以注入简单类型以及非简单类型。

public class People {
    private String name;
    private int age;
    private boolean sex;

    // c命名空间是简化构造注入的。
    // c命名空间注入办法是基于构造方法的。
    public People(String name, int age, boolean sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
}



    
    
    


util命名空间

使用util命名空间可以让配置复用。
使用util命名空间的前提是:在spring配置文件头部添加配置信息。



public class MyDataSource1 {
    // Properties属性类对象,这是一个Map集合,
    // key和value都是String类型。
    private Properties properties;

    public void setProperties(Properties properties) {
        this.properties = properties;
    }
}
package com.powernode.spring6.beans;

import java.util.Properties;

public class MyDataSource2 {
    private Properties properties;

    public void setProperties(Properties properties) {
        this.properties = properties;
    }
}



    
    
        com.mysql.cj.jdbc.Driver
        jdbc:mysql://localhost:3306/spring6
        root
        123
    

    
    
        
    

    
    
        
    


基于 XML 的自动装配

Spring还可以完成自动化的注入,自动化注入又被称为自动装配。
它可以根据名字进行自动装配,也可以根据类型进行自动装配。

根据名称自动装配

如果根据名称装配(byName),底层会调用set方法进行注入。
例如:setAge() 对应的名字是age,setPassword()对应的名字是password,setEmail()对应的名字是email。

public class OrderDao {
    public void insert(){
        logger.info("订单正在生成....");
    }
}
public class OrderService {

    private OrderDao orderDao;

    // 通过set方法给属性赋值。
    public void setOrderDao(OrderDao orderDao) {
        this.orderDao = orderDao;
    }

    // 生成订单的业务方法
    public void generate(){
        orderDao.insert();
    }
}







OrderService 类中有一个OrderDao属性,而OrderDao属性的名字是orderDao,对应的set方法是setOrderDao(),正好和OrderDao Bean的id是一样的,可以根据名称自动装配。

根据类型自动装配

无论是byName还是byType,在装配的时候都是基于set方法的,所以set方法是必须要提供的,提供构造方法是不行的
当byType进行自动装配的时候,配置文件中某种类型的Bean必须是唯一的,不能出现多个。如果存在多个类型一样的Bean,则Spring会不知道使用那个Bean进行注入。

public class CustomerService {

    private UserDao userDao;
    private VipDao vipDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void setVipDao(VipDao vipDao) {
        this.vipDao = vipDao;
    }

    public void save(){
        userDao.insert();
        vipDao.insert();
    }

}
    
    
    
    
    
    
    
    

Spring引入外部属性配置文件

我们都知道编写数据源的时候是需要连接数据库的信息的,例如:driver url username password等信息。这些信息可以单独写到一个属性配置文件properties中,这样用户修改起来会更加的方便

jdbc.driverClass=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring6
jdbc.username=root
jdbc.password=123



    
    

    
    
        
        
        
        
        
        
    


Bean的作用域

singleton 与 prototype

singleton单例

Spring默认情况下Bean是单例的
singleton在Spring上下文初始化的时候实例化。
每一次调用getBean()方法的时候,都返回那个单例的对象。

prototype多例

prototype在spring上下文初始化的时候,并不会初始化这些prototype的bean。
每一次调用getBean()方法的时候,实例化该bean对象。
prototype翻译为:原型。


Bean 的作用域类型

scope属性的值一共包括8个选项:

  • singleton:默认的,单例。
  • prototype:原型。每调用一次getBean()方法则获取一个新的Bean对象。或每次注入的时候都是新对象。
  • request:一个请求对应一个Bean。仅限于在WEB应用中使用。
  • session:一个会话对应一个Bean。仅限于在WEB应用中使用。
  • global session:portlet应用中专用的。如果在Servlet的WEB应用中使用global session的话,和session一个效果。(portlet和servlet都是规范。servlet运行在servlet容器中,例如Tomcat。portlet运行在portlet容器中。)
  • application:一个应用对应一个Bean。仅限于在WEB应用中使用。
  • websocket:一个websocket生命周期对应一个Bean。仅限于在WEB应用中使用。
  • 自定义scope:很少使用。

你可能感兴趣的:(spring)