想自己写框架?不会写Java注解可不行

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 想自己写框架?不会写Java注解可不行

image.png

用注解一时爽,一直用一直爽


Java后端开发进入spring全家桶时代后,开发一个微服务提供简单的增删改查接口跟玩泥巴似的非常简单,一顿操作猛如虎,回头一看代码加了一堆注解:@Controller @Autowired @Value,面向注解编程变成了大家不可缺少的操作。


想象一下如果没有注解Java程序员可以要哭瞎😭


既然注解(annotation)这么重要,用的这么爽,那注解的实现原理你知道么?我猜你只会用注解不会自己写注解(手动滑稽)。


好了,下面的内容带大家从零开始写一个注解,揭开注解神秘的面纱。


原来注解不神秘


注解用大白话来说就是一个标记或者说是特殊的注释,如果没有解析这些标记的操作那它啥也不是。


注解的格式如同类或者方法一样有自己特殊的语法,这个语法下文会详细介绍。


那如何去解析注解呢?这就要用到Java强大的反射功能了。反射大家应该都用过,可以通过类对象获取到这个类的各种信息比如成员变量、方法等,那注解标记能不能通过反射获取呢?当然可以了。


所以注解的原理其实很简单,本质上是通过反射功能动态获取注解标记,然后按照不同的注解执行不同的操作,比如@Autowired可以注入一个对象给变量赋值。


看到这里是不是很躁动啊,来吧自己也撸一个注解。


造火箭啦,自己动手写一个注解


便于大家理解,这里先引入一个场景:在线教育火了,经理让我写一个模块实现学生信息管理功能,考虑到分布式并发问题,经理让我务必加上分布式锁。


经理问我几天能搞定?我说至少3天。如是脑补了以下代码:

/**
     * 更新学生信息
     * @param student 学生对象
     * @return true 更新成功,false 更新失败
     */
    public boolean updateStudentInfo(Student student) {
        // 尝试获取分布式锁
        String lockKey = "student:" + student.getId();
        if (RedisTool.tryLock(lockKey, 10,
                TimeUnit.SECONDS, 5)) {
            try {
                // 这里写业务逻辑
            } finally {
                RedisTool.releaseLock(lockKey);
            }
        }
        // 获取锁失败
        return false;
    }

 

经理走后我在思考,我能不能只花一天时间写完,剩下两天时间用来写博客划水呢?突然灵感来了,我可以把重复的代码逻辑抽出来用注解实现不就节省代码了,哈哈,赶紧写。


使用注解之后整个方法清爽了很多,HR小姐姐都夸我写的好呢。

@EnableRedisLock(lockKey = "student", expireTime = 10,
            timeUnit = TimeUnit.SECONDS, retryTimes = 5)
    public boolean updateStudentInfo(Student student) {
        // 这里写业务逻辑
        // studentDao.update(student);
        return true;
    }

 

代码已经写完上库了,现在我在划水写博客呢。是不是很简洁很优雅很牛逼,怎么做到的呢,主要分为三步:1打开冰箱门,2把大象放进去,3把冰箱门关好。好了,扯远了,大家接着往下看。


第一步定义一个注解

image.png

一个注解可以简单拆解为三个部分:


第一部分:注解体

注解的定义有点类似于接口(interface),只不过前面一个加了一个@符号,这个千万不能省。


第二部分:注解变量

注解变量的语法有点类似于接口里面定义的方法,变量名后面带一对括号,不同的是注解变量后面可以有默认值。另外返回值只能是Java基本类型、String类型或者枚举类,不可以是对象类型。


第三部分:元注解

元注解(meta-annotation)说白了就是给注解加注解的注解,是不是有点晕了,这种注解是JDK提前内置好的,可以直接拿来用的。不太懂也没有关系反正数量也不多,总共就4个,我们背下来吧:@Target @Retention @Documented @Inherited

  • Target注解

用来描述注解的使用范围,即被修饰的注解可以用在什么地方 。


注解可以用于修饰 packages、types(类、接口、枚举、注解类)、类成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数),在定义注解类时使用了@Target 能够更加清晰的知道它能够被用来修饰哪些对象,具体的取值范围定义在ElementType.java 枚举类中。


比如上面我们写的Redis锁的注解就只能用于方法上了。


  • Retention注解

用来描述注解保留的时间范围,即注解的生命周期。在 RetentionPolicy 枚举类中定义了三个周期:

public enum RetentionPolicy {
    SOURCE, // 源文件保留
    CLASS,  // 编译期保留,默认值
    RUNTIME // 运行期保留,可通过反射去获取注解信息
}

像我们熟知的@Override注解就只能保留在源文件中,代码编译后注解就消失了。 比如上面我们写的Redis锁的注解就保留到了运行期,运行的时候可以通过反射获取信息。


  • Documented注解

用来描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息,很简单不多解释了。


  • Inherited注解

被Inherited注解修饰的注解具有继承性,如果父类使用了被@Inherited修饰的注解,则其子类将自动继承该注解。


好了,这一步我们已经将注解定义好了,但是这个注解如何工作呢?接着看。


第二步实现注解的业务逻辑

在第一步中我们发现定义的注解(@EnableRedisLock)中没有业务逻辑,只有一些变量,别忘了我们的注解是要使能Redis分布式锁的功能,那这个注解到底是怎么实现加锁和释放锁的功能呢?这个就需要我们借助反射的强大功能了。

