自定义注解

自定义注解

1.先看一个dome

import java.lang.annotation.*;

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogAutoRecord {
    // 强制记录方法功能(使用注解必须填写该属性)
    String methodDesc();
	// 带有默认值,非必填
    String defValue() default "";
}

对于上述dome中,有以下几个属性。

  • 注解定义用@interface关键字修饰

  • @Target注解,是专门用来限定某个自定义注解能够被应用在哪些Java元素上面的

  • @Retention注解,翻译为持久力、保持力。即用来修饰自定义注解的生命周期

  • @Documented注解,是被用来指定自定义注解是否能随着被定义的java文件生成到JavaDoc文档当中

  • @Inherited注解,是指定某个自定义注解如果写在了父类的声明部分,那么子类的声明部分也能自动拥有该注解

2.属性解释

  • @interface关键字修饰,表明这是一个注解,可将注解挂到别的头上,至于挂到哪要看@Target注解了。

  • @Target注解,是专门用来限定某个自定义注解能够被应用在哪些Java元素上面的

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Target {
        /**
         * Returns an array of the kinds of elements an annotation type
         * can be applied to.
         * @return an array of the kinds of elements an annotation type
         * can be applied to
         */
        ElementType[] value();
    }
    

    从源码中看出 该注解传入 ElementType[] ,是一个数组,可以传多个,如:

    @Target({ElementType.METHOD,ElementType.TYPE})
    

    上面是修饰的注解表明该注解可以用在方法上,也可以用在类上。具体属性看源码

    	/** Class, interface (including annotation type), or enum declaration */
    	/**类、接口(包括注释类型)或枚举声明*/
        TYPE,
    
        /** Field declaration (includes enum constants) */
    	/**字段声明(包括枚举常量)*/
        FIELD,
    
        /** Method declaration */
    	/**方法声明*/
        METHOD,
    
        /** Formal parameter declaration */
    	/**形式参数声明*/
        PARAMETER,
    
        /** Constructor declaration */
    	/**构造函数声明*/
        CONSTRUCTOR,
    
        /** Local variable declaration */
    	/**局部变量声明*/
        LOCAL_VARIABLE,
    
        /** Annotation type declaration */
    	/**注释类型声明*/
        ANNOTATION_TYPE,
    
        /** Package declaration */
    	/**包装声明*/
        PACKAGE,
    
        /**
         * Type parameter declaration
         *
         * @since 1.8
         * 类型参数声明,1.8以后才有
         */
        TYPE_PARAMETER,
    
        /**
         * Use of a type
         *
         * @since 1.8 字体的使用
         */
        TYPE_USE
    
  • @Retention注解,可以理解为生效时期

    Retention 源码

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Retention {
        /**
         * Returns the retention policy.
         * @return the retention policy
         */
        RetentionPolicy value();
    }
    

    Retention 源码中属性值为RetentionPolicy对象,所以不能像Target一样配置多个,下面看一下RetentionPolicy属性的源码:

    public enum RetentionPolicy {
        /**
         * Annotations are to be discarded by the compiler.
         * 编译器将丢弃注释。
         * 这个属性的表明只会将注解信息保存到程序源码中,在经过编译器编译后就会将信息抛弃,不会保存到.class文件中,也不会被加载到Jvm中。基本就是看着玩呢,标记一下
         */
        SOURCE,
    
        /**
         * Annotations are to be recorded in the class file by the compiler
         * but need not be retained by the VM at run time.  This is the default
         * behavior.
         * 这个属性的表明只会将注解信息保存到程序源码中,也会保存到.class文件中,但不会加载到jvm中。标记一下,读取class时候可以读取到。
         */
        CLASS,
    
        /**
         * Annotations are to be recorded in the class file by the compiler and
         * retained by the VM at run time, so they may be read reflectively.
         *
         * @see java.lang.reflect.AnnotatedElement
         * 这个属性的表明只会将注解信息保存到程序源码中,也会保存到.class文件中,也会加载到jvm中。在程序中可以读取到哪些类或方法标记了该注解,便于我们后续操作
         */
        RUNTIME
    }
    

    测试这三个属性,创建三个注解,Retention分别标记为RUNTIME,SOURCE,CLASS

    @Retention(RetentionPolicy.RUNTIME)
    public @interface LogAutoRecord { // 省略 1
        
    @Retention(RetentionPolicy.CLASS)
    public @interface LogAutoRecordTestClass { // 省略2
        
    @Retention(RetentionPolicy.SOURCE)
    public @interface LogAutoRecordTestSource { // 省略 3 
    

    controller

    // 我在controller中使用注解,方法如下:
        @GetMapping("/getUserByCommon")
        @LogAutoRecord(methodDesc = "根据统一请求查询用户信息")
        public CommonResponse getUserByCommonRequest(@RequestBody UserRequestDTO userRequestDTO){
            String userById = userService.getUserById(userRequestDTO.getUserId());
            return CommonResponse.succeed(userById);
        }
        @GetMapping("/getUserByCommonTestSource")
        @LogAutoRecordTestSource(methodDesc = "根据统一请求查询用户信息,测试注解属性source")
        public CommonResponse getUserByCommonTestSource(@RequestBody UserRequestDTO userRequestDTO){
            String userById = userService.getUserById(userRequestDTO.getUserId());
            return CommonResponse.succeed(userById);
        }
        @GetMapping("/getUserByCommonTestClass")
        @LogAutoRecordTestClass(methodDesc = "根据统一请求查询用户信息,测试注解属性Class")
        public CommonResponse getUserByCommonTestClass(@RequestBody UserRequestDTO userRequestDTO){
            String userById = userService.getUserById(userRequestDTO.getUserId());
            return CommonResponse.succeed(userById);
        }
    

    编译后的class文件

    // 编译后的.class 文件
        @GetMapping({"/getUserByCommon"})
        @LogAutoRecord(
            methodDesc = "根据统一请求查询用户信息"
        )
        public CommonResponse getUserByCommonRequest(@RequestBody UserRequestDTO userRequestDTO) {
            String userById = this.userService.getUserById(userRequestDTO.getUserId());
            return CommonResponse.succeed(userById);
        }
    
        @GetMapping({"/getUserByCommonTestSource"})
        public CommonResponse getUserByCommonTestSource(@RequestBody UserRequestDTO userRequestDTO) {
            String userById = this.userService.getUserById(userRequestDTO.getUserId());
            return CommonResponse.succeed(userById);
        }
    
        @GetMapping({"/getUserByCommonTestClass"})
        @LogAutoRecordTestClass(
            methodDesc = "根据统一请求查询用户信息,测试注解属性Class"
        )
        public CommonResponse getUserByCommonTestClass(@RequestBody UserRequestDTO userRequestDTO) {
            String userById = this.userService.getUserById(userRequestDTO.getUserId());
            return CommonResponse.succeed(userById);
        } 
    

    根据编译后的文件可以看出source标记的编译后就不见了,class的还在。

  • @Documented注解,是被用来指定自定义注解是否能随着被定义的java文件生成到JavaDoc文档当中

  • @Inherited注解,是指定某个自定义注解如果写在了父类的声明部分,那么子类的声明部分也能自动拥有该注解

    @Inherited注解只对那些@Target被定义为ElementType.TYPE的自定义注解起作用。

3. 获取注解

  • 给出指定包

    public class GetAnnotation {
        public static void getAnnotation() {
            // 要扫描的包
            String packageName = "com.zhoust.fastdome.business.controller";
            Reflections f = new Reflections(packageName);
            // 获取扫描到的标记注解的集合
            Set<Class<?>> set = f.getTypesAnnotatedWith(LogAutoRecord.class);
            for (Class<?> c : set) {
                // 循环获取标记的注解
                LogAutoRecord annotation = c.getAnnotation(LogAutoRecord.class);
                // 打印注解中的内容
                System.out.println(annotation.methodDesc());
            }
        }
    }
    

    使用Reflections反射需要导入反射包,或者自己使用java自带的反射包

            <dependency>
                <groupId>org.reflections</groupId>
                <artifactId>reflections</artifactId>
                <version>0.9.11</version>
            </dependency>
    
  • aop切面 中获取注解的对象

    MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            String methodName = method.getName();
            LogAutoRecord annotation = method.getAnnotation(LogAutoRecord.class);
            String methodDesc = "记录日志";
            if(null != annotation){
                methodDesc = annotation.methodDesc();
            }
    

活到老学到老

Tags: