您现在的位置是:首页 >技术杂谈 >Spring学习小结_2网站首页技术杂谈
Spring学习小结_2
篇1
Spring学习小结_1
https://blog.csdn.net/m0_58730471/article/details/130075657?spm=1001.2014.3001.5501
12 Bean的生命周期
Spring Bean的生命周期是从 Bean 实例化之后(在内存中开辟空间),即通过反射创建出对象之后,到Bean成为一个完整对象,最终存储到单例池中,这个过程被称为Spring Bean的生命周期(完整的说还要加上消亡)。
Spring Bean的生命周期大体上分为三个阶段:
⚫ Bean的实例化阶段:Spring框架会取出BeanDefinition的信息进行判断当前Bean的范围是否是singleton的,是否是延迟加载的,是否是FactoryBean等,最终将一个普通的singleton的Bean通过反射进行实例化;
⚫ Bean的初始化阶段:Bean创建之后还仅仅是个"半成品",还需要对Bean实例的属性进行填充、执行一些Aware接口方法、执行BeanPostProcessor方法、执行InitializingBean接口的初始化方法、执行自定义初始化init方法等。
⚫ Bean的完成阶段:经过初始化阶段,Bean就成为了一个完整的Spring Bean,被存储到单例池singletonObjects中去了,即完成了Spring Bean的整个生命周期。
Bean的初始化阶段是Spring最具技术含量和复杂度的阶段,Aop增强功能,后面要学习的Spring的注解功能等、spring高频面试题Bean的循环引用问题都是在这个阶段体现的,所以重点阐述初始化阶段。
Bean的初始化阶段
Spring Bean的初始化过程涉及如下几个过程:
⚫ Bean实例的属性填充
⚫ Aware接口属性注入
⚫ BeanPostProcessor的before()方法回调
⚫ InitializingBean接口的初始化方法回调
⚫ 自定义初始化方法init回调
⚫ BeanPostProcessor的after()方法回调
Bean实例属性填充
Spring在进行属性注入时,会分为如下几种情况:
注入普通属性,String、int或存储基本类型的集合时,直接通过set方法的反射设置进去;
<bean class="com.hyl.service.impl.UserServiceImpl" id="userService">
<property name="name" value="hyl"/>
</bean>
注入单向对象引用属性时,从容器中getBean获取后通过set方法反射设置进去,如果容器中没有,则先创建被注入对象Bean实例(完成整个生命周期)后,在进行注入操作;
<bean class="com.hyl.mapper.impl.UserDaoImpl" id="userDao"/>
<bean class="com.hyl.service.impl.UserServiceImpl" id="userService">
<property name="userDao" ref="userDao"/>
</bean>
注入双向对象引用属性时,就比较复杂了,涉及了循环引用(循环依赖)问题
<bean class="com.hyl.mapper.impl.UserDaoImpl" id="userDao">
<property name="userService" ref="userService"/>
</bean>
<bean class="com.hyl.service.impl.UserServiceImpl" id="userService">
<property name="userDao" ref="userDao"/>
</bean>
循环引用
多个实体之间相互依赖并形成闭环的情况就叫做"循环依赖",也叫做"循环引用"
<bean class="com.hyl.service.impl.UserServiceImpl" id="userService">
<property name="userDao" ref="userDao"/>
</bean>
<bean class="com.hyl.mapper.impl.UserDaoImpl" id="userDao">
<property name="userService" ref="userService"/>
</bean>
执行创建过程:
Spring提供了三级缓存
存储 完整Bean实例
和 半成品Bean实例
,用于解决循环引用问题
在DefaultListableBeanFactory的上四级父类DefaultSingletonBeanRegistry中提供如下三个Map:
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
//1、最终存储单例Bean成品的容器,即实例化和初始化都完成的Bean,称之为"一级缓存"
Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
//2、早期Bean单例池,缓存半成品对象,且当前对象已经被其他对象引用了,称之为"二级缓存"
Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);
//3、单例Bean的工厂池,缓存半成品对象,对象未被引用,使用时在通过工厂创建Bean,称之为"三级缓存"
Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
}
UserService和UserDao循环依赖的过程结合上述三级缓存描述一下
UserService 实例化对象,但尚未初始化,将UserService存储到三级缓存;
UserService 属性注入,需要UserDao,从缓存中获取,没有UserDao;
UserDao实例化对象,但尚未初始化,将UserDao存储到到三级缓存;
UserDao属性注入,需要UserService,从三级缓存获取UserService,UserService从三级缓存移入二级缓存;
UserDao执行其他生命周期过程,最终成为一个完成Bean,存储到一级缓存,删除二三级缓存;
UserService 注入UserDao;
UserService执行其他生命周期过程,最终成为一个完成Bean,存储到一级缓存,删除二三级缓存。
图解:
按上述配置文件中的bean书写顺序,容器创建,先去实例化id=userService的bean(在内从中开辟空间),
当前属于半成品bean,存入三级缓存中
,之后进行属性的注入(需要注入id=userDao的bean),现在容器中还没有id=userDao的bean,所以暂定userService的后续创建,拐去创建userDao,同理也是先实例化userDao,当前也属于半成品bean,也存入三级缓存
。//3、单例Bean的工厂池,缓存半成品对象,对象未被引用,使用时在通过工厂创建Bean,称之为"三级缓存" Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
由源码可知放入三级缓存中的半成品bean外边是套了一层ObjectFactory的。
userDao在实例化存入三级缓存后,进行属性的注入。唉,发现,他需要注入userService,
就先去一级缓存(单利池)中找,没有找到,再去二级缓存中找,还是没有,就去三级缓存中找
,发现有需要的userService,就将其注入,并将三级缓存中的userService删除,在将userService存入二级缓存。
userDao在属性填充完后,继续执行后续的其他初始化流程,都执行完毕,称为一个完整的bean后,就从三级缓存中删除原来的userDao,将完整的userDao放入一级缓存(单利池)中。
之后userService结束暂停状态,从一级缓存中查找userDao,现在userDao已经存在了,就进行对应的属性注入,之后在执行完其他的初始化流程,得到一个完整的userService,就将二级缓存中的userService删除,将完整的userService也存入一级缓存(单利池)中。
Aware接口
Aware接口是一种框架辅助属性注入的一种思想,其他框架中也可以看到类似的接口。框架具备高度封装性,我们接触到的一般都是业务代码,一个底层功能API不能轻易的获取到,但是这不意味着永远用不到这些对象,如果用到了,就可以使用框架提供的类似Aware的接口,让框架给我们注入该对象。
Aware接口 | 回调方法 | 作用 |
---|---|---|
ServletContextAware | setServletContext(ServletContext context) | Spring框架回调方法注入ServletContext对象,web环境下才生效 |
BeanFactoryAware | setBeanFactory(BeanFactory factory) | Spring框架回调方法注入beanFactory对象 |
BeanNameAware | setBeanName(String beanName) | Spring框架回调方法注入当前Bean在容器中的beanName |
ApplicationContextAware | setApplicationContext(ApplicationContext applicationContext) | Spring框架回调方法注入applicationContext对象 |
Spring IoC 整体流程总结
13 Spring整合MyBatis剖析
xml整合形式
导入坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.3.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
applicationConfig.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
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:db.properties"/>
<!--创建userService对应的bean-->
<bean class="com.hyl.service.impl.UserServiceImpl" id="userService" >
<property name="userMapper" ref="userMapper"/>
</bean>
<!--配置数据源,使用el表达式,从db.properties中加载-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${driverClassName}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</bean>
<!--创建SqlSessionFactory存入容器中-->
<bean id="sessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--扫描指定路径下的mapper接口,自动创建对象,还是通过
this.getSqlSession().getMapper(this.mapperInterface)-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.hyl.mapper"/>
</bean>
</beans>
原理剖析
整合包里提供了一个SqlSessionFactoryBean和一个扫描Mapper的配置对象,SqlSessionFactoryBean一旦被实例化,就开始扫描Mapper并通过动态代理产生Mapper的实现类存储到Spring容器中。相关的有如下四个类:
- SqlSessionFactoryBean:需要进行配置,用于提供SqlSessionFactory;
- MapperScannerConfigurer:需要进行配置,用于扫描指定mapper注册BeanDefinition;
- MapperFactoryBean:Mapper的FactoryBean,获得指定Mapper时调用getObject方法;
- ClassPathMapperScanner:definition.setAutowireMode(2) 修改了自动注入状态,所以
MapperFactoryBean中的setSqlSessionFactory会自动注入进去。
配置SqlSessionFactoryBean作用是向容器中提供SqlSessionFactory
SqlSessionFactoryBean实现了FactoryBean和InitializingBean两个接口,所以会自动执行getObject() 和afterPropertiesSet()方法
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
...
public void afterPropertiesSet() throws Exception {
Assert.notNull(this.dataSource, "Property 'dataSource' is required");
Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
Assert.state(this.configuration == null && this.configLocation == null || this.configuration == null || this.configLocation == null, "Property 'configuration' and 'configLocation' can not specified with together");
this.sqlSessionFactory = this.buildSqlSessionFactory();
}
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
this.afterPropertiesSet();
}
return this.sqlSessionFactory;
}
....
}
配置MapperScannerConfigurer作用是扫描Mapper,向容器中注册Mapper对应的MapperFactoryBean
MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor和InitializingBean两个接口,会在postProcessBeanDefinitionRegistry方法中向容器中注册MapperFactoryBean
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
this.processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.set....
...
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",;
"));
}
}
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
...
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
this.logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
this.processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
...
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
....
//设置Mapper的beanClass是org.mybatis.spring.mapper.MapperFactoryBean
definition.setBeanClass(this.mapperFactoryBeanClass);
.....
//设置MapperBeanFactory 进行自动注入
//autowireMode取值:1是根据名称自动装配,2是根据类型自动装配
definition.setAutowireMode(2);
}
}
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
public int scan(String... basePackages) {
...
this.doScan(basePackages);
...
}
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
...
//将扫描到的类注册到beanDefinitionMap中,此时beanClass是当前类全限定名
this.registerBeanDefinition(definitionHolder, this.registry);
...
return beanDefinitions;
}
}
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionTemplate = this.createSqlSessionTemplate(sqlSessionFactory);
}
public T getObject() throws Exception {
return this.getSqlSession().getMapper(this.mapperInterface);
}
}