Java 注解详情与自定义注解实战

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
云解析 DNS,旗舰版 1个月
简介: 在本篇中,主要是针对注解的概念及运行时注解进行解释说明,附带有三个实战的案例为1、自定义注解+aop实现自动收集日志,2、自定义注解+Redis+AOP实现防止重复提交,3、自定义注解+SpringMVC 实现权限控制。尽可能的让大家能够理解透彻并且能够加以应用。

关于我为啥突然想要深入的了解 Java 注解和反射

  1. 好奇心来啦
  2. 打算看源码
  3. 巩固 Java 基础知识(基础不牢,地动山摇)

不要说我内卷,每个人有每个人选择的路,坚持初心。

一、逻辑思维图🧐

网络异常,图片无法展示
|


第 1-5 小节均偏向于理论知识,若只是想要了解如何自定义注解和如何应用注解,请跳转至第 6 小节开始阅读。

在本篇中,主要是针对注解的概念运行时注解进行解释说明,附带有三个实战的案例,尽可能的让大家能够理解透彻并且能够加以应用。

二、什么是注解👨‍🏫

Java 注解(Annotation)用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。Java 注解是从 Java5 开始添加到 Java 的。--官方文档

2.1、注解

Annotion(注解)是一个接口,程序可以通过反射来获取指定程序元素的Annotion对象,然后通过Annotion对象来获取注解里面的元数据。


我们常常使用的注解,@Data、@Controller等等,这些都是注解,创建一个注解,也很简单,创建一个类,然后将class改为 @interface就是一个注解啦。

2.2、注解出现的位置

Java代码中的包、类型、构造方法、方法、成员变量、参数、本地变量的声明都可以用注解来修饰。注解本质上可以看作是一种特殊的标记,程序在编译或者运行时可以检测到这些标记而进行一些特殊的处理。

2.3、关于注解的处理

我们一般将利用反射来处理注解的方式称之为运行时注解


另外一种则是编译时注解,如我们常常使用的 lombok 里的注解,@Data,它能够帮我们省略set/get方法,我们在Class上加上这个注解后,在编译的时候,lombok其实是修改了.class文件的,将set/get方法放进去了,不然的话,你可以看看编译完后的.class文件。诸如这种,我们常称为编译时注解,也就是使用javac处理注解。

网络异常,图片无法展示
|


--图:来自于极客学院


这幅图就是从.java文件到class文件的,再到class文件被 JVM 加载的过程。


而其中的注解抽象语法树这一阶段,就是去解析注解,然后根据定义的注解处理器进行相关的逻辑处理。


这一块不是我的关注点,略过略过啦,朋友们,好奇可以去研究研究噢

3、注解的目的或作用💞

  • 生成文档。这是最常见的,也是 Java 最早提供的注解。如@param、@return等等
  • **跟踪代码依赖性,实现替代配置文件功能。**作用就是减少配置,如 SpringBean的装载注入,而且现在的框架基本上都是使用注解来减少配置文件的数量,同时这样也使得编程更加简洁,代码更加清晰。
  • 在编译时进行格式检查。如@Override放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出;
  • 标识作用。当Java编译时或运行时,检测到这里的注解,做什么的处理,自定义注解一般如此。
  • 携带信息。 注解的成员提供了程序元素的关联信息,Annotation 的成员在 Annotation类型中以无参数的方法的形式被声明。其方法名和返回值定义了该成员的名字和类型。在此有一个特定的默认 语法:允许声明任何Annotation成员的默认值。一个Annotation可以将name=value对作为没有定义默认值的Annotation 成员的值,当然也可以使用name=value对来覆盖其它成员默认值。这一点有些近似类的继承特性,父类的构造函数可以作为子类的默认构造函数,但是也 可以被子类覆盖。
  • 这么一大段话,其实就是关于注解中成员的解释。

网络异常,图片无法展示
|


说了这么多,其实一句话也能表达完。


注解就是一张便利贴,它贴在那里,你看到的那一刻,就明白该做什么事啦。


