嗨,我开始使用 gradle 在 Java 中学习注释处理,遵循多项目结构:
ROOT
|
|-sample # contains main classes
|-annotation # annotations read by processor
|-processor # processor to generate code
我尝试创建一个注释,就像 lombok @Builder 一样,我可以在 build/ generated/sources/annotationProcessor/java 中生成一个类...但是如果我尝试在带注释的类中的同一个包中生成类,但构建任务失败说我尝试替换文件
样品:
plugins {
id("java")
}
group = "br.com.dio"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
dependencies {
implementation(project(":annotation"))
compileOnly(project(":annotation"))
implementation(project(":processor"))
annotationProcessor(project(":processor"))
}
处理器:
plugins {
id("java")
}
group = "br.com.dio"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
val autoServiceVersion = "1.1.1"
dependencies {
compileOnly("com.google.auto.service:auto-service:$autoServiceVersion")
annotationProcessor("com.google.auto.service:auto-service:$autoServiceVersion")
implementation("com.squareup:javapoet:1.13.0")
}
注释:
plugins {
id("java")
}
group = "br.com.dio"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
dependencies {
}
设置.gradle.kts
rootProject.name = "annotation-processor"
include("processor")
include("sample")
include("annotation")
按照我的代码操作:
public class BuilderProcessor extends AbstractProcessor {
@Override
public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) {
for (TypeElement annotation : annotations) {
for (Element element : roundEnv.getElementsAnnotatedWith(annotation)) {
Map<String, TypeMirror> fields =element.getEnclosedElements().stream()
.filter(e -> e.getKind() == FIELD)
.collect(Collectors.toMap(e -> e.getSimpleName().toString(), Element::asType));
final var packageName = processingEnv.getElementUtils().getPackageOf(element).toString();
final var className = element.getSimpleName().toString();
final var fullNameClass = packageName + "." + className;
final var typeSpec = new BuilderCreate().create(packageName, className, fields);
var javaFile = JavaFile.builder(packageName, typeSpec)
.indent(" ")
.build();
try(
var out = new PrintWriter(processingEnv.getFiler()
.createSourceFile(className)
.openWriter()
)
){
out.println(javaFile);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
return true;
}
}
为了解决这个问题,我完成了它使用注释处理器在另一个文件中创建一个构建器类,我想如何修改生成文件并创建一个内部构建器,如lombok,但在评论中Slaw帮助我理解这更复杂(这是我第一次使用注释处理器)遵循我的最终代码:
@SupportedAnnotationTypes("br.com.dio.annotation.Builder")
@SupportedSourceVersion(SourceVersion.RELEASE_21)
@AutoService(Processor.class)
public class BuilderProcessor extends AbstractProcessor {
@Override
public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) {
for (TypeElement annotation : annotations) {
for (Element element : roundEnv.getElementsAnnotatedWith(annotation)) {
Map<String, TypeMirror> fields =element.getEnclosedElements().stream()
.filter(e -> e.getKind() == FIELD)
.collect(Collectors.toMap(e -> e.getSimpleName().toString(), Element::asType));
final var packageName = processingEnv.getElementUtils().getPackageOf(element).toString();
final var className = element.getSimpleName().toString();
final var builderName = className + "Builder";
final var typeSpec = new BuilderCreate().create(packageName, className, builderName,fields);
var javaFile = JavaFile.builder(packageName, typeSpec)
.indent(" ")
.build();
try(
var out = new PrintWriter(processingEnv.getFiler()
.createSourceFile(className + "Builder")
.openWriter()
)
){
out.write(javaFile.toString());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
return true;
}
}
public class BuilderCreate {
public TypeSpec create(final String packageName, final String className,
final String builderName,final Map<String, TypeMirror> fields){
var generatedBuilderClass = TypeSpec.classBuilder(builderName)
.addModifiers(PUBLIC);
fields.forEach((k, v) -> generatedBuilderClass.addField(TypeName.get(v), k, PRIVATE));
fields.forEach((k, v) -> generatedBuilderClass.addMethod(buildBuilderSetter(
packageName,
builderName,
k,
TypeName.get(v))
)
);
var buildMethod = MethodSpec.methodBuilder("build")
.addModifiers(PUBLIC)
.returns(ClassName.get(packageName, className))
.addStatement("var target = new $N()", className);
fields.keySet().forEach(f -> buildMethod.addStatement(
"target.set$N($N)",
f.substring(0,1).toUpperCase() + f.substring(1, f.length()),
f)
);
buildMethod.addStatement("return target");
return generatedBuilderClass.addMethod(buildMethod.build())
.build();
}
private MethodSpec buildBuilderSetter(final String packageName, final String name, final String param, final TypeName type){
return MethodSpec.methodBuilder(param)
.addModifiers(PUBLIC)
.returns(ClassName.get(packageName, name))
.addParameter(type, param, FINAL)
.addStatement("this.$N = $N", param, param)
.addStatement("return this")
.build();
}
}