揭开 Java 注解的面纱:从“黑魔法”到实战原理
揭开 Java 注解的面纱从“黑魔法”到实战原理很多开发者用了几年 Spring 框架依然觉得注解是某种“黑魔法”。只要在方法头上加一个符号事务就生效了缓存就加上了权限就校验了。但其实一旦你把注解从神坛上拉下来它就只是一种非常基础的元数据机制。结合一个自定义的RedisCache缓存注解让我们通过四个直击本质的问题彻底搞懂它。1. 注解的定义和语法什么是注解注解本质上就是一种**“贴在代码上的标签”**。它可以贴在类、方法、变量或参数上。非常重要的一点注解本身没有任何逻辑它不会直接影响代码的执行它只是把信息比如缓存过期时间存在那里等待被其他程序比如反射机制或 Spring AOP来读取和执行。语法拆解让我们看你提供的这段RedisCache代码它包含了注解的核心要素Target(ElementType.METHOD)Retention(RetentionPolicy.RUNTIME)DocumentedpublicinterfaceRedisCache{/** * 缓存 Key 的前缀例如 user:info: */StringkeyPrefix();/** * 动态 Key 的 SpEL 表达式例如 #id 或 #p0 * 如果为空默认使用方法的第一个参数作为 ID */Stringkey()default;/** * 过期时间默认 30 分钟 */longttl()default30;/** * 时间单位 */TimeUnitunit()defaultTimeUnit.MINUTES;}1. 声明关键字 (interface)底层其实就是声明了一个继承自java.lang.annotation.Annotation的接口。2. 元注解修饰标签的标签Target(ElementType.METHOD)规定这个标签只能贴在“方法”上。Retention(RetentionPolicy.RUNTIME)最关键的一句。规定这个标签在程序运行期间一直保留在内存中。如果不写这个你的切面代码在运行时根本“看”不到这个注解。Documented生成 JavaDoc 时把这个注解也收录进去。3. 属性看起来像方法其实是变量String keyPrefix();必填属性。使用时必须写RedisCache(keyPrefix ...)。long ttl() default 30;带有default的是选填属性。不写就默认是 30。2. 什么时候应该定义和使用注解当你发现代码中存在大量**“非核心业务但又无处不在的通用逻辑即横切关注点”**时就是使用注解的最佳时机。典型场景缓存控制像你的RedisCache查库前先查缓存查库后写回缓存。权限校验比如RequiresRole(ADMIN)在执行方法前先检查当前登录用户有没有权限。日志记录比如LogOperate(修改用户密码)方法执行完后自动往操作日志表里插入一条记录。事务管理著名的Transactional方法报错自动回滚数据库不用你手动写Connection.rollback()。参数校验比如NotNull、Email在进入业务逻辑前自动拦截非法参数。总结原则凡是你想在方法执行的**“前、后、异常时”**做一些通用处理并且不想让这些处理脏了你原本的业务代码就用注解结合 AOP。3. 为什么需要注解注解的核心价值在于两个词解耦和声明式编程。1. 业务逻辑更纯粹解耦如果没有RedisCache你的getUserById方法可能长这样publicUsergetUserById(Longid){Stringkeyuser:id;UseruserredisTemplate.get(key);if(user!null){returnuser;}// 核心逻辑只有这一行useruserMapper.selectById(id);redisTemplate.set(key,user,30,TimeUnit.MINUTES);returnuser;}你看真正查数据库的核心代码只有一行却被大量的 Redis 样板代码淹没了。用了注解方法里就只剩下纯粹的业务逻辑代码瞬间变得干净。2. 声明式编程你只需要**“声明你想要什么”**贴个标签说“我要缓存”而不需要“亲自去写怎么做”。这极大地提高了开发效率和代码可读性。看一眼方法头就知道这个方法具备什么额外能力。3. 消灭重复不用在每个需要缓存的方法里复制粘贴那几十行相同的 Redis 判断逻辑了。4. 除了注解还有什么其他可以替代的东西如果不用注解要实现同样的“配置或拦截”效果业界通常有以下几种替代方案方案一XML 配置传统的替代品在注解流行之前比如早期的 Spring配置全部写在巨大的 XML 文件里。做法在一个cache-config.xml文件里配置cache methodgetUserById prefixuser: ttl30/。缺点“XML 地狱”。配置文件又长又难懂且跟代码严重割裂。你要看一个方法有没有缓存还得去另一个文件夹里翻 XML。现在几乎被淘汰。方案二硬编码 / 模板模式最直接的替代品直接写一个工具类把业务逻辑包裹起来不搞 AOP不搞反射。做法// 伪代码使用函数式接口publicUsergetUserById(Longid){returnredisTemplate.executeWithCache(user:id,30,()-{returnuserMapper.selectById(id);// 真正的业务逻辑传进去});}优点执行流程极其清晰没有 AOP 的“黑魔法”出错了非常容易打断点调试。缺点有点侵入性虽然比直接写全套 Redis 代码强但依然需要在业务方法内部显式调用。方案三动态代理不基于注解的 AOPAOP 不一定要看注解。你可以配置 AOP 拦截特定命名规则的方法。做法配置切面说“凡是名字以get或select开头的方法我统统给它们加上缓存”。优点连注解都不用贴了全自动化。缺点太死板一刀切。如果某个get方法你由于特殊原因不想缓存或者想设置过期时间为 60 分钟处理起来非常麻烦。扩展元注解的常见参数详解在定义注解时Target和Retention是最核心的元注解。除了上面代码用到的还有哪些常见参数1. Target这个标签还能贴在哪里Target接收的是ElementType枚举。除了METHOD方法常用的还有参数作用范围常见框架案例TYPE类、接口、枚举Spring 的RestController、ServiceMyBatis 的Mapper。FIELD类的属性成员变量Spring 的Autowired注入 Bean、Value读取配置。PARAMETER方法的参数Spring MVC 的PathVariable、RequestBody告诉框架把前端数据塞给哪个参数。CONSTRUCTOR构造函数构造器注入时把Autowired贴在构造方法上。ANNOTATION_TYPE另一个注解上像Target和Retention自身就是贴在别的注解上的“元注解”。补充说明Target可以传入多个值。例如Target({ElementType.TYPE, ElementType.METHOD})表示该注解既能贴在类上也能贴在方法上。2. Retention这个标签能活到什么时候Retention接收的是RetentionPolicy枚举。它决定了注解的生命周期参数生命周期核心作用SOURCE只活在 .java 源码里给编译器看。编译成 .class 时会被丢弃。典型代表Override、Lombok 的Data。CLASS保留在 .class 文件里给字节码工具看。这是默认值JVM 运行时会忽略。较少在业务中使用。RUNTIME一直存活到 JVM 运行时给反射和 AOP 看。只要你想在代码运行期间动态读取配置如读取缓存过期时间必须用这个。总结如果你写注解是为了配合 AOP 做业务增强拦截方法、处理缓存、校验权限等请直接使用Retention(RetentionPolicy.RUNTIME)。如果你写注解是为了让 IDE 不报错、或者在编译时生成代码像 Lombok 那样才需要用到SOURCE。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2422294.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!