[java]-自定义注解

aop+自定义注解

注解基础

1.Java注解的描述

java注解又叫做java标注,是java的一种注释机制,在jdk5.0引入。其可以作用在类、方法、变量、参数和包上。另外,其可以通过反射来获取注解标注的内容。可以说注解就是实现了Annotation的接口

2.Java注解的分类

Java注解分类的话,大致可以分为三类,分别是标准注解,元注解与自定义注解。

  • 标准注解

    标准注解就是java内置的注解,主要有@Override,@Deprecated,@SuppressWarnings,@FunctionalInterface

    • @Override:这个注解的作用主要是检查该注解标注的方法是否是重写方法,如果不是重写方法的话编译会不通过会报错。

    • @Deprecated:这个注解的作用主要是标记该方法可能要废弃了,使用的话会报个警告。

    • @SuppressWarnings:这个注解主要是告诉编译器要忽视一些警告。其常见的参数有:

      参数 作用
      deprecation 使用了不赞成使用的类或方法的警告
      unchecked 执行了未检查的转换时的警告,例如使用集合时没有用泛型来指定集合保存的类型
      fallthrough 当switch程序块两个case之间没有break时
      path 在类路径,源文件路径中有不存在的路径时的警告
      serial 在可序列化的类上缺少serialVersionUID定义时的警告
      all 所有的警告
    • @FunctionalInterface:用于指示被修饰的接口是函数式接口,在jdk8被引入

  • 元注解

    主要有@Retention,@Documented,@Target,@Inherited,@Repeatable

    • @Retention:用来定义该注解在哪个级别可用,也就是注解的生命周期,一个注解只能定义一个级别。具体有三个级别:
      image

      参数 描述
      SOURCE 在编译时被丢弃,不包含在类文件中,既不会参与编译也不会在运行时起到什么作用
      CLASS JVM加载时被丢弃,包含在类文件中,但是不能在运行时被获取到,默认值
      RUNTIME 由JVM加载包含在类文件中,能在运行时被获取到,一般开发自定义注解使用该级别
    • @Documented:表示该注解能不能出现在javadoc中,被该元注解修饰的注解表示能出现在javadoc中,如果没有被该元注解修饰,则是不会出现在javadoc中

    • @Target:用来表示该注解的适用范围,一个注解可以有多个适用范围,每个使用范围之间在大括号内用逗号隔开。如:
      @Target({ElementType.TYPE, ElementType.METHOD})
      使用范围的值总共有十个:
      image

      参数 描述
      TYPE 应用于类、接口(包括注解类型)或枚举
      FIELD 应用于字段(包括枚举常量)
      METHOD 应用于方法
      PARAMETER 应用于形参,也就是方法的参数
      CONSTRUCTOR 应用于构造函数
      LOCAL_VARIABLE 应用于局部变量
      ANNOTATION_TYPE 应用于注解
      PACKAGE 应用于包
      TYPE_PARAMETER 应用于类型参数,JDK1.8引入
      TYPE_USE 应用于任何类型,JDK1.8引入
    • @Inherited:该注解说明子类可以继承父类的注解,也就是说,如果被该元注解修饰的注解,能够在子类继承父类的时候把注解也继承过去。但是这边有一个需要注意的点,就是被该元注解修饰的注解只有应用在类上的时候才能够被继承过去,应用在方法或其他地方时是没有的,这边我刚开始就是用在了方法上,然后一直没效果。例子:

      注解:
      package com.mcj.music.annotation;
      
      import java.lang.annotation.*;
      
      /**
       * @author mcj
       * @date 2022/10/28 20:27
       * @description 日志有关的自定义注解
       */
      @Retention(RetentionPolicy.RUNTIME)
      @Target({ElementType.TYPE, ElementType.METHOD})
      @Inherited
      public @interface LogAnnotation {
      
          String value() default "";
      
      }
      
      父类:
      package com.mcj.music.annotation;
      
      /**
       * @author mcj
       * @date 2022/10/29 9:40
       * @description
       */
      @LogAnnotation("测试inherited注解")
      public class TestInherited {
      }
      
      子类:
      package com.mcj.music.annotation;
      
      /**
       * @author mcj
       * @date 2022/10/29 9:40
       * @description
       */
      public class ChildTestInherited extends TestInherited{
      
          public static void main(String[] args) {
      	 System.out.println(TestInherited.class.isAnnotationPresent(LogAnnotation.class));
      	 System.out.println(ChildTestInherited.class.isAnnotationPresent(LogAnnotation.class));
          }
      
      }
      

      输出结果:
      image
      去掉@Inherited注解的结果:
      image
      image

    • @Repeatable:表示被该元注解修饰的注解能够重复使用,也就是可以在一个方法上使用多次该注解,可以说将该注解用一个数组容器包了起来。例子:

      //LogAnnotation注解
      package com.mcj.music.annotation;
      
      import java.lang.annotation.*;
      
      /**
       * @author mcj
       * @date 2022/10/28 20:27
       * @description 日志有关的自定义注解
       */
      @Retention(RetentionPolicy.RUNTIME)
      @Target({ElementType.TYPE, ElementType.METHOD})
      @Repeatable(LogAnnotations.class)
      public @interface LogAnnotation {
      
          String value() default "";
      
      }
      
      
      // LogAnnotations注解
      package com.mcj.music.annotation;
      
      import java.lang.annotation.ElementType;
      import java.lang.annotation.Retention;
      import java.lang.annotation.RetentionPolicy;
      import java.lang.annotation.Target;
      
      /**
       * @author mcj
       * @date 2022/10/29 9:52
       * @description
       */
      @Retention(RetentionPolicy.RUNTIME)
      @Target({ElementType.TYPE, ElementType.METHOD})
      public @interface LogAnnotations {
          LogAnnotation[] value();
      }
      
      //测试类:
      package com.mcj.music.annotation;
      
      import java.lang.annotation.Annotation;
      import java.lang.reflect.Array;
      import java.util.Arrays;
      
      /**
       * @author mcj
       * @date 2022/10/29 9:55
       * @description
       */
      @LogAnnotation("测试repeatable1")
      @LogAnnotation("测试repeatable2")
      public class TestRepeatable {
      
          public static void main(String[] args) {
              Annotation[] annotations = TestRepeatable.class.getAnnotations();
              System.out.println(annotations.length);
              System.out.println(Arrays.toString(annotations));
          }
      
      }
      

      运行结果:
      image
      可以从结果看出,虽然注解的长度是1,但是它里面有两个不同的值
      PS:需要注意的是@Repeatable注解声明的注解(也就是LogAnnotation)要比@Repeatable值中的注解(也就是LogAnnotations)的作用范围大或相等,可用级别小或相等。

  • 自定义注解

    自定义注解大致可以分为三步:1.定义注解,2.配置注解,3.解析注解

    • 1.定义注解-也就是声明一个注解,创建一个注解对象

      注解与方法、变量、枚举等声明方式相似,唯一不同的是其关键字为@interface,也就是只要是声明时使用了该关键字,底层在实现的时候都会继承java.lang.annotation.Annotation接口。

      public @interface LogAnnotation {
      
      }
      

      在注解的方法体里面只能写一个东西:注解类型元素,也就是注解的属性。其格式为:

      修饰符 数据类型 属性名() default 默认值;
      

      其中修饰符必须是public,所以可以直接不写,其默认值即是public

      其中可以写的数据类型有:八种基本类型(int,short,long,double,byte,char,boolean,float),String,Class,注解类型,枚举类以及上面任一种类型的数组形式。

      其中的属性名是自定义的

      其中的()并不是定义参数的地方,只是一种语法而已

      default表示该属性的默认值,其默认值与前面的数据类型一致,有个要注意的点是如果没有设置默认值,则该注解在使用的时候一定要赋值。如下:

      public @interface LogAnnotation {
      
          // 值描述
          String value() default "";
      	
          // 名字
          String name();
      
      }
      
    • 2.配置注解-也就是对上面定义的注解配置其作用范围及生命周期

      配置注解则是利用上面记录的几种元注解对自定义的注解进行修饰,来描述自定义注解的生命周期与作用范围。如:

      @Retention(RetentionPolicy.RUNTIME)
      @Target(ElementType.METHOD)
      public @interface LogAnnotation {
      
          // 值描述
          String value() default "";
      	
          // 名字
          String name();
      
      }
      
    • 3.解析注解-也就是在程序运行时检测注解,并进行一系列操作

      注解的解析主要是通过java的反射来实现的,可以通过反射来获取相应的类、方法或字段,通过获取的类、方法、字段来判断其上是否使用了该注解,使用的话则获取该注解与该注解内各个元素的值。例子如下:

      package com.mcj.music.annotation;
      
      import java.lang.reflect.Method;
      
      /**
       * @author mcj
       * @date 2022/10/29 14:26
       * @description
       */
      public class TestAnnotation {
      
          @LogAnnotation(name = "mcj", value = "10.09测试")
          public void printString() {
              System.out.println("输出一个字符串");
          }
      
          public static void main(String[] args) {
              Class<TestAnnotation> testAnnotationClass = TestAnnotation.class;
              Method[] methods = testAnnotationClass.getMethods();
              for (Method method : methods) {
                  if (method.isAnnotationPresent(LogAnnotation.class)) {
                      LogAnnotation annotation = method.getAnnotation(LogAnnotation.class);
                      String name = annotation.name();
                      String value = annotation.value();
                      System.out.println(String.format("方法%s用了LogAnnotation注解,注解值name为:%s,value为:%s", method.getName(), name, value));
                  }
              }
          }
      
      }
      

      输出结果:

      描述:方法中的TestAnnotation.class就是利用反射获取TestAnnotation这个类对象,testAnnotationClass.getMethods()这个则是根据类对象获取该类中所有的方法,method.isAnnotationPresent(LogAnnotation.class)则是判断方法是否存在LogAnnotation注解,需要注意的是要看注解是标注再什么上面,如果是类上面则是

      类.isAnnotationPresent()
      

      如果是字段则是

      字段.isAnnotationPresent()
      

      这里是标注在方法上的。最后的getAnnotation()则是获取这个注解,然后利用使用枚举值的方式就可以获取注解中的属性值了。

