0
点赞
收藏
分享

微信扫一扫

Spring全家桶 源码 入门系列(一) --------容器与 bean


容器与 bean

文章目录

  • ​​容器与 bean​​
  • ​​1) 容器接口​​
  • ​​演示1 - BeanFactory 与 ApplicationContext 的区别​​
  • ​​代码参考​​
  • ​​收获💡​​
  • ​​演示2 - 国际化​​
  • ​​2) 容器实现​​
  • ​​演示1 - DefaultListableBeanFactory​​
  • ​​代码参考​​
  • ​​收获💡​​
  • ​​演示2 - 常见 ApplicationContext 实现​​
  • ​​代码参考​​
  • ​​收获💡​​
  • ​​3) Bean 的生命周期​​
  • ​​演示1 - bean 生命周期​​
  • ​​代码参考​​
  • ​​收获💡​​
  • ​​演示2 - 模板方法设计模式​​
  • ​​关键代码​​
  • ​​演示3 - bean 后处理器排序​​
  • ​​代码参考​​
  • ​​收获💡​​
  • ​​4) Bean 后处理器​​
  • ​​演示1 - 后处理器作用​​
  • ​​代码参考​​
  • ​​收获💡​​
  • ​​演示2 - @Autowired bean 后处理器运行分析​​
  • ​​代码参考​​
  • ​​收获💡​​
  • ​​5) BeanFactory 后处理器​​
  • ​​演示1 - BeanFactory 后处理器的作用​​
  • ​​代码参考​​
  • ​​收获💡​​
  • ​​演示2 - 模拟解析 @ComponentScan​​
  • ​​代码参考​​
  • ​​收获💡​​
  • ​​演示3 - 模拟解析 @Bean​​
  • ​​代码参考​​
  • ​​收获💡​​
  • ​​演示4 - 模拟解析 Mapper 接口​​
  • ​​代码参考​​
  • ​​收获💡​​
  • ​​6) Aware 接口​​
  • ​​演示 - Aware 接口及 InitializingBean 接口​​
  • ​​代码参考​​
  • ​​收获💡​​
  • ​​配置类 @Autowired 失效分析​​
  • ​​7) 初始化与销毁​​
  • ​​演示 - 初始化销毁顺序​​
  • ​​代码参考​​
  • ​​收获💡​​
  • ​​8) Scope​​
  • ​​演示1 - request, session, application 作用域​​
  • ​​代码参考​​
  • ​​收获💡​​
  • ​​分析 - singleton 注入其它 scope 失效​​
  • ​​演示2 - 4种解决方法​​
  • ​​代码参考​​
  • ​​收获💡​​
  • ​​结语​​

1) 容器接口

  • BeanFactory 接口,典型功能有:
  • getBean
  • ApplicationContext 接口,是 BeanFactory 的子接口。它扩展了 BeanFactory 接口的功能,如:
  • 国际化
  • 通配符方式获取一组 Resource 资源
  • 整合 Environment 环境(能通过它获取各种来源的配置信息)
  • 事件发布与监听,实现组件之间的解耦

可以看到,我们课上讲的,都是 BeanFactory 提供的基本功能,ApplicationContext 中的扩展功能都没有用到。

演示1 - BeanFactory 与 ApplicationContext 的区别
代码参考

com.libin.a01

收获💡

通过这个示例结合 debug 查看 ApplicationContext 对象的内部结构,学到:

  1. 到底什么是 BeanFactory
  • 它是 ApplicationContext 的父接口
  • 它才是 Spring 的核心容器, 主要的 ApplicationContext 实现都【组合】了它的功能,【组合】是指 ApplicationContext 的一个重要成员变量就是 BeanFactory
  1. BeanFactory 能干点啥
  • 表面上只有 getBean
  • 实际上控制反转、基本的依赖注入、直至 Bean 的生命周期的各种功能,都由它的实现类提供
  • 例子中通过反射查看了它的成员变量 singletonObjects,内部包含了所有的单例 bean
  1. ApplicationContext 比 BeanFactory 多点啥
  • ApplicationContext 组合并扩展了 BeanFactory 的功能
  • 国际化、通配符方式获取一组 Resource 资源、整合 Environment 环境、事件发布与监听
  • 新学一种代码之间解耦途径,事件解耦

建议练习:完成用户注册与发送短信之间的解耦,用事件方式、和 AOP 方式分别实现

