上周写了Spring IOC的简单使用:Spring 使用之 IOC,这篇将继续补充说明Spring IOC,顺便稍稍总结《Spring 揭秘》中关于Spring IOC的原理。
我们依然通过XML的方式来讲解,实际的开发中,经常使用XML+自动包扫描的方式来告诉Spring 容器对象间的依赖关系。
一、XML配置补充
1、依赖配置补充
首先,在上一篇文章中XML配置注入对象时,1、使用了Set注入,即通过<property></property>标签,说明实例需要被配置的对象。2、通过构造方法的方式注入依赖的对象。分别如下:
<bean id="company" class="cn.springstudy.vo.Company">
<property name="employee" ref="employee"></property>
</bean>
复制代码
<bean id="company" class="cn.springstudy.vo.Company"
c:employee-ref="employee">
</bean>
复制代码
那如果我们懒得指定Company依赖于Employee对象呢??有没办法让容器自己去找依赖关系呢,答案当然是肯定的,我们可以指定autowire属性,autowire属性可取[no | byName | byType | constructor | autodetect]中的值。
1、默认为no,即我们通过上面的构造方法,或者set去手动指定依赖。
2、byName,即Spring自己去找bean标识符(bean的name或id)与定义的set方法中set部分后面的名称一致的对象注入(这里需要注意了,实际上不是按实例名称去查找的对象),如:我们在Company中定义了employee实例,但如果我们的set方法名为 setSpecialEmployee(...){....},那么Spring容器会去找标识符为specialEmployee对象注入。
3、byType,根据实例定义类型,分析其依赖类型。
4、construct 构造器注入,通过类型,不过类型并不是实例类型,而是构造器参数类型。
5、autodetect 如果对象拥有默认无参构造方法,使用byType类型,否则使用construct类型。如果构造方法注入绑定后还有其他属性没绑定,容器也会使用byType对剩余的对象属性进行自动绑定。
<bean id="company" class="cn.springstudy.vo.Company" autowire="byType">
</bean>复制代码
2、继承
上节的例子,我们加上我们又增加类型的公司,我们定义科技、电商公司
package cn.springstudy.vo;
public class TechnologyCompany extends Company{
}
复制代码
package cn.springstudy.vo;
public class ECommerceCompany extends Company{
}复制代码
那我们是不是配分别配置他们的都依赖于雇员类??如下,当然,这样配置并没有问题。有没有更好的的方法??
<bean id="technologyCompany" class="cn.springstudy.vo.TechnologyCompany">
<property name="employee" ref="employee"></property>
</bean>
<bean id="eCommerceCompany" class="cn.springstudy.vo.ECommerceCompany">
<property name="employee" ref="employee"></property>
</bean>复制代码
哈哈哈哈,当然是有的啦,就是指定他们分别继承的类,就不用自己去指定依赖于Emplouee类了
<bean id="company" class="cn.springstudy.vo.Company">
<property name="employee" ref="employee"></property>
</bean>
<bean id="technologyCompany" class="cn.springstudy.vo.TechnologyCompany" parent="company">
</bean>
<bean id="eCommerceCompany" class="cn.springstudy.vo.ECommerceCompany" parent="company">
</bean>复制代码
3、 scrop中的prototype陷阱
上篇文章说过,Spring中管理的对象默认是单例的,我们可以指定作用域让对象是其他模式的,prototype就是原型模式,默认每次注入都会创建一个对象。
现在我们将以上公司、雇员场景做下改吧,之前我们每次让公司提供服务,我们调公司对象的supportService()方法都是同一个雇员来提供服务,现在我们假设每个雇员提供服务没那么快,那么当我再调 supportService(),我们希望是新的雇员来给我们提供服务。
首先,我们先改造下Employee对象,让我们测试更方便,在work方法中把现在执行Employee对象this打印出来。
package cn.springstudy.vo;
import org.springframework.stereotype.Component;
public class Employee {
public void work(){
System.out.println(this+":Employ start to Work:");
};
}
复制代码
package cn.springstudy.vo;
public class Company {
private Employee employee;
public void supportService(){
employee.work();
}
public Employee getEmployee() {
return employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
}复制代码
现在重点来了................,我们把Employee对象作用域指定为原型
<bean name="employee" class="cn.springstudy.vo.Employee" scope="prototype"></bean>
<bean id="company" class="cn.springstudy.vo.Company">
<property name="employee" ref="employee"></property>
</bean>复制代码
测试起来............
package cn.springstudy.spring;
import cn.springstudy.vo.Company;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringStudy {
public static void main(String arg[]){
//方式一
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Company company = (Company) classPathXmlApplicationContext.getBean("company");
company.supportService();
company.supportService();
}
}复制代码
结果..............,什么情况??为我们提供服务的还是同一个雇员。。。
好像不太符合我们想要的结果,因为创建Company对象的时候是单独创建了Employee对象,之后set到Company中,后面我们每次拿到的Employee对象都是一开始set进去的,所以调supportService方法时都是同一个雇员对象执行。有几下几个方法进行改造
其一、方法注入
将Company类中work方法稍稍做变动
package cn.springstudy.vo;
public class Company {
private Employee employee;
public void supportService(){
//这里不用employee,而是使用getEmployee()方法
getEmployee().work();
}
................
}复制代码
改配置为
<bean id="company" class="cn.springstudy.vo.Company">
<!--方法注入,调用getEmployee()方法的时候注入一个employee对象-->
<lookup-method name="getEmployee" bean="employee"/>
</bean>复制代码
再执行上面的测试代码,每次调company.supportService()都是不同的Employee对象为我们服务了
方法二、从容器中获取Employee对象
Company方法实现BeanFactoryAware接口,实现该接口的Bean,容器创建后,会把容器的引用传给该对象,于是Company类改为以下,因为Employee是原型的,所以我们每次从容器中获取都会取得一个新的Employee对象。执行结果同上
package cn.springstudy.vo;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
public class Company implements BeanFactoryAware{
private Employee employee;
private BeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
public void supportService(){
getEmployee().work();
}
public Employee getEmployee() {
return (Employee) beanFactory.getBean("employee");
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
}复制代码
二、Spring IOC原理窥探
Spring 容器实现分为两个阶段:1、容器启动阶段,2、Bean实例化阶段。
1、容器启动阶段
(1)、加载Configuration MataData解析
(2)、分析 将Bean信息保存到BeanDefinition
(3)、将BeanDefinition 注册到BeanDefinitionRegistry
(4)、其他.....................
那我们是否有机会在容器启动后对容器做一些操作呢??Spring提供了BeanFactoryPostProcessor 机制,我们可通过实现BeanFactoryPostProcessor 接口来实现启动容器后对BeanDefinition(根据配置文件解析得到的Bean信息)做修改。但我们很少直接去实现BeanFactoryPostProcessor,更多的是使用Spring提供给我们的BeanFactoryPostProcessor,常见的有一下两个:PropertyPlaceholderConfigurer和OverrideConfigurer 。
为了处理配置文件中的数据类型与真正的业务对象所定义的数据类型转换,Spring还允许我们通过CustomEditorConfigurer 来 注 册 自 定 义 的 PropertyEditor以补助容器中默认的PropertyEditor
1)、PropertyPlaceholderConfigurer主要是用来加载外部的配置文件,并通过占位符的方式将Properties文件中的数据写入对应的BeanDefinition。我们可在工厂跟目录下创建一个dataSource.properties文件来存储数据库的配置信息,通过 ${name}占位符的方式将数据注入到对象中
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations" value="classpath:datasource.properties"/>
</bean>
<bean id="dataSource" destroy-method="close"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>复制代码
2)、PropertyOverrideConfigurer可对容器中配置的任何你想处理的bean定义的property信息进行覆盖替换。 如1)中我们突然觉得jdbc.url想做修改,我们又不想修改PropertyPlaceholderConfigurer的配置文件,于是我们可以创建另一个配置文件,将jdbc.url改为新的值,再做如下配置
<bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
<property name="location" value="classpath:adjustment.properties"/>
</bean>
复制代码
3)、参数类型转换CustomEditorConfigurer
现在我们想给公司增加一个成员变量,类型为Date,表示公司创建时间,我们在XML中配置的是字符串,那么我们怎么让我们配置的 2018/11/15自动转换为Date类型???
先定义一个转换类,继承自PropertyEditorSupport,在这个类中实现转换
package cn.springstudy.spring;
import java.beans.PropertyEditorSupport;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DatePropertyEditor extends PropertyEditorSupport {
private String datePattern;
@Override
public void setAsText(String text) throws IllegalArgumentException {
SimpleDateFormat dateTimeFormatter = new SimpleDateFormat(getDatePattern());
Date dateValue = null;
try {
dateValue = dateTimeFormatter.parse(text);
} catch (ParseException e) {
e.printStackTrace();
}
setValue(dateValue);
}
public String getDatePattern() {
return datePattern;
}
public void setDatePattern(String datePattern) {
this.datePattern = datePattern;
}
}复制代码
再写一个注册类
package cn.springstudy.spring;
import org.springframework.beans.PropertyEditorRegistrar;
import org.springframework.beans.PropertyEditorRegistry;
import java.beans.PropertyEditor;
import java.util.Date;
public class DatePropertyEditorRegistrar implements PropertyEditorRegistrar {
private PropertyEditor propertyEditor;
public PropertyEditor getPropertyEditor() {
return propertyEditor;
}
public void setPropertyEditor(PropertyEditor propertyEditor) {
this.propertyEditor = propertyEditor;
}
@Override
public void registerCustomEditors(PropertyEditorRegistry propertyEditorRegistry) {
propertyEditorRegistry.registerCustomEditor(Date.class, propertyEditor);
}
}复制代码
增加配置
<bean id="datePropertyEditor" class="cn.springstudy.spring.DatePropertyEditor">
<property name="datePattern">
<value>yyyy/MM/dd</value>
</property>
</bean>
<bean id="datePropertyEditorRegistrar" class="cn.springstudy.spring.DatePropertyEditorRegistrar">
<property name="propertyEditor">
<ref bean="datePropertyEditor"/>
</property>
</bean>
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<list>
<ref bean="datePropertyEditorRegistrar"/>
</list></property>
</bean>复制代码
现在我们就可以给Company加上一个创建时间的实例了。
package cn.springstudy.vo;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import java.util.Date;
public class Company{
private Employee employee;
private Date createDate;
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
...
}
复制代码
<bean id="company" class="cn.springstudy.vo.Company">
<property name="employee" ref="employee"></property>
<property name="createDate">
<value>2018/11/18</value>
</property>
</bean>复制代码
这样就能将配置的2018/11/18自动解析为Date类型。
2、Bean实例化阶段
(1)、检查请求的对象是否已经初始化,
(2)、没有则根据BeanDefinitionRegistry中的信息实例化被请求对象, 并为其注入依赖
(3)、生命周期回调、注册回调接口
再来看看bean的生命周期
实例化Bean --> 填充属性(依赖注入) --> 各种Aware方法调用
--> BeanPostProcess的预初始化方法
--> 调用InitializingBean的afterPropertiesSet方法
--> 调用自定义的初始化方法
--> 调用BeanPostProcess的初始化后方法
--> -----到这里Bean可以使用--------
--> 调用DisposableBean的destroy()方法
--> 调用自定义的销毁方法
1)、实例化,默认通过CglibSubclassingInstantiationStrategy来实例化对象,继承了SimpleInstantiationStrategy的以反射方式实例化对象的功能,并且通过CGLIB 的动态字节码生成功能,该策略实现类可以动态生成某个类的子类,进而满足了方法注入所需的对象 实例化需求
2)、Spring容器会检查当前对象实例是否实现了一系列的以Aware命名结尾的接口定义。如果是,则将这些Aware接口定义中规定的依赖注入给当前对象实例
(1)、BeanNameAware。如果Spring容器检测到当前对象实 例实现了该接口,会将该对象实例的bean定义对应的beanName设置到当前对象实例。
(2)、BeanClassLoaderAware。如果容器检测到当前对 象实例实现了该接口,会将对应加载当前bean的Classloader注入当前对象实例。
(3)、BeanFactoryAware。如果对象声明实现了 BeanFactoryAware接口,BeanFactory容器会将自身设置到当前对象实例。
3)、BeanPostProcess
之前的BeanFactoryPostProcess是在容器启动阶段即将结束时,回调,用于我们对容器中Bean。这里的BeanPostProcess是在Bean初始化的时候调用。BeanPostProcess接口定义如下
public interface BeanPostProcessor
{
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
复制代码
<!--配置为bean即可-->
<beans>
<bean id="passwordDecodePostProcessor" class="package.name.PasswordDecodePostProcessor"></bean>
</beans>
复制代码
我们可以定义类,实现BeanPostProcessor,使用场景,比如我们如果将密码密文配置在配置文件中,就可在BeanPostProcessor中解出来并设置到Bean中。
4)、InitializingBean和init-method
要与容器的bean生命周期管理进行交互,可以实现Spring InitializingBean和DisposableBean接口。容器为前者调用afterPropertiesSet(),为后者调用destroy()以允许bean在初始化和销毁bean时执行某些操作----------------来自官网
可见InitializingBean的方法是在bean初始化后生命周期的的回调,InitializingBean接口定义如下
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}
复制代码
使用:我们的bean直接实现initializingBean接口,初始化完bean之后就会调afterPropertiesSet方法
package cn.springstudy.vo;
...
public class Company implements BeanFactoryAware,InitializingBean{
private Employee employee;
private Date createDate;
@Override
public void afterPropertiesSet() throws Exception {
//bean初始化,doSomething
}
...
}
复制代码
另一种实现方式,如下,效果同上。
package cn.springstudy.vo;
...
public class Company implements BeanFactoryAware{
private Employee employee;
private Date createDate;
public void init(){
//bean初始化,doSomething
}
}复制代码
<!-- 指定init-method属性,属性值为执行的方法名 -->
<bean id="company" class="cn.springstudy.vo.Company" init-method="init">
</bean>
复制代码
好了,这一节主要还是对上一节IOC的补充,主要还是讲Spring IOC的一些东西,两篇下来,可能对IOC还不是面面俱到,但也有个大概的框架。未完,待续...........