前言

本系列全部基于 Spring 5.2.2.BUILD-SNAPSHOT 版本。因为 Spring 整个体系太过于庞大,所以只会进行关键部分的源码解析。

什么是公共注解?公共注解就是常见的Java注解,特别是JSR-250中的注解。例如:@Resource@PostConstructor@PreDestroy 等等,本文也就主要分析这三个注解在 Spring 中是如何处理的。

正文

@Resource 注解的处理

@Resource 注解的处理类是 CommonAnnotationBeanPostProcessor,它通过实现 InstantiationAwareBeanPostProcessor 接口,重写 postProcessProperties() 方法实现对标注了 @Resource 注解的字段或方法的自动注入

InstantiationAwareBeanPostProcessor 接口的详细信息可以查看Spring IoC bean 的创建

关于 CommonAnnotationBeanPostProcessor 这个后置处理器是怎么加入到 beanFactory 中的,我们在 Spring IoC component-scan 节点详解 一文中介绍过主要是通过 AnnotationConfigUtils#registerAnnotationConfigProcessors() 实现的。

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(BeanDefinitionRegistry registry, @Nullable Object source) {

    // 省略其他代码...
// 注册用于处理@Resource、@PostConstructor、@PostDestroy注解的后置处理器
if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
} // 省略其他代码... }

BeanDefinition 合并后的后置处理

CommonAnnotationBeanPostProcessor#postProcessMergedBeanDefinition

public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
super.postProcessMergedBeanDefinition(beanDefinition, beanType, beanName);
// 寻找需要注入的字段或方法,并封装成 InjectionMetadata
InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
// 检查元数据中的注解信息
metadata.checkConfigMembers(beanDefinition);
}

上面代码中的 findAutowiringMetadata() 方法就是利用反射遍历类的所有字段和方法,找到标注了 @Resource 注解的,并缓存进 injectionMetadataCache 中。

注意:静态字段和静态方法会过滤掉。

findAutowiringMetadata() 方法基本和 AutowiredAnnotationBeanPostProcessor 中的一致,只是处理的注解不同而已,可以查查看Spring IoC @Autowired 注解详解一文中该方法的详解。

bean 属性后置处理

CommonAnnotationBeanPostProcessor#postProcessProperties

public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
// 从injectionMetadataCache缓存中获取需要注入的字段和方法
InjectionMetadata metadata = findResourceMetadata(beanName, bean.getClass(), pvs);
try {
// 进行注入
metadata.inject(bean, beanName, pvs);
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of resource dependencies failed", ex);
}
return pvs;
} // InjectMetadata.java
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
// 获取检查后的元素
Collection<InjectedElement> checkedElements = this.checkedElements;
// 如果checkedElements不为空就使用checkedElements,否则使用injectedElements
Collection<InjectedElement> elementsToIterate =
(checkedElements != null ? checkedElements : this.injectedElements);
if (!elementsToIterate.isEmpty()) {
// 遍历elementsToIterate
for (InjectedElement element : elementsToIterate) {
if (logger.isTraceEnabled()) {
logger.trace("Processing injected element of bean '" + beanName + "': " + element);
}
// 进行元素注入,见下文详解
element.inject(target, beanName, pvs);
}
}
}

元素注入

