spring学习笔记(IOC应用以及源码解析)
1-7.spring的IOC基础说明
基础说明:
BeanFactory与ApplicationContext区别:
BeanFactory是Spring框架中IoC容器的顶层接⼝,它只是⽤来定义⼀些基础功能,定义⼀些基础规范,⽽ApplicationContext是它的⼀个⼦接⼝,所以ApplicationContext是具备BeanFactory提供的全部功能的。
通常,我们称BeanFactory为SpringIOC的基础容器,ApplicationContext是容器的⾼级接⼝,⽐BeanFactory要拥有更多的功能,⽐如说国际化⽀持和资源访问(xml,java配置类)等等
启动ioc容器的方式:
Java环境下启动IoC容器
ClassPathXmlApplicationContext:从类的根路径下加载配置⽂件(推荐使⽤)
FileSystemXmlApplicationContext:从磁盘路径上加载配置⽂件
AnnotationConfigApplicationContext:纯注解模式下启动Spring容器
Web环境下启动IoC容器
从xml启动容器
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--配置Spring ioc容器的配置⽂件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--使⽤监听器启动Spring的IOC容器-->
<listener>
<listenerclass>org.springframework.web.context.ContextLoaderListener</listenerclass>
</listener>
</web-app>
从配置类启动容器
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--告诉ContextloaderListener知道我们使⽤注解的⽅式启动ioc容器-->
<context-param>
<param-name>contextClass</param-name>
<paramvalue>org.springframework.web.context.support.AnnotationConfigWebAppli
cationContext</param-value>
</context-param>
<!--配置启动类的全限定类名-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.zmx.edu.SpringConfig</param-value>
</context-param>
<!--使⽤监听器启动Spring的IOC容器-->
<listener>
<listenerclass>org.springframework.web.context.ContextLoaderListener</listenerclass>
</listener>
</web-app>
纯xml方式:
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
https://www.springframework.org/schema/beans/spring-beans.xsd">
实例化Bean的三种⽅式
⽅式⼀:使⽤⽆参构造函数
在默认情况下,它会通过反射调⽤⽆参构造函数来创建对象。如果类中没有⽆参构造函数,将创建失败。
<!--配置service对象-->
<bean id="userService" class="com.zmx.service.impl.TransferServiceImpl">
</bean>
⽅式⼆:使⽤静态⽅法创建
在实际开发中,我们使⽤的对象有些时候并不是直接通过构造函数就可以创建出来的,它可能在创建的过程 中会做很多额外的操作。此时会提供⼀个创建对象的⽅法,恰好这个⽅法是static修饰的⽅法,即是此种情 况。
例如,我们在做Jdbc操作时,会⽤到java.sql.Connection接⼝的实现类,如果是mysql数据库,那么⽤的就 是JDBC4Connection,但是我们不会去写 JDBC4Connection connection = newJDBC4Connection() ,因 为我们要注册驱动,还要提供URL和凭证信息,⽤ DriverManager.getConnection ⽅法来获取连接。那么在实际开发中,尤其早期的项⽬没有使⽤Spring框架来管理对象的创建,但是在设计时使⽤了⼯⼚模式 解耦,那么当接⼊spring之后,⼯⼚类创建对象就具有和上述例⼦相同特征,即可采⽤此种⽅式配置
<!--使⽤静态⽅法创建对象的配置⽅式-->
<bean id="userService" class="com.zmx.factory.BeanFactory" factory-method="getTransferService"></bean>
⽅式三:使⽤实例化⽅法创建
此种⽅式和上⾯静态⽅法创建其实类似,区别是⽤于获取对象的⽅法不再是static修饰的了,⽽是类中的⼀ 个普通⽅法。此种⽅式⽐静态⽅法创建的使⽤⼏率要⾼⼀些。
在早期开发的项⽬中,⼯⼚类中的⽅法有可能是静态的,也有可能是⾮静态⽅法,当是⾮静态⽅法时,即可 采⽤下⾯的配置⽅式:
<!--使⽤实例⽅法创建对象的配置⽅式-->
<bean id="beanFactory" class="com.zmx.factory.instancemethod.BeanFactory"></bean>
<bean id="transferService" factory-bean="beanFactory" factorymethod="getTransferService"></bean>
Bean的作用范围及⽣命周期
作⽤范围的改变
在spring框架管理Bean对象的创建时,Bean对象默认都是单例的,但是它⽀持配置的⽅式改
变作⽤范围。作⽤范围官⽅提供的说明如下图:
开发中⽤到最多的作⽤范围就是singleton(单例模式)和prototype(原型模式,也叫多例模式)
<!--配置service对象-->
<bean id="transferService" class="com.zmx.service.impl.TransferServiceImpl" scope="singleton">
</bean>
不同作⽤范围的⽣命周期
单例模式:singleton
对象出⽣:当创建容器时,对象就被创建了。
对象活着:只要容器在,对象⼀直活着。
对象死亡:当销毁容器时,对象就被销毁了。
⼀句话总结:单例模式的bean对象⽣命周期与容器相同。
多例模式:prototype
对象出⽣:当使⽤对象时,创建新的对象实例。
对象活着:只要对象在使⽤中,就⼀直活着。
对象死亡:当对象⻓时间不⽤时,被java的垃圾回收器回收了。
⼀句话总结:多例模式的bean对象,spring框架只负责创建,不负责销毁。
Bean标签属性
在基于xml的IoC配置中,bean标签是最基础的标签。它表示了IoC容器中的⼀个对象。换句话说,如果⼀个对象想让spring管理,在XML的配置中都需要使⽤此标签配置,Bean标签的属性如下:
id属性: ⽤于给bean提供⼀个唯⼀标识。在⼀个标签内部,标识必须唯⼀。
class属性:⽤于指定创建Bean对象的全限定类名。
name属性:⽤于给bean提供⼀个或多个名称。多个名称⽤空格分隔。
factory-bean属性:⽤于指定创建当前bean对象的⼯⼚bean的唯⼀标识。当指定了此属性之后,class属性失效。
factory-method属性:⽤于指定创建当前bean对象的⼯⼚⽅法,如配合factory-bean属性使⽤,则class属性失效。如配合class属性使⽤,则⽅法必须是static的。
**scope属性:**⽤于指定bean对象的作⽤范围。通常情况下就是singleton。当要⽤到多例模式时,可以配置为prototype。
**init-method属性:**⽤于指定bean对象的初始化⽅法,此⽅法会在bean对象装配后调⽤。必须是⼀个⽆参⽅法。
**destory-method属性:**⽤于指定bean对象的销毁⽅法,此⽅法会在bean对象销毁前执⾏。它只能为scope是singleton时起作⽤。
DI依赖注入xml配置:
依赖注⼊分类
构造函数注⼊:顾名思义,就是利⽤带参构造函数实现对类成员的数据赋值。
利⽤构造函数实现对类成员的赋值。它的使⽤要求是,类中提供的构造函数参数个数必须和配置的参数个数⼀致,且数据类型匹配。同时需要注意的是,当没有⽆参构造时,则必须提供构造函数参数的注⼊,否则Spring框架会报错。
name:⽤于给构造函数中指定名称的参数赋值。
index:⽤于给构造函数中指定索引位置的参数赋值。
value:⽤于指定基本类型或者String类型的数据。
ref:⽤于指定其他Bean类型的数据。写的是其他bean的唯⼀标识。
<constructor-arg index="0" ref="connectionsUtils"/>
<constructor-arg index="1" value="张三"/>
<constructor-arg index="2" value="1"/>
<constructor-arg index="3" value="100.3"/>
<constructor-arg name="connectionsUtils" ref="connectionsUtils"/>
<constructor-arg name="name" value="张三"/>
<constructor-arg name="sex" value="1"/>
<constructor-arg name="money" value="100.3"/>
public JdbcAccountDaoImpl(ConnectionsUtils connectionsUtils, String name, int sex, float money) {
this.connectionsUtils = connectionsUtils;
this.name = name;
this.sex = sex;
this.money = money;
}
set⽅法注⼊:它是通过类成员的set⽅法实现数据的注⼊。(使⽤最多的)
利⽤字段的set⽅法实现赋值的注⼊⽅式。此种⽅式在实际开发中是使⽤最多的注⼊⽅式。
<!--set注入使用property标签 如果注入的是另外一个bean 使用ref属性 注入的是普通值 使用value属性-->
<property name="ConnectionsUtils" ref="connectionsUtils"></property>
<property name="name" value="张三"></property>
<property name="sex" value="1"></property>
<property name="money" value="100.3"></property>
public void setName(String name) {
this.name = name;
}
public void setSex(int sex) {
this.sex = sex;
}
public void setMoney(float money) {
this.money = money;
}
按照注⼊的数据类型分类
基本类型和String
注⼊的数据类型是基本类型或者是字符串类型的数据。
其他Bean类型注⼊的数据类型是对象类型,称为其他Bean的原因是,这个对象是要求出现在IoC容器
中的。那么针对当前Bean来说,就是其他Bean了。
复杂类型(集合类型)
复杂类型数据,指的是集合类型数据。集合分为两类,⼀类 是List结构(数组结构),⼀类是Map接⼝(键值对) 。
注⼊的数据类型是Aarry,List,Set,Map,Properties中的⼀种类型。
<property name="myArray">
<array>
<value>array1</value>
<value>array2</value>
<value>array3</value>
</array>
</property>
<property name="myMap">
<map>
<entry key="key1" value="value1"/>
<entry key="key2" value="value2"/>
</map>
</property>
<property name="mySet">
<set>
<value>set1</value>
<value>set2</value>
</set>
</property>
<property name="myProperties">
<props>
<prop key="prop1">value1</prop>
<prop key="prop2">value2</prop>
</props>
</property>
在List结构的集合数据注⼊时, array , list , set 这三个标签通⽤,另外注值的 value 标签内部可以直接写值,也可以使⽤ bean 标签配置⼀个对象,或者⽤ ref 标签引⽤⼀个已经配合的bean的唯⼀标识。在Map结构的集合数据注⼊时, map 标签使⽤ entry ⼦标签实现数据注⼊, entry 标签可以使⽤key和value属性指定存⼊map中的数据。使⽤value-ref属性指定已经配置好的bean的引⽤。同时 entry 标签中也可以使⽤ ref 标签,但是不能使⽤ bean 标签。⽽ property 标签中不能使⽤ ref 或者 bean 标签引⽤对象
xml与注解相结合:
注意:
1)实际开发中,纯xml模式使⽤已经很少
2)引⼊注解功能,不需要引⼊额外的jar
3)xml+注解结合模式,xml⽂件依然存在,所以,spring IOC容器的启动仍然从加载xml开始xml形式 对应的注解形式
标签
xml中标签与注解的对应(IoC)
DI 依赖注⼊的注解实现⽅式
@Autowired(推荐)
@Autowired为Spring提供的注解,需要导⼊包 org.springframework.beans.factory.annotation.Autowired。
@Autowired采取的策略为按照类型注⼊。
当⼀个类型有多个bean值的时候,会造成⽆法选择具体注⼊哪⼀个的情况,这个时候我们需要配合着@Qualifier使⽤。@Qualifier告诉Spring具体去装配哪个对象。
public class TransferServiceImpl {
@Autowired
@Qualifier(name="jdbcAccountDaoImpl")
private AccountDao accountDao;
}
@Resource
@Resource 注解由 J2EE 提供,需要导⼊包 javax.annotation.Resource。
@Resource 默认按照 ByName ⾃动注⼊。
public class TransferService {
@Resource
private AccountDao accountDao;
@Resource(name="studentDao")
private StudentDao studentDao;
@Resource(type="TeacherDao")
private TeacherDao teacherDao;
@Resource(name="manDao",type="ManDao")
private ManDao manDao;
}
如果同时指定了 name 和 type,则从Spring上下⽂中找到唯⼀匹配的bean进⾏装配,找不到则抛出异常。
如果指定了 name,则从上下⽂中查找名称(id)匹配的bean进⾏装配,找不到则抛出异常。
如果指定了 type,则从上下⽂中找到类似匹配的唯⼀bean进⾏装配,找不到或是找到多个,都会抛出异常。
如果既没有指定name,⼜没有指定type,则⾃动按照byName⽅式进⾏装配;
依赖:
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
纯注解模式:
对应注解
@Configuration 注解,表名当前类是⼀个配置类
@ComponentScan 注解,替代 context:component-scan
@PropertySource,引⼊外部属性配置⽂件
@Import 引⼊其他配置类
@Value 对变量赋值,可以直接赋值,也可以使⽤ ${} 读取资源配置⽂件中的信息
@Bean 将⽅法返回对象加⼊ SpringIOC 容器
//@Configuration 注解表明当前类是一个配置类
@Configuration
@ComponentScan({"com.zmx.edu"})
@PropertySource({"classpath:jdbc.properties"})
/*@Import()*/
public class SpringConfig {
@Value("${jdbc.driver}")
private String driverClassName;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean("dataSource")
public DataSource createDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(driverClassName);
druidDataSource.setUrl(url);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
return druidDataSource;
}
}
8-10 Spring高级特性
延迟加载
ApplicationContext 容器的默认⾏为是在启动服务器时将所有 singleton bean 提前进⾏实例化。提前实例化意味着作为初始化过程的⼀部分,ApplicationContext 实例会创建并配置所有的singletonbean。
<bean id="testBean" class="cn.zmx.LazyBean" />
该bean默认的设置为:
lazy-init="false",⽴即加载,表示在spring启动时,⽴刻进⾏实例化。
<bean id="testBean" calss="cn.zmx.LazyBean" lazy-init="false" />
lazy-init 为 true 的 bean 将不会在 ApplicationContext 启动时提前被实例化,⽽是第⼀次向容器通过 getBean 索取 bean 时实例化的。
<bean id="testBean" calss="cn.zmx.LazyBean" lazy-init="true" />
如果⼀个设置了⽴即加载的 bean1,引⽤了⼀个延迟加载的 bean2 ,那么 bean1 在容器启动时被实例化,⽽ bean2 由于被 bean1 引⽤,所以也被实例化,这种情况也符合延时加载的 bean 在第⼀次调⽤时才被实例化的规则。
可以在容器层次中通过在 元素上使⽤ “default-lazy-init” 属性来控制延时初始化。如下⾯配置:
<beans default-lazy-init="true">
<!-- no beans will be eagerly pre-instantiated... -->
</beans>
如果⼀个 bean 的 scope 属性为 scope=“pototype” 时,即使设置了 lazy-init=“false”,容器启动时也不
会实例化bean,⽽是调⽤ getBean ⽅法实例化的。
应⽤场景
(1)开启延迟加载⼀定程度提⾼容器启动和运转性能
(2)对于不常使⽤的 Bean 设置延迟加载,这样偶尔使⽤的时候再加载,不必要从⼀开始该 Bean 就占
⽤资源
FactoryBean 和 BeanFactory
BeanFactory接⼝是容器的顶级接⼝,定义了容器的⼀些基础⾏为,负责⽣产和管理Bean的⼀个⼯⼚,具体使⽤它下⾯的⼦接⼝类型,⽐如ApplicationContext
Spring中Bean有两种,⼀种是普通Bean,⼀种是⼯⼚Bean(FactoryBean),FactoryBean可以⽣成某⼀个类型的Bean实例(返回给我们),也就是说我们可以借助于它⾃定义Bean的创建过程。
Bean创建的三种⽅式中的静态⽅法和实例化⽅法和FactoryBean作⽤类似,FactoryBean使⽤较多,尤其在Spring框架⼀些组件中会使⽤,还有其他框架和Spring框架整合时使⽤
public class CompanyFactoryBean implements FactoryBean<Company> {
private String companyInfo;
public void setCompanyInfo(String companyInfo) {
this.companyInfo = companyInfo;
}
@Override
public Company getObject() throws Exception {
//创建复杂对象Company
Company company = new Company();
String[] strings = companyInfo.split(",");
company.setName(strings[0]);
company.setAddress(strings[1]);
company.setScale(Integer.parseInt(strings[2]));
return company;
}
@Override
public Class<?> getObjectType() {
return Company.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
public class Company {
private String name;
private String address;
private int scale;
}
<bean id="companyBean" class="com.zmx.edu.factory.CompanyFactoryBean" >
<property name="companyInfo" value="达达,北京,300"/>
</bean>
Object bean = applicationContext.getBean("&companyBean");
//&companyBean 获取FactoryBean
//companyBean 获取FactoryBean产生的对象
后置处理器
Spring提供了两种后处理bean的扩展接⼝,分别为 BeanPostProcessor 和BeanFactoryPostProcessor,两者在使⽤上是有所区别的。
⼯⼚初始化(BeanFactory)—> Bean对象
在BeanFactory初始化之后可以使⽤BeanFactoryPostProcessor进⾏后置处理做⼀些事情
在Bean对象实例化(并不是Bean的整个⽣命周期完成)之后可以使⽤BeanPostProcessor进⾏后置处理做⼀些事情
注意:对象不⼀定是springbean,⽽springbean⼀定是个对象
BeanPostProcessor
BeanPostProcessor是针对Bean级别的处理,可以针对某个具体的Bean.
//拦截实例化之后的对象(实例化并且注入属性)
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if("lazyResult".equalsIgnoreCase(beanName)){
System.out.println("MyBeanPostProcessor before 拦截方法处理 ");
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if("lazyResult".equalsIgnoreCase(beanName)){
System.out.println("MyBeanPostProcessor after 拦截方法处理 ");
}
return bean;
}
}
该接⼝提供了两个⽅法,分别在Bean的初始化⽅法前和初始化⽅法后执⾏,具体这个初始化⽅法指的是什么⽅法,类似我们在定义bean时,定义了init-method所指定的⽅法
定义⼀个类实现了BeanPostProcessor,默认是会对整个Spring容器中所有的bean进⾏处理。如果要对具体的某个bean处理,可以通过⽅法参数判断,两个类型参数分别为Object和String,第⼀个参数是每个bean的实例,第⼆个参数是每个bean的name或者id属性的值。所以我们可以通过第⼆个参数,来判断我们将要处理的具体的bean。
注意:处理是发⽣在Spring容器的实例化和依赖注⼊之后。
BeanFactoryPostProcessor
BeanFactory级别的处理,是针对整个Bean的⼯⼚进⾏处理,典型应⽤:PropertyPlaceholderConfigurer
BeanFactoryPostProcessor接⼝只提供了⼀个⽅法,⽅法参数为ConfigurableListableBeanFactory,该参数类型定义了⼀些⽅法
其中有个⽅法名为getBeanDefinition的⽅法,我们可以根据此⽅法,找到我们定义bean 的BeanDefinition对象。然后我们可以对定义的属性进⾏修改,以下是BeanDefinition中的⽅法
⽅法名字类似bean标签的属性,setBeanClassName对应bean标签中的class属性,所以当拿BeanDefinition对象时,我们可以⼿动修改bean标签中所定义的属性值。
BeanDefinition对象:在 XML 中定义的 bean标签,Spring解析 bean 标签成为⼀个JavaBean这个JavaBean 就是 BeanDefinition
注意:调⽤ BeanFactoryPostProcessor ⽅法时,这时候bean还没有实例化,此时 bean 刚被解析成BeanDefinition对象
11-19.Spring源码剖析(*********)
BeanFactory 及容器继承体系
BeanFactory 顶级接⼝⽅法栈
BeanFactory 容器继承体系
使⽤的 ApplicationContext 除了继承BeanFactory的⼦接⼝,还继承了ResourceLoader、MessageSource等接⼝,因此其提供的功能也就更丰富了。
ClasspathXmlApplicationContext 为例,源码说明 IoC 容器的初始化流程
IOC容器初始化主体流程:
IoC容器是Spring的核⼼模块,是抽象了对象管理、依赖关系管理的框架解决⽅案。Spring 提供了很多的容器,其中 BeanFactory 是顶层容器(根容器),不能被实例化,它定义了所有 IoC 容器 必须遵从的⼀套原则,具体的容器实现可以增加额外的功能,⽐如我们常⽤到的ApplicationContext,其下更具体的实现如 ClassPathXmlApplicationContext 包含了解析 xml 等⼀系列的内容,
AnnotationConfigApplicationContext 则是包含了注解解析等⼀系列的内容。Spring IoC 容器继承体系⾮使⽤哪个层次⽤哪个层次即可,不必使⽤功能⼤⽽全的。
Bean⽣命周期关键时机点
方法:创建⼀个类XXXBean ,让其实现⼏个特殊的接⼝,并分别在接⼝实现的构造器、接⼝⽅法中
断点,观察线程调⽤栈,分析出 Bean 对象创建和管理关键点的触发时机。
XXXBean:
public class XXXBean implements InitializingBean{
/**
* 构造函数
*/
public XXXBean(){
System.out.println("XXXBean 构造器...");
}
/**
* InitializingBean 接⼝实现
*/
public void afterPropertiesSet() throws Exception {
System.out.println("XXXBean afterPropertiesSet...");
}
}
BeanPostProcessor 接⼝实现类
public class MyBeanPostProcessor implements BeanPostProcessor {
public MyBeanPostProcessor() {
System.out.println("BeanPostProcessor 实现类构造函数...");
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if("XXXBean".equals(beanName)) {
System.out.println("BeanPostProcessor实现类postProcessBeforeInitialization⽅法被调⽤中");
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if("XXXBean".equals(beanName)) {
System.out.println("BeanPostProcessor 实现类 postProcessAfterInitialization⽅法被调⽤中");
}
return bean;
}
}
BeanFactoryPostProcessor 接⼝实现类
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
public MyBeanFactoryPostProcessor() {
System.out.println("BeanFactoryPostProcessor的实现类构造函数...");
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
System.out.println("BeanFactoryPostProcessor的实现⽅法调⽤中......");
}
}
applicationContext.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
https://www.springframework.org/schema/beans/spring-beans.xsd
">
<bean id="XXXBean" class="com.lagou.LagouBean"/>
<bean id="myBeanFactoryPostProcessor" class="com.zmx.MyBeanFactoryPostProcessor"/>
<bean id="myBeanPostProcessor" class="com.lagou.MyBeanPostProcessor"/>
</beans>
案例分析:
/**
* Ioc 容器源码分析基础案例
*/
@Test
public void testIoC() {
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
XXXBean bean = applicationContext.getBean(XXXBean.class);
System.out.println(bean);
}
分析结果:
1.Bean 的创建是在容器初始化时还是在 getBean 时
2.分析构造函数调⽤情况
3.分析 InitializingBean 之 afterPropertiesSet 初始化⽅法调⽤情况
4.分析BeanFactoryPostProcessor 初始化和调⽤情况
分别在构造函数、postProcessBeanFactory ⽅法处打断点,观察调⽤栈,发现BeanFactoryPostProcessor 初始化在AbstractApplicationContext类refresh⽅法的invokeBeanFactoryPostProcessors(beanFactory);
postProcessBeanFactory 在AbstractApplicationContext类refresh⽅法invokeBeanFactoryPostProcessors(beanFactory)调用
5.分析 BeanPostProcessor 初始化和调⽤情况
分别在构造函数、postProcessBeanFactory ⽅法处打断点,观察调⽤栈,发现BeanPostProcessor 初始化在AbstractApplicationContext类refresh⽅法的registerBeanPostProcessors(beanFactory);
postProcessBeforeInitialization 调⽤在AbstractApplicationContext类refresh⽅法的
finishBeanFactoryInitialization(beanFactory);
postProcessAfterInitialization 调⽤在AbstractApplicationContext类refresh⽅法的finishBeanFactoryInitialization(beanFactory);
6.总结
现 Bean对象创建的⼏个关键时机点代码层级的调⽤都在AbstractApplicationContext 类 的 refresh ⽅法中,这个⽅法对于Spring IoC 容器初始化来说相当关键,汇总如下:
容器初始化主流程
Spring IoC 容器初始化的关键环节就在 AbstractApplicationContext#refresh() ⽅法中,我们查看 refresh ⽅法来看容器创建的主体流程
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 第⼀步:刷新前的预处理
prepareRefresh();
/*
第⼆步:
获取BeanFactory;默认实现是DefaultListableBeanFactory
加载BeanDefition 并注册到 BeanDefitionRegistry
*/
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 第三步:BeanFactory的预准备⼯作(BeanFactory进⾏⼀些设置,⽐如context的类加载器等)
prepareBeanFactory(beanFactory);
try {
// 第四步:BeanFactory准备⼯作完成后进⾏的后置处理⼯作
postProcessBeanFactory(beanFactory);
// 第五步:实例化并调⽤实现了BeanFactoryPostProcessor接⼝的Bean
invokeBeanFactoryPostProcessors(beanFactory);
// 第六步:注册BeanPostProcessor(Bean的后置处理器),在创建bean的前后等执 ⾏
registerBeanPostProcessors(beanFactory);
// 第七步:初始化MessageSource组件(做国际化功能;消息绑定,消息解析);
initMessageSource();
// 第⼋步:初始化事件派发器
initApplicationEventMulticaster();
// 第九步:⼦类重写这个⽅法,在容器刷新的时候可以⾃定义逻辑
onRefresh();
// 第⼗步:注册应⽤的监听器。就是注册实现了ApplicationListener接⼝的监听器 bean
registerListeners();
/*
第⼗⼀步:
初始化所有剩下的⾮懒加载的单例bean
初始化创建⾮懒加载⽅式的单例Bean实例(未设置属性)
填充属性
初始化⽅法调⽤(⽐如调⽤afterPropertiesSet⽅法、init-method⽅法)
调⽤BeanPostProcessor(后置处理器)对实例bean进⾏后置处
*/
finishBeanFactoryInitialization(beanFactory);
/*
第⼗⼆步:
完成context的刷新。主要是调⽤LifecycleProcessor的onRefresh()⽅法,并且发布事
件 (ContextRefreshedEvent)
*/
finishRefresh();
}
BeanFactory创建流程
获取BeanFactory⼦流程
时序图:
BeanDefinition加载解析及注册⼦流程
(1)该⼦流程涉及到如下⼏个关键步骤
Resource定位:指对BeanDefinition的资源定位过程。通俗讲就是找到定义Javabean信息的XML⽂件,并将其封装成Resource对象。
BeanDefinition载⼊ :把⽤户定义好的Javabean表示为IoC容器内部的数据结构,这个容器内部的数据结构就是BeanDefinition。
注册BeanDefinition到 IoC 容器
(2)过程分析
Bean对象创建流程
Bean创建⼦流程⼊⼝在AbstractApplicationContext#refresh()⽅法的finishBeanFactoryInitialization(beanFactory) 处
DefaultListableBeanFactory类的preInstantiateSingletons⽅法,⼯⼚Bean或者普通Bean,最终都是通过getBean的⽅法获取实例
AbstractBeanFactory类的doGetBean⽅法, 核⼼部分
AbstractAutowireCapableBeanFactory类的⽅法
进⼊doCreateBean⽅法
lazy-init 延迟加载机制原理
普通 Bean 的初始化是在容器启动初始化阶段执⾏的,⽽被lazy-init=true修饰的 bean 则是在从容器⾥第⼀次进⾏context.getBean() 时进⾏触发。Spring 启动的时候会把所有bean信息(包括XML和注解)解析转化成Spring能够识别的BeanDefinition并存到Hashmap⾥供下⾯的初始化时⽤,然后对每个BeanDefinition 进⾏处理,如果是懒加载的则在容器初始化阶段不处理,其他的则在容器初始化阶段进⾏初始化并依赖注⼊。
public void preInstantiateSingletons() throws BeansException {
// 所有beanDefinition集合
List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);
// 触发所有⾮懒加载单例bean的初始化
for (String beanName : beanNames) {
// 获取bean 定义
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
// 判断是否是懒加载单例bean,如果是单例的并且不是懒加载的则在容器创建时初始化
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
// 判断是否是 FactoryBean
if (isFactoryBean(beanName)) {
final FactoryBean<?> factory = (FactoryBean<?>)
getBean(FACTORY_BEAN_PREFIX + beanName);
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
@Override
public Boolean run() {
return ((SmartFactoryBean<?>) factory).isEagerInit();
}
}, getAccessControlContext());
}
}else {
/*
如果是普通bean则进⾏初始化并依赖注⼊,此 getBean(beanName)接下来触发的逻辑
和
懒加载时 context.getBean("beanName") 所触发的逻辑是⼀样的
*/
getBean(beanName);
}
}
}
}
对于被修饰为lazy-init的bean Spring 容器初始化阶段不会进⾏ init 并且依赖注⼊,当第⼀次进⾏getBean时候才进⾏初始化并依赖注⼊
对于⾮懒加载的bean,getBean的时候会从缓存⾥头获取,因为容器初始化阶段 Bean 已经初始化完成并缓存了起来
Spring IoC循环依赖问题
概念:
循环依赖其实就是循环引⽤,也就是两个或者两个以上的 Bean 互相持有对⽅,最终形成闭环。⽐如A依赖于B,B依赖于C,C⼜依赖于A。
注意,这⾥不是函数的循环调⽤,是对象的相互依赖关系。循环调⽤其实就是⼀个死循环,除⾮有终结条件。
Spring中循环依赖场景有:
构造器的循环依赖(构造器注⼊)
Field 属性的循环依赖(set注⼊)
其中,构造器的循环依赖问题⽆法解决,只能拋出 BeanCurrentlyInCreationException 异常,在解决属性循环依赖时,spring采⽤的是提前暴露对象的⽅法。
循环依赖处理机制
单例 bean 构造器参数循环依赖(⽆法解决)
prototype 原型 bean循环依赖(⽆法解决)
对于原型bean的初始化过程中不论是通过构造器参数循环依赖还是通过setXxx⽅法产⽣循环依赖,Spring都 会直接报错处理。
AbstractBeanFactory.doGetBean()⽅法:
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
protected boolean isPrototypeCurrentlyInCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
return (curVal != null && (curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>)
curVal).contains(beanName))));
}
在获取bean之前如果这个原型bean正在被创建则直接抛出异常。原型bean在创建之前会进⾏标记这个beanName正在被创建,等创建结束之后会删除标记
try {
//创建原型bean之前添加标记
beforePrototypeCreation(beanName);
//创建原型bean
prototypeInstance = createBean(beanName, mbd, args);
} finally {
//创建原型bean之后删除标记
afterPrototypeCreation(beanName);
}
总结:Spring 不⽀持原型 bean 的循环依赖。
单例bean通过setXxx或者@Autowired进⾏循环依赖
Spring 的循环依赖的理论依据基于 Java 的引⽤传递,当获得对象的引⽤时,对象的属性是可以延后设置的,但是构造器必须是在获取引⽤之前
Spring通过setXxx或者@Autowired⽅法解决循环依赖其实是通过提前暴露⼀个ObjectFactory对象来完成的,简单来说ClassA在调⽤构造器完成对象初始化之后,在调⽤ClassA的setClassB⽅法之前就把ClassA实例化的对象通过ObjectFactory提前暴露到Spring容器中。
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences
&& isSingletonCurrentlyInCreation(beanName));
if (earl ySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean '" + beanName
+ "' to allow for resolving potential circular references");
}
//将初始化后的对象提前已ObjectFactory对象注⼊到容器中
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
ClassA调⽤setClassB⽅法,Spring⾸先尝试从容器中获取ClassB,此时ClassB不存在Spring容器中。
Spring容器初始化ClassB,同时也会将ClassB提前暴露到Spring容器中
ClassB调⽤setClassA⽅法,Spring从容器中获取ClassA ,因为第⼀步中已经提前暴露了ClassA,因此可以获取到ClassA实例
ClassA通过spring容器获取到ClassB,完成了对象初始化操作。这样ClassA和ClassB都完成了对象初始化操作,解决了循环依赖问题。