一、Ioc基础特性
1.1 BeanFactory和ApplicationContext的区别
BeanFactory是Spring Ioc容器的顶级接口,它定义了一些基础的功能与规范。ApplicationContexts是BeanFactory的子接口,它包含了BeanFactory全部的功能,并且还提供了一些额外的功能,比如国际化支持和资源访问(加载xml、java配置)等。
1.2 启动Ioc容器的方式
- Java模式下
- ClassPathXmlApplicationContext : 从classpath下加载配置文件
- FileSystemXmlApplicationContext : 从文件系统下加载配置文件(全路径名)
- AnnotationConfigApplicationContext : 纯注解模式下启动
- Web模式下
通过在web.xml中配置监听器ContextLoaderListener来启动,该类在spring-web模块下- xml方式
<?xml version="1.0" encoding="UTF-8"?>
<web-app
version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<!-- 配置全局参数,配置xml所在的位置-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!-- 配置启动监听-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
- 配置类方式
<?xml version="1.0" encoding="UTF-8"?>
<web-app
version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<!-- 全部配置参数,配置要使用注解方式启动Ico-->
<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.config.SpringConfig</param-value>
</context-param>
<!-- 配置启动监听-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
1.3 纯xml方式
- 文件头
xmlns:xx : xml nameSpace 命名空间,xx为前缀,因为beans 是最基本的标签,所以没有指定前缀。
xsd :指定命名空间应该包含哪些标签,允许使用哪些标签。
<?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">
</beans>
- Bean实例化的方式
- 无参构造方法
spring通过反射的方法去实例化bean
<bean id="accountDao" class="com.lagou.dao.Impl.AccountDaoImpl"/>
我们可以自己创建某个类,并把它放入Ioc容器进行管理,根据获取这个类的方式是否是static的分为一下两种
- 使用静态方法创建
<bean id="accountDao" class="com.lagou.factory.AccountDaoFactory" factory-method="getAccountDaoStatic"/>
- 使用实例化方法构建
<bean id="factory" class="com.lagou.factory.AccountDaoFactory">
<bean id="accountDao" factory-bean="factory" factory-method="getAccountDao"/>
- Bean的作用域
spring 默认的作用域为singleton,可以通过scope 来配置生命周期
<bean id="accountDao" class="com.lagou.dao.Impl.AccountDaoImpl" scop="xxx"/>
scop | 解释 |
---|---|
singleton | 整个Ioc容器中只有一个改类的实例 |
prototype | 每次获取都创建一个新的实例 |
request | web应用中,每次请求都创建一个新的实例 |
session | web应用中,每次回话都创建一个新的实例 |
- Bean的生命周期
- singleton:生命周期与容器生命周期一致
创建:容器创建时,对象就被创建
存在:只要容器在,对象一直存在
销毁:当容器销毁时,对象也一起销毁 - prototype:容器只负责创建,不负责销毁
创建:获取对象时,才创建对象
存在:对象一直在使用
销毁:对象不再使用会被jvm回收
- Bean的属性
属性 | 解释 |
---|---|
id | 类的唯一标识,不可重复 |
class | 创建Bean对象的全路径 |
name | 给Bean指定名称,多个名称可以用空格分隔,一般不使用 |
factory-bean | ⽤于指定创建当前bean对象的⼯⼚bean的唯⼀标识。当指定了此属性之后,class属性失效。 |
factory-method | ⽤于指定创建当前bean对象的⼯⼚⽅法,与class配合使用时,该方法必须是static的。与factory-bean配合使用时,方法无需是static的。 |
scope | 作用域 |
init-method | Bean初始化方法,在类创建时会去调用,必须是无参的 |
destory-method | Bean销毁前会执行的方法,只有在scop=singleton时生效(其他作用域容器不负责对象的销毁) |
- 依赖注入的方式
- 构造器注入
Bean需要提供有参的构造方法,使用constructor-arg标签,该标签属性如下
属性 | 解释 |
---|---|
name | 指定要赋值的参数名称 |
index | 指定要赋值的参数的索引坐标,从0开始 |
value | 赋予参数的值,用于基本数据类型和string类型 |
ref | 赋予参数的值,用于引用类型,内容为bean的id |
- set方法注入
Bean需要提供注入属性的set方法,使用property标签,该标签属性如下
属性 | 解释 |
---|---|
name | 指定要赋值的参数名称 |
value | 赋予参数的值,用于基本数据类型和string类型 |
ref | 赋予参数的值,用于引用类型,内容为bean的id |
对于复杂类型的注入,分为两类,一类是集合,一类是键值对
<bean id="transferService" class = "com.lagou.service.Impl.TransferServiceImpl">
<property name="accountDao" ref ="accountDao"/>
<property name="setParam">
<set>
<value>value1</value>
<value>value2</value>
</set>
</property>
<property name="listParam">
<list>
<value>list1</value>
<value>list2</value>
</list>
</property>
<property name="mapParam">
<map>
<entry key="key1" value="val1"/>
<entry key="key2" value="val2"/>
</map>
</property>
<property name="propertyParam">
<props>
<prop key="key1">val1</prop>
<prop key="key2">val2</prop>
</props>
</property>
</bean>
在List结构的集合数据注⼊时, array , list , set 这三个标签通⽤,除了可以value 标签内部直接写值,也可以使⽤ bean标签配置⼀个对象,或者⽤ ref 标签引⽤⼀个已经配合的bean的唯⼀标识。
在Map结构的集合数据注⼊时, map 标签使⽤ entry ⼦标签实现数据注⼊, entry 标签可以使⽤key和value属性指定存⼊map中的数据。使⽤value-ref属性指定已经配置好的bean的引⽤。同时 entry 标签中也可以使⽤ ref 标签,但是不能使⽤ bean 标签。⽽ prop 标签中不能使⽤ ref 或者 bean 标签引⽤对象
1.4 xml+注解方式
实际项目开发中,一般都是采用xml+注解的方式进行配置。自己开发的类一般使用注解,对于第三方的类采用xml配置。使用注解不需要引入额外的jar包,在配置启动的时候仍然以加载xml开始。
- 在xml中开启对注解的扫描
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 开启注解扫描,并指定要扫描的包-->
<context:component-scan base-package="com.lagou"/>
- Ioc中xml与注解的对应
xml属性 | 对应注解 |
---|---|
bean | @Component(""),添加在类上,可以在括号内指定id,如果不指定默认为首字母小写的类名,为了方便区分层级,还提供了@Controller,@Service,@Repository 三个注解,他们功能和@Componnet完全一致 |
scope | @Scope(“prototype”),添加在类上 |
init-method | @PostContruct,添加在类上 |
destory-method | @PreDestory,添加在类上 |
- Di中xml与注解的对应
- @Autowired:Spring提供的注解,可以填在属性或set方法上,它是根据类型进行注入,如果一个类型有多个子类时,还需要@Qualifier配合,找到唯一的bean。
- @Qualifier :选择注解,可以指定条件来选择bean,与其他标签是与的关系
- @Resource:j2ee提供的注入注解,默认是按照名字(id)进行注入,也可以指定类型注入(type=“xxx”)或同时指定(name=“xx”,type=“xx”),在jdk11以后移除了
1.5 纯注解方式
纯注解的方式就是把xml文件中的内容转换成java类配置,并删除xml文件,系统的启动入口变为java配置类
- @Configuration : 添加在类上,标识这是一个spring的配置类
- @ComponentScan : 添加在类上,指定要扫描的类
- @PropertySource : 添加在类上,指定要加载的配置文件
- @Import :添加在类上,指定要引入的其他配置文件
- @Bean : 添加在配置类的方法上,标识这个类要添加到容器中
- @Value : 添加在配置类的属性上,对属性赋值
二、Ioc高级特性
2.1 lazy-init 延迟加载
默认单例的bean,会在spring容器启动时,进行实例化,可以通过 lazy-init属性来设置,一个类的延迟加载,该属性默认值为false
- 该属性只对scop=singleton的类有效
- 为true时,只有在调用getBean方法时,才会实例化这个对象
<bean id="test" class="com.lagou.Test" lazy-init="true"/>
- 当用在bean上时,只有这个类延迟加载。如果用在beans上时,全部的类都延迟加载,但是bean上的延迟加载设置优先级更高
<beans defalut-lazy-init="true">
......
</beans>
- 对应的注解是@Lazy,用在@Bean处或具体类上
@Bean
@Lazy
public Test getTest(){
return new Test();
}
- 延迟加载是为了提高容器的启动速度,避免某些很少使用的类从一开始就占用资源
注意:虽然一个类A设置了延迟加载,但是如果另一个类B引用了A,而B没有延迟加载,那么在实例化B的时候,仍然回去实例化A,这种情况也符合延时加载的 bean 在第⼀次调⽤时才被实例化的规则。
2.2 FactoryBean
BeanFactory 是容器的顶级接口,它定义了以一些基础的功能,负责创建和管理bean。
FactoryBean 是自定义bean创建过程的接口,可以生产某一个类型的Bean,通过它我们可以自定义bean的创建过程。他多用于spring的一些组件或者整合第三发框架时。
public interface FactoryBean<T> {
@Nullable
T getObject() throws Exception;
@Nullable
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
}
- 对于factoryBean,通过id拿到的bean是它所产生的具体对象,而不是factoryBean本身。只有通过&+id 拿到的才是factoryBean本身
2.3 后置处理器
spring提供了两种后置处理器,BeanPostProcessor和BeanFactoryPostProcessor。
工厂初始化 -> bean 初始化
- BeanPostProcessor
在bean对象初始化(并不是bean整个生命周期完成)后进行一些操作。他可以针对某个bean进行处理。
该接口具体的实现位置如下图
注意: - init-method对应的注解postConstruct所执行位置在 6和7之间,并不是init-method的8。
- destory-method对应的注解PreDestorys所执行的位置在9之后,虚线框之前。
- BeanFactoryPostProcessor
BeanFactory级别的处理,是针对整个Bean的⼯⼚进⾏处理,在工厂初始化后,上图第一步之前执行
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
参数ConfigurableListableBeanFactory类中可以获取到所有的BeanDefinition,这样我们就可以对所有的bean定义进行处理,该接口一个典型的实现类是PropertyPlaceholderConfigurer,
他在解析完所有BeanDefinition后,把${xxx}的具体值进行替换。
三、源码剖析
- 从GitHub上下载源码并解压,下载地址spring5.1.x下载地址
- 安装Gradle,Gradle6.8.2下载地址,并配置环境变量,默认的仓库地址为用户目录下.gradle\caches\modules-2\files-2.1
#Gradle 的根目录
export GRADLE_HOME=/Users/soft/gradle-6.8.2
#Gradle 仓库地址
export GRADLE_USER_HOME=/Users/xxx/repository
export PATH=$PATH:$GRADLE_HOME/bin:$GRADLE_USER_HOME
- 导入项目并按照(core-oxm-context-beans-aspect-aop)的顺序进行构建。
工程->task->other->compileTestJava
1.Spring IoC容器初始化主体流程
1.1 Spring IoC的容器体系
Ioc容器是spring的核心模块,是抽象了对象管理、依赖关系管理的框架解决方案。Spring提供了很多的容器,其中BeanFactory是顶层容器,定义了所有Ico容器必须遵从的一套原则。具体的容器实现可以增加额外的功能。这个样细粒度的拆分,可以在需要哪些层次就继承哪些层次,避免想要实现一个功能而引入了一大堆不需要的功能。
BeanFactory提供的方法如下
BeanFactory继承体系
1.2 Bean生命周期关键点
- 创建MyTestBean、MyBeanPostProcessor、MyBeanFactoryProcessor三个类,并在构造方法、实现方法上打上断点
package com.lagou.pojo;
import org.springframework.beans.factory.InitializingBean;
public class MyTestBean implements InitializingBean {
public MyTestBean() {
System.out.println("MyTestBean构造器执行");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("MyTestBean类初始化方法执行");
}
}
package com.lagou.process;
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("MyBeanFactoryPostProcessor构造器执行");
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println("工厂类后置处理器执行");
}
}
package com.lagou.process;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanPostProcessor implements BeanPostProcessor {
public MyBeanPostProcessor() {
System.out.println("MyBeanPostProcessor构造器执行");
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("Bean后置拦截器before方法执行");
return null;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("Bean后置拦截器after方法执行");
return null;
}
}
- 在xml中配置三个类
<bean id="myTest" class="com.lagou.pojo.MyTestBean"/>
<bean id="myBeanFactoryPostProcessor" class="com.lagou.process.MyBeanFactoryPostProcessor"/>
<bean id="myBeanPostProcessor" class="com.lagou.process.MyBeanPostProcessor"/>
- 通过debug,观察调用栈,得出如下结论
关键点名称 | 触发时机 |
---|---|
Bean 构造器、初始化方法、before、after | refresh # finishBeanFactoryInitialization |
BeanPostProcessor 构造器 | refresh # registerBeanPostProcessors |
BeanFactoryPostProcessor 构造器、post方法 | refresh # invokeBeanFactoryPostProcessors |
1.3 Ioc容器初始化主体流程
@Override
public void refresh() throws BeansException, IllegalStateException {
//加锁处理,防止处理时进行destory操作
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
//容器启动前的准备工作
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
//创建beanFactory,并解析配置文件,加载beanDefinition 注册到 BeanDefinitionRegister(map)中
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
//对BeanFactory进行一些设置
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
//BeanFactory 后置处理工作
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
//实例化 实现BeanFactoryPostProcessor接口的类,并执行后置方法
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
// 注册bean后置处理器,只是注册,并没有执行after、before,因为此时bean还没初始化
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
// 实例化国际化功能的组件
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
//注册监听器,实现了ApplicationListener接口的类
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
// 初始化非懒加载的单例bean,
// 填充属性、
// 执行初始化方法(如init-method、afterPropertiesSet)
// 执行bean后置处理器方法(after、before)
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
// 完成刷新,并发布事件
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
2.BeanFactory的获取流程
2.1 获取beanFactory子流程
2.2 BeanDefinition加载解析及注册流程
3.Bean对象创建流程
4. lazy-init延迟加载原理
- 分析
普通bean的初始化,是在容器启动初期阶段执行的,而被lazy-init=true修饰的bean则是在从容器中第一次进行getBean时触发的。spring启动的时候会把所有的bean信息加载成beanDefinition存储在map中,然后对每个beanDefinition进行处理,如果发现是懒加载则跳过,否则进行初始化和依赖注入。
@Override
public void preInstantiateSingletons() throws BeansException {
if (logger.isTraceEnabled()) {
logger.trace("Pre-instantiating singletons in " + this);
}
// Iterate over a copy to allow for init methods which in turn register new bean definitions.
// While this may not be part of the regular factory bootstrap, it does otherwise work fine.
//所有的beanid 存入一个集合
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
// Trigger initialization of all non-lazy singleton beans...
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
//非抽象的、单例的、非懒加载的bean才进行创建
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
if (isFactoryBean(beanName)) {
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
if (bean instanceof FactoryBean) {
FactoryBean<?> factory = (FactoryBean<?>) bean;
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged(
(PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit,
getAccessControlContext());
}
else {
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());
}
if (isEagerInit) {
getBean(beanName);
}
}
}
else {
getBean(beanName);
}
}
}
}
- 总结
- 对于被lazy-init=true修饰的bean Spring容器初始化的时候不会进行初始化,当第一次进行getBean时才进行初始化
- 对于非懒加载的bean,getBean是首先会去从缓存中获取,因为容器初始化时已经实例化并存储起来了
5. 循环依赖问题
5.1 什么是循环依赖问题
循环依赖其实就是循环引用,也就是两个或两个以上的bean相互引用,最终形成一个闭环。
注意:循环引用是指对象的相互依赖,并不是指方法的循环调用
循环引用的场景有两个:
- 构造器的循环依赖
- 属性的循环依赖
其中,构造器循环依赖spring无法处理,只能抛出BeanCurrentlyInCreationException 异常。属性循环依赖,spring采用提前暴露来解决
5.2循环依赖的处理机制
- bean单例的构造器循环依赖(无法解决)
spring的循环引用的理论基于java的引用传递,当获得对象引用后,属性的设置可以延后进行,但是构造器的执行在获取引用之前。 - 原型Bean的循环依赖(无法解决)
无论是构造器还是属性spring都会直接报错处理
//AbstractBeanFactory.doGetBean()方法
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
- 单例属性或@Autowrite循环依赖
spring通过提前暴露一个ObjectFactory对象来实现,在创建完ClassA之后,在调用setClassB之前,就将ClassA实例化的对象通过ObjectFactory提前暴露到spring容器中。