注意

  • 如果 jdk > 8, 运行时请添加 --add-opens java.base/java.lang=ALL-UNNAMED,这是因为这些版本的 jdk 默认不允许跨 module 反射
  • 事件发布还可以异步,这个视频中没有展示,请自行查阅 @EnableAsync,@Async 的用法
演示2 - 国际化

public class TestMessageSource {
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();

context.registerBean("messageSource", MessageSource.class, () -> {
ResourceBundleMessageSource ms = new ResourceBundleMessageSource();
ms.setDefaultEncoding("utf-8");
ms.setBasename("messages");
return ms;
});

context.refresh();

System.out.println(context.getMessage("hi", null, Locale.ENGLISH));
System.out.println(context.getMessage("hi", null, Locale.CHINESE));
System.out.println(context.getMessage("hi", null, Locale.JAPANESE));
}
}

国际化文件均在 src/resources 目录下

messages.properties(空)

messages_en.properties

hi=Hello

messages_ja.properties

hi=こんにちは

messages_zh.properties

hi=你好

注意

  • ApplicationContext 中 MessageSource bean 的名字固定为 messageSource
  • 使用 SpringBoot 时,国际化文件名固定为 messages
  • 空的 messages.properties 也必须存在

2) 容器实现

Spring 的发展历史较为悠久,因此很多资料还在讲解它较旧的实现,这里出于怀旧的原因,把它们都列出来,供大家参考

  • DefaultListableBeanFactory,是 BeanFactory 最重要的实现,像控制反转依赖注入功能,都是它来实现
  • ClassPathXmlApplicationContext,从类路径查找 XML 配置文件,创建容器(旧)
  • FileSystemXmlApplicationContext,从磁盘路径查找 XML 配置文件,创建容器(旧)
  • XmlWebApplicationContext,传统 SSM 整合时,基于 XML 配置文件的容器(旧)
  • AnnotationConfigWebApplicationContext,传统 SSM 整合时,基于 java 配置类的容器(旧)
  • AnnotationConfigApplicationContext,Spring boot 中非 web 环境容器(新)
  • AnnotationConfigServletWebServerApplicationContext,Spring boot 中 servlet web 环境容器(新)
  • AnnotationConfigReactiveWebServerApplicationContext,Spring boot 中 reactive web 环境容器(新)

另外要注意的是,后面这些带有 ApplicationContext 的类都是 ApplicationContext 接口的实现,但它们是组合了 DefaultListableBeanFactory 的功能,并非继承而来

演示1 - DefaultListableBeanFactory
代码参考

com.libin.a02.TestBeanFactory

收获💡
  • beanFactory 可以通过 registerBeanDefinition 注册一个 bean definition 对象
  • 我们平时使用的配置类、xml、组件扫描等方式都是生成 bean definition 对象注册到 beanFactory 当中
  • bean definition 描述了这个 bean 的创建蓝图:scope 是什么、用构造还是工厂创建、初始化销毁方法是什么,等等
  • beanFactory 需要手动调用 beanFactory 后处理器对它做增强
  • 例如通过解析 @Bean、@ComponentScan 等注解,来补充一些 bean definition
  • beanFactory 需要手动添加 bean 后处理器,以便对后续 bean 的创建过程提供增强
  • 例如 @Autowired,@Resource 等注解的解析都是 bean 后处理器完成的
  • bean 后处理的添加顺序会对解析结果有影响,见视频中同时加 @Autowired,@Resource 的例子
  • beanFactory 需要手动调用方法来初始化单例
  • beanFactory 需要额外设置才能解析 ${} 与 #{}
演示2 - 常见 ApplicationContext 实现
代码参考

com.libin.a02.A02

收获💡
  1. 常见的 ApplicationContext 容器实现
  2. 内嵌容器、DispatcherServlet 的创建方法、作用

3) Bean 的生命周期

一个受 Spring 管理的 bean,生命周期主要阶段有

  1. 创建:根据 bean 的构造方法或者工厂方法来创建 bean 实例对象
  2. 依赖注入:根据 @Autowired,@Value 或其它一些手段,为 bean 的成员变量填充值、建立关系
  3. 初始化:回调各种 Aware 接口,调用对象的各种初始化方法
  4. 销毁:在容器关闭时,会销毁所有单例对象(即调用它们的销毁方法)
  • prototype 对象也能够销毁,不过需要容器这边主动调用