InjectionMetadata#inject
protected void inject(Object target, @Nullable String requestingBeanName, @Nullable PropertyValues pvs)
throws Throwable {
// 如果元素是字段
if (this.isField) {
// 强转成Field类型
Field field = (Field) this.member;
// 并设置为可访问
ReflectionUtils.makeAccessible(field);
// 然后使用反射设置值
field.set(target, getResourceToInject(target, requestingBeanName));
}
else {
// 检查是否跳过
if (checkPropertySkipping(pvs)) {
return;
}
try {
// 强转成Method类型
Method method = (Method) this.member;
// 并设置为可访问
ReflectionUtils.makeAccessible(method);
// 使用反射调用方法
method.invoke(target, getResourceToInject(target, requestingBeanName));
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
}

获取注入资源

ResourceElement#getResourceToInject
protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {
return (this.lazyLookup ? buildLazyResourceProxy(this, requestingBeanName) : getResource(this, requestingBeanName));
}

上面的 lazyLookup 就是是否在属性或方法上标注了 @Lazy 注解,该注解先暂不讨论,所以调用后面的 getResource() 方法。

CommonAnnotationBeanPostProcessor#getResource
protected Object getResource(LookupElement element, @Nullable String requestingBeanName) throws NoSuchBeanDefinitionException {
// 省略其它代码...
return autowireResource(this.resourceFactory, element, requestingBeanName);
} protected Object autowireResource(BeanFactory factory, LookupElement element, @Nullable String requestingBeanName) throws NoSuchBeanDefinitionException { Object resource;
Set<String> autowiredBeanNames;
String name = element.name; if (factory instanceof AutowireCapableBeanFactory) {
AutowireCapableBeanFactory beanFactory = (AutowireCapableBeanFactory) factory;
DependencyDescriptor descriptor = element.getDependencyDescriptor();
// 类型匹配(默认为true) && @Resource注解name属性不为空 && 当前beanFactory不包含名称为name的bean
if (this.fallbackToDefaultTypeMatch && element.isDefaultName && !factory.containsBean(name)) {
autowiredBeanNames = new LinkedHashSet<>();
// 按类型查找bean
resource = beanFactory.resolveDependency(descriptor, requestingBeanName, autowiredBeanNames, null);
if (resource == null) {
throw new NoSuchBeanDefinitionException(element.getLookupType(), "No resolvable resource object");
}
}
else {
// 按名称查找bean
resource = beanFactory.resolveBeanByName(name, descriptor);
autowiredBeanNames = Collections.singleton(name);
}
}
// 省略其它代码... return resource;
}

上面 autowireResource() 方法中按类型查找的 resolveDependency() 方法在Spring IoC bean 的创建一文中分析过,按名称查找 beanresolveBeanByName() 方法实际就是调用 getBean() 通过名称和类型去获取 bean

从上面的代码也可以看出一般情况下 @Resource 注解是按名称注入;而 @Autowired 注解时按类型注入,具体可以查看Spring IoC @Autowired 注解详解

@PostConstruct、@PreDestroy 注解的处理

处理 @PostConstruct@PreDestroy 注解的处理类是 InitDestroyAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor 继承与该类,相当于注册 CommonAnnotationBeanPostProcessor 时也注册了 InitDestroyAnnotationBeanPostProcessor

BeanDefinition 合并后的后置处理

InitDestroyAnnotationBeanPostProcessor#postProcessMergedBeanDefinition() 方法是通过 CommonAnnotationBeanPostProcessor#postProcessMergedBeanDefinition() 方法调用的,如下:

CommonAnnotationBeanPostProcessor#postProcessMergedBeanDefinition

public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
// 调用InitDestroyAnnotationBeanPostProcessor的postProcessMergedBeanDefinition()方法
super.postProcessMergedBeanDefinition(beanDefinition, beanType, beanName);
// 寻找需要注入的字段或方法,并封装成 InjectionMetadata
InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
// 检查元数据中的注解信息
metadata.checkConfigMembers(beanDefinition);
}

InitDestroyAnnotationBeanPostProcessor#postProcessMergedBeanDefinition

public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
// 寻找需要标注了@PostConstruct和@PreDestroy注解的方法,并封装进LifecycleMetadata
LifecycleMetadata metadata = findLifecycleMetadata(beanType);
// 检查元数据中的注解信息
metadata.checkConfigMembers(beanDefinition);
}

上面代码中的 findLifecycleMetadata() 方法,就是遍历当前初始化的 bean 包括其父类中所有标注了 @PostConstruct@PreDestroy 注解的方法,并封装成 LifecycleMetadata (该类是 InitDestroyAnnotationBeanPostProcessor 中一个内部类),并放入 lifecycleMetadataCache 缓存中。