如出门前,门上贴着一张便利贴📌,上面写着"出门记得带钥匙",当你看到的那一刻,你就会去检查一下自己是否带钥匙啦。

在 Java 中也是一样的,你定义了一个注解,注解上可以写一些东西,然后你再将它贴在某个上面,说明白执行规则,当编译到这里的时候需要干嘛干嘛,又或者是当运行到这里的时候需要干嘛干嘛。

因为注解写的东西的不同,或者是处理注解的规则不同,而产生了不同的注解及作用。

4、JDK 内置注解💫

Java 中 内置的注解有 5 类,具体包括:

@Deprecated:过时注解,用于标记已过时 & 被抛弃的元素(类、方法等)

@Override:复写注解,用于标记该方法需要被子类复写

@SuppressWarnings:阻止警告注解,用于标记的元素会阻止编译器发出警告提醒

@SafeVarargs:参数安全类型注解,用于提醒开发者不要用参数做不安全的操作 & 阻止编译器产生 unchecked 警告,Java 1.7 后引入

5、元注解 🎯

何为元注解?就是注解的注解,就是给你自己定义的注解添加注解,你自己定义了一个注解,但你想要你的注解有什么样的功能,此时就需要用元注解对你的注解进行说明了。


接着上一个比喻


注解有很多很多吗,门上贴一个,冰箱上贴一个,书桌上贴一个等等

元注解勒就是把他们整合起来称呼的,像上面这些可以统称为生活类注解啊。所以也就是注解的注解。

5.1、@Target

在 @Target 注解中指定的每一个 ElementType 就是一个约束,它告诉编译器,这 个自定义的注解只能用于指定的类型。


说明了注解所修饰的对象范围:注解可被用于 packages、types(类、接口、枚举、Annotation 类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch 参数)。


5.2、@Retention

定义了该注解的生命周期:


  1. 某些注解仅出现在源代码中,而被编译器丢弃; (源码级)
  2. 而另一些却被编译在 class 文件中; (字节码级)
  3. 编译在 class 文件中的注解可能会被虚拟机忽略,而另一些在 class 被装载时将被读取(请注意并不影响 class 的执行,因为注解与 class 在使用上是被分离的)。绝大多数开发者都是使用 RUNTIME,因为我们期望在程序运行时,能够获取到这些注解,并干点有意思的事儿,而只有 RetentionPolicy.RUNTIME,能确保自定义的注解在运行时依然可见。(运行级)


使用这个元注解可以对自定义注解的“生命周期”进行限制。



RetentionPolicy.SOURCE 一般开发者很少用到,大都是 Java 内置的注解。如@Override


@Target(ElementType.METHOD)@Retention(RetentionPolicy.SOURCE)public @interface Override {}

复制代码


