
定义注解com.google.auto.service auto-service-annotations1.0.rc7 true compile com.google.auto.service auto-service1.0.rc7 true compile com.sun tools1.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 extends TypeElement> 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 extends Element> 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;
}
}
三、创建测试工程
pom.xml自定义的注解处理器,主要 *** 作Javac在编译被注解标注的方法时,在生成字节码时添加自己的逻辑
首先在 方法体的开头 插入一条当前时间的变量 ,并赋值为 System.currentTimeMillis()然后将整个方法体包括在try块中,添加catch 即finally catch块中直接定义异常并跑出,finally块中打印用时语句!
源码com test1.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));
}
}
}
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)