一:ioc应用
Spring中,BeanFactory的作用是创建和管理bean的,它IoC容器的顶层接⼝,不过只定义了⼀些基础功能。 实际用的最多的是它的实现ApplicationContext,扩展了很多功能,是ioc容器的⾼级接⼝。
ioc容器启动方式:
- Java环境下启动
ClassPathXmlApplicationContext :从类的根路径下加载配置⽂件(推荐使⽤) FileSystemXmlApplicationContext :从磁盘路径上加载配置⽂件 AnnotationConfifigApplicationContext :纯注解模式下启动 Spring 容器@Test public void testIoC() throws Exception { // 通过读取classpath下的xml文件来启动容器(xml模式SE应用下推荐) ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); // 不推荐使用 //ApplicationContext applicationContext1 = new FileSystemXmlApplicationContext("文件系统的绝对路径"); // 第一次getBean该对象 Object accountPojo = applicationContext.getBean("accountPojo"); //执行业务……………… applicationContext.close(); }
- Web环境下启动(web.xml中配置)
- 从xml启动容器,先在在applictionContext.xml中配置bean,然后:
<!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> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </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> <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value> </context-param> <!--配置启动类--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>com.lagou.edu.SpringConfig</param-value> </context-param> <!--使用监听器启动Spring的IOC容器--> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> </web-app>
bean的配置就不是使用xml了,需要使用注解类SpringConfig
@Configuration @ComponentScan({"com.lagou.edu"}) @PropertySource({"classpath:jdbc.properties"}) 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; } }
知道启动方式后,分别来看看xml下和注解下的使用:
1-1:实例化对象的方式
a、无参构造
<!--配置service对象-->
<bean id="userService" class="com.lagou.service.impl.TransferServiceImpl">
</bean>
b、使⽤静态⽅法创建(不是简单的创建对象,可能在创建的过程 中会做很多额外的操作)
<!--使⽤静态⽅法创建对象的配置⽅式-->
<bean id="connectionUtils" class="com.lagou.edu.factory.CreateBeanFactory" factory-method="getInstanceStatic"/>
public class CreateBeanFactory {
public static ConnectionUtils getInstanceStatic() {
ConnectionUtils connectionUtils = new ConnectionUtils()
//做很多操作xxxxxx,赋值什么的
//……………………………………………………………………………………
return connectionUtils;
}
}
c、使用非静态方法创建(作用和静态方法类似)
<bean id="createBeanFactory" class="com.lagou.edu.factory.CreateBeanFactory"></bean>
<bean id="connectionUtils" factory-bean="createBeanFactory" factory-method="getInstance"/>
public class CreateBeanFactory {
public ConnectionUtils getInstance() {
return new ConnectionUtils();
}
}
1-2:Bean的作用范围及⽣命周期
认识单例和多例两种,默认单例:
singleton(单例模式)
prototype (原型模式,也叫多例模式)
<bean id="transferService"
class="com.lagou.service.impl.TransferServiceImpl" scope="singleton">
</bean>
单例模式: singleton
- 对象出⽣:当创建容器时,对象就被创建了。
- 对象活着:只要容器在,对象⼀直活着。
- 对象死亡:当销毁容器时,对象就被销毁了。
多例模式: prototype
- 对象出⽣:当使⽤对象时,创建新的对象实例。
- 对象活着:只要对象在使⽤中,就⼀直活着。
- 对象死亡:当对象⻓时间不⽤时,被java的垃圾回收器回收了。
除了scope,还有两个常用属性:
init-method 属性: ⽤于指定 bean 对象的初始化⽅法,此⽅法会在 bean 对象装配后调⽤。必须是⼀个⽆参⽅法。destory-method 属性: ⽤于指定 bean 对象的销毁⽅法,此⽅法会在 bean 对象销毁前执⾏。它只能为scope 是 singleton 时起作⽤
1-3:属性注入
构造函数注入:使用constructor-arg标签,类中必须有相应的有参构造
<constructor-arg name="connectionUtils" ref="connectionUtils"/>
<constructor-arg name="name" value="zhangsan"/>
<constructor-arg name="sex" value="1"/>
<constructor-arg name="money" value="100.6"/>
set注入:使用property标签
//简单属性注入:
<property name="ConnectionUtils" ref="connectionUtils"/>
<property name="name" value="zhangsan"/>
<property name="sex" value="1"/>
<property name="money" value="100.3"/>
//复杂属性注入:
<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>
实际使用中,纯xml已经很少了,像注入什么的,都是使用注解。
2、xml+注解模式:
ioc容器的启动,仍然从xml开始。但是xml中只配置一些第三方jar包中的bean,以及本项目中的bean扫描路径等等。
<!--开启注解扫描,base-package指定扫描的包路径-->
<context:component-scan base-package="com.lagou.edu"/>
<!--引入外部资源文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--第三方jar中的bean定义在xml中-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
注解解析:
@Component("accountDao") ,注解加在类上bean的 id 属性内容直接配置在注解后⾯,如果不配置,默认 为类的类名⾸字⺟⼩写;另外提供了@Controller 、 @Service、 @Repository 分别⽤于控制层类、服务层类、 dao 层类的 bean 定义,只是 为了更清晰的区分⽽已@Scope("prototype") ,默认单例,注解加在类上@PostConstruct,注解加在⽅法上,针对xml中的init-method标签。@PreDestory,注解加在⽅法上,针对xml中的destory-method标签。@Autowired 依赖注入,如果注入的接口有多个实现,使用@Qualififier选择@Resource注入dao, 同Autowired,清晰区分而已。 注意:jdk11中去掉了,要用需要单独引入依赖包<dependency><groupId> javax.annotation </groupId><artifactId> javax.annotation-api </artifactId><version> 1.3.2 </version></dependency>
3、纯注解模式(使用最广泛):
@Confifiguration 注解标记一个类为配置类,就不需要ApplicationContext.xml了。@ComponentScan 注解,替代 context:component-scan@PropertySource ,引⼊外部属性配置⽂件@Import 引⼊其他配置类@Value 对变量赋值,可以直接赋值,也可以使⽤ ${} 读取资源配置⽂件中的信息@Bean 将⽅法返回对象加⼊ SpringIOC 容器
下面来补充几个高级特性:
- lazy-Init=true 开启延迟加载
<bean id="testBean" calss="cn.lagou.LazyBean" lazy-init="true" />
启动ioc容器时,ApplicationContext 实例所有的singleton bean,开启延迟加载后,则会在第一次getBean的时候实例化。 如果bean1中引用了bean2,bean2开启了延迟加载, 那么由于 bean1 被实例 化时需要 bean2,所以bean2 也被实例化,这种情况也符合 延时加载的 bean 在第⼀次调⽤ 时才被实例化的规则。
开启延迟加载⼀定程度提⾼容器启动和运转性能,对于不长使用的bean可以开启。
- FactoryBean 和 BeanFactory
BeanFactory 接⼝是容器的顶级接⼝,定义了容器的⼀些基础⾏为,负责⽣产和管理 Bean 的⼀个⼯⼚。FactoryBean是一种特殊的bean,叫做工厂bean,作用是帮助生成一些普通的bean。我们上面说了,有一些bean的实例化过程,需要做很多事情,就可以借助工厂bean。
使用方式上面xml配置中介绍过。
- 后置处理器
- BeanPostProcessor:Bean对象实例化对bean进行后置处理
- BeanFactoryPostProcessor:在BeanFactory初始化之后进⾏后置处理
public interface BeanPostProcessor { @Nullable default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Nullable default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } }
提供了两个方法,分别在初始化方法(init-method指定的方法)前后执行。 默认对所有的bean都生效,如果要针对某个bean,用beanName来判定。 beanName代表bean的id或name属性。
注意:处理是发⽣在Spring容器的实例化和依赖注⼊之后。
BeanFactoryPostProcessor
BeanFactory 级别的处理,是针对整个Bean的⼯⼚进⾏处理。 比如拿到bean工厂中所有的BeanDefifinition对象,对原始属性进行修改。BeanDefifinition是刚把xml中bean标签的内容,读取解析为一个JavaBean,还没有实例化。
二:源码解析
准备工作:
知道了ioc的使用,其实ioc容器就是spring框架对bean如何创建和管理的一套综合流程的总称。ioc中BeanFactory是顶层容器,是一个接口,定了了所有ioc容器必须遵从的原则, 具体实现和扩展,有子类自己去实现。比如常用的ApplicationContext,其子类ClassPathXmlApplicationContext用于解析xml内容,AnnotationConfigApplicationContext用于解析注解的内容。 当然还有一些其他的,根据场景自由选择或扩展。
源码解析,我们已ClassPathXmlApplicationContext为例,看看如何实现ioc对bean的创建和管理功能。
我们来创建一个JavaBean,观察生命周期中的几个关键节点。
创建MyBean
import org.springframework.beans.factory.InitializingBean;
public class MyBean implements InitializingBean {
public MyBean() {
System.out.println("MyBean 构造器...");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("MyBean afterPropertiesSet...");
}
}
创建后置处理器
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanPostProcessor implements BeanPostProcessor {
public MyBeanPostProcessor() {
System.out.println("BeanPostProcessor 实现类构造函数...");
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if ("myBean".equals(beanName)) {
System.out.println("BeanPostProcessor 实现类postProcessBeforeInitialization ⽅法被调⽤中......");
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if ("myBean".equals(beanName)) {
System.out.println("BeanPostProcessor 实现类 postProcessAfterInitialization ⽅法被调⽤中......");
}
return bean;
}
}
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
public MyBeanFactoryPostProcessor() {
System.out.println("BeanFactoryPostProcessor 实现类构造函数...");
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory)
throws BeansException {
System.out.println("BeanFactoryPostProcessor的实现⽅法调⽤中......");
}
}
在ApplicationContext.xml中配置bean信息
<bean id="myBean" class="com.lagou.edu.bean.MyBean"/>
<bean id="myBeanFactoryPostProcessor"
class="com.lagou.edu.processor.MyBeanFactoryPostProcessor"/>
<bean id="myBeanPostProcessor" class="com.lagou.edu.processor.MyBeanPostProcessor"/>
编写test
@Test
public void testIoc() throws Exception {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
MyBean myBean = applicationContext.getBean(MyBean.class);
System.out.println(myBean);
}
开始解析:
打断点,源码解析开始:
1、bean是什么时候创建的?
未设置延迟加载情况下, 容器初始化过程中,已经创建了bean。
2、分析MyBean的构造函数
在构造函数处打个断点,看看什么时候调用构造函数的,之前都做了什么?
从调用栈中看到,从入口到最终的构造方法,中间走过很多流程。 它的顶层时机点在AbstractApplicationContext 类 的 refresh ⽅法中。
同理,分别对afterPropertiesSet方法,以及后置处理器中的各个方法打断点查看调用链,发现顶层时机点都在refresh方法中,说明该方法是Spring ioc的核心方法。
分析refresh()方法:
refresh中主要调用了以下方法:
// 第⼀步:刷新前的预处理 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();
refresh中我们需要关心的有几步:
第二步、获取BeanFactory
通过调用链看到,最终是在AbstractRefreshableApplicationContext的refreshBeanFactory
中,创建了DefaultListableBeanFactory。 但是这里还没有返回,接着就使用loadBeanDefinitions去加载bean信息了。来看看如何加载的?
依次调⽤多个类的 loadBeanDefifinitions ⽅法 ,⼀直执⾏到XmlBeanDefifinitionReader 的 doLoadBeanDefifinitions ⽅法
如上图,首先把xml文件流信息解析到document中,然后解析document对象,把对应bean标签解析到BeanDefinition中.
继续追踪,直到DefaultBeanDefinitionDocumentReader的doRegisterBeanDefinitions方法:
解析默认标签,也就是spring的标签。
然后就会判断是spring的哪一种标签,我们看bean标签。
继续跟注册方法, 发现是使用BeanDefinitionRegistry注册的,而我们用的DefaultListableBeanFactory就是BeanDefinitionRegistry其中一个子类,进入注册方法发现,就是用beanName为key,beanDefiniton为value,放到一个map中
到这里,就算是注册完成了。 主要流程包含了读取xml为流,将流解析为Document文档,再解析文档标签中每一个bean标签,生成对应的BeanDefinition,放到map中。主要流程中的时序图如下:
时序图总结来说就是:
1、AbstractBeanDefinitionReader中根据配置文件路径,加载为Resource
2、调用子类XmlBeanDefinitionReader将resource通过流解析为Document对象。
3、有了Document文档对象,就可以获取标签并解析了。 XmlBeanDefinitionReader并没有直接解析,而是使用BeanDefinitionDocumentReader来解析
4、最终,BeanDefinitionDocumentReader使用了BeanDefinitionParserDelegate来解析,BeanDefinitionParserDelegate就是专门解析生成BeanDefinition的。
beanDefinition注册之后(还未实例化),这时候才将beanFactory返回去。
紧接着refresh方法中的第四步和第五步,分别注册和调用了BeanFactory级别的后置处理器。之前说了factory级别的处理器,可以获取并操作BeanFactory中注册的bean的元数据,所以它必须在bean实例化之前就调用。
第六步,会注册bean级别的后置处理器,这里只是注册,后面会在bean实例化后,在初始化方法执行的前后调用。
其实不管哪一类后置处理器注册,都是从factory中所有的BeanDefinition中,查看哪些是后置处理器,重新放到另一个List中。 比如,bean级别处理器的就是放在下图的list中。
第十一步:bean的实例化
进入DefaultListableBeanFactory的preInstantiateSingletons方法,看到不管是FactroyBean还是普通bean,都是getBean时候实例化的
继续跟代码到bstractBeanFactory的doGetBean方法
继续跟代码到AbstractAutowireCapableBeanFactory的doCreateBean
实例化bean并且填充了属性之后,紧接着下一行的initializeBean方法就会调用bean的初始化方法。
当然,在调用的时候,会将refresh中第六步注册的所有后置处理器拿出来,循环看看是否对当前的bean生效并执行。
到这里,refresh中主要ioc注册及实例化就分析完成了。 要注意的是,上面说了bean的实例化是在getBean的方法中,对于懒加载的bean,容器初始化的时候并不会调用getBean方法,而是第一次使用获取bean的时候才调用并实例化。
循环依赖的问题:
在bean的实例化过程中,有个问题。 假如A的属性有B,那就要求实例化A的时候,B已经实例化好了。 这时候假如B也依赖A呢,也要求实例化B的时候,A已经好了。 怎么办?
spring使用了三级缓存来解决这个问题:
- 一级缓存 singletonObjects: 存放完整的bean实例 ,来源于二级缓存
- 二级缓存 earlySingletonObjects: 存放不完整的bean。 实例化后,还没有填充属性(初始化)的bean实例 (如果该bean是被代理过的,则返回的是代理对象),来源于三级缓存
- 三级缓存 singletonFactories: 存放一个单例工厂ObjectFactory,通过调用getObject方法,最终调用getEarlyBeanReference(beanName , mbd , bean)获取到不完整的bean(如果该bean是被代理过的,则返回的是代理对象)。
我们假设现在有这样的场景AService依赖BService,BService依赖AService
1. AService首先实例化,通过ObjectFactory半成品暴露在三级缓存中,并标记为创建中
2. 填充属性BService,发现BService还未进行过加载,就会先去加载BService
3. 实例化BService,也通过ObjectFactory半成品暴露在三级缓存,并标记为创建中
4. BService填充属性AService的时候,先从一级缓存获取不到,再从二级缓存获取不到,最后能够从三级缓存中拿到半成品的ObjectFactory
拿到ObjectFactory对象后,调用ObjectFactory.getObject()方法最终会调用getEarlyBeanReference()方法,这时我们会发现能够拿到AService实例(属性未填充),然后从三级缓存移除,放到二级缓存earlySingletonObjects中,而此时B注入的是一个半成品的实例A对象(不过随着B初始化完成后,A会继续进行后续的初始化操作,最终B会注入的是一个完整的A实例,因为在内存中它们是同一个对象)。下面是重点,我们发现这个二级缓存好像显得有点多余,可以去掉,只需要一级和三级缓存也可以做到解决循环依赖的问题???
只要两个缓存确实可以做到解决循环依赖的问题,但前提是这个bean没被AOP进行切面代理。 因为被代理后,getEarlyBeanReference中获取到的是代理对象,而每调用一次该法方法获取到的是一个新的代理对象,这样三级缓存中就不能保证是单例了。 所以我们在第一次从三级缓存 singletonFactories取出来之后,马上要放到二级缓存,同时删掉三级缓存。 这样下一个来获取的时候,在二级缓存中拿到的就是同一个对象,哪怕它是代理对象。
当最终bean完全实例化后,就会从二级缓存放到一级缓存中,并删除二级缓存。
bean实例化后先将半成品暴露到三级缓存
依赖注入时候,获取bean实例的流程和顺序: