本文我们来梳理一下 Spring 的那些注解, 如下图所示, 大概从几方面列出了 Spring 的一些注解:
如果此图看不清楚也没事, 请运行下面的代码输出所有的结果.
Spring 目前的趋势是使用注解结合 Java 代码而不是配置来定义行为, 属性, 功能, 规则和扩展点, 因此梳理注解也是梳理 Spring 功能点的很好的方式, 全面的梳理可以补足我们知识点的漏洞.
查找所有注解
首先, 我们来创建一个项目, 使用 SPRING INITIALIZR 生成一个引入 Spring 各种组件的项目模板, 然后引入如下工具包:
- <dependency>
- <groupId>org.reflections</groupId>
- <artifactId>reflections</artifactId>
- <version>0.9.11</version>
- </dependency>
通过这个反射工具包, 我们可以创建一个 Spring Boot 应用程序, 以一行代码打印出所有 Spring 框架的注解:
- import org.reflections.Reflections;
- import org.springframework.boot.CommandLineRunner;
- import org.springframework.stereotype.Component;
- import java.lang.annotation.Annotation;
- @Component
- public class ScanAnnotationRunner implements CommandLineRunner {
- @Override
- public void run(String... args) throws Exception {
- new Reflections("org.springframework")
- .getSubTypesOf(Annotation.class)
- .stream()
- .map(clazz->clazz.getName())
- .sorted()
- .forEach(System.out::println);
- }
- }
输出结果这里就不给出了, 下面我们逐一进行梳理其中的一些重要注解.
有关注解
Java 的 Annotation 注解 (类似于 C# 的 Attribute 特性), 说白了就是给代码打上标签的能力. 我们可以配置这个标签的保留阶段, 仅源代码, 源代码 + 字节码, 源代码 + 字节码 + 运行时. 通过引入注解, 我们可以简单快速赋予代码生命力, 大大提高代码可读性和扩展性. 注解本身不具有任何能力, 只是一个标签, 但是我们可以定义各种标签然后实现各种标签处理器来对类, 方法, 属性甚至参数等进行功能扩展, 功能开启, 属性定义, 行为定义, 规则定义, 关联处理, 元数据定义等等. 在实现各种框架的时候, 我们经常会自定义标签方便框架使用者仅仅通过在合适的地方引入合适的注解来启用(或自定义) 框架的一些能力并应用到我们的程序中.
不仅仅是框架的作者会大量使用注解, 在之前的系列文章中我们也多次自定义注解, 我们有通过定义 @Metrics 注解配合 Spring AOP 来为程序启动打点, 日志, 异常等功能, 我们有通过定义 @Sign 注解配合 Spring MVC 的 ResponseBodyAdvice 进行数据签名功能, 我们还经常会定义各种自定义注解配合 Spring MVC 的 HandlerMethodArgumentResolver 进行权限的校验等等功能. 采用这种模式, 我们的核心业务逻辑可以保持清晰干净, 通过注解配合 AOP 赋予代码额外的能力.
你可能会说, 注解还是有侵入性, 我们需要耦合框架定义的那些注解, 这个问题其实是无解的, 100% 无侵入性也代表了可读性的降低, 代码的功能和能力应当聚合在一起, 这也就是为什么 Spring 现在也不建议采用 xml 来做配置. Java 核心类库并没有什么注解, 好在 Spring 已经有了大量注解, 而 Spring 也变为了 Java 开发的标准, 所以其实我们很多时候如果希望自己的框架 (RPC 啥的) 完全没有侵入性的话可以借用 Spring 的那些注解 @Autowired,@Controller,@Service 等注解, 配合各种包的规范其实我们可以对目标元素的功能识别个八九不离十, 完全有可能实现 0 侵入的功能增强.
有关如何实现自定义注解不赘述, 这里我们简单回顾一下几个元注解(注解的注解):
@Documented: 将会在被此注解注解的元素的 javadoc 文档中列出注解, 一般都打上这个注解没坏处
@Target: 注解能被应用的目标元素, 比如类, 方法, 属性, 参数等等, 需要仔细思考
@Retention: 仅在源码保留, 还是保留到编译后的字节码, 还是到运行时也去加载, 超过 90% 的应用会在运行时去解析注解进行额外的处理, 所以大部分情况我们都会设置配置为 RetentionPolicy.RUNTIME
@Inherited: 如果子类没有定义注解的话, 能自动从父类获取定义了继承属性的注解, 比如 Spring 的 @Service 是没有继承特性的, 但是 @Transactional 是有继承特性的, 在 OO 继承体系中使用 Spring 注解的时候请特别注意这点, 理所当然认为注解是能被子类继承的话可能会引起不必要的 Bug, 需要仔细斟酌是否开启继承
@Repeatable:Java 8 引入的特性, 通过关联注解容器定义可重复注解, 小小语法糖提高了代码可读性, 对于元素有多个重复注解其实是很常见的事情, 比如某方法可以是 A 角色可以访问也可以是 B 角色可以访问, 某方法需要定时任务执行, 要在 A 条件执行也需要在 B 条件执行
@Native: 是否在. h 头文件中生成被标记的字段, 除非原生程序需要和 Java 程序交互, 否则很少会用到这个元注解
现在我们来从几个方面逐一温习一下 Spring 的那些常用的值得关注的注解.
Spring 核心注解
首先来看一下各种 stereotype: 按分类定义了由 Spring 管理的各种组件,@Controller 定义表现层组件,@Service 定义业务逻辑层组件,@Repository 定义数据访问层资源库组件,@Component 定义其它组件(比如访问外部服务的组件), 之前也说过了随着这些注解功能无区别, 但是对组件进行合适的分类意义重大, 不仅仅增加可读性而且方便我们通过 AOP 对不同类型的组件进行更多自动增强
再来看看 IoC 相关的一些注解:@Autowired 自动装配不用多说了;@Required 用于在 setter 方法标记属性值需要由 Spring 进行装配, 对于目前版本的 Spring 这个注解已经废弃, 现在 Spring 更推荐使用构造方法注入;@Qualifier 用于通过给 Bean 定义修饰语来注入相应的 Bean, 和 @Autowired 一起使用相当于 @Resource 的效果, 当然还有一种常见用法是嵌入其它注解用于对 Bean 进行区分, 然后配合 @Autowired 一起使用, 参见后面提到的 Spring Cloud 的 @LoadBalanced 注解;@Value 用于注入属性配置或 SpEL 表达式 (前者是我们常见用法, 后者可以从其它对象获取值, 功能更强大一点);@Lookup 可以实现方法注入, 如果我们的类是单例的, 但是又希望 Spring 注入的依赖的对象是 Prototype 生命周期(每次 new 一个出来) 的, 这个时候可以通过此注解进行方法注入
然后来看一下有关事务的几个注解:@EnableTransactionManagement 用于开启事务管理, 使用 Spring Boot 如果引入 Spring Data 的话不需要手动开启(不过建议大家在使用事务的时候还是通过日志来验证事务管理是否生效);@Transactional 大家都知道用于开启事务以及设置传播性, 隔离性, 回滚条件等;@TransactionalEventListener 用于配置事务的回调方法, 可以在事务提交前, 提交后, 完成后以及回滚后几个阶段接受回调事件
@Order 注解可以设置 Spring 管理对象的加载顺序, 在之前介绍 AOP 的文章中我们看到有的时候我们必须通过设置合理的 @Order 来合理安排切面的切入顺序避免一些问题, 还有在一些业务场景中, 我们往往会去定义一组类似于 Filter 的 @Component, 然后会从容器获得一组 Bean, 这个时候业务组件的运行顺序往往会比较重要, 也可以通过这个方式进行排序
@AliasFor 注解可以设置一组注解属性相互作为别名, 对于有歧义的时候会使代码更清晰, 此外还有一个用途是创建复合注解, Spring MVC 的 @GetMapping 注解就是基于 @RequestMapping 这个注解创建的复合注解, 我们可以很方便得通过这种方式来实现注解的继承
Spring 上下文注解
首先来看一下配置相关的一些注解:@Configuration 用于标注配置类, 启用 Java 配置方式的 Bean 配置;@Bean 用于配置一个 Bean;@ComponentScan(@ComponentScans 用于配置一组 @ComponentScan,Java 8 可以直接使用重复注解特性配置多个 @ComponentScan)用于扫描包方式配置 Bean;@PropertySource 以及 @PropertySources 用于导入配置文件;@Conditional 用于设置关联的条件类, 在合适的时候启用 Bean 的配置(Spring Boot 自动配置根基);@Import 用于导入其它配置类; @ImportResource 用于导入非 Java 配置方式的 xml 配置;@Profile 用于指定在合适的 Profile 下启用配置;@Lazy 用于告知容器延迟到使用的时候实例化 Bean(默认情况下容器启动的时候实例化 Bean 来检查所有的问题);@Description 用于给 Bean 设置描述;@Scope 用于设置 Bean 的生命周期;@Primary 用于在定义了多个 Bean 的时候指定首选的 Bean
其它一些注解包括:@EventListener 用于设置回调方法监听 Spring 制定的以及自定义的各种事件;@EnableAspectJAutoProxy 用于开启支持 AspectJ 的 @Aspect 切面配置支持, 使用 Spring Boot 引入了 AOP 启动器的话不需要显式开启
Spring web 注解
Spring MVC 的各种注解对应了 Spring MVC 各方面的功能, 下面我们来了解一下:
首先是三个定义了 Bean 特殊生命周期的复合注解:@RequestScope,@SessionScope 和 @ApplicationScope. 在 Web 应用中, 我们可能需要 Bean 跟随请求, 会话和应用程序的声明周期来进行创建, 这个时候可以直接使用这三个快捷的复合注解
接下去可以看到各种 @XXXMapping 的注解, 分别用于配置 HandlerMethod 匹配到不同的 Http Method, 当然不使用这些快捷的注解也是可以的, 直接使用 @RequestMapping 然后手动设置 method
@ResponseStatus 可以用到方法上也可以用到异常上, 前者会直接使请求得到指定的响应代码或原因 (可以配合 @ExceptionHandler 使用), 后者可以实现遇到指定异常的时候给出指定的响应代码或原因,@ResponseBody 我们实现 Restful 接口的时候(@RestController) 最常用了, 把返回内容 (序列化后) 输出到请求体
Spring MVC 给了我们各种注解方便我们从 HTTP 请求各种地方获取参数,@RequestBody 从请求体(处理复杂数据, 比如 JSON),@RequestHeader 从请求头,@CookieValue 从 cookie 中,@SessionAttribute 从会话中,@RequestAttribute 从请求的 Attribute 中(比如过滤器和拦截器手动设置的一些临时数据),@RequestParam 从请求参数(处理简单数据, 键值对),@PathVariable 从路径片段,@MatrixAttribute 矩阵变量允许我们采用特殊的规则在 URL 路径后加参数(分号区分不同参数, 逗号为参数增加多个值)
@ControllerAdvice 是一个重要注解, 允许我们在集中的地方配置控制器 (有 @RequestMapping 的方法) 相关的增强(@RestControllerAdvice 也是差不多的, 只是相当于为 @ExceptionHandler 加上了 @ResponseBody). 那么可以应用哪些增强呢? 首先是可以用 @ExceptionHandler 进行统一的全局异常处理; 第二是 @InitBinder 用来设置 WebDataBinder,WebDataBinder 用来自动绑定前台请求参数到 Model 中; 第三是 @ModelAttribute 让全局的 @RequestMapping 都能获得在此处设置的键值对. 当然, 这里说的 @InitBinder 和 @ExceptionHandler 也可以不定义在 @ControllerAdvice 内部(作为全局开启), 定义在 Controller 内部应用到某个 Controller 也是可以的
其它还有一些注解比如:@CrossOrigin 可以用到 Controller 或 Method 上 (需要配合 @RequestMapping) 设置细粒度的跨域行为
在之前的文章中我们也提到, 对于 Spring MVC, 定义自己的注解应用到参数, 方法, 控制器上, 配合 HandlerMethodArgumentResolver,XXAdvise, 以及 Interceptor 实现具体的功能来使用太太常见了, 几乎所有的非业务横切关注点, 我们都不应该在方法实现中重复任何一行代码.
Spring Boot 注解
来看一下上下文相关的注解:@ConfigurationProperties 很常用(配合 @EnableConfigurationProperties 注解来设置需要启用的配置类), 用来自定义配置类和配置文件进行关联;@DeprecatedConfigurationProperty 用于标记废弃的配置以及设置替代配置和告知废弃原因;@ConfigurationPropertiesBinding 用于指定自定义的转换器用于配置解析的时的类型转换; @NestedConfigurationProperty 用于关联外部的类型作为嵌套配置类
再看看自动配置相关的注解, 自动配置是 Spring Boot 最重要的特性, 在之前的系列文章中我有提到一个观点, IoC 是好事情, 但是把组件内部的一些默认配置以及组件和组件的组装交给外部用户来配置其实是不合理的, 组件应当可以自动进行自我配置实现开箱急用, 只有需要自定义组件的时候才要求外部来进行个性化配置:@EnableAutoConfiguration 注解可以启用自动配置, Spring Boot 应用程序一般我们会直接使用复合注解 @SpringBootApplication;@AutoConfigureOrder(值越小优先级越高),@AutoConfigureAfter,@AutoConfigureBefore 用于设置自动配置类加载顺序, 以及精确控制加载依赖关系, 有的时候我们的自动配置需要相互依赖或者会相互干扰, 需要手动调节
最后来看一下十几种配置条件, 用好这些注解是实现完善的自动配置的关键:@ConditionalOnBean 用于仅当容器中已经包含指定的 Bean 类型或名称时才匹配条件;@ConditionalOnClass 仅当 classpath 上存在指定类时条件匹配;@ConditionalOnCloudPlatform 仅当指定的云平台处于活动状态时条件匹配;@ConditionalOnExpression 依赖于 SpEL 表达式的值的条件元素的配置注解;@ConditionalOnJava 基于应用运行的 JVM 版本的条件匹配;@ConditionalOnJndi 基于 JNDI 可用和可以查找指定位置的条件匹配;@ConditionalOnMissingBean 仅当容器中不包含指定的 Bean 类型或名称时条件匹配;@ConditionalOnMissingClass 仅当 classpath 上不存在指定类时条件匹配;@ConditionalOnNotWebApplication 仅当不是 WebApplicationContext(非 Web 项目)时条件匹配, 对应 @ConditionalOnWebApplication;@ConditionalOnProperty 是检查指定的属性是否具有指定的值;@ConditionalOnResource 表示仅当 classpath 上存在指定资源时条件匹配;@ConditionalOnSingleCandidate 仅当容器中包含指定的 Bean 类并且可以判断只有单个候选者时条件匹配. 其实所有这些实现原理都是扩展 SpringBootCondition 抽象类(实现之前提到的 Condition 接口), 我们完全可以实现自己的条件注解(配合 @Conditional 注解关联到自己实现的 SpringBootCondition)
Spring Cloud 注解
在介绍本系列文章的第一篇中我们就提到了, Spring Cloud 整齐划一通过各种 EnableXXX 注解开启某个功能, 这里就不对这些注解进行说明了, 使用 Spring Boot 组件的功能非常简单, 基本就是引 POM+EnableXXX + 设置配置文件三部曲.
首先是 Netflix 包下的一些注解, 各种 EnableXXX 就不说了, 参考前一篇文章, 之前没介绍过 @RibbonClient, 这个注解用来为负载均衡客户端做一些自定义的配置, 可以进一步配置或自定义从哪里获取服务端列表, 负载均衡策略, Ping 也就是服务鉴活策略等等
client 包下的 @SpringCloudApplication 之前文章中我们也没有使用到, 这是一个复合注解就是 @SpringBootApplication+ @EnableDiscoveryClient+ @EnableCircuitBreaker,Spring Cloud 那堆东西很多, 还是自己亲手定义一个一个功能的注解来的踏实; @LoadBalanced 注解用于和 RestTemplate 配合使用构成一个负载均衡的 Http 客户端, 实现原理上其实这个注解是一个 @Qualifier 注解, Spring 会为所有 @LoadBalanced 的 RestTemplate 加入一个 LoadBalancerInterceptor(实现 ClientHttpRequestInterceptor)实现负载均衡
sleuth 包下面的注解和链路跟踪相关, 比较常用的是通过 @SpanName 手动设置 span 的名称, 其它注解对于业务开发并不常用
总结
好了, 写了本文我发现我看到 @已经 Markdown 的 ** 就眼花, 请点赞支持. 本文我们通过代码打印出了大部分 Spring 相关的注解, 你也可以通过这个方式熟悉其它框架的注解(毕竟注解是框架赋予我们各种便捷功能的一个重要入口, 对注解了解个八九成也往往可以对框架赋予我们的丰富功能了解六七成). 然后我们梳理了一下 Spring 相关的各种注解, 其中主要需要关注的是几方面:
元注解, 也就是注解的注解
Spring 容器相关的一些注解, 包括 @Qualifier,@AliasFor,@Order 等看似不重要但其实很重要的注解
Spring Java 配置相关的一些注解, 包括条件注解
Spring Boot 自动配置相关的一些注解
很多注解可以同时应用到类型, 方法, 参数上, 有的时候应用到不同的地方作用会略微不一样, 这个需要重点关注
我们知道注解其实只是一个标识, 注解如何起作用背后的实现原理还是比较多样的, 你可以进一步结合本文介绍的 Spring 的各种注解探寻一下背后实现的原理.
来源: https://www.cnblogs.com/lovecindywang/p/9846846.html