Spring-SpringIoC
IoC是inverse of control的简写,译为控制反转,是一种创建对象的思想
是控制反转就是将创建对象的权力交给Spring容器,其实就是让Spring容器帮你创建对象,而你不需要在java代码中new对象了
DI是Dependency Injection,译为依赖注入
当容器中对象与对象之间关系,是通过DI完成的
SpringIoC容器的配置方式有三种:
组件
我们在使用框架之前,完成业务逻辑的步骤是:
controller层 -> service层 -> dao层
这三层中的每一个有关于具体业务的类,都是可以看作为组件,组件就是可以复用的java对象
就分为三层组件:controller层组件 -> service层组件 -> dao层组件
而这些组件可以被Spring接管,缺省组件中相互调用
Spring组件管理动作包含:
- 组件对象实例化
- 组件属性的赋值
- 组件对象的引用
- 组件对象存活周期管理
- ……
Spring提供了组件的容器,由他接手创建、管理、存储组件
只有Spring接手管理的组件,才能使用Spring框架的其他功能
SpringIoC的接口和实现类
接口
实现类
常用的实现类有四个:
| 类名 |
说明 |
| ClassPathXmlApplicationContext |
通过读取类路径下的XML格式的配置文件创建IoC容器对象 |
| FileSystemXmlApplicationContext |
通过文件系统路径读取XML格式的配置文件创建IoC容器对象 |
| AnnotationCofigApplicationContext |
通过读取Java配置类创建IoC容器对象 |
| WebApplicationContext |
专门为Web应用准备,基于web环境创建IoC对象,并将对象引入存入ServletContext域中 |
SpringIoC的实践与应用
在使用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(); HappyComponent happyComponent = (HappyComponent) context.getBean("happyComponent");
context.getBean("happyComponent", HappyComponent.class);
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 bean = context.getBean(JavaBean.class); context.close(); }
|
注:如果不调用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>
|