@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})@Retention(RetentionPolicy.SOURCE)public @interface SuppressWarnings {

复制代码


这些注解只是在编译的时候用到,一旦编译完成后,运行时没有任何意义,所以他们被称作源码级别注解。


如果有了解过 lombok 一些简单原理的开发者, 都知道它是通过注解在编译时自动生成一部分代码,让源码看起来更简洁,字节码却很强大。


当然,这种方式有它自身的缺陷,譬如不一致性,问题排解时的困扰,以及依赖问题,不是本篇重点,扯回来。


  • 提供信息给编译器: 编译器可以利用注解来检测出错误或者警告信息,打印出日志。
  • 编译阶段时的处理: 软件工具可以用来利用注解信息来自动生成代码、文档或者做其它相应的自动处理。
  • 运行时处理: 某些注解可以在程序运行的时候接受代码的提取,自动做相应的操作。

5.3、@Documented

用于描述其它类型的 annotation 应该被作为被标注的程序成员的公共 API,因此可以被例如 javadoc此类的工具文档化。是一个标记注解,没有成员。

5.4、@Inherited

是一个标记注解阐述了某个被标注的类型是被继承的。使用了@Inherited修饰的注解类型被用于一个 class 时该 class 的子类也有了该注解

5.5、@Repeatable

允许一个注解可以被使用一次或者多次(Java 8)。

6、自定义注解📸

自定义注解实际上就是一种类型而已,也就是引用类型(Java 中除了 8 种基本类型之外,我们见到的任何类型都是引用类型)

6.1、定义注解

自定义注解过程:


  1. 声明一个类 MyAnnotation
  2. 把 class 关键字改为 @interface


这样我们就声明了一个自定义的注解,当我们用@interface声明一个注解的时候,实际上是声明了一个接口,这个接口自动的继承了java.lang.annotation.Annotation,但是我们只需要@interface这个关键字来声明注解,编译器会自动的完成相关的操作,不需要我们手动的指明继承Annotation接口


另外在定义注解时,不能再继承其他的注解或接口。


我举了四个例子,这四个注解分别是放在 类(接口、枚举类上)、构造函数、方法级别、成员属性上的。


@Documented    //定义可以被文档工具文档化@Retention(RetentionPolicy.RUNTIME)//声明周期为runtime,运行时可以通过反射拿到@Target(ElementType.TYPE)//注解修饰范围为类、接口、枚举public @interface ClassAnnotation {    public String name() default "defaultService";    public String version() default "1.1.0";}

复制代码


@Documented@Target(ElementType.CONSTRUCTOR)@Retention(RetentionPolicy.RUNTIME)public @interface ConstructorAnnotatin {    String constructorName() default "";    String remark() default "构造器";}

复制代码


@Documented@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface FieldAnnotation {    public String name() default "defaultName";
    public String value() default "defaultValue";}

复制代码


@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface MethodAnnotation {     public String name() default "defaultName";     public MethodTypeEnum type() default MethodTypeEnum.TYPE1; }

复制代码


public enum MethodTypeEnum {    TYPE1,TYPE2}

复制代码

6.2、注解的成员变量

  1. 成员以无参数无异常的方式声明   String constructorName() default "";
  2. 可以使用 default 为成员指定一个默认值 public String name() default "defaultName";
  3. 成员类型是受限的,合法的类型包括原始类型以及 String、Class、Annotation、Enumeration (JAVA 的基本数据类型有 8 种:byte(字节)、short(短整型)、int(整数型)、long(长整型)、float(单精度浮点数类型)、double(双精度浮点数类型)、char(字符类型)、boolean(布尔类型)
  4. public MethodTypeEnum type() default MethodTypeEnum.TYPE1;
  5. 注解类可以没有成员,没有成员的注解称为标识注解,例如 JDK 注解中的 @Override、@Deprecation
  6. 如果注解只有一个成员,并且把成员取名为 value(),则在使用时可以忽略成员名和赋值号“=”
  7. 例如 JDK 注解的 @SuppviseWarnings ;如果成员名 不为 value,则使用时需指明成员名和赋值号"="

6.3、使用注解

因为我们在注解中声明了属性,所以在使用注解的时候必须要指明属性值 ,多个属性之间没有顺序,多个属性之间通过逗号分隔


@ClassAnnotation(name = "personBean", version = "1.2.1")public class Person {
//    告诉大家是可以用的,但是影响我测试,我就又注释掉了.//    @ConstructorAnnotatin(constructorName="Person()")//    public Person(String description) {//        this.description = description;//    }
    @FieldAnnotation(name = "description", value = "This is my personal annotation")    private String description;
    public String getDescription() {        return description;    }
    public void setDescription(String description) {        this.description = description;    }
    @MethodAnnotation(name = "sayHello", type = MethodTypeEnum.TYPE2)    public void sayHello() {        System.out.println("Hello Annotation!");    }}

复制代码

6.4、浅提一下反射

想要去获取注解就不得不提到反射啦,但 Java 反射会带来一定的耗时,因此使用运行注解需要考虑对性能的影响。


我们声明一个Student类用来描述学生对象的信息的


class Student{   String name;   String school;   //...set/get}

复制代码


当我们创建一个学生对象时,学生对象的信息是保存在 Student 类中,所以 Student 类会提供获取这些信息的方法。


在 Java 类中,每个类都会有对应的 Class,要想执行反射操作,必须先要获取指定类名的 Class




了解 Class 对象


类是程序的一部分,每个类都有一个 Class 对象。换言之,每当我们编写并且编译 了一个新类,就会产生一个 Class 对象(更恰当的说,是被保存在一个同名的 .class 文件中)。为了生成这个类的对象,Java 虚拟机 (JVM) 先会调用 “类加载器” 子系统把 这个类加载到内存中。


Class类:简单说就是用来描述类对象的信息的


类对象的信息包括:


  1. 类的基本信息:包名、修饰符、类名、基类,实现的接口
  2. 属性的信息:修饰符、属性类型、属性名称、属性值,
  3. 方法的信息:修饰符、返回类型、方法名称、参数列表、抛出的异常
  4. 构造方法的信息:修饰符、类名、参数列表、抛出的异常
  5. 注解的相关信息:
  6. 因为:类对象的相关信息全部保存在 Class 类
  7. 所以:Class 类会提供获取这些信息的方法


一旦某个类的 Class 对象被载入内存,它就可以用来创建这个类的所有对象。




通过 Class 获取类的相关信息,通过 Class 创建对象,通过 Class 调用对象上面的属性,调用对象上面的方法,这种操作就称为反射,要想执行反射操作,必须先要获取到指定的类名的 Class


获取 Class 的不同方式


  • 获取基本类型的 Class
  • 1)基本类型 Class:如 int.Class 获取的就是 int 类型的 Class
  • 获取引用类型的 Class:
  • 1)引用类型的 Class:如 String.Class 获取的就是 String 类对应的 Class
  • 2)通过对象来获取:如:String obj="hello",Class calz = obj.getClass(),获取的就是 String 类对应的 Class
  • 3)Class.forName("java.lang.String"),获取的就是对应的 Class