3.总结

java的注解主要是对我们的开发起到一个辅助作用,帮助我们更容易的进行开发。其中自定义注解,主要是的就是那五个元注解,不过经常用的也就@Retention和@Target这两个元注解,另外对于注解的解析则是利用了java的反射机制。

自定义注解

自定义注解,并且实现,需要两个文件;

1155586-20180913201435650-1011449502

自定义注解类:

package com.clc.server.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(value = {ElementType.TYPE, ElementType.METHOD})//使用位置(类,方法)
@Retention(RetentionPolicy.RUNTIME)//加载到jvm里运行
public @interface Clc {
    String value(); //注解的属性,如果只有一个属性,一般叫value
    String name() default ""; //属性,默认值"",可以不写
}

定义好注解后,需要解析类来实现,此处使用aop来实现;

package com.clc.server.aop;

import com.clc.server.annotation.Clc;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 解析clc注解使用
 */
@Aspect//来定义一个切面
@Component
public class ClcAop {

    //定义切入点
    @Pointcut("@annotation(com.clc.server.annotation.Clc)")
    public void auditAspect() {
        System.out.println("1221212132");
    }

    //通知
    @Before("auditAspect()")
    public void doBefore(JoinPoint joinPoint) {
        System.out.println("触发到 @Before(\"auditAspect()\")");
    }

