看下 Spring boot tools 子項目包含的內容:
重點工具介紹
1拂封、spring-boot-annotation-processor
2善延、spring-boot-maven-plugin
3、spring-boot-loader
1签餐、Spring Boot Annotation Processor
Annotation Processor 是一種利用java 注解 擴展javac 編譯功能的一種方式包颁。
定義一個Processor 铆帽,Processor 可以通過 javac 指定參數 類名的方式獲取,也可以使用服務發(fā)現的方式羹与,javac 會自動掃描類路徑下面 META-INF/services/javax.annotation.processing.Processor文件里面的實現類故硅。spring boot 使用后者的方式,所以每次項目編譯纵搁,都會觸發(fā)Processor 里面的邏輯吃衅。我們可以使用 JavaCompiler 類對書寫的Processor 進行功能測試。詳見 Spring boot
org.springframework.boot.testsupport.compiler.TestCompiler
javac 命令說明:
javac -help
-processor <class1>[,<class2>,<class3>...] 要運行的注釋處理程序的名稱; 繞過默認的搜索進程
-processorpath <路徑> 指定查找注釋處理程序的位置
測試代碼:
1腾誉、定義一個BuilderProcessor 自動生成POJO 的builder
public class Person {
private int age;
private String name;
// getters and setters …
}
通過BuilderProcessor 在編譯時候生成如下類徘层。
Person person = new PersonBuilder()
.setAge(25)
.setName("John")
.build();
0、定義一個注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface BuilderProperty {
}
1利职、BuilderProcessor 實現 抽象類AbstractProcessor
@SupportedAnnotationTypes(
"com.github.yulechen.BuilderProperty")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class BuilderProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
System.out.println("start process java source files");
for (TypeElement annotation : annotations) {
Set<? extends Element> annotatedElements
= roundEnv.getElementsAnnotatedWith(annotation);
Map<Boolean, List<Element>> annotatedMethods = annotatedElements.stream().collect(
Collectors.partitioningBy(element ->
((ExecutableType) element.asType()).getParameterTypes().size() == 1
&& element.getSimpleName().toString().startsWith("set")));
List<Element> setters = annotatedMethods.get(true);
List<Element> otherMethods = annotatedMethods.get(false);
otherMethods.forEach(element ->
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"@BuilderProperty must be applied to a setXxx method "
+ "with a single argument", element));
if (setters.isEmpty()) {
continue;
}
String className = ((TypeElement) setters.get(0)
.getEnclosingElement()).getQualifiedName().toString();
Map<String, String> setterMap = setters.stream().collect(Collectors.toMap(
setter -> setter.getSimpleName().toString(),
setter -> ((ExecutableType) setter.asType())
.getParameterTypes().get(0).toString()
));
try {
writeBuilderFile(className,setterMap);
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
private void writeBuilderFile(
String className, Map<String, String> setterMap)
throws IOException {
String packageName = null;
int lastDot = className.lastIndexOf('.');
if (lastDot > 0) {
packageName = className.substring(0, lastDot);
}
String simpleClassName = className.substring(lastDot + 1);
String builderClassName = className + "Builder";
String builderSimpleClassName = builderClassName
.substring(lastDot + 1);
JavaFileObject builderFile = processingEnv.getFiler()
.createSourceFile(builderClassName);
try (PrintWriter out = new PrintWriter(builderFile.openWriter())) {
if (packageName != null) {
out.print("package ");
out.print(packageName);
out.println(";");
out.println();
}
out.print("public class ");
out.print(builderSimpleClassName);
out.println(" {");
out.println();
out.print(" private ");
out.print(simpleClassName);
out.print(" object = new ");
out.print(simpleClassName);
out.println("();");
out.println();
out.print(" public ");
out.print(simpleClassName);
out.println(" build() {");
out.println(" return object;");
out.println(" }");
out.println();
setterMap.entrySet().forEach(setter -> {
String methodName = setter.getKey();
String argumentType = setter.getValue();
out.print(" public ");
out.print(builderSimpleClassName);
out.print(" ");
out.print(methodName);
out.print("(");
out.print(argumentType);
out.println(" value) {");
out.print(" object.");
out.print(methodName);
out.println("(value);");
out.println(" return this;");
out.println(" }");
out.println();
});
out.println("}");
}
}
}
2趣效、建一個服務發(fā)現文件 META-INF/services/javax.annotation.processing.Processor ,文件里面為BuilderProcessor 全路徑名稱。
3猪贪、將0跷敬,1,2 編譯成jar 包热押,供其他項目引用西傀。加入jar 包名稱為processor.jar
4、另一個項目引用processor.jar
public class Person {
private int age;
private String name;
public int getAge() {
return age;
}
@BuilderProperty
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
@BuilderProperty
public void setName(String name) {
this.name = name;
}
}
編譯Person 文件
javac $XXXPATH/Person.java -cp $XXXLIBPATH/processor.jar
編譯結果楞黄,產生了一個PersonBuilder
Spring boot 有兩個processor
1池凄、spring-boot-configuration-annotation-processor 項目下面的 ConfigurationMetadataAnnotationProcessor 會掃描屬性相關的注解
ConfigurationProperties,NestedConfigurationProperty鬼廓,DeprecatedConfigurationProperty肿仑。然后生成 META-INF/spring-configuration-metadata.json 文件。
protected ConfigurationMetadata writeMetaData() throws Exception {
ConfigurationMetadata metadata = this.metadataCollector.getMetadata();
metadata = mergeAdditionalMetadata(metadata);
if (!metadata.getItems().isEmpty()) {
this.metadataStore.writeMetadata(metadata);
return metadata;
}
return null;
}
2、spring-boot-autoconfigure-annotation-processor 項目下面AutoConfigureAnnotationProcessor 尤慰,它處理的注解有
@ConditionalOnClass
@ConditionalOnBean
@ConditionalOnSingleCandidate
@ConditionalOnWebApplication
@AutoConfigureBefore
@AutoConfigureAfter
@AutoConfigureOrder
編譯處理邏輯:
生成一個"META-INF/spring-autoconfigure-metadata.properties" 文件馏锡。
private void writeProperties() throws IOException {
if (!this.properties.isEmpty()) {
Filer filer = this.processingEnv.getFiler();
FileObject file = filer.createResource(StandardLocation.CLASS_OUTPUT, "", PROPERTIES_PATH);
try (Writer writer = new OutputStreamWriter(file.openOutputStream(), StandardCharsets.UTF_8)) {
for (Map.Entry<String, String> entry : this.properties.entrySet()) {
writer.append(entry.getKey());
writer.append("=");
writer.append(entry.getValue());
writer.append(System.lineSeparator());
}
}
}
}