图灵课堂学习笔记
1. BeanFactory与ApplicationContext的关系
p56
ApplicationContext在BeanFactory基础上对功能进行了扩展
,例如:监听功能、国际化功能等
。BeanFactory的API更偏向底层,ApplicationContext的API大多数是对这些底层API的封装;- beanFactory容器对象:提供对于bean基本操作能力。查找bean 存储bean。
- applicationContext不仅继承了Beanfactory,而且还维护了beanfactory的引用。
- bean 的初始化时间不一样。
BeanFactory
是首次调用getBean()时才进行Bean 的创建。Application
是在配置文件加载,容器一创建Bean就实例化初始化好了。
1.1 beanfactory 和 application 继承体系
BeanFactory 实现->DefaultListableBreanFactory
ApplicationContext
1.2 bean 范围 初始化
- @scope
signleton、prototype
1.3 初始化 实现 InitializingBean
- InitializingBean初始化
afterPropertiesSet()在属性注入之后执行。
类似于在注入dao后,再执行 - DisposableBean 销毁
destroy()
除此之外,我们还可以通过实现 InitializingBean 接口,完成一些Bean的初始化操作,如下:
构造方法、注解postConstruct,实现InitializingBean方法afterPropertiesSet,bean初始化init方法执行顺序。
通过启动日志我们可以看出执行顺序优先级:构造方法 > postConstruct >afterPropertiesSet > init方法。
@Component
public class MyInitializingBean implements InitializingBean {
public MyInitializingBean() {
System.out.println("我是MyInitializingBean构造方法执行...");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("我是afterPropertiesSet方法执行...");
}
@PostConstruct
public void postConstruct() {
System.out.println("我是postConstruct方法执行...");
}
public void init(){
System.out.println("我是init方法执行...");
}
@Bean(initMethod = "init")
public MyInitializingBean test() {
return new MyInitializingBean();
}
}
1.4 实例化bean 方式 factoryBean
- 静态工厂方法实例化Bean
- 实例工厂方法实例化Bean
- 实现FactoryBean规范
延迟实例化Bean
public class MyFactoryBean implements FactoryBean<User> {
/**
* 返回bean
*
* @return
* @throws Exception
*/
@Override
public User getObject() throws Exception {
User user = new User();
user.setId(1L);
user.setName("zhangsan");
return user;
}
/**
* 返回bean 的类型 class
*
* @return
*/
@Override
public Class<?> getObjectType() {
return User.class;
}
}
1.5 spring的get 方法
Spring 的get方法
// 根据 beanName 获取容器中的 Bean 实例,需要手动强转
UserService userService = (UserService) applicationContext
.getBean("userService");
// 根据 Bean 类型去容器中匹配对应的 Bean 实例,如存在多个匹配 Bean 则报错
UserService userService2 = applicationContext
.getBean(UserService.class);
// 根据 beanName 获取容器中的 Bean 实例,指定 Bean 的 Type 类型
UserService userService3 = applicationContext
.getBean("userService",
UserService.class);
2.Bean 实例化的基本流
ioc p119
singletonObjects 单例池
- beanfactory :
- signletonObjects
单例池
- beandifinitionMap
bean定义集合
- signletonObjects
简化说:
- 加载xml配置文件,解析获取配置中的每个的信息,封装成一个个的BeanDefinition对象;
- 将BeanDefinition存储在一个名为beanDefinitionMap的Map<String,BeanDefinition>中;
- ApplicationContext底层遍历beanDefinitionMap,创建Bean实例对象;
- 创建好的Bean实例对象,被存储到一个名为singletonObjects的Map<String,Object>中;
- 当执行applicationContext.getBean(beanName)时,从singletonObjects去匹配Bean实例返回。
程
BeanDefinition
Spring容器在进行初始化时,会将xml配置的<bean>
的信息封装成一个BeanDefinition对象
,所有的BeanDefinition存储到一个名beanDefinitionMap
的Map集合中去,Spring框架在对该Map进行遍历,使用反射创建Bean实例对象
,创建好的Bean对象存储在一个名为singletonObjects的Map集合中
,当调用getBean方法
时则最终从该Map集合中取出Bean实例对象返回
beandifiniton:保存了bean的信息,比如beanclass,是否单例,是否懒加载。
2.1 spring 的后处理器
以达到动态注册BeanDefinition
,动态修改BeanDefinition
,以及动态修改Bean
的作用。Spring主要有两种后处理器:
⚫ BeanFactoryPostProcessor:Bean工厂后处理器,在BeanDefinitionMap填充完毕,Bean实例化之前执行
; 执行一次
⚫ BeanPostProcessor:Bean后处理器,一般在Bean实例化之后,填充到单例池singletonObjects之前执行
。每个bean 实例化后都执行一次。
2.1.1 bean工厂的后置处理器 BeanFactoryPostProcessor
Bean工厂后处理器 – BeanFactoryPostProcessor
可以注册beandefinition
BeanFactoryPostProcessor是一个接口规范
,实现了该接口的类只要交由Spring容器管理的话,那么Spring就会回调该接口的方法,用于对BeanDefinition注册和修改的功能。
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
/**
* @param beanFactory BeanFactory的子接口 其实是 DefaultListableBeanFactory
* @throws BeansException
*/
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
//动态注册BeanDefinition 是个接口 使用RootBeanDefinition
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition();
rootBeanDefinition.setBeanClassName("com.hyp.pojo.User");
// rootBeanDefinition.setBeanClass(User.class);
// 没有注册到beanDefinitionmap中的方法
//通过beanFactory 强转 DefaultListableBeanFactory
DefaultListableBeanFactory defaultListableBeanFactory=
(DefaultListableBeanFactory) beanFactory;
//注入注册到beanDefinitionmap中
defaultListableBeanFactory.registerBeanDefinition("user001",rootBeanDefinition);
}
}
2.1.2 专门注册beandefinition 的bean工厂后置处理器
提供了一个BeanFactoryPostProcessor
的子接口BeanDefinitionRegistryPostProcessor专门用于注册BeanDefinition操作
@Component
public class MyBeanDefinitionRegistyPostProcessor implements BeanDefinitionRegistryPostProcessor {
/**
* 本类实现,用来快速注册beandefinition 的方法
* @param registry
* @throws BeansException
*/
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
//动态注册BeanDefinition 是个接口 使用RootBeanDefinition
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition();
rootBeanDefinition.setBeanClassName("com.hyp.pojo.User");
// rootBeanDefinition.setBeanClass(User.class);
registry.registerBeanDefinition("user001",rootBeanDefinition);
System.out.println("先被调用");
}
/**
* BeanFactoryPostProcessor 的方法
* @param beanFactory
* @throws BeansException
*/
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println("后被调用");
}
}
2.2 bean 的后置处理器
Bean后处理器 – BeanPostProcessor
每个类都调用一次
Bean被实例化后,到最终缓存到名为singletonObjects单例池之前
,中间会经过Bean的初始化过程
,例如:属性的填充
、初始方法init
的执行等,其中有一个对外进行扩展的点BeanPostProcessor
,我们称为Bean后处理。跟上面的Bean工厂后处理器相似,它也是一个接口,实现了该接口并被容器管理的BeanPostProcessor,会在流程节点上被Spring自动调用
。
2.3 bean 的生命周期
p145
Spring Bean的生命周期是从 Bean 实例化之后
(反射创建对象),即通过反射创建出对象之后,到Bean成为一个完整对象
,最终存储到单例池中,这个过程被称为Spring Bean的生命周期。Spring Bean的生命周期大体上分为三个阶段:
Bean的实例化阶段
:
Spring框架会取出BeanDefinition的信息进行判断
(各种if的判断)当前Bean的范围是否是singleton的
,是否不是延迟加载的
,是否不是FactoryBean
等,最终将一个普通的singleton的Bean通过反射进行实例化;
Bean的初始化阶段:
·
Bean创建之后还仅仅是个"半成品",还需要对Bean实例的属性进行填充
、执行一些Aware接口方法
、执行BeanPostProcessor方法
、执行InitializingBean接口的初始化方法
、执行自定义初始化init方法等
。该阶段是Spring最具技术含量和复杂度的阶段,Aop增强功能
,后面要学习的Spring的注解功能等、spring高频面试题Bean的循环引用问题都是在这个阶段体现的;
Bean的完成阶段
:
经过初始化阶段,Bean就成为了一个完整的Spring Bean,被存储到单例池 singletonObjects中去了
,即完成了Spring Bean的整个生命周期。
2.3.1 生命周期步骤
- bean 的属性填充
- Aware接口的属性注入
如果自己实现aware接口,spring会注入属性
- BeanPostProcessor before() 方法
- @PostConstruct() 方法
- InitializingBean 接口的初始化方法
- 自定义初始化方法 回调 initMethod=“”
- BeanPostProcessor after() 方法调用
2.3.2 属性填充
BeanDefinition 中有对当前Bean实体的注入信息通过属性propertyValues进行了存储,
属性注入的三种情况。
- 注入普通属性,String、int或存储基本类型的集合时,直接通过set方法的反射设置进去;
注入单向对象引用属性时
,(user类需要注入userDao类,userDao容器中没有)从容器中getBean获取后通过set方法反射设置进去,如果容器中没有,则先创建被注入对象Bean实例(完成整个生命周期)后,在进行注入操作;- 注入双向对象引用属性时,就比较复杂了,涉及了循环引用(循环依赖)问题
循环依赖问题
解决
// 1.一级缓存 单例池map 最终存储完整bean成品的容器 初始化和实例化都完成的bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
//2.二级缓存 bean 不是完整的bean 还没有完全初始化
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
// 3.单例 Bean 的工厂池,缓存半成品对象,对象未被引用,使用时在通过工厂创建 Bean ,称之为 " 三级缓存
//半成品bean 包装一个ObjectFactory 未被别人引用,创建对象就注入进去。
// UserDao通过第三级缓存获取到了对象(user) 将该对象(user) 注入到获取UserDao中,把user从第三级缓存移除,user注入到第二级缓存中。
private final 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,存储到一级缓存,删除二三级缓存。
2.3.3 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对象 |
2.3 ioc 整体流程
3 整合mybatis
-
配置SqlSessionFactoryBean
作用 将sqlSessionFactory 存储到spring 容器中 -
MapperScannerConfigurer
作用 扫描指定包,产生mapper对象存储到spring 容器
3.1 原理
P166
Spring整合MyBatis的原理剖析
整合包里提供了·一个SqlSessionFactoryBean
和一个扫描Mapper的配置对象
,SqlSessionFactoryBean一旦被实例化,就开始扫描
Mapper并通过动态代理产生Mapper的实现类
存储到Spring容器中。相关的有如下四个类:
- SqlSessionFactoryBean:需要进行配置,用于提供SqlSessionFactory;
- 解释
配置SqlSessionFactoryBean作用是向容器中提供SqlSessionFactory,SqlSessionFactoryBean实现了FactoryBean
和InitializingBean
两个接口,所以会自动执行getObject() 和afterPropertiesSet()方法
SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean{
public void afterPropertiesSet() throws Exception {
// 创建 SqlSessionFactory 对象
this.sqlSessionFactory = this.buildSqlSessionFactory();
}
public SqlSessionFactory getObject() throws Exception {
return this.sqlSessionFactory;
}
}
- MapperScannerConfigurer:需要进行配置,用于扫描指定mapper注册BeanDefinition;
配置MapperScannerConfigurer
作用是扫描Mapper,向容器中注册Mapper对应的MapperFactoryBean
,MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor
和InitializingBean两个接口
,会在postProcessBeanDefinitionRegistry方法中向容器中注册MapperFactoryBean
- MapperFactoryBean:Mapper的FactoryBean,获得指定Mapper时调用getObject方法;
- ClassPathMapperScanner:definition.setAutowireMode(2) 修改了自动注入状态,所以MapperFactoryBean中的setSqlSessionFactory会自动注入进去。
PS:autowireMode取值:1是根据名称自动装配,2是根据类型自动装配
4.注解
4.1 注解依赖注入
xml配置 注解 描述
注解 | 描述 |
---|---|
@Scope | 在类上或使用了@Bean标注的方法上,标注Bean的作用范围,取值为singleton或prototype |
@Lazy | 在类上或使用了@Bean标注的方法上,标注Bean是否延迟加载,取值为true和false |
@PostConstruct | 在方法上使用,标注Bean的实例化后执行的方法 |
@PreDestroy | 在方法上使用,标注Bean的销毁前执行方法 |
容器中有多个Userdao 可以注入List
属性注入注解 | 描述 |
---|---|
@Value | 使用在字段或方法上,用于注入普通数据 |
@Autowired | 使用在字段或方法上,用于根据类型(byType)注入引用数据 ·默认通过byType方式注入 |
@Qualifier | 使用在字段或方法上,结合@Autowired,根据名称注入 |
@Resource | 使用在字段或方法上,根据类型或名称进行注入 默认通过byName方式注入 |
//通过@Value 注入properties文件中的属性
@Value("haohao")
private String username;
@Value("haohao")
public void setUsername(String username){
System.out.println(username);
}
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.username}")
public void setUsername(String username){
System.out.println(username);
}
4.2 非自定义bean注解开发
非自定义Bean不能像自定义Bean一样使用@Component进行管理
,非自定义Bean要通过工厂的方式进行实例化,使用@Bean标注方法即可,@Bean的属性为beanName,如不指定为当前工厂方法名称
// 将方法返回值 Bean 实例以 @Bean 注解指定的名称存储到 Spring 容器中
@Bean("dataSource")
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
// 使用@Qualifier 根据名称进行Bean的匹配;
// 使用@Value 根据名称进行普通数据类型匹配。
@Bean
public Object objectDemo02(@Qualifier("userDao") UserDao userDao,
@Value("${jdbc.username}") String username){
System.out.println(userDao);
System.out.println(username);
return new Object();
}
4.3 Spring 配置类开发
@Configuration 注解标识的类为配置类,替代原有xml配置文件,
该注解第一个作用是标识该类是一个配置类
,
第二个作用是具备@Component作用
@PropertySource 注解用于加载外部properties资源配置,替代原有xml中的 <context:property-
placeholder location=“”/> 配置
@Import 用于加载其他配置类
@import使用:
- ①直接编写到@Import中,并且id值 是全类名
- ②自定义ImportSelector接口的实现类,通过selectimports方法实现(方法的返回值 就是要纳入IoC容器的Bean) 。 并且 告知程序 自己编写的实现类。
@Import({Orange.class,MyImportSelector.class}) - ③编写ImportBeanDefinitionRegistrar接口的实现类,重写方法
@Import({Orange.class,MyImportSelector.class,ImportBeanDefinitionRegistrar.class})
@PropertySource({"classpath:jdbc.properties","classpath:xxx.properties"})
@Configuration
@ComponentScan("com.hyp")
public class SpringConfiguration {
}
4.3.1 import的使用 原理链接
链接@Import 学习使用
- 普通导入Bean.class
@Import(value = {User.class})
public class SpringConfiguration {}
- importSelector
public class MyImportSelector implements ImportSelector {
/**
* 为什么要使用ImportSelector?
* ImportSelector在SpringBoot中大量被使用,
* 各种@EnableXXX注解表示开启XXX,这些注解基本上都是使用了@Import注解导入一个ImportSelector。
* 比如需要开启Eureka,开启Nacos,只需要简单的一行注解就能搞定。
* @param importingClassMetadata
* @return
*/
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//数组中放入需要引入spring中的类名
return new String[]{Test.class.getName()};
}
}
然后创建一个自定义注解 @EnableTest:
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MySelector.class)//引入MySelector.class
public @interface EnableTest {
}
最后创建一个App用来测试:
@EnableTest//这里加上EnableTest注解
public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac=new AnnotationConfigApplicationContext(App.class);
System.out.println(ac.getBean(Test.class));//测试是否能够从容器中获取到外部的Test的对象。
}
}
测试结果肯定是能够获取到的!
为什么要使用ImportSelector?
ImportSelector在SpringBoot中大量被使用,各种@EnableXXX注解表示开启XXX,这些注解基本上都是使用了@Import注解导入一个ImportSelector。
比如需要开启Eureka,开启Nacos,只需要简单的一行注解就能搞定。
- ImportBeanDefinitionRegistrar