Spring-SpringIoC

Spring-SpringIoC

IoC是inverse of control的简写,译为控制反转,是一种创建对象的思想

是控制反转就是将创建对象的权力交给Spring容器,其实就是让Spring容器帮你创建对象,而你不需要在java代码中new对象了

DI是Dependency Injection,译为依赖注入

当容器中对象与对象之间关系,是通过DI完成的

SpringIoC容器的配置方式有三种:

  • XML配置方式

  • 注解方式

  • Java配置类方式


组件

我们在使用框架之前,完成业务逻辑的步骤是:

controller层 -> service层 -> dao层

这三层中的每一个有关于具体业务的类,都是可以看作为组件,组件就是可以复用的java对象

就分为三层组件:controller层组件 -> service层组件 -> dao层组件

而这些组件可以被Spring接管,缺省组件中相互调用

Spring组件管理动作包含:

  • 组件对象实例化
  • 组件属性的赋值
  • 组件对象的引用
  • 组件对象存活周期管理
  • ……

Spring提供了组件的容器,由他接手创建、管理、存储组件

只有Spring接手管理的组件,才能使用Spring框架的其他功能


SpringIoC的接口和实现类

接口
  • BeanFactory:是SpringIoC的标准化接口

  • ApplicationContext:基于BeanFactory的子接口,拓展了功能,使用的实现类都是此接口的实现类

实现类

常用的实现类有四个:

类名说明
ClassPathXmlApplicationContext通过读取类路径下的XML格式的配置文件创建IoC容器对象
FileSystemXmlApplicationContext通过文件系统路径读取XML格式的配置文件创建IoC容器对象
AnnotationCofigApplicationContext通过读取Java配置类创建IoC容器对象
WebApplicationContext专门为Web应用准备,基于web环境创建IoC对象,并将对象引入存入ServletContext域中

SpringIoC的实践与应用

  • 写配置文件
  • 创建IoC容器
  • 通过容器获取组件对象

在使用Spring之前,我们实例化对象的方式有:

  • 构造函数实例化
    • 有参构造函数实例化
    • 无参构造函数实例化
  • 工厂模式实例化
    • 静态工厂实例化
    • 非静态工厂实例化

在Spring中,不同方式的实例化有不同方法的配置方式


xml文件配置SpringIoC

在resources文件夹下新建xml文件作为IoC的配置文件

当导入了Spring依赖之后,idea右键新建xml配置文件中,会出现Spring配置文件,创建即可


配置无参构造函数的组件类

使用bean标签,即可根据组件类创建其对象,有两个标签属性:

  • id:别名(全局唯一)
  • class:类全限定符(类全名)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <bean id="happyComponent" class="com.xiaobai.ioc_01.HappyComponent"/>
    
</beans>

我们也可以根据组件类实例化多个组件对象

<bean id="happyComponent1" class="com.xiaobai.ioc_01.HappyComponent"/>

<bean id="happyComponent2" class="com.xiaobai.ioc_01.HappyComponent"/>

配置静态工厂组件类

静态工厂组件类其实就是使用了饿汉式的单例模式,私有化构造方法后,通过调用方法返回其类本身的静态对象

public class ClientService {
    private static ClientService clientService = new ClientService();

    private ClientService() {
    }

    public static ClientService createInstance() {

        return clientService;
    }
}
<bean id="clientService" class="com.xiaobai.ioc_01.ClientService" factory-method="createInstance"/>

使用bean标签创建静态工厂组件类的对象时,有三个标签属性:

  • id:别名
  • class:工厂类的全限定符
  • factory-method:静态工厂方法

配置非静态工厂组件类

非静态工厂组件类,是通过调用类中非静态方法返回非本身的静态对象

区别是:调用方法是非静态方法,需要实例化才能调用

public class FactoryClientService {
    private static ClientService clientService = new ClientService();

    private FactoryClientService() {
    }

    public ClientService createInstance() {
        return clientService;
    }
}
<bean id="factoryClientService" class="com.xiaobai.ioc_01.FactoryClientService"/>
<bean id="clientService" factory-bean="factoryClientService" factory-method="createInstance"/>