这里我们简单看一下 LifecycleMetadata 这个类:

private class LifecycleMetadata {

    // 目标类,也就是当前正在初始化的bean
private final Class<?> targetClass;
// 存放标注了@PostConstruct的方法
private final Collection<LifecycleElement> initMethods;
// 存放标注了@PreDestroy的方法
private final Collection<LifecycleElement> destroyMethods; // 省略其它代码...
}

bean 的初始化前回调

InitDestroyAnnotationBeanPostProcessor#postProcessBeforeInitialization

public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// 从lifecycleMetadataCache缓存中获取LifecycleMetadata
LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
try {
// 反射调用所有初始化方法
metadata.invokeInitMethods(bean, beanName);
}
// 省略异常处理...
return bean;
}

看到这里我们知道为什么标注了 @PostConstruct 注解的方法比 InitializingBean#afterPropertiesSet() 和自定义初始化方法先调用了;因为其在 bean 的初始化前回调就已经调用了,而剩下的两个是在初始化方法中调用的,详情可以查看Spring IoC bean 的初始化一文。

bean 销毁前回调

我们先了解一下 DestructionAwareBeanPostProcessor,它继承自 BeanPostProcessor,如下:

public interface DestructionAwareBeanPostProcessor extends BeanPostProcessor {

    /**
* Bean 销毁前阶段回调
*/
void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException; /**
* bean实例是否要由此方法销毁
*/
default boolean requiresDestruction(Object bean) {
return true;
} }

InitDestroyAnnotationBeanPostProcessor#postProcessBeforeDestruction

public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
// 从lifecycleMetadataCache缓存中获取LifecycleMetadata
LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
try {
// 反射调用所有销毁方法
metadata.invokeDestroyMethods(bean, beanName);
}
// 省略异常处理...
}

和上面的 @PostConstruct 注解一样,@PreDestroy 注解标注的方法也比 DisposableBean#destroy() 和自定义销毁方法先调用。

总结

本文主要介绍了 @Resource@PostConstruct@PreDestroy 注解 Spring 是如何对其处理的,可以看出 Spring 的注解驱动大多依靠 实现 BeanPostProcessor 及其子类中的 bean 生命周期各个阶段的回调方法来进行实现的。

最后,我模仿 Spring 写了一个精简版,代码会持续更新。地址:https://github.com/leisurexi/tiny-spring