一些资料会提到,生命周期中还有一类 bean 后处理器:BeanPostProcessor,会在 bean 的初始化的前后,提供一些扩展逻辑。但这种说法是不完整的,见下面的演示1

演示1 - bean 生命周期
代码参考

com.libin.a03

创建

依赖注入

初始化

可用

销毁


创建前后的增强

  • postProcessBeforeInstantiation
  • 这里返回的对象若不为 null 会替换掉原本的 bean,并且仅会走 postProcessAfterInitialization 流程
  • postProcessAfterInstantiation
  • 这里如果返回 false 会跳过依赖注入阶段

依赖注入前的增强

  • postProcessProperties
  • 如 @Autowired、@Value、@Resource

初始化前后的增强

  • postProcessBeforeInitialization
  • 这里返回的对象会替换掉原本的 bean
  • 如 @PostConstruct、@ConfigurationProperties
  • postProcessAfterInitialization
  • 这里返回的对象会替换掉原本的 bean
  • 如代理增强

销毁之前的增强

  • postProcessBeforeDestruction
  • 如 @PreDestroy
收获💡
  1. Spring bean 生命周期各个阶段
  2. 模板设计模式, 指大流程已经固定好了, 通过接口回调(bean 后处理器)在一些关键点前后提供扩展
演示2 - 模板方法设计模式
关键代码

public class TestMethodTemplate {

public static void main(String[] args) {
MyBeanFactory beanFactory = new MyBeanFactory();
beanFactory.addBeanPostProcessor(bean -> System.out.println("解析 @Autowired"));
beanFactory.addBeanPostProcessor(bean -> System.out.println("解析 @Resource"));
beanFactory.getBean();
}

// 模板方法 Template Method Pattern
static class MyBeanFactory {
public Object getBean() {
Object bean = new Object();
System.out.println("构造 " + bean);
System.out.println("依赖注入 " + bean); // @Autowired, @Resource
for (BeanPostProcessor processor : processors) {
processor.inject(bean);
}
System.out.println("初始化 " + bean);
return bean;
}

private List<BeanPostProcessor> processors = new ArrayList<>();

public void addBeanPostProcessor(BeanPostProcessor processor) {
processors.add(processor);
}
}

static interface BeanPostProcessor {
public void inject(Object bean); // 对依赖注入阶段的扩展
}
}

演示3 - bean 后处理器排序
代码参考

com.libin.a03.TestProcessOrder

收获💡
  1. 实现了 PriorityOrdered 接口的优先级最高
  2. 实现了 Ordered 接口与加了 @Order 注解的平级, 按数字升序
  3. 其它的排在最后

4) Bean 后处理器

演示1 - 后处理器作用
代码参考

com.libin.a04

收获💡
  1. @Autowired 等注解的解析属于 bean 生命周期阶段(依赖注入, 初始化)的扩展功能,这些扩展功能由 bean 后处理器来完成
  2. 每个后处理器各自增强什么功能
  • AutowiredAnnotationBeanPostProcessor 解析 @Autowired 与 @Value
  • CommonAnnotationBeanPostProcessor 解析 @Resource、@PostConstruct、@PreDestroy
  • ConfigurationPropertiesBindingPostProcessor 解析 @ConfigurationProperties
  1. 另外 ContextAnnotationAutowireCandidateResolver 负责获取 @Value 的值,解析 @Qualifier、泛型、@Lazy 等
演示2 - @Autowired bean 后处理器运行分析
代码参考

com.libin.a04.DigInAutowired

收获💡
  1. AutowiredAnnotationBeanPostProcessor.findAutowiringMetadata 用来获取某个 bean 上加了 @Value @Autowired 的成员变量,方法参数的信息,表示为 InjectionMetadata
  2. InjectionMetadata 可以完成依赖注入
  3. InjectionMetadata 内部根据成员变量,方法参数封装为 DependencyDescriptor 类型
  4. 有了 DependencyDescriptor,就可以利用 beanFactory.doResolveDependency 方法进行基于类型的查找

5) BeanFactory 后处理器

演示1 - BeanFactory 后处理器的作用
代码参考

com.libin.a05

  • ConfigurationClassPostProcessor 可以解析
  • @ComponentScan
  • @Bean
  • @Import
  • @ImportResource
  • MapperScannerConfigurer 可以解析
  • Mapper 接口
