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>