Spring IoC 公共注解详解的更多相关文章

  1. Spring IoC @Autowired 注解详解

    前言 本系列全部基于 Spring 5.2.2.BUILD-SNAPSHOT 版本.因为 Spring 整个体系太过于庞大,所以只会进行关键部分的源码解析. 我们平时使用 Spring 时,想要 依赖 ...

  2. Spring框架系列(6) - Spring IOC实现原理详解之IOC体系结构设计

    在对IoC有了初步的认知后,我们开始对IOC的实现原理进行深入理解.本文将帮助你站在设计者的角度去看IOC最顶层的结构设计.@pdai Spring框架系列(6) - Spring IOC实现原理详解 ...

  3. Spring框架系列(7) - Spring IOC实现原理详解之IOC初始化流程

    上文,我们看了IOC设计要点和设计结构:紧接着这篇,我们可以看下源码的实现了:Spring如何实现将资源配置(以xml配置为例)通过加载,解析,生成BeanDefination并注册到IoC容器中的. ...

  4. Spring框架系列(8) - Spring IOC实现原理详解之Bean实例化(生命周期,循环依赖等)

    上文,我们看了IOC设计要点和设计结构:以及Spring如何实现将资源配置(以xml配置为例)通过加载,解析,生成BeanDefination并注册到IoC容器中的:容器中存放的是Bean的定义即Be ...

  5. Spring IoC createBean 方法详解

    前言 本篇文章主要分析 Spring IoC 的 createBean() 方法的流程,以及 bean 的生命周期. 下面是一个大致的流程图: 正文 AbstractAutowireCapableBe ...

  6. Spring IoC component-scan 节点详解

    前言 我们在了解 Spring 容器的扩展功能 (ApplicationContext) 之前,先介绍下 context:component-scan 标签的解析过程,其作用很大是注解能生效的关键所在 ...

  7. Spring IoC getBean 方法详解

    前言 本篇文章主要介绍 Spring IoC 容器 getBean() 方法. 下图是一个大致的流程图: 正文 首先定义一个简单的 POJO,如下: public class User { priva ...

  8. Spring IOC 注入方式详解 附代码

    引言 Spring框架作为优秀的开源框架之一,深受各大Java开发者的追捧,相信对于大家来说并不陌生,Spring之所以这么流行,少不了他的两大核心技术IOC和IOP.我们这里重点讲述Spring框架 ...

  9. Spring MVC @RequestMapping注解详解

    @RequestMapping 参数说明 value:定义处理方法的请求的 URL 地址.(重点) method:定义处理方法的 http method 类型,如 GET.POST 等.(重点) pa ...

随机推荐

  1. mysql 大表mysqldump迁移方案

    场景 一张历史表product_history 500万数据,凌晨的才会将正式表的数据迁移到历史表,此次需求将历史表迁移到一个更便宜的数据库实例进行存储. 条件 1.此表不是实时写,凌晨才会更新 2. ...

  2. multimap遍历与查找

    std::multimap<int, std::string> m; m.insert(std::make_pair(0, "w0")); m.insert(std:: ...

  3. 03 . Django之腾讯云短信

    简介 由于项目在注册.登录.找回密码 时需要发送短信验证的功能,我们使用腾讯云短信做. 为什么要用腾讯云短信呢? 因为注册就送 100条免费短信 的额度. 注册腾讯云 注册一个腾讯云账户,腾讯云中提供 ...

  4. 谈谈Spring中的对象跟Bean,你知道Spring怎么创建对象的吗?

    本系列文章: 读源码,我们可以从第一行读起 你知道Spring是怎么解析配置类的吗? 配置类为什么要添加@Configuration注解? 推荐阅读: Spring官网阅读 | 总结篇 Spring杂 ...

  5. Java StringTokenizer 类使用方法,字符串分割

    Java StringTokenizer 属于 java.util 包,用于分隔字符串. StringTokenizer 构造方法: StringTokenizer(String str) :构造一个 ...

  6. IOS 发布 程序截图问题

    特别要注意那个有无状态栏时的像素要求 **注意:在截屏模拟器的时候,请把模拟器的Scale设置成100%(Window->Scale->100%) 开模拟器截图,运行每一个iOS型号,然后 ...

  7. 用头发换JS的第一天

    因为平时也在上班,这些都是在地铁上抽空开始看的,总结来说还是有一些收获存在,每次看都能发现一些新东西,废话不多说,开撸! JavaScript是一种网页编程语言,可以对网页的行为进行编程,简单来说HT ...

  8. 2019-02-12 html的初步学习

    前两天看了下python的爬虫,后面的抓包模拟登录实在是难弄,于是暂时放弃,来学学前端知识QAQ <!DOCTYPE html>文档类型符合HTML5标准 <html lang=&q ...

  9. Charles抓包1-Charles安装汉化(附正版注册码)

    目录 1.下载&&安装 2.汉化 1.下载&&安装 charles官网 charles下载 下载后直接安装即可. 2.汉化 下载提供的汉化包charles.jar(加群 ...

  10. C++入门-控制台版的通讯录管理系统

    通讯录管理系统 1.系统需求 通讯录是一个可以记录亲人.好友信息的工具. 本教程主要利用C++来实现一个通讯录管理系统 系统中需要实现的功能如下: 添加联系人:向通讯录中添加新人,信息包括(姓名.性别 ...