二、IoC容器
IoC(Inversion of Control,控制反转)也被称为DI(Dependency Injection,依赖注入)。在实际操作中,假如A调用了B,我们可以称A依赖于B,此时A是调用者,B是被调用者,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。在Spring依赖注入的模式下,创建被调用者的工作不再由调用者来完成,因此称为控制反转,创建被调用者实例的工作由Spring容器来完成,然后注入给调用者,因为也称为依赖注入。在Spring中完成这个功能的两个重要的接口:
BeanFactory
:提供一种高级的配置机制能够管理任何类型的对象。ApplicationContext
:是BeanFactory接口的子接口,它更容易集成Spring的AOP功能、消息资源处理、事件发布和特定的上下文应用层。比如:WebApplicationContext
。
在Spring中,由Spring的IoC容器管理的对象叫做beans,bean就是由Spring IoC容器实例化、组装和以其他方式管理的对象。此外bean只是你应用中许多对象中的一个,Beans以及他们之间的依赖关系是通过容器配置元数据反映出来的。下图是Spring如何工作的展示,你应用中所有的类都由元数据组装到一起,所以当ApplicationContext创建和实例化后,你就有一个完全可配置和中执行的系统或应用。
在使用Spring框架时,配置Bean的方式有三种:
- 基于XML配置
- 基于注解配置
- 基于Java配置
在后面我们会讲到基于三种方式的Bean配置,为了由浅入深地讲解, 使用BeanFactory
的实现类来写基于XML配置的Bean管理,使用ApplicationContext
的实现类来实现基于注解和基于Java的配置。
1. BeanFactory
使用前面创建好的Maven项目开始实验,使用简单的XmlBeanFactory
来实现Spring的IoC功能。
1.1 使用容器
- 创建两个实体类并且存在依赖关系
// 这个类是被调用方 public class BeanProvider { public void sayHello(String name, Integer age) { System.out.println("name = [" + name + "], age = [" + age + "]"); } } // 这个类是调用方 public class BeanExample { private String name; private Integer age; private BeanProvider beanProvider; // Getter and Setter public void run() { this.beanProvider.sayHello(this.name, this.age); } }
2、配置元数据使用Spring来管理类
在项目的src/main/resources
目录下创建bean.xml
文件内容如下:
<?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="beanExample" class="com.ajn.spring.beanfactory.bean.BeanExample"/> <bean id="beanProvider" class="com.ajn.spring.beanfactory.bean.BeanProvider"/>
3、编写主方法类开始实验
public static void main(String[] args) { BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("bean.xml")); BeanExample bean = (BeanExample) beanFactory.getBean("beanExample"); bean.run();
此时运行代码是会报空指针的,因为BeanExample
类中依赖的beanProvider
还未注入。
1.2 依赖注入
1.2.1 属性注入
<bean id="beanExample" class="com.ajn.spring.beanfactory.bean.BeanExample"> <property name="name" value="ajn"/> <property name="age" value="24"/> <property name="beanProvider" ref="beanProvider"/> </bean>
将前面配置好的bean添加property
子标签,这些属性会通过set方法注入到创建的实例中。此时运行主方法成功输出:
name = [ajn], age = [24]
我们发现BeanExample
类不仅注入了基本数据类型,还有引用的beanProvider
。配置实例属性的值有两种方式:
value
:等值注入,将具体的值注入到实例属性中ref
:引用注入,将被依赖类的实例引用注入到属性中
当有依赖注入的情况时,所有参与的类都必须交由Spring容器管理。
1.2.2 构造器注入
给BeanExample
类,添加一个全参构造方法:
public BeanExample() { } public BeanExample(String name, Integer age, BeanProvider beanProvider) { this.name = name; this.age = age; this.beanProvider = beanProvider; }
注上面配置的bean子标签替换成constructor-arg
配置,使用index
指定参数位置:
<bean id="beanExample" class="com.ajn.spring.beanfactory.bean.BeanExample"> <constructor-arg index="0" value="ajn"/> <constructor-arg index="1" value="24"/> <constructor-arg index="2" ref="beanProvider"/> </bean>
此时运行主方法输出结果是一样的,不过在这里我们要注意,Spring创建实例也是通过类的构造器来创建的,如果我们配置了一个全参的构造器,那么就无法使用无参构造器,这种情况我们在编码过程中要避免,养成一个好的习惯,创建有参构造器时,也要创建一个无参构造器。
1.2.3 静态工厂注入
创建一个静态工厂类如下:
public class BeanProivderFactory { public static BeanProvider getBeanProvider() { return new BeanProvider(); } }
配置通过静态工厂方式注入:
<bean id="beanExample" class="com.ajn.spring.beanfactory.bean.BeanExample"> <constructor-arg index="0" value="ajn"/> <constructor-arg index="1" value="24"/> <constructor-arg index="2" ref="beanProviderFactory"/> </bean> <bean id="beanProviderFactory" class="com.ajn.spring.beanfactory.bean.BeanProivderFactory" factory-method="getBeanProvider"/>
首先把工厂类的bean,然后配置factory-method
属性指定工厂方法,再把beanProvider的引用换成工厂类的引用。运行主方法会输出同样的内容。
1.3 自动注入(autowire)
<bean id="beanExample" class="com.ajn.spring.beanfactory.bean.BeanExample" autowire="byName"> <property name="name" value="ajn"/> <property name="age" value="24"/> </bean>
将Bean添加属性autowire
值为byName
配置自动注入,几种常用的自动注入方式:
no
:默认值,不使用自动装配,使用注入
byName
:通过属性名称和bean的名称自动注入byType
:通过属性类型和bean的类型自动注入constructor
:通过构造器参数以byType
的方式注入
可以在 标签添加
default-autowire
属性来指定默认自动注入方式。此时Spring中所有的bean都是自动注入的候选者,有时候我们不希望其中某一个bean通过自动注入机制注入到其他bean,可以在这个bean上添加 autowire-candidate="false"
属性,这样就从自动注入中排除这个bean。还有一种情况,当有多个候选者bean都符合注入的要求时,比如一个接口有多个实现类,这时Spring不知道会注入哪一个,这时可以在一个bean上的 标签上添加
primary="true"
属性来确定主要候选者。
这里我们最简单地展示注入,还有一些特殊情况配置,比如:注入内部类、注入集合、注入空值等。
1.4 指定依赖关系(depends-on)
有时候Bean之间的依赖关系并不是特别明确,比如初始化一个静态类,可以使用depends-on
属性来明确指定在初始化该bean之前,强制初始化一个或多个bean。例如:
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao"> <property name="manager" ref="manager" /> </bean> <bean id="manager" class="ManagerBean" /> <bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
depends-on
配置多个bean分隔符可以用逗号、空格或分号
除了指定依赖关系之外,标签还可以配置抽象
abstract="true"
属性和配置继承parent="accountDao"
指定继承关系。
1.5 作用域(scope)
1.5.1 单例作用域
默认Spring容器管理的bean都会以单例模式创建一个实例,当这个bean被别的bean引用时,都只会引用这一个实例,Spring的单例模式不同于我们平时使用的单例模式,四人帮定义的单例是指在JVM进程中仅有一个实例,而Spring的单例是指在一个Spring容器中仅有一个实例。
XML模式配置单例:
<!--默认作用域为单例--> <bean id="accountService" class="com.something.DefaultAccountService"/> <bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
1.5.2 原型作用域
每次请求以原型作用域模式创建的bean时都会创建一个该bean新的实例,也就是在该bean被注入其他bean或者使用getBean()
方法来请求它的时候。一般情况下,我们会对有状态bean使用原型作用域,对无状态bean使用单例作用域。比如:DAO层不会配置成原型作用域,因为典型的DAO不会保持任何会话状态。与其他作用域不同的是,Spring不管理一个原型作用域bean的完整的生命周期。
XML模式配置原型:
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
除了单例模式和原型模式的作用域之外,还有Request, Session, Application, 和WebSocket作用域,我们还可以自定义作用域,具体怎么操作自己拓展。
1.6 懒加载(lazy-init)
默认情况下,IoC容器会以单例模式初始化创建好所有的bean实例,通常这种预先实例化的方案是可取的,因为配置或环境的错误在容器启动的时候就会被立即发现。如果这种方案不能满足你的需求,可以配置在第一次请求bean的时候创建实例,在XML配置中,在 标签使用
lazy-init
属性:
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
但是,如果一个懒加载的bean被别的非懒加载单例bean依赖时,它也会在容器启动的时候创建,因为必须要保持一个安全的依赖关系。
1.7 使用外部属性值(${ })
我们可以将一些配置属性,比如:数据库连接地址、用户名和密码等相关配置,这些配置通常是与环境相关并且修改的几率很大,所以为了降低修改主XML文件或容器文件的复杂性和风险,我们会把它们抽象到一个.properties
文件中,并通过占位符来注入到bean属性中。
修改bean.xml
配置,使用来导入资源文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:property-placeholder location="classpath:spring.properties"/> <bean id="beanExample" class="com.ajn.spring.beanfactory.bean.BeanExample"> <property name="name" value="${name}"/> <property name="age" value="${age}"/> <property name="beanProvider" ref="beanProvider"/> </bean> <bean id="beanProvider" class="com.ajn.spring.beanfactory.bean.BeanProvider"/> </beans>
在src/main/resources
目录下新建资源文件spring.properties
如下:
name=ajn age=24
classpath
表示类所在的路径,${key}
为占位符,key对应.properties
文件中的key。
2. ApplicationContext
前面我们使用简单的BeanFactory
接口完成了IoC容器管理Bean的实验,后面将使用功能更多的ApplicationContext
来实现其他功能。该接口下有很多开箱即用的实现类,比如ClassPathXmlApplicationContext
和FileSystemXmlApplicationContext
,因为我们在实际应用中,用的最多的也是该接口,它是BeanFactory
的子接口,所有功能更强大。修改主方法类:
public static void main(String[] args) { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml"); // applicationContext.registerShutdownHook(); BeanExample bean = (BeanExample) applicationContext.getBean("beanExample"); bean.run(); applicationContext.close();
applicationContext.registerShutdownHook();
和 applicationContext.close();
这两个方法都是为了演示销毁的生命周期而用,至于它们有什么区别自己拓展。
2.1 生命周期
生命周期
InitializingBean
接口只含有一个方法afterPropertiesSet()
,在容器初始化bean之前执行DisposableBean
接口也只有一个方法destroy()
,在容器销毁bean之后执行BeanPostProcessor
接口提供两个接口postProcessBeforeInitialization()
和postProcessAfterInitialization
分别在初始化前后执行,如果你需要自定义一些Spring默认不提供的特性或生命周期行为,你可以使用它- 一般不推荐使用
InitializingBean
接口和DisposableBean
接口,因为它们会加大代码与Spring的耦合,推荐使用bean的自定义初始化方法init-method
和销毁方法destroy-method
,配置方法如下:
添加初始化和销毁方法:
public class BeanProvider { // ... public void init() { System.out.println("BeanProvider.init"); } public void destroy() { System.out.println("BeanProvider.destroy"); } }
然后在对应标签添加两个属性:
<bean id="beanProvider" class="com.ajn.spring.beanfactory.bean.BeanProvider" init-method="init" destroy-method="destroy"/>
在基于注解配置情况时,可以使用 @PostConstruct
和 @PreDestroy
来代替init-method
和destroy-method
Aware接口
容器管理的bean一般不需要了解容器的状态和直接使用容器,但是在某些情况下,是需要在bean中直接对IoC容器进行操作的,这时候,就需要在Bean中设定对容器的感知。该类接口就会提供对应操作的感知,例如:ApplicationContextAware
和 BeanNameAware
,可以获取相应的ApplicationContext和BeanName的信息。需要注意的是,这些接口会将代码绑定到Spring API而且不遵循IoC风格,所以我们只建议将他们使用于需要以编程方式访问容器的基础框架bean。
BeanPostProcessor接口
BeanPostProcessor
接口可以用来自定义扩展bean,实现该接口来提供你自己的实例逻辑、依赖解析逻辑等等。如果要在Spring容器完成实例化、配置和初始化bean之后实现一些自定义逻辑,你也可以定义一个或多个BeanValidationPostProcessor
接口的实现类来完成。
2.2 生命周期实例
新建类BeanLifeCycle.java
并实现相关接口如下:
public class BeanLifeCycle implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, BeanPostProcessor, InitializingBean, DisposableBean { @Override public void setBeanName(String name) { System.out.println("BeanLifeCycle.setBeanName"); } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { System.out.println("BeanLifeCycle.setBeanFactory"); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { System.out.println("BeanLifeCycle.setApplicationContext"); } @Override public void afterPropertiesSet() throws Exception { System.out.println("BeanLifeCycle.afterPropertiesSet"); } @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("BeanLifeCycle.postProcessBeforeInitialization beanName: " + beanName); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("BeanLifeCycle.postProcessAfterInitialization beanName: " + beanName); return bean; } @Override public void destroy() throws Exception { System.out.println("BeanLifeCycle.destroy"); } private void init() { System.out.println("BeanLifeCycle.init"); } private void destroyByClass() { System.out.println("BeanLifeCycle.destroyByClass"); } }
添加XML配置如下:
<bean id="beanLifeCycle" class="com.ajn.spring.applicationcontext.BeanLifeCycle" init-method="init" destroy-method="destroyByClass"/>
运行输出结果:
BeanLifeCycle.setBeanName BeanLifeCycle.setBeanFactory BeanLifeCycle.setApplicationContext BeanLifeCycle.afterPropertiesSet BeanLifeCycle.init BeanLifeCycle.postProcessBeforeInitialization beanName: beanProvider BeanProvider.init BeanLifeCycle.postProcessAfterInitialization beanName: beanProvider BeanLifeCycle.postProcessBeforeInitialization beanName: beanExample BeanExample.init BeanLifeCycle.postProcessAfterInitialization beanName: beanExample name = [ajn], age = [24] BeanExample.destroy BeanProvider.destroy BeanLifeCycle.destroy BeanLifeCycle.destroyByClass