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:类全限定符(类全名)
1
2
3
4
5
6
7
8
<?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>

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

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

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

配置静态工厂组件类

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

1
2
3
4
5
6
7
8
9
10
11
public class ClientService {
private static ClientService clientService = new ClientService();

private ClientService() {
}

public static ClientService createInstance() {

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

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

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

配置非静态工厂组件类

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

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

1
2
3
4
5
6
7
8
9
10
public class FactoryClientService {
private static ClientService clientService = new ClientService();

private FactoryClientService() {
}

public ClientService createInstance() {
return clientService;
}
}
1
2
<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容器接管

1
2
3
4
5
6
7
8
public class UserService {

private UserDao userDao;

public UserService(UserDao userDao) {
this.userDao = userDao;
}
}
1
2
3
4
<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是一个高级容器,会先创建对象,再去进行属性赋值,所以先后顺序无所谓


多个构造参数注入(顺序赋值)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
}
}
1
2
3
4
5
6
<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标签按照顺序赋值即可,不推荐使用这种方法


多个构造参数注入(按照属性名赋值)*
1
2
3
4
5
<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属性,给构造方法中的属性赋值,推荐使用这种方法


多个构造参数注入(按照下角标方式)
1
2
3
4
5
6
<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方法的方式注入*
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
}
}
1
2
3
4
5
<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容器的创建和读取

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

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

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

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


Bean组件的读取

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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组件的周期方法有初始化方法和销毁方法,配置好后,会在特定的时间节点自动回调方法

1
2
3
4
5
6
7
8
public class JavaBean {
public void init(){
System.out.println("JavaBean.init");
}
public void destroy(){
System.out.println("JavaBean.destroy");
}
}
1
<bean id="javaBean" class="com.xiaobai.ioc_04.JavaBean" init-method="init" destroy-method="destroy"/>

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

  • init-method:初始化方法
  • destroy-method:销毁方法
1
2
3
4
5
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使用场景:

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

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


FactoryBean传参问题

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

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

注:FactoryBean和BeanFactory不同!!!

1
2
3
4
5
6
7
8
9
10
11
12
public class JavaBean {

private String name;

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

public String getName() {
return name;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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;
}
}
1
2
3
<bean id="javaBean" class="com.xiaobai.ioc_04.JavaBeanFactoryBean">
<constructor-arg name="name" value="xiaobai"/>
</bean>


Spring-SpringIoC
http://blog.170827.xyz/2024/05/25/Spring-SpringIoC/
作者
XIAOBAI
发布于
2024年5月25日
许可协议