使用带有Gradle应用程序插件的焊接se时的Bean发现问题

时间:2015-05-15 09:16:44

标签: java gradle jboss-weld weld-se

我正在构建一个基于Gradle的Java SE应用程序,它构建在Hibernate之上,是我的首选ORM。我的计划是使用weld-se能够在整个应用程序中使用CDI注释注入EntityManagers

基于Hibernate文档中的常见HibernateUtil辅助类,我转向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无法解析正确的生成器方法并产生异常:

  

线程中的异常" main"   org.jboss.weld.exceptions.UnsatisfiedResolutionException:WELD-001308:   无法解析Type:class app.DemoApplication;的任何bean。   资格赛:[@ javax.enterprise.inject.Any()]           在org.jboss.weld.bean.builtin.InstanceImpl.get(InstanceImpl.java:101)           在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发现过程出了点问题。在将我的焊接依赖关系更新到新发布的3.0.0.Alpha8版本(参见链接的Github repo)后,我能够通过在Main.java中手动告诉Weld关于我的bean来使应用程序工作:

final Weld weld = new Weld()
        .enableDiscovery()
        .addPackage(false, HibernateUtil.class)
        .addPackage(false, DemoApplication.class);

尽管有一个空的META-INF/beans.xml到位,但为什么没有自动发现bean的任何建议都非常感谢!

更新2015-05-19:

揭开神秘面纱,请参阅下面我自己的答案。我更改了问题标题,以反映问题的实际性质。

5 个答案:

答案 0 :(得分:16)

在这样的问题上花了更多的时间而不是看似理智,我终于能够找到问题的根源。它既不是Weld也不是Jandex,而是Gradle构造其输出目录的方式:

:build任务为实际编译结果和其他资源(build/classesbuild/resources)创建两个单独的输出文件夹。只有在创建JAR存档时,才会合并这两个文件夹。但是,:run任务直接从编译输出文件夹启动应用程序,并为类和资源创建两个单独的类路径条目。

Weld的bean发现机制显然只是尝试发现与META-INF/beans.xml文件相同的类路径条目的bean,在本例中是build/resources/main文件夹。反过来,从未发现任何豆类,也无法在任何地方注射。

我现在的解决方法(参见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

感谢大家的提示!

答案 1 :(得分:2)

static声明存在问题。你能以非静态方式做到吗?

关于https://issues.jboss.org/browse/WELD-1505应该在2.2.0.Beta2中修复

答案 2 :(得分:1)

就我从GIT示例中看到的那样,就对象的层次结构而言,你有这样的东西:

Main -> DemoApplication -> EntityManager

如果您在DemoApplication中注入EntityManager,则必须将DemoApplication注入Main.java,否则您将打破注入链。

尝试用@Singleton注释你的DemoApplication和HibernateUtil类,然后将它注入你的Main.java类:

@Inject private EntityManager entity;
@Inject private DemoApplication demo;

这应该有助于跳过手动WELD bean检测。

另外,如果您要生成JAR,那么beans.xml应该在META-INF中,但如果您要从项目中生成WAR,那么beans.xml必须在WEB-INF。

答案 3 :(得分:1)

也可以通过在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'
    }
}

在我看来看起来更干净。当然,然后将每个类声明为一个包。

答案 4 :(得分:0)

作为改进:Gradle 4为类使用不同的输出目录。

other answer中的任务定义略有错误。查看运行任务的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