美文网首页
日更挑战-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注解实现代码调试

    越不懂的越爱装 大家都同等:IT世界没有难不难,只有是否了解过 挑战目录[https://www.jianshu....

  • 浅谈java注解

    Java注解用于为 Java代码提供元数据, 通过关键字@interface来定义一个注解,借助元注解实现一个注解...

  • Java注解

    在写java代码的过程中,经常会遇到注解,但是没有去理解注解背后的原理,也没有实现过注解。网上关于java注解的文...

  • java注解详解

    介绍 java 1.5引入了注解(annotation),注解类似注释,不同的是注解除了提供代码说明,还能实现程序...

  • java注解

    介绍 java 1.5引入了注解(annotation),注解类似注释,不同的是注解除了提供代码说明,还能实现程序...

  • java注解

    介绍 java 1.5引入了注解(annotation),注解类似注释,不同的是注解除了提供代码说明,还能实现程序...

  • Java 注解(一):创建自定义注解

    Java 注解 Java Annotations 什么是java注解 Java注解不属于代码的一部分,但用来给代码...

  • 打造简单的依赖注入功能

    让代码帮我们自动写代码。 目标:实现类似于Butterknife的id注入。首先先了解Java 的注解吧。注解的分...

  • java:自定义注解以及注解解析

    注解主要是用来减少代码的冗余,用较少的代码就可以实现更多的功能. java提供了四类元注解,我们构建新的注解都是用...

  • 10 Dagger2

    1 注解 1.1 注解的定义 Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执...

网友评论

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

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