Java 远程 JAR 的注解处理器
Java Annotation processor for remote JAR
一般问题
我有两个项目A和B; B 依赖于 A。我想根据 A 中对象的注释在 B 中使用注释处理器生成一些代码。当我 运行 使用正确的处理器实现进行编译时,只有来自 B 的注释对象是捡到。
我知道默认情况下必须禁用扫描其他 JAR,因为您通常不想对所有依赖项进行注释扫描。我也明白,由于编译器的魔力,我可能无法做我想做的事——我对此知之甚少——但我希望不是这样。
具体案例
我的项目叫DB和WEB。 WEB 显然依赖于 DB 的 JPA 访问;这是在 Maven 中配置的。由于许多体系结构选择,DB 必须保留为单独的 JAR。 DB 不使用 Spring 除了一些被 WEB 使用的注解; WEB 使用 Spring MVC.
我正在尝试使用注释处理器为我的所有 JPA 实体生成 CrudRepository
接口。 @Repository
对象应该放在 WEB 项目的 repo
包中,因此它们可以在我的 WEB 应用程序的任何地方与 @Autowired
一起使用。我正在执行扫描的注释是 @javax.persistence.Entity
,但我也尝试了自定义注释,结果相同。
@SupportedAnnotationTypes("javax.persistence.Entity")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class RepositoryFactory extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (Element e : roundEnv.getElementsAnnotatedWith(Entity.class)) {
if (e.getKind() != ElementKind.CLASS) {
continue;
}
// TODO: implement logic to skip manually implemented Repos
try {
String name = e.getSimpleName().toString();
TypeElement clazz = (TypeElement) e;
JavaFileObject f = processingEnv.getFiler().
createSourceFile("blagae.web.repo." + name + "Repo");
try (Writer w = f.openWriter()) {
PrintWriter pw = new PrintWriter(w);
pw.println("package blagae.web.repo;");
pw.println("import org.springframework.data.repository.CrudRepository;");
pw.printf("import %s;\n", clazz.toString());
pw.println("import org.springframework.stereotype.Repository;");
pw.println("@Repository");
pw.printf("public interface %sRepo extends CrudRepository<%s, Long> {}\n", name, name);
pw.flush();
}
} catch (IOException ex) {
Logger.getLogger(RepositoryFactory.class.getName()).log(Level.SEVERE, null, ex);
}
}
return false;
}
}
理想情况下,我希望有人能告诉我一个像
这样简单的注释
@ComponentScan(basePackages = "blagae.db.*")
当然,我不指望它,因为它可能会在某处记录下来。作为一种解决方法,我可以将 Spring 依赖项添加到数据库并在那里生成 类,但它们仅在 Spring MVC 应用程序中起作用。我也很担心完成这项工作可能需要的配置。
更新
一些额外的信息:我正在使用 maven-processor-plugin,我已经验证它可以在 WEB 项目中为那里定义的 类 正常工作。但是,我特别希望访问 类 在依赖项目数据库中注释。我已经研究了方法 AbstractProcessor::getSupportedOptions
,但我不清楚我能在那里做什么。
Maven 配置:
<plugin>
<groupId>org.bsc.maven</groupId>
<artifactId>maven-processor-plugin</artifactId>
<version>2.2.4</version>
<configuration>
<processors>
<processor>blagae.utils.RepositoryFactory</processor>
</processors>
</configuration>
<executions>
<execution>
<id>process</id>
<goals>
<goal>process</goal>
</goals>
<phase>generate-sources</phase>
</execution>
</executions>
</plugin>
建议
我的另一个随机想法是 运行 WEB 数据库项目的 JavaCompiler
进程,但我将如何注入我的 Processor
?
就个人而言,我会在单独的 maven 模块中提取注释处理器,并从 WEB 模块向其添加依赖项。
但是,这对于成功触发注释处理器来说并不重要。
为了让注解处理器工作,您必须提供两件事:
- a class 扩展
javax.annotation.processing.AbstractProcessor
class.
- 一个特殊文件,嵌套在项目的
META-INF/services
中。
由于您提到目前没有生成 classes,我认为您缺少元文件。因此,打开您的 WEB 项目并导航到 src/main/resouces
文件夹。在其中,您必须创建一个 META-INF
文件夹,其中包含一个嵌套的 services
文件夹。然后,在services
中创建一个文件,命名为javax.annotation.processing.Processor
。该文件的内容应列出注释处理器的完全限定 class 名称。如果有多个注解处理器,完全限定的 class 名称应该在不同的行上。但是因为你只有一个,所以你会得到类似的东西:
com.yourdomain.processor.RepositoryFactory
请注意,您必须使用注释处理器的实际完全限定 class-名称更改此行。
最后,你应该得到类似的结构:
这个元文件很重要,否则编译器无法识别用户定义的注释处理器。有了它,它将使用所有已注册的处理器。
之后,当您执行 mvn clean install
时,您的所有模块都将被清理和构建。但是,由于编译器现在知道您的注解处理器,因此它会触发它。所有生成的源都将位于(默认情况下)target/generated-sources
文件夹中。此外,它们都将在您在注释过程中配置的包下,即 blagae.web.repo
.
为了在您的代码中使用生成的源代码,您必须将 targer/generated-sources
添加到项目 class 路径中。如果您不想依赖 IDE 来执行此操作,您可以通过将 target/generated-sources
添加到 class 路径来扩展 Maven <build>
。类似于:
<build>
<resources>
...
<resource>
<directory>${project.build.directory}/generated-resources</directory>
</resource>
</resources>
</build>
注释处理器在您的项目(在您的情况下为 WEB)的编译阶段工作,编译器编译该项目。当前项目的依赖项已经编译,编译器(因此您的注释处理器)不会接触(或无法访问)第三方库 (DB)。
您可以尝试将注释处理器提取到单独的 project/jar 中,并在 WEB 和 DB 项目中使用它。在这种情况下,注释处理器将在具体项目的编译阶段创建 CrudRepository
。并且在DB项目中生成的所有类都将在WEB中可用。
在您的项目 A 中包含 META-INF/beans.xml 文件,
其中将包含以下内容:
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee <a href="http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd">http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"</a>
version="1.1" bean-discovery-mode="all">
</beans>
并试一试。您应该使用 JavaEE 7/CDI 1.1。有关详细信息,请参阅 Java EE 7 Deployment Descriptors。
你也可以参考这个相关问题:How to @Inject object from different project module included as jar
一般问题
我有两个项目A和B; B 依赖于 A。我想根据 A 中对象的注释在 B 中使用注释处理器生成一些代码。当我 运行 使用正确的处理器实现进行编译时,只有来自 B 的注释对象是捡到。
我知道默认情况下必须禁用扫描其他 JAR,因为您通常不想对所有依赖项进行注释扫描。我也明白,由于编译器的魔力,我可能无法做我想做的事——我对此知之甚少——但我希望不是这样。
具体案例
我的项目叫DB和WEB。 WEB 显然依赖于 DB 的 JPA 访问;这是在 Maven 中配置的。由于许多体系结构选择,DB 必须保留为单独的 JAR。 DB 不使用 Spring 除了一些被 WEB 使用的注解; WEB 使用 Spring MVC.
我正在尝试使用注释处理器为我的所有 JPA 实体生成 CrudRepository
接口。 @Repository
对象应该放在 WEB 项目的 repo
包中,因此它们可以在我的 WEB 应用程序的任何地方与 @Autowired
一起使用。我正在执行扫描的注释是 @javax.persistence.Entity
,但我也尝试了自定义注释,结果相同。
@SupportedAnnotationTypes("javax.persistence.Entity")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class RepositoryFactory extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (Element e : roundEnv.getElementsAnnotatedWith(Entity.class)) {
if (e.getKind() != ElementKind.CLASS) {
continue;
}
// TODO: implement logic to skip manually implemented Repos
try {
String name = e.getSimpleName().toString();
TypeElement clazz = (TypeElement) e;
JavaFileObject f = processingEnv.getFiler().
createSourceFile("blagae.web.repo." + name + "Repo");
try (Writer w = f.openWriter()) {
PrintWriter pw = new PrintWriter(w);
pw.println("package blagae.web.repo;");
pw.println("import org.springframework.data.repository.CrudRepository;");
pw.printf("import %s;\n", clazz.toString());
pw.println("import org.springframework.stereotype.Repository;");
pw.println("@Repository");
pw.printf("public interface %sRepo extends CrudRepository<%s, Long> {}\n", name, name);
pw.flush();
}
} catch (IOException ex) {
Logger.getLogger(RepositoryFactory.class.getName()).log(Level.SEVERE, null, ex);
}
}
return false;
}
}
理想情况下,我希望有人能告诉我一个像
这样简单的注释@ComponentScan(basePackages = "blagae.db.*")
当然,我不指望它,因为它可能会在某处记录下来。作为一种解决方法,我可以将 Spring 依赖项添加到数据库并在那里生成 类,但它们仅在 Spring MVC 应用程序中起作用。我也很担心完成这项工作可能需要的配置。
更新
一些额外的信息:我正在使用 maven-processor-plugin,我已经验证它可以在 WEB 项目中为那里定义的 类 正常工作。但是,我特别希望访问 类 在依赖项目数据库中注释。我已经研究了方法 AbstractProcessor::getSupportedOptions
,但我不清楚我能在那里做什么。
Maven 配置:
<plugin>
<groupId>org.bsc.maven</groupId>
<artifactId>maven-processor-plugin</artifactId>
<version>2.2.4</version>
<configuration>
<processors>
<processor>blagae.utils.RepositoryFactory</processor>
</processors>
</configuration>
<executions>
<execution>
<id>process</id>
<goals>
<goal>process</goal>
</goals>
<phase>generate-sources</phase>
</execution>
</executions>
</plugin>
建议
我的另一个随机想法是 运行 WEB 数据库项目的 JavaCompiler
进程,但我将如何注入我的 Processor
?
就个人而言,我会在单独的 maven 模块中提取注释处理器,并从 WEB 模块向其添加依赖项。
但是,这对于成功触发注释处理器来说并不重要。
为了让注解处理器工作,您必须提供两件事:
- a class 扩展
javax.annotation.processing.AbstractProcessor
class. - 一个特殊文件,嵌套在项目的
META-INF/services
中。
由于您提到目前没有生成 classes,我认为您缺少元文件。因此,打开您的 WEB 项目并导航到 src/main/resouces
文件夹。在其中,您必须创建一个 META-INF
文件夹,其中包含一个嵌套的 services
文件夹。然后,在services
中创建一个文件,命名为javax.annotation.processing.Processor
。该文件的内容应列出注释处理器的完全限定 class 名称。如果有多个注解处理器,完全限定的 class 名称应该在不同的行上。但是因为你只有一个,所以你会得到类似的东西:
com.yourdomain.processor.RepositoryFactory
请注意,您必须使用注释处理器的实际完全限定 class-名称更改此行。
最后,你应该得到类似的结构:
这个元文件很重要,否则编译器无法识别用户定义的注释处理器。有了它,它将使用所有已注册的处理器。
之后,当您执行 mvn clean install
时,您的所有模块都将被清理和构建。但是,由于编译器现在知道您的注解处理器,因此它会触发它。所有生成的源都将位于(默认情况下)target/generated-sources
文件夹中。此外,它们都将在您在注释过程中配置的包下,即 blagae.web.repo
.
为了在您的代码中使用生成的源代码,您必须将 targer/generated-sources
添加到项目 class 路径中。如果您不想依赖 IDE 来执行此操作,您可以通过将 target/generated-sources
添加到 class 路径来扩展 Maven <build>
。类似于:
<build>
<resources>
...
<resource>
<directory>${project.build.directory}/generated-resources</directory>
</resource>
</resources>
</build>
注释处理器在您的项目(在您的情况下为 WEB)的编译阶段工作,编译器编译该项目。当前项目的依赖项已经编译,编译器(因此您的注释处理器)不会接触(或无法访问)第三方库 (DB)。
您可以尝试将注释处理器提取到单独的 project/jar 中,并在 WEB 和 DB 项目中使用它。在这种情况下,注释处理器将在具体项目的编译阶段创建 CrudRepository
。并且在DB项目中生成的所有类都将在WEB中可用。
在您的项目 A 中包含 META-INF/beans.xml 文件, 其中将包含以下内容:
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee <a href="http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd">http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"</a>
version="1.1" bean-discovery-mode="all">
</beans>
并试一试。您应该使用 JavaEE 7/CDI 1.1。有关详细信息,请参阅 Java EE 7 Deployment Descriptors。
你也可以参考这个相关问题:How to @Inject object from different project module included as jar