javac 编译拓展,像 lombok 一样通过注解生成 自定义方法
<<深入理解java虚拟机>> 第十章介绍了javac的相关知识,提到可以通过拓展 javax.annotation.processing.Processor
在编译阶段可以自定义一些操作。还提到lombok也是通过这种方式进行代码生成。所以尝试自己来写一个玩玩,根据注解生成一个方法。
参照书上的做法 :
新建一个项目,结构如下
具体内容:
CreatePrint注解:
package com.rxf113;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author rxf113
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface CreatePrint {
}
CreatePrintProcessor 类:
package com.rxf113;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.JavaFileObject;
import java.io.*;
import java.util.Set;
/**
* CreatePrintProcessor
*
* @author rxf113
*/
@SupportedAnnotationTypes(value = "com.rxf113.CreatePrint")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class CreatePrintProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (TypeElement typeElement : annotations) {
for (Element element : roundEnv.getElementsAnnotatedWith(typeElement)) {
//获取全限类名
String fullClassName = element.toString();
fullClassName = fullClassName.replace('.', '\\');
//读取源文件
File file = new File(fullClassName + ".java");
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
String line;
//原代码
StringBuilder originCode = new StringBuilder();
while ((line = reader.readLine()) != null) {
originCode.append(line).append("\n");
}
//自定义一个print方法
String printFuncOne = "\tpublic void print() {\n" +
" System.out.println(\" I am the method created by cusProcessor !!! \");\n" +
" System.out.printf(\"age: %d name: %s\", age, name);\n" +
" }\n" +
"}";
//在原代码最后 添加方法
String newCode = originCode.toString().replaceAll("}", printFuncOne);
//创建一个和原来同名的类
JavaFileObject fileObject = processingEnv.getFiler().createSourceFile("com.rxf113.Person");
Writer w = fileObject.openWriter();
w.write(newCode);
w.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
return true;
}
}
Person 类:
package com.rxf113;
/**
* @author rxf113
*/
@CreatePrint
public class Person {
private int age;
private String name;
}
下载 openjdk 源码 并放入目录,进行具体调试
下载地址 http://hg.openjdk.java.net/jdk8u/jdk8u-dev/langtools/archive/tip.zip
解压后将 \src\share\classes\com 下的 sun文件夹复制到项目目录
同时创建一个类,手动调用 com.sun.tools.javac.main.Main 下的compile方法 (运行此方法就会执行到自己创建的CreatePrintProcessor 类)
调试完毕
控制台运行:
- 首先编译 CreatePrint 注解 和 CreatePrintProcessor 类 在src目录下执行:
- 指定 CreatePrintProcessor 编译Person:
控制台抛出异常:大概原因是原来的类存在之类的 (测试了下如果生成一个新的类,不会抛异常)
虽然抛出异常,但此时Person类的源码已经改变:
但是创建类文件时,抛出异常了,所以编译也没继续完成,我再手动编译一次Person
编译完成后,此时项目结构如下:
3. 修改Main类 ,通过反射加载 测试一下
控制台成功输出