收获💡
  1. @ComponentScan, @Bean, @Mapper 等注解的解析属于核心容器(即 BeanFactory)的扩展功能
  2. 这些扩展功能由不同的 BeanFactory 后处理器来完成,其实主要就是补充了一些 bean 定义
演示2 - 模拟解析 @ComponentScan
代码参考

com.libin.a05.ComponentScanPostProcessor

收获💡
  1. Spring 操作元数据的工具类 CachingMetadataReaderFactory
  2. 通过注解元数据(AnnotationMetadata)获取直接或间接标注的注解信息
  3. 通过类元数据(ClassMetadata)获取类名,AnnotationBeanNameGenerator 生成 bean 名
  4. 解析元数据是基于 ASM 技术
演示3 - 模拟解析 @Bean
代码参考

com.libin.a05.AtBeanPostProcessor

收获💡
  1. 进一步熟悉注解元数据(AnnotationMetadata)获取方法上注解信息
演示4 - 模拟解析 Mapper 接口
代码参考

com.libin.a05.MapperPostProcessor

收获💡
  1. Mapper 接口被 Spring 管理的本质:实际是被作为 MapperFactoryBean 注册到容器中
  2. Spring 的诡异做法,根据接口生成的 BeanDefinition 仅为根据接口名生成 bean 名

6) Aware 接口

演示 - Aware 接口及 InitializingBean 接口
代码参考

com.libin.a06

收获💡
  1. Aware 接口提供了一种【内置】 的注入手段,例如
  • BeanNameAware 注入 bean 的名字
  • BeanFactoryAware 注入 BeanFactory 容器
  • ApplicationContextAware 注入 ApplicationContext 容器
  • EmbeddedValueResolverAware 注入 ${} 解析器
  1. InitializingBean 接口提供了一种【内置】的初始化手段
  2. 对比
  • 内置的注入和初始化不受扩展功能的影响,总会被执行
  • 而扩展功能受某些情况影响可能会失效
  • 因此 Spring 框架内部的类常用内置注入和初始化
配置类 @Autowired 失效分析

Java 配置类不包含 BeanFactoryPostProcessor 的情况


ApplicationContext BeanFactoryPostProcessor BeanPostProcessor Java配置类 1. 执行 BeanFactoryPostProcessor 2. 注册 BeanPostProcessor 3. 创建和初始化 3.1 依赖注入扩展(如 @Value 和 @Autowired) 3.2 初始化扩展(如 @PostConstruct) 3.3 执行 Aware 及 InitializingBean 3.4 创建成功 ApplicationContext BeanFactoryPostProcessor BeanPostProcessor Java配置类


Java 配置类包含 BeanFactoryPostProcessor 的情况,因此要创建其中的 BeanFactoryPostProcessor 必须提前创建 Java 配置类,而此时的 BeanPostProcessor 还未准备好,导致 @Autowired 等注解失效


ApplicationContext BeanFactoryPostProcessor BeanPostProcessor Java配置类 3. 创建和初始化 3.1 执行 Aware 及 InitializingBean 3.2 创建成功 1. 执行 BeanFactoryPostProcessor 2. 注册 BeanPostProcessor ApplicationContext BeanFactoryPostProcessor BeanPostProcessor Java配置类


对应代码

@Configuration
public class MyConfig1 {

private static final Logger log = LoggerFactory.getLogger(MyConfig1.class);

@Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
log.debug("注入 ApplicationContext");
}

@PostConstruct
public void init() {
log.debug("初始化");
}

@Bean // ⬅️ 注释或添加 beanFactory 后处理器对应上方两种情况
public BeanFactoryPostProcessor processor1() {
return beanFactory -> {
log.debug("执行 processor1");
};
}

}

注意

解决方法:

  • 用内置依赖注入和初始化取代扩展依赖注入和初始化
  • 用静态工厂方法代替实例工厂方法,避免工厂对象提前被创建

7) 初始化与销毁

演示 - 初始化销毁顺序
代码参考

com.libin.a07

收获💡

Spring 提供了多种初始化手段,除了课堂上讲的 @PostConstruct,@Bean(initMethod) 之外,还可以实现 InitializingBean 接口来进行初始化,如果同一个 bean 用了以上手段声明了 3 个初始化方法,那么它们的执行顺序是

  1. @PostConstruct 标注的初始化方法
  2. InitializingBean 接口的初始化方法
  3. @Bean(initMethod) 指定的初始化方法

