如果你看到了注解,那么一定有什么代码在什么地方处理了它.

Alan Hohn

我教Java课程时强调的一点是注解是惰性的。换句话说,它们只是标记,可能具有某些属性,但没有自己的行为。因此,每当你在一段Java代码上看到一个注解时,就意味着必须有一些其他的Java代码来寻找那个注解并包含真正的智能来做一些有用的东西。

不幸的是,这种推理的问题在于,确切地确定哪一段代码正在处理注解是非常困难的,特别是如果它在库中。处理注解的代码可能会令人困惑,因为它使用反射并且必须以非常抽象的方式编写。所以我认为值得看看一个做得很好的例子来看看它是如何运行的。

我们详细研究一下 Spring 框架中的 InitDestroyAnnotationBeanPostProcessor 类是如何工作的。选择这个,因为它相对简单,只做了一些相对容易解释的事情, 碰巧和我手头的工作相关。

Spring Bean 的后处理

首先,我想首先解释一下 Spring 的用途。Spring 框架所做的一件事就是“依赖注入”。这改变了我们以往用代码将模块串在一起的方式。例如,假设我们编写了一些需要连接数据库的应用程序逻辑, 但并想将提供该连接的特定硬类编码到应用程序逻辑中,我们可以在构造函数或setter方法中将其表示为依赖项:

class MyApplication {
private DataConnection data;
...
public void setData(DataConnection data) {
this.data = data;
}
...
}

当然,如果想的话, 我们可以自己编写一个简单的库完成这种依赖注入,从而避免添加对 Spring 的依赖项。但是如果我们在编写一个复杂的应用程序, 想将各模块连接在一起,那么Spring可以非常方便。

既然没有什么神秘的,如果我们要让 Spring 为我们注入这些依赖,那么就会有一个权衡。Spring 需要“知道”依赖关系以及应用程序中的类和对象。Spring 处理这个问题的方法多是由 Spring 框架对对象进行实例化; 从而可以在称为"应用程序上下文"的大数据结构中跟踪管理这此对象。

后处理和初始化

而且这里是 InitDestroyBeanPostProcessor 进入的地方 。如果 Spring 要处理实例化,那么在对象实例化完成之后,但是在应用程序开始真正的运行之前,需要进行一些“额外工作”。需要做的一件“额外工作”就是调用对象来告诉他们什么时候完全设置好,这样他们就可以进行任何需要的额外初始化。如果我们使用“setter”注入,如上所述,便通过调用setXxx() 方法注入依赖项,这一点尤其重要,因为在调用对象的构造函数时这些依赖项并不可用。所以 Spring 需要允许用户指定在初始化对象后才应该调用的某个方法的名称。

Spring 一直支持使用XML配置文件来定义由 Spring 来实例化的对象,在这种情况下,有一个 'init-method' 属性可以用来指定初始化的方法。显然,在这种情况下,它仍然需要反射来实际查找并调用该方法。自Java 5起, 增加了注解,所以Spring 也支持带注解的标记方法,将它们标识为Spring应该实例化的对象,识别需要注入的依赖项,以及识别应该调用的初始化和销毁​​方法。

最后一项 InitDestroyBeanPostProcessor 由其子类或其中一个子类处理。后处理器是一种特殊的对象,由Spring实例化,实现后处理器接口。因为它实现了这个接口,所以Spring会在每个Spring实例化的对象上调用一个方法,允许它修改甚至替换该对象。这是Spring采用模块化架构方法的一部分,可以更轻松地扩展功能。

这是怎么运作的?

事实上, JSR-250 确定了一些“常见”注解,包括 @PostConstruct, 用于标记初始化方法,@PreDestroy 注解, 用于注解销毁方法的。不同的是,InitDestroyBeanPostProcessor 被设计成可以处理任何注解集,因此它提供了识别注解的方法:

    public void setInitAnnotationType(Class<? extends Annotation> initAnnotationType) {
this.initAnnotationType = initAnnotationType;
}
...
public void setDestroyAnnotationType(Class<? extends Annotation> destroyAnnotationType) {
this.destroyAnnotationType = destroyAnnotationType;
}

请注意,这些是普通的 setter 方法,因此这个对象本身可以使用 Spring 进行设置。就我而言,我使用Spring 的 StaticApplicationContext,见我以前的文章

一旦 Spring 实例化了各种对象并注入了所有依赖项,它就会在所有后处理器上为每个对象调用 postProcessBeforeInitialization 方法 。这使后处理器有机会在初始化之前修改或替换对象。因为已经注入了依赖项,所以这是 InitDestroyAnnotationBeanPostProcessor 调用初始化方法的地方。

    LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
try {
metadata.invokeInitMethods(bean, beanName);
}

由于我们对代码如何处理注解感兴趣,我们感兴趣 findLifecycleMetadata() 方法,因为这是对类进行检查的地方。该方法检查缓存,该缓存用于避免执行超过必要的反射,因为它可能很昂贵。如果尚未检查该类,则调用 buildLifecycleMetadata() 方法。该方法的内容如下:

ReflectionUtils.doWithLocalMethods(targetClass, new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
if (initAnnotationType != null) {
if (method.getAnnotation(initAnnotationType) != null) {
LifecycleElement element = new LifecycleElement(method);
currInitMethods.add(element);
}
}
...
}
});