6.5、获取注解

这里没有再对 Class 的 api 深度挖掘了,大家感兴趣的话,可以再去了解,跟着这篇文章后面,还有注解下半场和 Java 反射方面的知识,所以这里没有深聊了。

public class TestClassAnnotation {
    private static Person person = new Person();
    public static void main(String[] args) {        Class<?> clazz = person.getClass();        //因为注解是作用于类上面的,所以可以通过isAnnotationPresent来判断是否是一个具有指定注解的类        if (clazz.isAnnotationPresent(ClassAnnotation.class)) {            System.out.println("This is a class with annotation ClassAnnotation!");            //通过getAnnotation可以获取注解对象            ClassAnnotation annotation = clazz.getAnnotation(ClassAnnotation.class);            if (null != annotation) {                System.out.println("BeanName = " + annotation.name());                System.out.println("BeanVersion = " + annotation.version());            } else {                System.out.println("the annotation that we get is null");            }        } else {            System.out.println("This is not the class that with ClassAnnotation");        }    }}

复制代码


This is a class with annotation ClassAnnotation!BeanName = personBeanBeanVersion = 1.2.1

复制代码


public class AnnotationTest {   public static void main(String[] args) throws ClassNotFoundException {    Class<?> clazz = Class.forName("com.nzc.my_annotation.shang.Person");    System.out.println("==============类注解解析==============");    printClassAnno(clazz);        System.out.println("==============成员变量注解解析==============");    printFieldAnno(clazz);        System.out.println("==============成员方法注解解析==============");    printMethodAnno(clazz);        System.out.println("==============构造器注解解析==============");    printConstructorAnno(clazz);      }    /**   * 打印类的注解   */  private static void printClassAnno(Class<?> clazz) throws ClassNotFoundException {    //判断是否有AuthorAnnotatin注解    if(clazz.isAnnotationPresent(ClassAnnotation.class)) {      //获取AuthorAnnotatin类型的注解      ClassAnnotation annotation = clazz.getAnnotation(ClassAnnotation.class);      System.out.println(annotation.name()+"\t"+annotation.version());    }  }      /**   * 打印成员变量的注解   */  private static void printFieldAnno(Class<?> clazz) throws ClassNotFoundException {    Field[] fields = clazz.getDeclaredFields();    for (Field field : fields) {      if(field.isAnnotationPresent(FieldAnnotation.class)) {        FieldAnnotation annotation = field.getAnnotation(FieldAnnotation.class);        System.out.println(annotation.name()+"\t"+annotation.value());      }    }  }    /**   * 打印成员变量的注解   */  private static void printMethodAnno(Class<?> clazz) throws ClassNotFoundException {    Method[] methods = clazz.getDeclaredMethods();    for (Method method : methods) {      if(method.isAnnotationPresent(MethodAnnotation.class)) {        MethodAnnotation annotation = method.getAnnotation(MethodAnnotation.class);        System.out.println(annotation.name()+"\t"+annotation.type());      }    }  }    /**   * 打印成员变量的注解   */  private static void printConstructorAnno(Class<?> clazz) throws ClassNotFoundException {    Constructor<?>[] constructors = clazz.getDeclaredConstructors();    for (Constructor<?> constructor : constructors) {      if(constructor.isAnnotationPresent(ConstructorAnnotatin.class)) {        ConstructorAnnotatin annotation = constructor.getAnnotation(ConstructorAnnotatin.class);        System.out.println(annotation.constructorName()+"\t"+annotation.remark());      }    }    System.out.println("无");  }  }

复制代码


==============类注解解析==============personBean  1.2.1==============成员变量注解解析==============description  This is my personal annotation==============成员方法注解解析==============sayHello  TYPE2==============构造器注解解析==============无

复制代码

7、自定义注解实战🐱‍🏍

注解大多时候与反射或者 AOP 切面结合使用,它的作用有很多,比如标记和检查,最重要的一点就是简化代码,降低耦合性,提高执行效率

7.1、自定义注解 + SpringMVC 拦截器实现权限控制功能

还有一种应用场景,权限判断或者说是登录校验。


这个是我当时还没有学习市面上的权限框架,就是使用了这种自定义注解+拦截器的方式来实现简单的权限控制。


注意:此案例不可 CV 直接运行,代码很容易实现,大家理解思路即可。




定义注解:


@Target({ElementType.METHOD,ElementType.TYPE}) // 这个注解可以放在也可以放在方法上的。@Retention(RetentionPolicy.RUNTIME)public @interface Authority {    Role[] roles() ;}

复制代码


public enum Role {    SADMIN,    ADMIN,    TEACHER,    STUDENT}

复制代码




使用注解:


@Authority(roles = {Role.ADMIN, Role.SADMIN}) // 放在类上 说明这个类下所有的方法都需要有这个权限才可以进行访问@RestController@RequestMapping("/admin")public class AdminController {        @GetMapping("/hello")    public String Hello(){        return "hello 你最近还好吗";    }}

复制代码


@Controller@RequestMapping("/student")public class StudentController {
  @Authority(roles = {Role.STUDENT}) // 放在方法上则说明此方法需要注解上的权限才能进行访问    @GetMapping("/test")    public String test(){        return "你好,我已经不是一名学生啦";    }
}

复制代码




编写 SpringMVC 拦截器及处理注解的Handler


在其中进行 Token 的判断,和访问方法的权限判断,看方法上是否有注解,有的话,


就和当前用户对比,成功就可以访问,失败就直接拒绝。


当时用的是SSM框架,所以才会看到有 response.sendRedirect(contextPath + "/login");这样的。


public class LoginInterceptor extends HandlerInterceptorAdapter {
    private static final Logger log = LoggerFactory.getLogger(WebExceptionHandler.class);
    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String url = request.getRequestURI();//        log.info(request.getMethod()+" 请求URL:"+url);
        //从Token中解析User信息        User user = TokenUtil.verifyToken(request);
        String contextPath = request.getContextPath();        //user 为空则 表示 Token 不存在        if (user != null) {            if (user.getRole().equals("sadmin")) {                //检查方法上 是否有注解的 Role.SADMIN 或者 Role.ADMIN 权限 , 没有则检查类上有没有 如果符合要求则放行                if (HandlerUitl.checkAuthority(handler, new Role[]{Role.SADMIN, Role.ADMIN})) {                    request.setAttribute("user", user);                    return true;                }            }            if (user.getRole().equals("admin")) {                if (HandlerUitl.checkAuthority(handler, new Role[]{Role.ADMIN})) {                    request.setAttribute("user", user);                    return true;                }else {                    response.sendRedirect(contextPath + "/login");                }            }
            if (user.getRole().equals("teacher")) {                if (HandlerUitl.checkAuthority(handler, new Role[]{Role.TEACHER})) {
                    return true;                } else {                    response.sendRedirect(contextPath + "/login");                }            }            if (user.getRole().equals("student")) {                if (HandlerUitl.checkAuthority(handler, new Role[]{Role.STUDENT})) {
                    return true;                } else {
                    response.sendRedirect(contextPath + "/student/login");                }            }        }else {            response.sendRedirect(contextPath + "/login");        }
        return false;    }}

复制代码


  • 用于检查 方法 或者  类  是否需要权限
  • 并和 拥有的权限做对比
  • 如果方法上有 ,则以方法的 优先


public class HandlerUitl {
    public static boolean checkAuthority(Object handler, Role[] roles1){            if (handler instanceof HandlerMethod) {            HandlerMethod handlerMethod = (HandlerMethod) handler;            // 获取方法上的注解            Authority authority = handlerMethod.getMethod().getAnnotation(Authority.class);            // 如果方法上的注解为空 则获取类的注解            if (authority == null) {                authority = handlerMethod.getMethod().getDeclaringClass().getAnnotation(Authority.class);            }            // 如果标记了注解,则判断权限            if (authority != null) {                Role[] roles = authority.roles();                //如果 方法权限为 0 则通过                if(roles.length==0){                    return true;                }                //判断 拥有的权限 是否 符合 方法所需权限                for(int i = 0; i < roles.length; i++){                    for(int j = 0; j < roles1.length; j++){                        if(roles[i]==roles1[j]){//                            System.out.println("可以访问");                            return true;                        }                    }                }
            }            return false;        }        return true;
    }
}

复制代码

7.2、自定义注解+AOP+Redis 防止重复提交

先简单说一下防止重复提交注解的逻辑:


