【工作笔记】Pluggable Annotation Processing API

起因

简单的说就是我不想手写 model 里边的 builder 了,就问度娘类似 lombok 里边 @Builder 功能实现的原理,打算自己实现一个。于是便有了此文,此文质量好全靠度娘,质量差全赖我,反正我懒。。。

介绍

APIJSR 269 提供一套标准 API(Pluggable Annotation Processing)来处理 Annotations JSR 175,JSR 269 用 Annotation Processor 在编译期间而不是运行期间处理 Annotation,Annotation Processor 相当于编译器的一个插件,所以称为插件式注解处理
如果 Annotation Processor 处理 Annotation 时(执行process方法)产生了新的 Java 代码,编译器会再调用一次 Annotation Processor,如果第二次处理还有新代码产生,就会接着调用 Annotation Processor,直到没有新代码产生为止。每执行一次 process() 方法被称为一个"round",这样整个 Annotation processing 过程可以看作是一个 round 的序列。JSR 269 主要被设计成为针对 Tools 或者容器的 API。这个特性虽然在 JavaSE 6 已经存在,但是很少人知道它的存在

作用

这是在编译阶段生成附加源文件的便捷技术。源文件不必是 Java 文件-您可以根据源代码中的注释生成任何类型的描述,元数据,文档,资源或任何其他类型的文件。注释处理已在许多无处不在的 Java 库中积极使用,例如,在 QueryDSL 和 JPA 中生成元类,以在 Lombok 库中使用样板代码扩展类,lombok 就是使用这个特性实现编译期的代码插入的。另外,如果没有猜错,像 IDEA 在编写代码时候的标记语法错误的红色下划线也是通过这个特性实现的。KAPT(Annotation Processing for Kotlin),也就是 Kotlin 的编译也是通过此特性的

使用步骤

插件化注解处理 API 的使用步骤大概如下:

  1. 自定义一个 Annotation Processor,需要继承 javax.annotation.processing.AbstractProcessor,并重写 process 方法
  2. 自定义一个注解,注解的元注解需要指定 @Retention(RetentionPolicy.SOURCE)
  3. 需要在声明的自定义 Annotation Processor 中使用 javax.annotation.processing.SupportedAnnotationTypes 指定在第 2 步创建的注解类型的名称(注意需要全类名,“包名.注解类型名称”,否则会不生效)
  4. 需要在声明的自定义 Annotation Processor 中使用 javax.annotation.processing.SupportedSourceVersion 指定编译版本
  5. 可选操作,可以通在声明的自定义 Annotation Processor 中使用 javax.annotation.processing.SupportedOptions 指定编译参数
    需要注意的是:
  6. 插件化注解处理 API 的局限性-它只能用于生成新文件,而不能更改现有文件
  7. 老外的观点:值得注意的例外是 Lombok 库,该库使用注释处理作为一种引导机制,将其自身包含在编译过程中,并通过一些内部编译器 API 修改 AST。这种骇人听闻的技术与注解处理的预期目的无关,因此本文不予讨论

实践

一:定义对象
package com.just.demo.demo.builder;

import com.just.common.service.builder.Builder;

/**
 * @author lbh
 * @date 2020/7/23
 */
public class TestA {
    private String username;
    private String password;
    private String school;

    public TestA() {
    }

    public TestA(String username, String password, String school) {
        this.username = username;
        this.password = password;
        this.school = school;
    }


    public TestA(String userName) {
        this.username = userName;
    }

    public String getSchool() {
        return school;
    }

    @Builder
    public void setSchool(String school) {
        this.school = school;
    }

    public String getUsername() {
        return username;
    }

    @Builder
    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    @Builder
    public void setPassword(String password) {
        this.password = password;
    }

    public TestA(String username, String password) {
        this.username = username;
        this.password = password;
    }

    @Override
    public String toString() {
        return "TestA{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", school='" + school + '\'' +
                '}';
    }
}
二:定义注解
package com.just.common.service.builder;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author lbh
 * @date 2021/2/1 10:35
 * @describe
 */
@Target({ElementType.METHOD})
// 必须是SOURCE级别
@Retention(RetentionPolicy.SOURCE)
public @interface Builder {
}