首先是使用bean标签创建了FactoryClientService的组件对象factoryClientService

bean中标签有三个属性:

  • id:别名
  • factory-bean:工厂类的对象名(别名)
  • factory-method:工厂方法(非静态)

DI

在JavaWeb中,多层结构之间的调用是通过实例化对象后调用方法的方式实现的

比如说,controller层调用service层,需要实例化一个对象,这个对象作为controller中的全局变量赋值给属性,再调用其方法

而Spring直接通过依赖注入的方式,将service层的对象,通过构造方法或setter方法注入到controller的全局变量(属性)中

注:引用与被引用的组件,都必须被IoC容器接管

public class UserService {

    private UserDao userDao;

    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }
}
<bean id="userDao" class="com.xiaobai.ioc_02.UserDao"/>
<bean id="userService" class="com.xiaobai.ioc_02.UserService">
    <constructor-arg ref="userDao"/>
</bean>

在bean标签内使用constructor-arg标签,完成构造中参数的注入(DI)

  • value:直接属性值,比如基本类型属性值(String,int)
  • ref:引用其他的对象,使用其他Bean的Id(别名)

SpringIoC是一个高级容器,会先创建对象,再去进行属性赋值,所以先后顺序无所谓


多个构造参数注入(顺序赋值)
public class UserService {

    private UserDao userDao;

    private int age;

    private String name;

    public UserService(int age, String name, UserDao userDao) {
        this.userDao = userDao;
        this.age = age;
        this.name = name;
    }
}
<bean id="userDao" class="com.xiaobai.ioc_02.UserDao"/>
<bean id="userService" class="com.xiaobai.ioc_02.UserService">
    <constructor-arg value="18"/>
    <constructor-arg value="xiaobai"/>
    <constructor-arg ref="userDao"/>
</bean>

当构造方法有多个参数需要传值时,使用多个constructor-arg标签按照顺序赋值即可,不推荐使用这种方法


多个构造参数注入(按照属性名赋值)*
<bean id="userService" class="com.xiaobai.ioc_02.UserService">
    <constructor-arg name="age" value="18"/>
    <constructor-arg name="name" value="xiaobai"/>
    <constructor-arg name="userDao" ref="userDao"/>
</bean>

使用constructor-arg的name属性,给构造方法中的属性赋值,推荐使用这种方法


多个构造参数注入(按照下角标方式)
<bean id="userDao" class="com.xiaobai.ioc_02.UserDao"/>
<bean id="userService" class="com.xiaobai.ioc_02.UserService">
    <constructor-arg index="0" value="18"/>
    <constructor-arg index="1" value="xiaobai"/>
    <constructor-arg index="2" ref="userDao"/>
</bean>

使用constructor-arg的index属性,给构造方法中的属性赋值,不推荐使用这种方法


以set方法的方式注入*
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    private String movieName;

    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    public void setMovieName(String movieName) {
        this.movieName = movieName;
    }
}
<bean id="movieFinder" class="com.xiaobai.ioc_02.MovieFinder"/>
<bean id="simpleMovieLister" class="com.xiaobai.ioc_02.SimpleMovieLister">
    <property name="movieName" value="三体"/>
    <property name="movieFinder" ref="movieFinder"/>
</bean>

使用bean标签中的property标签,完成对set方法的依赖注入

  • name:set方法去掉set并首字母小写的方法,以此名调用set方法
  • value/ref:参数

注:name属性中的值并不是属性名,而是set方法名经过变化得来的,但一般情况下与属性名相同


SpringIoC容器的创建和读取

ApplicationContext context = new ClassPathXmlApplicationContext("Spring-03.xml");

使用实现类的构造方法直接创建容器对象,一般情况下直接使用这种

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext();
context.setConfigLocations("spring-03.xml");
context.refresh();

源码的配置过程:先指定配置文件之后,再刷新


Bean组件的读取

使用容器调用getBean方法即可读取容器中组件(对象)