    /**
     * 后置通知
     *
     * @param joinPoint 切点
     */
    @AfterReturning("auditAspect()")
    public void doAfrterReturning(JoinPoint joinPoint) {

        Object[] args = joinPoint.getArgs();
        System.out.println("触发 @AfterReturning(\"auditAspect()\")");
        System.out.println(args.length);
        getControllerMethodDescription(joinPoint);
    }

    /**
     * 获取注解中对方法的描述信息
     *
     * @param joinPoint 切点
     * @return 方法描述
     */
    public static void getControllerMethodDescription(JoinPoint joinPoint) {
        String targetName = joinPoint.getTarget().getClass().getName();    //获得执行方法的类名
        String methodName = joinPoint.getSignature().getName();            //获得执行方法的方法名
        Object[] arguments = joinPoint.getArgs();                          //获取切点方法的所有参数类型
        try {
            Class targetClass = Class.forName(targetName);

            Method[] methods = targetClass.getMethods();    //获取公共方法,不包括类私有的
            String value = "";
            String name = "";
            for (Method method : methods) {
                if (method.getName().equals(methodName)) {
                    Class[] clazzs = method.getParameterTypes();     //对比方法中参数的个数
                    if (clazzs.length == arguments.length) {
                        value = method.getAnnotation(Clc.class).value();
                        name = method.getAnnotation(Clc.class).name();
                        break;
                    }
                }
            }
            System.out.println("value=" + value);
            System.out.println("name=" + name);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

[复制代码](javascript:void(0)😉

测试注解,使用

[复制代码](javascript:void(0)😉

   /**
     * 测试自定义注解
     */
    @Clc(value = "clc", name = "name")
    @RequestMapping(value = "/add2", method = RequestMethod.GET)
    public String add2() {
        //获取本服务的信息
        ServiceInstance instance = client.getLocalServiceInstance();
        Integer r = 2;
        String info = "/add, host:" + instance.getHost() + ", service_id:" + instance.getServiceId() + "结果:" + r;
        logger.info(info);
        return info;
    }

[复制代码](javascript:void(0)😉

触发注解后:

[复制代码](javascript:void(0)😉

2018-09-13 20:11:07.487  INFO 14012 --- [nio-9003-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2018-09-13 20:11:07.487  INFO 14012 --- [nio-9003-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2018-09-13 20:11:07.514  INFO 14012 --- [nio-9003-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 27 ms
触发到 @Before("auditAspect()")
2018-09-13 20:11:07.554  INFO 14012 --- [nio-9003-exec-1] c.c.s.c.ComputeController@7fcff1b9       : /add, host:localhost, service_id:clc-service结果:2
触发 @AfterReturning("auditAspect()")
0
value=clc
name=name
2018-09-13 20:15:44.844  INFO 14012 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver      : Resolving eureka endpoints via configuration

[复制代码](javascript:void(0)😉

简单的自定义注解,已经实现

posted on 2023-03-08 12:43  共感的艺术  阅读(208)  评论(0编辑  收藏  举报