三:定义注解的实现
package com.just.common.service.builder;

import com.google.auto.service.AutoService;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.ExecutableType;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author lbh
 * @date 2021/2/1 10:36
 * @describe 其实就是扫描属性,然后输出成文件
 */
@SupportedAnnotationTypes(value = {"com.just.common.service.builder.Builder"})
@SupportedSourceVersion(value = SourceVersion.RELEASE_8)
// @AutoService是谷歌的一个包,该处理器生成包含 BuilderProcessor 类名称的META-INF/services/javax.annotation.processing.Processor 文件
//         <dependency>
//            <groupId>com.google.auto.service</groupId>
//            <artifactId>auto-service</artifactId>
//            <version>1.0-rc2</version>
//        </dependency>
@AutoService(Processor.class)
public class BuilderProcess extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (TypeElement typeElement : annotations) {
            Set<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(typeElement);
            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, "@Builder must be applied to a setXxx method with a single argument", element));
            Map<String, String> setterMap = setters.stream().collect(Collectors.toMap(setter -> setter.getSimpleName().toString(),setter -> ((ExecutableType) setter.asType()).getParameterTypes().get(0).toString()));
            String className = ((TypeElement) setters.get(0).getEnclosingElement()).getQualifiedName().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.forEach((methodName, argumentType) -> {
                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("}");
        }
    }
}
四:运行示例

这里我是项目部署在两个模块里了,注解和注解的实现都在 common 包里,使用是在 demo 包里。必须先编译 common 包,然后再编译 demo 包
demo 包的 pom 需要有额外配置,即你的注解实现以及生成地址

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.5.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                    <generatedSourcesDirectory>${project.build.directory}/generated-sources/</generatedSourcesDirectory>
                    <annotationProcessors>
                        <annotationProcessor>com.just.common.service.builder.BuilderProcess</annotationProcessor>
                    </annotationProcessors>
                </configuration>
            </plugin>
        </plugins>
    </build>

编译完 demo 包既可看到生成的新文件

// target/generated-sources/com/just/demo/demo/builder/TestABuilder
package com.just.demo.demo.builder;

public class TestABuilder {

    private TestA object = new TestA();

    public TestA build() {
        return object;
    }

    public TestABuilder setPassword(java.lang.String value) {
        object.setPassword(value);
        return this;
    }

    public TestABuilder setUsername(java.lang.String value) {
        object.setUsername(value);
        return this;
    }

    public TestABuilder setSchool(java.lang.String value) {
        object.setSchool(value);
        return this;
    }

}

五:应用
package com.just.demo.demo.builder;

import com.just.demo.test.vo.model.TestB;

/**
 * @author lbh
 * @date 2021/2/1 10:44
 * @describe
 */
public class BuilderTest {

    public static void main(String[] args) {
        TestA testA = new TestABuilder().setSchool("1").setUsername("1").setPassword("1").build();
        System.out.println(testA.toString());
    }
}

六:额外方式

第四步有很多种方法可以实现 BuilderProcess 的创建,我使用了比较简单的通过 maven 加 @AutoService(Processor.class) 的方式。本质其实就是开始讲解的,循环生成需要的 Annotation Processor,对于 model 比较多的情况这种方式可能比较好一点
其他方式看参考吧