  1. 在需要防止重复提交的接口的方法,加上注解。
  2. 发送请求写接口携带 Token
  3. 请求的路径+ Token 拼接程 key,value 值为生成的 UUID 码
  4. 然后 set Redis 分布式锁,能获取到就顺利提交(分布式锁默认 5 秒过期),不能获取就是重复提交了,报错。


定义注解


import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;
@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface NoRepeatSubmit {
    /**     * 设置请求锁定时间     * @return     */    int lockTime() default 5;}

复制代码


定义处理注解的切面类


import com.eshop.api.ApiResult;import com.eshop.common.aop.NoRepeatSubmit;import com.eshop.common.util.RedisLock;import com.eshop.common.util.RequestUtils;import lombok.extern.slf4j.Slf4j;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import org.springframework.util.Assert;
import javax.servlet.http.HttpServletRequest;import java.util.UUID;
/** * 重复提交aop */@Aspect@Component@Slf4jpublic class RepeatSubmitAspect {
    @Autowired    private RedisLock redisLock;
    @Pointcut("@annotation(noRepeatSubmit)")    public void pointCut(NoRepeatSubmit noRepeatSubmit) {    }
    @Around("pointCut(noRepeatSubmit)")    public Object around(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) throws Throwable {        int lockSeconds = noRepeatSubmit.lockTime();
        HttpServletRequest request = RequestUtils.getRequest();        Assert.notNull(request, "request can not null");
        String bearerToken = request.getHeader("Authorization");        String[] tokens = bearerToken.split(" ");        String token = tokens[1];        String path = request.getServletPath();        String key = getKey(token, path);        String clientId = getClientId();
        boolean isSuccess = redisLock.tryLock(key, clientId, lockSeconds);        log.info("tryLock key = [{}], clientId = [{}]", key, clientId);
        if (isSuccess) {            log.info("tryLock success, key = [{}], clientId = [{}]", key, clientId);            // 获取锁成功            Object result;
            try {                // 执行进程                result = pjp.proceed();            } finally {                // 解锁                redisLock.releaseLock(key, clientId);                log.info("releaseLock success, key = [{}], clientId = [{}]", key, clientId);            }            return result;        } else {            // 获取锁失败,认为是重复提交的请求            log.info("tryLock fail, key = [{}]", key);            return  ApiResult.fail("重复请求,请稍后再试");        }    }
    private String getKey(String token, String path) {        return token + path;    }
    private String getClientId() {        return UUID.randomUUID().toString();    }
}

复制代码


import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.RedisCallback;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.stereotype.Service;import redis.clients.jedis.Jedis;import redis.clients.jedis.params.SetParams;
import java.util.Collections;
/** * Redis 分布式锁实现 */@Servicepublic class RedisLock {
    private static final Long RELEASE_SUCCESS = 1L;    private static final String LOCK_SUCCESS = "OK";    private static final String SET_IF_NOT_EXIST = "NX";    // 当前设置 过期时间单位, EX = seconds; PX = milliseconds    private static final String SET_WITH_EXPIRE_TIME = "EX";    // if get(key) == value return del(key)    private static final String RELEASE_LOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
    @Autowired    private StringRedisTemplate redisTemplate;
    /**     * 该加锁方法仅针对单实例 Redis 可实现分布式加锁     * 对于 Redis 集群则无法使用     *     * 支持重复,线程安全     *     * @param lockKey   加锁键     * @param clientId  加锁客户端唯一标识(采用UUID)     * @param seconds   锁过期时间     * @return     */    public boolean tryLock(String lockKey, String clientId, long seconds) {        return redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {            Jedis jedis = (Jedis) redisConnection.getNativeConnection();            SetParams setParams = new SetParams();            String result = jedis.set(lockKey, clientId, setParams.nx().px(seconds));            if (LOCK_SUCCESS.equals(result)) {                return true;            }            return false;        });    }
    /**     * 与 tryLock 相对应,用作释放锁     *     * @param lockKey     * @param clientId     * @return     */    public boolean releaseLock(String lockKey, String clientId) {        return redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {            Jedis jedis = (Jedis) redisConnection.getNativeConnection();            Object result = jedis.eval(RELEASE_LOCK_SCRIPT, Collections.singletonList(lockKey),                    Collections.singletonList(clientId));            if (RELEASE_SUCCESS.equals(result)) {                return true;            }            return false;        });    }}

复制代码


使用注解


/**     * 添加收藏     */@NoRepeatSubmit@PostMapping("/collect/add")@ApiOperation(value = "添加收藏",notes = "添加收藏")public ApiResult<Boolean> collectAdd(@Validated @RequestBody StoreProductRelationQueryParam param){    // 处理业务逻辑    return ApiResult.ok();}

复制代码

7.3、自定义注解 + Aop 实现日志收集

有关于这个,我之前有写过一篇文章,就不再此处特意贴出来增加篇幅啦。


自定义注解 + Aop 实现日志收集



8、自言自语💌

原本还想找点面试题的,但是到处找了找,面试大部分也就是面试上面这些知识点,所以就删掉啦。


本篇主要是针对Java运行时的注解的讲解及应用,但是你想一想,我们使用lombok的注解时,它的实现原理又是什么样的呢?为什么可以帮我们自动生成代码呢?是谁给我们做了这件事情呢?


下篇主要是针对上述的几个疑问来展开的,文章的大纲和构思倒是有点想法,但是不知道能不能写好下篇。


另外 Java 注解的下半场,主要是围绕着 Lombok相关来讲的,其中牵扯到的 AbstractProcessor ,其实也算是冷门知识了,随着好奇心继续去挖掘吧。


也非常感谢大家的阅读,觉得有所收获的话,可以点点赞,或者留下评论,让我收到你的反馈吧

下篇文章见。


参考

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
11天前
|
Java
在 Java 中捕获和处理自定义异常的代码示例
本文提供了一个 Java 代码示例,展示了如何捕获和处理自定义异常。通过创建自定义异常类并使用 try-catch 语句,可以更灵活地处理程序中的错误情况。
|
11天前
|
Java
在 Java 中,如何自定义`NumberFormatException`异常
在Java中,自定义`NumberFormatException`异常可以通过继承`IllegalArgumentException`类并重写其构造方法来实现。自定义异常类可以添加额外的错误信息或行为,以便更精确地处理特定的数字格式转换错误。
|
1月前
|
存储 Java 开发者
Java Map实战:用HashMap和TreeMap轻松解决复杂数据结构问题!
【10月更文挑战第17天】本文深入探讨了Java中HashMap和TreeMap两种Map类型的特性和应用场景。HashMap基于哈希表实现,支持高效的数据操作且允许键值为null;TreeMap基于红黑树实现,支持自然排序或自定义排序,确保元素有序。文章通过具体示例展示了两者的实战应用,帮助开发者根据实际需求选择合适的数据结构,提高开发效率。
61 2
|
1月前
|
XML Java 编译器
Java学习十六—掌握注解:让编程更简单
Java 注解(Annotation)是一种特殊的语法结构,可以在代码中嵌入元数据。它们不直接影响代码的运行,但可以通过工具和框架提供额外的信息,帮助在编译、部署或运行时进行处理。
88 43
Java学习十六—掌握注解:让编程更简单
|
7天前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
|
1月前
|
存储 消息中间件 安全
JUC组件实战:实现RRPC(Java与硬件通过MQTT的同步通信)
【10月更文挑战第9天】本文介绍了如何利用JUC组件实现Java服务与硬件通过MQTT的同步通信(RRPC)。通过模拟MQTT通信流程,使用`LinkedBlockingQueue`作为消息队列,详细讲解了消息发送、接收及响应的同步处理机制,包括任务超时处理和内存泄漏的预防措施。文中还提供了具体的类设计和方法实现,帮助理解同步通信的内部工作原理。
JUC组件实战:实现RRPC(Java与硬件通过MQTT的同步通信)
|
27天前
|
Java 开发者 Spring
[Java]自定义注解
本文介绍了Java中的四个元注解(@Target、@Retention、@Documented、@Inherited)及其使用方法,并详细讲解了自定义注解的定义和使用细节。文章还提到了Spring框架中的@AliasFor注解,通过示例帮助读者更好地理解和应用这些注解。文中强调了注解的生命周期、继承性和文档化特性,适合初学者和进阶开发者参考。
47 14
|
27天前
|
前端开发 Java
[Java]讲解@CallerSensitive注解
本文介绍了 `@CallerSensitive` 注解及其作用,通过 `Reflection.getCallerClass()` 方法返回调用方的 Class 对象。文章还详细解释了如何通过配置 VM Options 使自定义类被启动类加载器加载,以识别该注解。涉及的 VM Options 包括 `-Xbootclasspath`、`-Xbootclasspath/a` 和 `-Xbootclasspath/p`。最后,推荐了几篇关于 ClassLoader 的详细文章,供读者进一步学习。
32 12
|
1月前
|
安全 Java
如何在 Java 中创建自定义安全管理器
在Java中创建自定义安全管理器需要继承SecurityManager类并重写其方法,以实现特定的安全策略。通过设置系统安全属性来启用自定义安全管理器,从而控制应用程序的访问权限和安全行为。
|
1月前
|
开发框架 Java 程序员
揭开Java反射的神秘面纱:从原理到实战应用!
本文介绍了Java反射的基本概念、原理及应用场景。反射允许程序在运行时动态获取类的信息并操作其属性和方法,广泛应用于开发框架、动态代理和自定义注解等领域。通过反射,可以实现更灵活的代码设计,但也需注意其性能开销。
47 1
下一篇
无影云桌面