与初始化类似,Spring 也提供了多种销毁手段,执行顺序为

  1. @PreDestroy 标注的销毁方法
  2. DisposableBean 接口的销毁方法
  3. @Bean(destroyMethod) 指定的销毁方法

8) Scope

在当前版本的 Spring 和 Spring Boot 程序中,支持五种 Scope

  • singleton,容器启动时创建(未设置延迟),容器关闭时销毁
  • prototype,每次使用时创建,不会自动销毁,需要调用 DefaultListableBeanFactory.destroyBean(bean) 销毁
  • request,每次请求用到此 bean 时创建,请求结束时销毁
  • session,每个会话用到此 bean 时创建,会话结束时销毁
  • application,web 容器用到此 bean 时创建,容器停止时销毁

有些文章提到有 globalSession 这一 Scope,也是陈旧的说法,目前 Spring 中已废弃

但要注意,如果在 singleton 注入其它 scope 都会有问题,解决方法有

  • @Lazy
  • @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
  • ObjectFactory
  • ApplicationContext.getBean
演示1 - request, session, application 作用域
代码参考

com.libin.a08

  • 打开不同的浏览器, 刷新 http://localhost:8080/test 即可查看效果
  • 如果 jdk > 8, 运行时请添加 --add-opens java.base/java.lang=ALL-UNNAMED
收获💡
  1. 有几种 scope
  2. 在 singleton 中使用其它几种 scope 的方法
  3. 其它 scope 的销毁时机
  • 可以将通过 server.servlet.session.timeout=30s 观察 session bean 的销毁
  • ServletContextScope 销毁机制疑似实现有误
分析 - singleton 注入其它 scope 失效

以单例注入多例为例

有一个单例对象 E

@Component
public class E {
private static final Logger log = LoggerFactory.getLogger(E.class);

private F f;

public E() {
log.info("E()");
}

@Autowired
public void setF(F f) {
this.f = f;
log.info("setF(F f) {}", f.getClass());
}

public F getF() {
return f;
}
}

要注入的对象 F 期望是多例

@Component
@Scope("prototype")
public class F {
private static final Logger log = LoggerFactory.getLogger(F.class);

public F() {
log.info("F()");
}
}

测试

E e = context.getBean(E.class);
F f1 = e.getF();
F f2 = e.getF();
System.out.println(f1);
System.out.println(f2);

输出

com.libin.demo.cycle.F@6622fc65
com.libin.demo.cycle.F@6622fc65

发现它们是同一个对象,而不是期望的多例对象

对于单例对象来讲,依赖注入仅发生了一次,后续再没有用到多例的 F,因此 E 用的始终是第一次依赖注入的 F




e 创建

e set 注入 f

f 创建


解决

  • 仍然使用 @Lazy 生成代理
  • 代理对象虽然还是同一个,但当每次使用代理对象的任意方法时,由代理创建新的 f 对象


使用f方法

使用f方法

使用f方法

e 创建

e set 注入 f代理

f 创建

f 创建

f 创建


@Component
public class E {

@Autowired
@Lazy
public void setF(F f) {
this.f = f;
log.info("setF(F f) {}", f.getClass());
}

// ...
}

注意

  • @Lazy 加在也可以加在成员变量上,但加在 set 方法上的目的是可以观察输出,加在成员变量上就不行了
  • @Autowired 加在 set 方法的目的类似

输出

E: setF(F f) class com.libin.demo.cycle.F$$EnhancerBySpringCGLIB$$8b54f2bc
F: F()
com.libin.demo.cycle.F@3a6f2de3
F: F()
com.libin.demo.cycle.F@56303b57

从输出日志可以看到调用 setF 方法时,f 对象的类型是代理类型

演示2 - 4种解决方法
代码参考

com.libin.a08.sub

  • 如果 jdk > 8, 运行时请添加 --add-opens java.base/java.lang=ALL-UNNAMED
收获💡
  1. 单例注入其它 scope 的四种解决方法
  • @Lazy
  • @Scope(value = “prototype”, proxyMode = ScopedProxyMode.TARGET_CLASS)
  • ObjectFactory
  • ApplicationContext
  1. 解决方法虽然不同,但理念上殊途同归: 都是推迟其它 scope bean 的获取

结语

如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、评论、收藏➕关注,您的支持是我坚持写作最大的动力。


举报

相关推荐

0 条评论