参考

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: 将11g数据库还原到12c可通过以下步骤完成: 1. 首先,创建一个新的12c数据库,确保数据库版本与12c兼容,并确保已正确安装12c数据库软件。 2. 使用RMAN(恢复管理器)进行备份:在11g数据库中,使用RMAN执行完整备份以将数据库备份到外部介质(如磁盘、磁带等)。确保备份文件包含数据库的所有数据文件、控制文件和参数文件。 3. 将备份文件复制到12c数据库服务器。可以使用文件传输工具(如scp、ftp等)将备份文件转移到12c数据库服务器。 4. 在12c数据库服务器上使用RMAN恢复备份:启动12c数据库实例,然后连接到RMAN命令行界面,通过指定11g备份文件的位置和名称来恢复11g数据库。 5. 确保12c数据库实例正常运行,并且数据库文件正确还原。验证数据库文件是否完整,以及是否可以正常访问。 6. 针对每个需要还原的11g pluggable database,创建一个新的12c可插件数据库。使用Oracle Database Configuration Assistant(DBCA)工具创建新的12c pluggable database实例。 7. 在12c pluggable database的新实例上通过导入工具导入11g pluggable database的数据。使用Oracle Data Pump或其他适用的工具将11g pluggable database的数据导入到新的12c pluggable database中。 8. 验证还原和导入的数据:确保数据的完整性和一致性,验证所有表、视图和其他对象是否正确还原。 9. 执行必要的后续步骤:根据需要,执行必要的配置、设置和更新,以确保12c pluggable database与原始11g数据库一致。 请注意,以上步骤仅提供了一个基本的概述,实际操作可能会因数据库的特定要求而有所差异。在执行此过程时,请确保了解您的环境,并参考相关文档和指南。 ### 回答2: 将Oracle 11g还原到Oracle 12c可插拔数据库的过程相对比较简单,下面我会用300字中文来解释该过程。 首先,我们需要使用Oracle Database 12c的安装介质来安装一个12c的Oracle实例。你可以选择将该实例安装为容器数据库(CDB)或非容器数据库(非CDB),取决于你的需求。 接下来,使用RMAN(Oracle的备份恢复管理工具)来备份你的Oracle 11g数据库。确保备份包含了你想要恢复的所有文件,如数据文件、控制文件、归档日志等。 在12c数据库上创建一个新的容器数据库(CDB)。你可以使用创建数据库语句(CREATE DATABASE)或使用数据库配置助手(Database Configuration Assistant)来完成这个任务。 创建容器数据库后,创建一个新的可插拔数据库(PDB)。你可以使用“CREATE PLUGGABLE DATABASE”语句来创建一个空的PDB。 在创建PDB之后,使用RMAN来还原11g数据库。使用RMAN的RESTORE命令来还原数据文件、控制文件和归档日志等文件到指定的位置。 完成还原后,将还原的数据文件和控制文件复制到PDB的目录中。你可以使用操作系统的命令或者文件管理器来完成这个任务。 最后,通过ALTER PLUGGABLE DATABASE语句将PDB打开,使其可供访问。这样就完成了将11g还原到12c可插拔数据库的过程。 需要注意的是,在还原的过程中可能会遇到一些问题,比如版本不兼容或者数据不一致等。为了保证还原的成功,建议在还原之前先仔细阅读Oracle官方文档,确保你了解该过程的每一个步骤和相关的注意事项。 ### 回答3: 要将11g数据库还原到12c可插拔数据库,以下是操作步骤: 1. 首先,确保已经安装了12c数据库软件,并且已经创建了一个空的12c可插拔数据库实例。 2. 在11g数据库中执行数据泵导出操作,将数据和对象导出到一个数据泵文件中。可以使用expdp命令来执行导出操作。示例命令如下: ``` expdp system/password@source_db directory=data_pump_dir dumpfile=export.dmp logfile=export.log full=y ``` 这将导出整个11g数据库的内容到一个名为export.dmp的数据泵文件中。 3. 将导出的数据泵文件复制到12c数据库服务器上,并确保数据库服务器具有适当的读取权限。 4. 在12c数据库中执行数据泵导入操作,导入从11g数据库中导出的数据。可以使用impdp命令来执行导入操作。示例命令如下: ``` impdp system/password@target_db directory=data_pump_dir dumpfile=export.dmp logfile=import.log remap_schema=source_schema:target_schema ``` 这将从export.dmp文件中导入数据到12c数据库。`source_schema`是11g数据库中的架构名称,`target_schema`是12c数据库中的目标架构名称。使用remap_schema参数可以将导入的对象重定向到新的架构中。 5. 在导入完成后,可以验证数据是否成功导入到12c可插拔数据库中。可以查询表数据,检查是否与11g数据库中相同。 通过以上步骤,您可以将11g数据库还原到12c可插拔数据库中,并且可以在12c数据库中使用先前导出的数据和对象。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值