Spring是如何处理注解的

若是你看到了注解,那么必定有什么代码在什么地方处理了它.

Alan Hohnspring

我教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能够很是方便。app

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

后处理和初始化

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

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代码; 它可能不会当即显现出代码的位置,但它就在那里。

相关文章
相关标签/搜索