这里 ReflectionUtils 是一个方便的类,简化了反射的使用。除此之外,它还将经过反射的众多已检查异常转换为未经检查的异常(?),从而使事情变得更容易。此特定方法仅迭代本地方法(即不是继承的方法),并为每个方法调用回调。

完成所有设置之后,检查注解的部分非常无聊; 它只是调用Java反射方法来检查注解,如果找到它,则将该方法存储为初始化方法。

总结

事实上,这里最终发生的事情很简单,这就是我在教反射时所要做的事情。调试使用注解来控制行为的代码可能具有挑战性,因为从外部来看它非常不透明,所以很难想象发生了什么(或者没有发生)和什么时候发生。但最终,正在发生的事情只是Java代码; 它可能不会立即显现出代码的位置,但它就在那里。

Spring是如何处理注解的的更多相关文章

  1. Spring IoC @Autowired 注解详解

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

  2. Spring IoC 公共注解详解

    前言 本系列全部基于 Spring 5.2.2.BUILD-SNAPSHOT 版本.因为 Spring 整个体系太过于庞大,所以只会进行关键部分的源码解析. 什么是公共注解?公共注解就是常见的Java ...

  3. Spring+Mybatis基于注解整合Redis

    基于这段时间折腾redis遇到了各种问题,想着整理一下.本文主要介绍基于Spring+Mybatis以注解的形式整合Redis.废话少说,进入正题. 首先准备Redis,我下的是Windows版,下载 ...

  4. Spring MVC常用注解

    cp by http://www.cnblogs.com/leskang/p/5445698.html 1.@Controller 在SpringMVC 中,控制器Controller 负责处理由Di ...

  5. 利用spring AOP 和注解实现方法中查cache-我们到底能走多远系列(46)

    主题:这份代码是开发中常见的代码,查询数据库某个主表的数据,为了提高性能,做一次缓存,每次调用时先拿缓存数据,有则直接返回,没有才向数据库查数据,降低数据库压力. public Merchant lo ...

  6. Spring中@Autowired注解、@Resource注解的区别

    Spring不但支持自己定义的@Autowired注解,还支持几个由JSR-250规范定义的注解,它们分别是@Resource.@PostConstruct以及@PreDestroy. @Resour ...

  7. spring mvc3的注解@ResponseBody 自动返回jason

    第三种利用spring mvc3的注解@ResponseBody 例如: @ResponseBody @RequestMapping("/list") public List< ...

  8. 基于spring mvc的注解DEMO完整例子

    弃用了struts,用spring mvc框架做了几个项目,感觉都不错,而且使用了注解方式,可以省掉一大堆配置文件.本文主要介绍使用注解方式配置的spring mvc,之前写的spring3.0 mv ...

  9. 转:Spring中@Autowired注解、@Resource注解的区别

    Pay attention: When using these annotations, the object itself has to be created by Spring context. ...

随机推荐

  1. react 使用 redux 的时候 用 ref获取子组件的state

    由于 redux是无状态的,所以当我们在子组件中使用了 redux的时候,再父组件中,使用  ref 来获取子组件的state时,发现为一个空对象. 其实这个是有解决方案法的,原因在于 我们使用的 r ...

  2. MySQL--4操作数据表中的记录小结

    最常用,最复杂的语句: 每一项的: 表的参照  From 条件    WHERE 进行记录的分组 GROUP BY 分组的时候对分组的条件进行设定  HAVING 对结果进行排序  ORDER BY ...

  3. (转载)Spring定时任务的几种实现

    spring框架定时任务 一.分类 从实现的技术上来分类,目前主要有三种技术(或者说有三种产品): Java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerT ...

  4. MessageBox ---> error C2660: “CWnd::MessageBoxA”: 函数不接受 4 个参数

    解决办法: ::MessageBoxA(NULL,TEXT("程序即将退出,谢谢你的试用!"),TEXT("SIMPLETRBO有线调度台"),MB_OK); ...

  5. 20180309 - C# demo - 1

    using System; namespace HelloWorldApplication { class HelloWorld { static void Main(string[] args) { ...

  6. MPI Maelstrom(East Central North America 1996)(poj1502)

    MPI Maelstrom 总时间限制:  1000ms 内存限制:  65536kB 描述 BIT has recently taken delivery of their new supercom ...

  7. jQuery事件--keypress([[data],fn])和trigger(type,[data])

    keypress([[data],fn]) 概述 当键盘或按钮被按下时,发生 keypress 事件 keypress 事件与 keydown 事件类似.当按钮被按下时,会发生该事件.它发生在当前获得 ...

  8. copy on write,代理模式

    StringBuilder 内部用了代理的技术实现, 是可变字符串的代理 .net设计模式实例之代理模式(Proxy Pattern) 一.代理模式简介(Brief Introduction) 代理模 ...

  9. rt.jar sun package

          安装完JDK后,会在%JAVA_HOME% /jdk文件夹下生成一个src.zip,此文件夹对应rt.jar中的java源码,但细心研究后发现rt.jar中sun包下的文件不存在,也就是说 ...

  10. Java技术整理1---反射机制及动态代理详解

    1.反射是指在程序运行过程中动态获取类的相关信息,包括类是通过哪个加载器进行加载,类的方法和成员变量.构造方法等. 如下示例可以通过三种方法根据类的实例来获取该类的相关信息 public static ...