Spring是如何处理注解的

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

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


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

相关文章
|
24天前
|
缓存 监控 Java
SpringBoot @Scheduled 注解详解
使用`@Scheduled`注解实现方法周期性执行,支持固定间隔、延迟或Cron表达式触发,基于Spring Task,适用于日志清理、数据同步等定时任务场景。需启用`@EnableScheduling`,注意线程阻塞与分布式重复问题,推荐结合`@Async`异步处理,提升任务调度效率。
379 128
|
1月前
|
XML 安全 Java
使用 Spring 的 @Aspect 和 @Pointcut 注解简化面向方面的编程 (AOP)
面向方面编程(AOP)通过分离横切关注点,如日志、安全和事务,提升代码模块化与可维护性。Spring 提供了对 AOP 的强大支持,核心注解 `@Aspect` 和 `@Pointcut` 使得定义切面与切入点变得简洁直观。`@Aspect` 标记切面类,集中处理通用逻辑;`@Pointcut` 则通过表达式定义通知的应用位置,提高代码可读性与复用性。二者结合,使开发者能清晰划分业务逻辑与辅助功能,简化维护并提升系统灵活性。Spring AOP 借助代理机制实现运行时织入,与 Spring 容器无缝集成,支持依赖注入与声明式配置,是构建清晰、高内聚应用的理想选择。
306 0
|
9天前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
113 2
|
25天前
|
XML Java 数据格式
常用SpringBoot注解汇总与用法说明
这些注解的使用和组合是Spring Boot快速开发和微服务实现的基础,通过它们,可以有效地指导Spring容器进行类发现、自动装配、配置、代理和管理等核心功能。开发者应当根据项目实际需求,运用这些注解来优化代码结构和服务逻辑。
181 12
|
1月前
|
Java 测试技术 数据库
使用Spring的@Retryable注解进行自动重试
在现代软件开发中,容错性和弹性至关重要。Spring框架提供的`@Retryable`注解为处理瞬时故障提供了一种声明式、可配置的重试机制,使开发者能够以简洁的方式增强应用的自我恢复能力。本文深入解析了`@Retryable`的使用方法及其参数配置,并结合`@Recover`实现失败回退策略,帮助构建更健壮、可靠的应用程序。
155 1
使用Spring的@Retryable注解进行自动重试
|
1月前
|
传感器 Java 数据库
探索Spring Boot的@Conditional注解的上下文配置
Spring Boot 的 `@Conditional` 注解可根据不同条件动态控制 Bean 的加载,提升应用的灵活性与可配置性。本文深入解析其用法与优势,并结合实例展示如何通过自定义条件类实现环境适配的智能配置。
104 0
探索Spring Boot的@Conditional注解的上下文配置
|
1月前
|
智能设计 Java 测试技术
Spring中最大化@Lazy注解,实现资源高效利用
本文深入探讨了 Spring 框架中的 `@Lazy` 注解,介绍了其在资源管理和性能优化中的作用。通过延迟初始化 Bean,`@Lazy` 可显著提升应用启动速度,合理利用系统资源,并增强对 Bean 生命周期的控制。文章还分析了 `@Lazy` 的工作机制、使用场景、最佳实践以及常见陷阱与解决方案,帮助开发者更高效地构建可扩展、高性能的 Spring 应用程序。
Spring中最大化@Lazy注解,实现资源高效利用
|
1月前
|
安全 IDE Java
Spring 的@FieldDefaults和@Data:Lombok 注解以实现更简洁的代码
本文介绍了如何在 Spring 应用程序中使用 Project Lombok 的 `@Data` 和 `@FieldDefaults` 注解来减少样板代码,提升代码可读性和可维护性,并探讨了其适用场景与限制。
Spring 的@FieldDefaults和@Data:Lombok 注解以实现更简洁的代码
|
1月前
|
Java 测试技术 编译器
@GrpcService使用注解在 Spring Boot 中开始使用 gRPC
本文介绍了如何在Spring Boot应用中集成gRPC框架,使用`@GrpcService`注解实现高效、可扩展的服务间通信。内容涵盖gRPC与Protocol Buffers的原理、环境配置、服务定义与实现、测试方法等,帮助开发者快速构建高性能的微服务系统。
236 0
|
1月前
|
XML Java 测试技术
使用 Spring 的 @Import 和 @ImportResource 注解构建模块化应用程序
本文介绍了Spring框架中的两个重要注解`@Import`和`@ImportResource`,它们在模块化开发中起着关键作用。文章详细分析了这两个注解的功能、使用场景及最佳实践,帮助开发者构建更清晰、可维护和可扩展的Java应用程序。
147 0
下一篇
oss教程