JVM--插入式注解处理器

JVM--插入式注解处理器,第1张

JVM--插入式注解处理器 一、创建自定义注解处理器工程 pom.xml中引入
    
        com.google.auto.service
        auto-service-annotations
        1.0.rc7
        true
        compile
    
    
        com.google.auto.service
         auto-service
         1.0.rc7
         true
         compile
      
	
	
	   
        com.sun
         tools
         1.8.0
         true
         compile
     
定义注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
@documented
public @interface TakeTime {
    
    String tag() default "";
}
实现注解处理器
import com.google.auto.service.AutoService;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.model.JavacElements;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.Names;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import java.util.Set;



@SupportedSourceVersion(value = SourceVersion.RELEASE_8)
@SupportedAnnotationTypes(value = {"cn.kanyun.annotation_processor.taketime.TakeTime"})
@AutoService(Processor.class)
public class TakeTimeProcessor extends AbstractProcessor {

    
    private Messager messager;

    
    private Filer filer;

    
    private JavacElements elementUtils;

    
    private Types typeUtils;

    
    private JavacTrees trees;

    
    private TreeMaker treeMaker;

    private Names names;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        messager = processingEnv.getMessager();
        filer = processingEnv.getFiler();
        elementUtils = (JavacElements) processingEnv.getElementUtils();
        typeUtils = processingEnv.getTypeUtils();
        this.trees = JavacTrees.instance(processingEnv);
        Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
        this.treeMaker = TreeMaker.instance(context);
        this.names = Names.instance(context);
    }

    
    @Override
    public boolean process(Set annotations, RoundEnvironment roundEnv) {
//        roundEnv.getRootElements()会返回工程中所有的Class,在实际应用中需要对各个Class先做过滤以提高效率,避免对每个Class的内容都进行扫描
        roundEnv.getRootElements();
        messager.printMessage(Diagnostic.Kind.NOTE, "TakeTimeProcessor注解处理器处理中");
        TypeElement currentAnnotation = null;
//        遍历注解集合,也即@SupportedAnnotationTypes中标注的类型
        for (TypeElement annotation : annotations) {
            messager.printMessage(Diagnostic.Kind.NOTE, "遍历本注解处理器处理的所有注解,当前遍历到的注解是:" + annotation.getSimpleName());
            currentAnnotation = annotation;
        }
//      获取所有包含 TakeTime 注解的元素(roundEnv.getElementsAnnotatedWith(TakeTime.class))返回所有被注解了@Factory的元素的列表。你可能已经注意到,我们并没有说“所有被注解了@TakeTime的方法的列表”,因为它真的是返回Element的列表。请记住:Element可以是类、方法、变量等。所以,接下来,我们必须检查这些Element是否是一个方法)
        Set elementSet = roundEnv.getElementsAnnotatedWith(TakeTime.class);
        messager.printMessage(Diagnostic.Kind.NOTE, "TakeTimeProcessor注解处理器处理@TakeTime注解");
        for (Element element : elementSet) {
                //获取注解
                TakeTime TakeTimeAnnotation = element.getAnnotation(TakeTime.class);
                //获取注解中配置的值
                String tag = TakeTimeAnnotation.tag();
                messager.printMessage(Diagnostic.Kind.NOTE, currentAnnotation.getSimpleName() + "注解上设置的值为:" + tag);

//                TypeSpec typeSpec = generateCodeByPoet(typeElement, null);

//                方法名(这里之所以是方法名,是因为这个注解是标注在方法上的)
                String methodName = element.getSimpleName().toString();

//                类名[全限定名]
//                element.getEnclosingElement()返回封装此元素(非严格意义上)的最里层元素,由于我们在上面判断了element是method类型,所以直接封装method的的就是类了
//                http://www.169it.com/article/3400309390285698450.html
                String className = element.getEnclosingElement().toString();

                messager.printMessage(Diagnostic.Kind.NOTE, "当前被标注注解的方法所在的类是:" + className);
                messager.printMessage(Diagnostic.Kind.NOTE, currentAnnotation.getSimpleName() + "当前被标注注解的方法是:" + methodName);

//                JavaFile javaFile = JavaFile.builder(packageName, typeSpec).build();
                enhanceMethodDecl(elementUtils.getTree(element), tag, className + "." + methodName);



            if (element.getKind() == ElementKind.FIELD) {
//                当前element是字段类型
                VariableElement variableElement = (VariableElement) element;
                messager.printMessage(Diagnostic.Kind.ERROR, "字段不能使用@TakeTime注解", element);
            }

            if (element.getKind() == ElementKind.CONSTRUCTOR) {
//                当前element是构造方法类型

            }
        }


        return false;
    }


    
    private JCTree.JCMethodDecl enhanceMethodDecl(JCTree jcTree, String tag, String methodName) {
        JCTree.JCMethodDecl jcMethodDecl = (JCTree.JCMethodDecl) jcTree;

//        生成表达式System.currentTimeMillis()
        JCTree.JCexpressionStatement time = treeMaker.Exec(treeMaker.Apply(
                //参数类型(传入方法的参数的类型) 如果是无参的不能设置为null 使用 List.nil()
                List.nil(),
                memberAccess("java.lang.System.currentTimeMillis"),
                //因为不需要传递参数,所以直接设置为List.nil() 不能设置为null
                List.nil()
                //参数集合[集合中每一项的类型需要跟第一个参数对照]
//                List.of(treeMaker.Literal())
                )
        );


//        编译后该方法会存在一个startTime的变量,其值为编译时的时间
        JCTree.JCVariableDecl startTime = createVarDef(treeMaker.Modifiers(0), "startTime", memberAccess("java.lang.Long"), treeMaker.Literal(System.currentTimeMillis()));

//        耗时计算表示式
        JCTree.JCexpressionStatement timeoutStatement = treeMaker.Exec(
                treeMaker.Apply(
                        List.of(memberAccess("java.lang.Long"), memberAccess("java.lang.Long")),
                        memberAccess("java.lang.Math.subtractExact"),
                        List.of(time.expr, treeMaker.Ident(startTime.name))
                )

        );
//
        messager.printMessage(Diagnostic.Kind.NOTE, "::::::::::::::::::::");
        messager.printMessage(Diagnostic.Kind.NOTE, timeoutStatement.expr.toString());

//        生成表达式System.out.println()
        JCTree.JCexpressionStatement TakeTime = treeMaker.Exec(treeMaker.Apply(
                //参数类型(传入方法的参数的类型) 如果是无参的不能设置为null 使用 List.nil()
                List.of(memberAccess("java.lang.String"), memberAccess("java.lang.String"), memberAccess("java.lang.Long")),
//                因为这里要传多个参数,所以此处应使用printf,而不是println
                memberAccess("java.lang.System.out.printf"),
                //取到前面定义的startTime的变量
//                List.of(treeMaker.Ident(startTime.name))
//                取得结果
                List.of(treeMaker.Literal(">>>>>>>>TAG:%s -> 方法%s执行用时:%d<<<<<<<"), treeMaker.Literal(tag), treeMaker.Literal(methodName), timeoutStatement.getexpression())
                )
        );

//        catch中的代码块
        JCTree.JCBlock catchBlock = treeMaker.Block(0, List.of(
                treeMaker.Throw(
//                        e 这个字符是catch块中定义的变量
                        treeMaker.Ident(getNameFromString("e"))
                )
        ));
//        finally代码块中的代码
        JCTree.JCBlock finallyBlock = treeMaker.Block(0, List.of(TakeTime));


        List statements = jcMethodDecl.body.getStatements();
//        遍历方法体中每一行(断句符【分号/大括号】)代码
        for (JCTree.JCStatement statement : statements) {
            messager.printMessage(Diagnostic.Kind.NOTE, "遍历方法体中的statement:" + statement);
            messager.printMessage(Diagnostic.Kind.NOTE, "该statement的类型:" + statement.getKind());
            if (statement.getKind() == Tree.Kind.RETURN) {
                messager.printMessage(Diagnostic.Kind.NOTE, "该statement是Return语句");
                break;
            }

        }

//        jcMethodDecl.body即为方法体,利用treeMaker的Block方法获取到一个新方法体,将原来的替换掉
        jcMethodDecl.body = treeMaker.Block(0, List.of(
//                定义开始时间,并附上初始值 ,初始值为编译时的时间
                startTime,
                treeMaker.Exec(
//                        这一步 将startTime变量进行赋值 其值 为(表达式也即运行时时间) startTime = System.currentTimeMillis()
                        treeMaker.Assign(
                                treeMaker.Ident(getNameFromString("startTime")),
                                time.getexpression()
                        )
                ),
//                添加TryCatch
                treeMaker.Try(jcMethodDecl.body,
                        List.of(treeMaker.Catch(createVarDef(treeMaker.Modifiers(0), "e", memberAccess("java.lang.Exception"),
                                null), catchBlock)), finallyBlock)

//                下面这段是IF代码,是我想在try catch finally后添加return代码(如果有需要的话),结果发现 如果不写下面的代码的话
//                Javac会进行判断,如果这个方法有返回值的话,那么Javac会自动在try块外定义一个变量,同时找到要上一个return的变量并赋值
//                然后返回,具体可以查看编译后的字节码的反编译文件,如果该方法没有返回值,那么什么也不做

//                根据返回值类型,判断是否在方法末尾添加 return  语句  判断返回类型的Kind是否等于TypeKind.VOID
//                treeMaker.If(treeMaker.Parens(
//                        treeMaker.Binary(
//                                JCTree.Tag.EQ,
//                                treeMaker.Literal(returnType.getKind().toString()),
//                                treeMaker.Literal(TypeKind.VOID.toString()))
//                        ),
//
//                        //符合IF判断的Statement
//                        treeMaker.Exec(treeMaker.Literal("返回类型是Void,不需要return")),
                        不符合IF判断的Statement
//                        null
//                )
                )


        );


        return jcMethodDecl;
    }


    
    private JCTree.JCVariableDecl createVarDef(JCTree.JCModifiers modifiers, String name, JCTree.JCexpression varType, JCTree.JCexpression init) {
        return treeMaker.VarDef(
                modifiers,
                //名字
                getNameFromString(name),
                //类型
                varType,
                //初始化语句
                init
        );
    }


    
    private com.sun.tools.javac.util.Name getNameFromString(String s) {
        return names.fromString(s);
    }


    
    private JCTree.JCexpression memberAccess(String components) {
        String[] componentArray = components.split("\.");
        JCTree.JCexpression expr = treeMaker.Ident(getNameFromString(componentArray[0]));
        for (int i = 1; i < componentArray.length; i++) {
            expr = treeMaker.Select(expr, getNameFromString(componentArray[i]));
        }
        return expr;
    }
}
三、创建测试工程

自定义的注解处理器,主要 *** 作Javac在编译被注解标注的方法时,在生成字节码时添加自己的逻辑
首先在 方法体的开头 插入一条当前时间的变量 ,并赋值为 System.currentTimeMillis()然后将整个方法体包括在try块中,添加catch 即finally catch块中直接定义异常并跑出,finally块中打印用时语句!

pom.xml
	
   
       com
       test
       1.0.0
   
源码
public class Test {
	@TakeTime
	public void test() {
		System.out.println("Hello World");
	}
}
编译后
public class Test {
	Long startTime = 1642848534783L;
	startTime = System.currentTimeMillis();

	public void test() {
		try {
			System.out.println("Hello World!");
		} catch (Exception var6) {
			throw var6;
		} fianlly {
			System.out.printf(">>>>>>>TAG:%s --> 方法%s执行用时: %d<<<", "", "com.huawei.com.it.czrp.Test.test", Math.subtractExact(System.currentTimeMillis(), startTime));
		}
	}
}

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5710822.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存