@Aspect
public class RedisLockAspect {
    @Around(value = "@annotation(com.smilelioncoder.EnableRedisLock)")
    public void handleRedisLock(ProceedingJoinPoint joinPoint)
            throws Throwable {
        // 通过反射获取到注解对象,可见反射非常重要的
        EnableRedisLock redisLock = ((MethodSignature) joinPoint.getSignature())
                .getMethod()
                .getAnnotation(EnableRedisLock.class);
        // 获取注解对象的变量值
        String lockKey = redisLock.lockKey();
        long expireTime = redisLock.expireTime();
        TimeUnit timeUnit = redisLock.timeUnit();
        int retryTimes = redisLock.retryTimes();
        // 获取锁
        if (tryLock(lockKey, expireTime, timeUnit, retryTimes)) {
            try {
                // 获取锁成功继续执行业务逻辑
                joinPoint.proceed();
            } finally {
                releseLock();
            }
        }
    }
}

这里借助了切面的功能,将EnableRedisLock注解作为一个切点,只要方法上标注了这个注解就会自动执行这里的代码逻辑。


通过反射机制拿到注解对象后就可以执行加锁解锁的常用逻辑啦。Redis实现分布式锁相信大家已经很熟悉了,这里就不在啰嗦了。


第三步在业务代码中尽情的使用注解

@EnableRedisLock(lockKey = "student", expireTime = 10, timeUnit = TimeUnit.SECONDS, retryTimes = 5)
public void method1(Student student) {
    // 这里写业务逻辑
}

在需要加锁的方法上直接加上注解就可以啦,怎么样是不是很简单呀,赶紧在你的项目中运用起来吧。


好了,自己写一个注解的内容就介绍到这里了,学会了吗?


相关文章
|
1月前
|
安全 前端开发 Java
《深入理解Spring》:现代Java开发的核心框架
Spring自2003年诞生以来,已成为Java企业级开发的基石,凭借IoC、AOP、声明式编程等核心特性,极大简化了开发复杂度。本系列将深入解析Spring框架核心原理及Spring Boot、Cloud、Security等生态组件,助力开发者构建高效、可扩展的应用体系。(238字)
|
2月前
|
人工智能 Java 开发者
阿里出手!Java 开发者狂喜!开源 AI Agent 框架 JManus 来了,初次见面就心动~
JManus是阿里开源的Java版OpenManus,基于Spring AI Alibaba框架,助力Java开发者便捷应用AI技术。支持多Agent框架、网页配置、MCP协议及PLAN-ACT模式,可集成多模型,适配阿里云百炼平台与本地ollama。提供Docker与源码部署方式,具备无限上下文处理能力,适用于复杂AI场景。当前仍在完善模型配置等功能,欢迎参与开源共建。
1355 58
阿里出手!Java 开发者狂喜!开源 AI Agent 框架 JManus 来了,初次见面就心动~
|
1月前
|
存储 安全 Java
《数据之美》:Java集合框架全景解析
Java集合框架是数据管理的核心工具,涵盖List、Set、Map等体系,提供丰富接口与实现类,支持高效的数据操作与算法处理。
|
1月前
|
消息中间件 缓存 Java
Spring框架优化:提高Java应用的性能与适应性
以上方法均旨在综合考虑Java Spring 应该程序设计原则, 数据库交互, 编码实践和系统架构布局等多角度因素, 旨在达到高效稳定运转目标同时也易于未来扩展.
117 8
|
1月前
|
存储 算法 安全
Java集合框架:理解类型多样性与限制
总之,在 Java 题材中正确地应对多样化与约束条件要求开发人员深入理解面向对象原则、范式编程思想以及JVM工作机理等核心知识点。通过精心设计与周密规划能够有效地利用 Java 高级特征打造出既健壮又灵活易维护系统软件产品。
71 7
|
3月前
|
存储 缓存 安全
Java集合框架(二):Set接口与哈希表原理
本文深入解析Java中Set集合的工作原理及其实现机制,涵盖HashSet、LinkedHashSet和TreeSet三大实现类。从Set接口的特性出发,对比List理解去重机制,并详解哈希表原理、hashCode与equals方法的作用。进一步剖析HashSet的底层HashMap实现、LinkedHashSet的双向链表维护顺序特性,以及TreeSet基于红黑树的排序功能。文章还包含性能对比、自定义对象去重、集合运算实战和线程安全方案,帮助读者全面掌握Set的应用与选择策略。
249 23
|
2月前
|
SQL Java 数据库连接
区分iBatis与MyBatis:两个Java数据库框架的比较
总结起来:虽然从技术角度看,iBATIS已经停止更新但仍然可用;然而考虑到长期项目健康度及未来可能需求变化情况下MYBATISS无疑会是一个更佳选择因其具备良好生命周期管理机制同时也因为社区力量背书确保问题修复新特征添加速度快捷有效.
186 12
|
3月前
|
存储 缓存 安全
Java集合框架(三):Map体系与ConcurrentHashMap
本文深入解析Java中Map接口体系及其实现类,包括HashMap、ConcurrentHashMap等的工作原理与线程安全机制。内容涵盖哈希冲突解决、扩容策略、并发优化,以及不同Map实现的适用场景,助你掌握高并发编程核心技巧。
|
3月前
|
安全 Java 开发者
Java集合框架:详解Deque接口的栈操作方法全集
理解和掌握这些方法对于实现像浏览器后退功能这样的栈操作来说至关重要,它们能够帮助开发者编写既高效又稳定的应用程序。此外,在多线程环境中想保证线程安全,可以考虑使用ConcurrentLinkedDeque,它是Deque的线程安全版本,尽管它并未直接实现栈操作的方法,但是Deque的接口方法可以相对应地使用。
235 12
|
3月前
|
存储 安全 Java
Java集合框架(一):List接口及其实现类剖析
本文深入解析Java中List集合的实现原理,涵盖ArrayList的动态数组机制、LinkedList的链表结构、Vector与Stack的线程安全性及其不推荐使用的原因,对比了不同实现的性能与适用场景,帮助开发者根据实际需求选择合适的List实现。