美文网首页
日更挑战-java注解实现代码调试

日更挑战-java注解实现代码调试

作者: 愿你我皆是黑马 | 来源:发表于2021-06-07 20:28 被阅读0次

    越不懂的越爱装
    大家都同等:IT世界没有难不难,只有是否了解过

    挑战目录

    为什么可以使用注解进行代码调试

    • 因为研究源码的方法里面有一个重要的方法是:为每个方法写上描述方法作用的log,然后运行时会打印这些方法的运行过程。那么流程就一目了然了。
    • 静态代理(Aspectd):我们可以在编译时改变编译的代码(需要特殊处理的编译器, Aspectd的编译器 ),去代理目标方法,在目标方法前后织入我们的代码段。
    • 动态代理(Spring AOP): 不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法

    注解的实现

    • 元注解:为注解的注解,用于定义注解

      分别为@Retention、 @Target、 @Document、 @Inherited和@Repeatable(JDK1.8加入)五种

      • @Retention 注解的作用时间:源码(编译期阶段),class(类加载阶段),运行时(JVM阶段)

        • @Retention(RetentionPolicy.SOURCE) 在源码,不在class,不在运行时
        • @Retention(RetentionPolicy.CLASS) 在源码,在class,不在运行时 (默认的)
        • @Retention(RetentionPolicy.RUNTIME) 在源码,在class,在运行时
      • @Target 注解的作用范围:接口、类、枚举、注解、枚举的常量、方法、方法参数、属性字段、构造函数、局部变量、泛型方法、泛型类、泛型接口 、任意类型除了 class

        • @Target(ElementType.TYPE) 作用接口、类、枚举、注解
        • @Target(ElementType.FIELD) 作用属性字段、枚举的常量
        • @Target(ElementType.METHOD) 作用方法
        • @Target(ElementType.PARAMETER) 作用方法参数
        • @Target(ElementType.CONSTRUCTOR) 作用构造函数
        • @Target(ElementType.LOCAL_VARIABLE)作用局部变量
        • @Target(ElementType.ANNOTATION_TYPE)作用于注解(@Retention注解中就使用该属性)
        • @Target(ElementType.PACKAGE) 作用于包
        • @Target(ElementType.TYPE_PARAMETER) 作用于类型泛型,即泛型方法、泛型类、泛型接口 (jdk1.8加入)
        • @Target(ElementType.TYPE_USE) 类型使用.可以用于标注任意类型除了 class (jdk1.8加入)
      • @Document 注解的Javadoc文档

      • @Inherited 注解作用与类的继承:如果java父类使用了该注解,当子类没有使用注解时。默认也使用该注解。

      • @Repeatable 可以使用多次:可以以不同的作用多次作用目标

    • 注解的属性:为注解的属性,用于定义注解里面的变量
    @Retention(RetentionPolicy.SOURCE)
    @Target(ElementType.METHOD)
    public @interface TestAnnotation {
       String methodName() default ""; //属性一
       String desc() default ""; //属性二
    }
    

    获取注解属性:根据@Retention的描述,知道可能有三种方式

    • 源码(编译期阶段)
      • 使用注解处理器 Annotation Processing Tool 简称APT

        1. 实现注解处理器(继承 AbstractProcessor 抽象类)

          public class TestProcessor extends AbstractProcessor {
          
             @Override
             public SourceVersion getSupportedSourceVersion() {
                 //处理器支持的版本
                 return SourceVersion.RELEASE_8;
             }
          
             @Override
             public Set<String> getSupportedAnnotationTypes() {
                 Set<String> 处理器支持处理的注解集合 = new HashSet<>();
                 处理器支持处理的注解集合.add(Test.class.toString());
                 return 处理器支持处理的注解集合;
             }
          
             /**
              * @param processingEnvironment
              */
             @Override
             public synchronized void init(ProcessingEnvironment processingEnvironment){
                 super.init(processingEnvironment);
             }
          
             @Override
             public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
                 Set<? extends Element> es = roundEnvironment.getElementsAnnotatedWith(Test.class);//获取被Test注解标记的元素集合
                 es.forEach(e -> { //e 标识被标注的对象,相当于反射获取的那个
                     //获取注解标注在哪
                     e.getKind().isClass();
                     e.getKind().isField();
                     e.getKind().isInterface();
                     //获取被标注的地方的名字
                     e.getSimpleName();
                     //获取使用的注解对象
                     Test test = e.getAnnotation(Test.class);
                     //获取对象属性
                     test.name();
                     //使用反射api对e和test对象的操作
                     ...
                 });
                 return false;
             }
          }
          
        2. 声明注解处理器

          • 创建resources/META-INF/services/javax.annotation.processing.Process文件
            在文件中添加注解处理器的全类名(一行只能写一个)

          • 如果是作为第三方jar包提供给别人 : 需要在maven打包时增加如下配置,主要也是把javax. annotation.processing.Processor文件打包到对应目录:

          <build>
              <resources>
                  <resource>
                      <directory>src/main/resources</directory>
                      <excludes>
                          <exclude>META-INF/**/*</exclude>
                      </excludes>
                  </resource>
              </resources>
              <plugins>
                  <plugin>
                      <groupId>org.apache.maven.plugins</groupId>
                      <artifactId>maven-resources-plugin</artifactId>
                      <version>2.6</version>
                      <executions>
                          <execution>
                              <id>process-META</id>
                              <phase>prepare-package</phase>
                              <goals>
                                  <goal>copy-resources</goal>
                              </goals>
                              <configuration>
                                  <outputDirectory>target/classes</outputDirectory>
                                  <resources>
                                      <resource>
                                          <directory>${basedir}/src/main/resources/</directory>
                                          <includes>
                                              <include>**/*</include>
                                          </includes>
                                      </resource>
                                  </resources>
                              </configuration>
                          </execution>
                      </executions>
                  </plugin>
                  <plugin>
                      <groupId>org.apache.maven.plugins</groupId>
                      <artifactId>maven-compiler-plugin</artifactId>
                      <configuration>
                          <source>1.8</source>
                          <target>1.8</target>
                      </configuration>
                  </plugin>
              </plugins>
          </build>
          
    • class(类加载阶段)
    • 运行时(JVM阶段)
      • 使用反射

        反射提供的支持方法有:

        1. 是否存在注解对象:isAnnotationPresent(Class<? extends Annotation> annotationClass) )

        2. 获取指定注解对象:getAnnotation(Class<A> annotationClass)

        3. 获取所有注解对象数组:getAnnotations()

        4. 示例

          /**
          * 获取类注解属性
          */
          Class<C> c = C.class;
          boolean exist = c.isAnnotationPresent(A.class);
            if(exist){
               A a = c.getAnnotation(A.class);
               System.out.println(a.name());//注解A中定义了name属性
            }
            
          try {
            /**
            * 获取变量注解属性
            */
                Field name = c.getDeclaredField("name");
                boolean nameAExist = name.isAnnotationPresent(Name.class);
              if(nameAExist){
                 Name nameA = name.getAnnotation(Name.class);
                 System.out.println(nameA.value());
              }
              
              /**
            * 获取方法注解属性
            */
              Method setName = c.class.getDeclaredMethod("setName");
              if (setName!=null){
                  A a = setName.getAnnotation(A.class);
                  A[] as = a.value();
                  for (A a : as) {
                     System.out.println(a.value());
                  }
               }
              } catch (NoSuchFieldException e) {
                e.printStackTrace();
              }
          

    使用静态代理(Aspectd)实现方法日志打印

    1. 定义注解

      @Retention(RetentionPolicy.SOURCE)
      @Target(ElementType.METHOD)
      public @interface TestAnnotation {
          String methodName() default "";
          String desc() default "";
      }
      
    2. 实现处理

      import org.aspectj.lang.ProceedingJoinPoint;
      import org.aspectj.lang.annotation.Around;
      import org.aspectj.lang.annotation.Aspect;
      import org.aspectj.lang.reflect.MethodSignature;
      
      import java.lang.reflect.Method;
      
      @Aspect
      public class mp {
      
          @Around("@annotation(TestAnnotation)")
          public Object beforeReturnValue(ProceedingJoinPoint point) throws Throwable {
              Method methodSignart = ((MethodSignature) point.getSignature()).getMethod();
              TestAnnotation methodAnnotation = methodSignart.getAnnotation(TestAnnotation.class);
              if (methodAnnotation != null) {
                  String methodName = methodAnnotation.methodName();
                  String desc = methodAnnotation.desc();
                  System.out.println("----------方法开始执行----------" + methodName);
                  System.out.println("----------方法描述----------" + desc);
                  Object proceed = point.proceed();
                  System.out.println("----------方法执行结束----------" + methodName);
                  return proceed;
              }
              return point.proceed();
          }
      
      }
      
    3. 编写测试类

      public class TestMain {
         public static void main(String[] args) {
           new Test().test("x");
      }
      }
      
      public class Test {
          @TestAnnotation
          String test(String name){
              System.out.println("exe test");
              return name;
          }
      }
      

    使用动态代理(Spring AOP)实现方法日志打印

    略,以后写Spring AOP实现原理的时候在写

    相关文章

      网友评论

          本文标题:日更挑战-java注解实现代码调试

          本文链接:https://www.haomeiwen.com/subject/hclreltx.html