美文网首页Spring
Spring boot | tools

Spring boot | tools

作者: 不一样的卡梅利多 | 来源:发表于2020-05-30 12:14 被阅读0次

    看下 Spring boot tools 子项目包含的内容:


    Spring-boot-tools.png

    重点工具介绍
    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 指定参数 类名的方式获取,也可以使用服务发现的方式,javac 会自动扫描类路径下面 META-INF/services/javax.annotation.processing.Processor文件里面的实现类。spring boot 使用后者的方式,所以每次项目编译,都会触发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、建一个服务发现文件 META-INF/services/javax.annotation.processing.Processor ,文件里面为BuilderProcessor 全路径名称。

    3、将0,1,2 编译成jar 包,供其他项目引用。加入jar 包名称为processor.jar


    processor.jar.png

    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


    BuilderProcessor编译结果.png

    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());
                    }
                }
            }
        }
    

    Spring 专题

    相关文章

      网友评论

        本文标题:Spring boot | tools

        本文链接:https://www.haomeiwen.com/subject/wmmczhtx.html