public void getBeanFromIoC() {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext();
    context.setConfigLocations("spring-03.xml");
    context.refresh();
    //方案1:直接根据组件名称(beanID)获取即可,返回值是Object,需要强转
    HappyComponent happyComponent = (HappyComponent) context.getBean("happyComponent");

    //方案2:根据beanID获取对象的同时,将类对象作为参数传入
    context.getBean("happyComponent", HappyComponent.class);

    //方案3:根据类型直接获取(单例模式的好处)
    context.getBean(HappyComponent.class);
}

方案3在使用时,要保证容器中只有一个同类型的bean,如果存在多个,则会出现NoUniqueBeanDefinitionException(不唯一异常)

getBean方法只是将容器中的组件取出来,而不是生成一个对象


接口问题

在IoC容器的创建中,是不可以对接口进行创建的

但是在读取IoC容器时,是可以直接根据接口类型来获取到具体的对象的(getBean的参数可以为接口名)

这是因为,getBean的底层实现是通过类对象(.class)和调用instanceof的反射机制

instanceof在判断接口类型的类对象的结果依然为true,即为通过判断,就可以获取到具体的类对象啦 😊


组件的作用域和周期方法

周期方法

Spring组件的周期方法有初始化方法和销毁方法,配置好后,会在特定的时间节点自动回调方法

public class JavaBean {
    public void init(){
        System.out.println("JavaBean.init");
    }
    public void destroy(){
        System.out.println("JavaBean.destroy");
    }
}
<bean id="javaBean" class="com.xiaobai.ioc_04.JavaBean" init-method="init" destroy-method="destroy"/>

使用bean标签的属性配置指定的初始化方法和销毁方法

  • init-method:初始化方法
  • destroy-method:销毁方法
public void test04(){
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-04.xml");// JavaBean.init
    JavaBean bean = context.getBean(JavaBean.class);
    context.close();// JavaBean.destroy
}

注:如果不调用close方法,则容器非自然死亡,不会进入生命周期的destroy,也就不会执行销毁方法


作用域

组件被bean标签声明后,被SpringIoC容器接管,在IoC容器内,会产生一个BeanDefinition,这个类包裹了Bean标签的所有属性,例:

  • id
  • class
  • init-method
  • destory-method
  • ……

SpringIoC容器会根据BeanDefinition对象反射创建多个Bean对象实例

容器的作用域由Bean标签的scope属性决定,默认是单例模式,一个Bean标签对应一个组件对象

  • ingleton:单例(默认值)
  • protorype:多例
  • request:每次请求时创建对象
  • session:每次会话时创建对象

可以设置为多例模式,一个Bean标签对应一个BeanDefinition对象,但对应多个组件对象

一般情况下都是用单例模式


FactoryBean

标准化工厂,重写getObject方法,编写自己的实例化逻辑

  • T getObject():返回此工厂创建对象的实例
  • boolean isSingleton:判断是否为单例模式
  • class<?> getObjectType:返回对象类型,如果不知道类型则返回null

FactoryBean使用场景:

  • 代理类的创建
  • 第三方框架整合
  • 复杂对象实例化
  • ……
<bean id="javaBean" class="com.xiaobai.ioc_04.JavaBeanFactoryBean"/>

FactoryBean也会被放在BeanDefinition中,名字为&Id


FactoryBean传参问题

当我们直接使用property标签给工厂Bean传参时,传到的是&Id对象中(也就是工厂Bean对象)

如果想给工厂Bean中的JavaBean对象传参,则需要包裹一层set,给工厂Bean设置一个属性,再将这个属性传到里面的JavaBean中

注:FactoryBean和BeanFactory不同!!!

public class JavaBean {

    private String name;

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

    public String getName() {
        return name;
    }
}
public class JavaBeanFactoryBean implements FactoryBean<JavaBean> {

    String name;

    public JavaBeanFactoryBean(String name) {
        this.name = name;
    }

    @Override
    public Class<?> getObjectType() {
        return JavaBean.class;
    }

    @Override
    public JavaBean getObject() throws Exception {
        JavaBean javaBean = new JavaBean();
        javaBean.setName(name);
        return javaBean;
    }
}
<bean id="javaBean" class="com.xiaobai.ioc_04.JavaBeanFactoryBean">
    <constructor-arg name="name" value="xiaobai"/>
</bean>