将 weld-se 与 Gradle 应用程序插件一起使用时的 Bean 发现问题
Bean discovery problems when using weld-se with Gradle application plugin
我正在构建一个 Gradle-based Java SE 应用程序,该应用程序构建在 Hibernate 之上作为我选择的 ORM。我的计划是使用 weld-se
以便能够在整个应用程序中使用 CDI 注释来注入 EntityManagers
。
基于在 Hibernate 文档中找到的常见 HibernateUtil
帮助程序 class,我转向 JPA 接口并添加了 @Produces
注释以提供生产者方法(我添加了一个空的 META-INF/beans.xml
还有):
package dao;
import javax.enterprise.inject.Disposes;
import javax.enterprise.inject.Produces;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class HibernateUtil {
private static final EntityManagerFactory emf = buildEntityManagerFactory();
private static EntityManagerFactory buildEntityManagerFactory() {
try {
return Persistence.createEntityManagerFactory("persistenceUnit");
} catch (Throwable ex) {
System.err.println("Initial EntityManagerFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}
@Produces
public static EntityManager createEntityManager() {
return emf.createEntityManager();
}
public static void closeEntityManager(@Disposes EntityManager em) {
System.out.println("Closing EM");
try {
em.close();
} catch (Throwable t) {
t.printStackTrace();
}
}
}
然而,当我尝试在字段上使用 @Inject
注释时,Weld 无法解析正确的生成器方法并生成异常:
Exception in thread "main"
org.jboss.weld.exceptions.UnsatisfiedResolutionException: WELD-001308:
Unable to resolve any beans for Type: class app.DemoApplication;
Qualifiers: [@javax.enterprise.inject.Any()]
at org.jboss.weld.bean.builtin.InstanceImpl.get(InstanceImpl.java:101)
at app.Main.main(Main.java:14)
违规代码通过 Weld 容器实例化以支持 CDI,并且非常基础:
package app;
import javax.inject.Inject;
import javax.persistence.EntityManager;
public class DemoApplication {
@Inject private EntityManager em;
public void run() {
try {
em.getTransaction().begin();
System.out.println("Inside transaction");
} catch (Throwable t) {
t.printStackTrace();
} finally {
em.getTransaction().rollback();
em.close();
}
}
}
我是不是漏掉了一个明显的点?如何让 Weld 发现用于注入依赖项的生产者方法?
我整理了一个最小的项目来重现我在 Github 上遇到的问题。感谢您提供任何有用的建议! :)
2015-05-18 更新:
看来我误解了错误信息。事实上,Weld 甚至没有解析 DemoApplication
bean,这让我相信 bean 发现过程出了问题。在将我的 weld-se 依赖项更新到新发布的 3.0.0.Alpha8 版本(参见链接的 Github 存储库)之后,我能够通过手动告诉 Weld 关于我在 Main.java
:
final Weld weld = new Weld()
.enableDiscovery()
.addPackage(false, HibernateUtil.class)
.addPackage(false, DemoApplication.class);
尽管如此,尽管有一个空的META-INF/beans.xml
,但对于为什么没有自动发现 beans 的任何建议都非常感谢!
2015-05-19 更新:
谜底已揭开,请看下面我自己的回答。我更改了问题标题,以反映问题的实际性质。
static
声明有问题。你能以非静态方式做到这一点吗?
关于 https://issues.jboss.org/browse/WELD-1505 应该在 2.2.0.Beta2
中修复
据我从您的 GIT 示例中可以看出,您在对象层次结构方面有类似的东西:
Main -> DemoApplication -> EntityManager
如果您要将 EntityManager 注入到您的 DemoApplication 中,则必须将 DemoApplication 注入到您的 Main.java 中,否则您将中断注入链。
尝试使用 @Singleton
注释您的 DemoApplication 和 HibernateUtil class,然后将其注入您的 Main.java class:
@Inject private EntityManager entity;
@Inject private DemoApplication demo;
这应该有助于跳过手动 WELD bean 检测。
此外,如果您要生成 JAR,您的 beans.xml 应该在 META-INF 中,但是如果您要从项目中生成 WAR,beans.xml 必须在 WEB-INF 中。
在这样的问题上花费了比看起来更理智的时间,我终于能够找到问题的根源。它与 Weld 或 Jandex 无关,而是 Gradle 构建其输出目录的方式:
:build
任务为实际编译结果和其他资源(build/classes
和 build/resources
)创建了两个单独的输出文件夹。只有当您创建 JAR 存档时,这两个文件夹才会合并。然而,:run
任务直接从编译输出文件夹启动应用程序,其中包含 类 和资源的两个单独的类路径条目。
Weld 的 bean 发现机制显然只尝试为与 META-INF/beans.xml
文件相同的类路径条目发现 bean,在本例中为 build/resources/main
文件夹。反过来,没有任何 beans 被发现并且永远没有资格在任何地方注入。
我现在的解决方法(参见 Git 存储库)是创建一个额外的 Gradle 任务来将资源复制到适当的文件夹中,以便在正确的类路径条目上发现 bean:
task copyResources(type: Copy) {
from "${projectDir}/src/main/resources"
into "${buildDir}/classes/main"
}
processResources.dependsOn copyResources
Gradle 论坛中描述了类似的相同问题:https://discuss.gradle.org/t/application-plugin-run-task-should-first-consolidate-classes-and-resources-folder-or-depend-on-installapp-or-stuff-like-weld-se-wont-work/1248
感谢大家的指点!
也可以通过在 gradle 中定义 sourceSets 找到解决方案。
像这样:
// Override Gradle defaults - a force an exploded JAR view
sourceSets {
main {
output.resourcesDir = 'build/classes/main'
output.classesDir = 'build/classes/main'
}
test {
output.resourcesDir = 'build/classes/test'
output.classesDir = 'build/classes/test'
}
}
我认为看起来更干净。肯定比将每个 class 声明为一个包更好。
作为一项改进:Gradle 4 使用不同的输出目录 类。
中的任务定义略有错误。查看 gradle 日志 运行 任务,这里是使用的类路径:
Command: /home/rizalp/package/jdk1.8.0_141/bin/java -Dfile.encoding=UTF-8 -Duser.country=US -Duser.language=en -Duser.variant -cp /home/rizalp/code/helloworld-cdi2-se/build/classes/java/main:/home/rizalp/code/helloworld-cdi2-se/build/resources/main:/home/rizalp/.gradle/caches/modules-2/files-2.1/org.glassfish.jersey.inject/jersey-cdi2-se/2.26-b09/c540e230762ab07ebb3d372ee13446419d70dd28/jersey-cdi2-se-2.26-b09.jar.......
所以它使用 /build/classes/java/main
然后 /build/resources/main
。这是我的任务:
task copyResources(type: Copy) {
from "${projectDir}/src/main/resources/META-INF"
into "${buildDir}/classes/java/main/META-INF"
}
processResources.dependsOn copyResources
我正在构建一个 Gradle-based Java SE 应用程序,该应用程序构建在 Hibernate 之上作为我选择的 ORM。我的计划是使用 weld-se
以便能够在整个应用程序中使用 CDI 注释来注入 EntityManagers
。
基于在 Hibernate 文档中找到的常见 HibernateUtil
帮助程序 class,我转向 JPA 接口并添加了 @Produces
注释以提供生产者方法(我添加了一个空的 META-INF/beans.xml
还有):
package dao;
import javax.enterprise.inject.Disposes;
import javax.enterprise.inject.Produces;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class HibernateUtil {
private static final EntityManagerFactory emf = buildEntityManagerFactory();
private static EntityManagerFactory buildEntityManagerFactory() {
try {
return Persistence.createEntityManagerFactory("persistenceUnit");
} catch (Throwable ex) {
System.err.println("Initial EntityManagerFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}
@Produces
public static EntityManager createEntityManager() {
return emf.createEntityManager();
}
public static void closeEntityManager(@Disposes EntityManager em) {
System.out.println("Closing EM");
try {
em.close();
} catch (Throwable t) {
t.printStackTrace();
}
}
}
然而,当我尝试在字段上使用 @Inject
注释时,Weld 无法解析正确的生成器方法并生成异常:
Exception in thread "main" org.jboss.weld.exceptions.UnsatisfiedResolutionException: WELD-001308: Unable to resolve any beans for Type: class app.DemoApplication; Qualifiers: [@javax.enterprise.inject.Any()] at org.jboss.weld.bean.builtin.InstanceImpl.get(InstanceImpl.java:101) at app.Main.main(Main.java:14)
违规代码通过 Weld 容器实例化以支持 CDI,并且非常基础:
package app;
import javax.inject.Inject;
import javax.persistence.EntityManager;
public class DemoApplication {
@Inject private EntityManager em;
public void run() {
try {
em.getTransaction().begin();
System.out.println("Inside transaction");
} catch (Throwable t) {
t.printStackTrace();
} finally {
em.getTransaction().rollback();
em.close();
}
}
}
我是不是漏掉了一个明显的点?如何让 Weld 发现用于注入依赖项的生产者方法?
我整理了一个最小的项目来重现我在 Github 上遇到的问题。感谢您提供任何有用的建议! :)
2015-05-18 更新:
看来我误解了错误信息。事实上,Weld 甚至没有解析 DemoApplication
bean,这让我相信 bean 发现过程出了问题。在将我的 weld-se 依赖项更新到新发布的 3.0.0.Alpha8 版本(参见链接的 Github 存储库)之后,我能够通过手动告诉 Weld 关于我在 Main.java
:
final Weld weld = new Weld()
.enableDiscovery()
.addPackage(false, HibernateUtil.class)
.addPackage(false, DemoApplication.class);
尽管如此,尽管有一个空的META-INF/beans.xml
,但对于为什么没有自动发现 beans 的任何建议都非常感谢!
2015-05-19 更新:
谜底已揭开,请看下面我自己的回答。我更改了问题标题,以反映问题的实际性质。
static
声明有问题。你能以非静态方式做到这一点吗?
关于 https://issues.jboss.org/browse/WELD-1505 应该在 2.2.0.Beta2
中修复据我从您的 GIT 示例中可以看出,您在对象层次结构方面有类似的东西:
Main -> DemoApplication -> EntityManager
如果您要将 EntityManager 注入到您的 DemoApplication 中,则必须将 DemoApplication 注入到您的 Main.java 中,否则您将中断注入链。
尝试使用 @Singleton
注释您的 DemoApplication 和 HibernateUtil class,然后将其注入您的 Main.java class:
@Inject private EntityManager entity;
@Inject private DemoApplication demo;
这应该有助于跳过手动 WELD bean 检测。
此外,如果您要生成 JAR,您的 beans.xml 应该在 META-INF 中,但是如果您要从项目中生成 WAR,beans.xml 必须在 WEB-INF 中。
在这样的问题上花费了比看起来更理智的时间,我终于能够找到问题的根源。它与 Weld 或 Jandex 无关,而是 Gradle 构建其输出目录的方式:
:build
任务为实际编译结果和其他资源(build/classes
和 build/resources
)创建了两个单独的输出文件夹。只有当您创建 JAR 存档时,这两个文件夹才会合并。然而,:run
任务直接从编译输出文件夹启动应用程序,其中包含 类 和资源的两个单独的类路径条目。
Weld 的 bean 发现机制显然只尝试为与 META-INF/beans.xml
文件相同的类路径条目发现 bean,在本例中为 build/resources/main
文件夹。反过来,没有任何 beans 被发现并且永远没有资格在任何地方注入。
我现在的解决方法(参见 Git 存储库)是创建一个额外的 Gradle 任务来将资源复制到适当的文件夹中,以便在正确的类路径条目上发现 bean:
task copyResources(type: Copy) {
from "${projectDir}/src/main/resources"
into "${buildDir}/classes/main"
}
processResources.dependsOn copyResources
Gradle 论坛中描述了类似的相同问题:https://discuss.gradle.org/t/application-plugin-run-task-should-first-consolidate-classes-and-resources-folder-or-depend-on-installapp-or-stuff-like-weld-se-wont-work/1248
感谢大家的指点!
也可以通过在 gradle 中定义 sourceSets 找到解决方案。
像这样:
// Override Gradle defaults - a force an exploded JAR view
sourceSets {
main {
output.resourcesDir = 'build/classes/main'
output.classesDir = 'build/classes/main'
}
test {
output.resourcesDir = 'build/classes/test'
output.classesDir = 'build/classes/test'
}
}
我认为看起来更干净。肯定比将每个 class 声明为一个包更好。
作为一项改进:Gradle 4 使用不同的输出目录 类。
Command: /home/rizalp/package/jdk1.8.0_141/bin/java -Dfile.encoding=UTF-8 -Duser.country=US -Duser.language=en -Duser.variant -cp /home/rizalp/code/helloworld-cdi2-se/build/classes/java/main:/home/rizalp/code/helloworld-cdi2-se/build/resources/main:/home/rizalp/.gradle/caches/modules-2/files-2.1/org.glassfish.jersey.inject/jersey-cdi2-se/2.26-b09/c540e230762ab07ebb3d372ee13446419d70dd28/jersey-cdi2-se-2.26-b09.jar.......
所以它使用 /build/classes/java/main
然后 /build/resources/main
。这是我的任务:
task copyResources(type: Copy) {
from "${projectDir}/src/main/resources/META-INF"
into "${buildDir}/classes/java/main/META-INF"
}
processResources.dependsOn copyResources