Bean discovery problems when using weld-se with Gradle application plugin

微笑、不失礼 提交于 2019-12-10 12:32:29

问题


I am building a Gradle-based Java SE application built on top of Hibernate as my ORM of choice. My plan is to use weld-se to be able to use CDI annotations for injections of EntityManagers throughout the application.

Based on the common HibernateUtil helper class found in the Hibernate documentation, I moved towards JPA interfaces and added @Produces annotations to provide producer methods (I have added an empty META-INF/beans.xml as well):

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

When I try to use the @Inject annotation on a field, however, Weld fails to resolve the correct producer method and produces an exception instead:

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)

The offending code is instantiated through the Weld container for CDI support and is incredibly basic:

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

Am I missing an obvious point here? How can I get Weld to discover the producer method for injecting my dependencies?

I have put together a minimal project reproducing my problem on Github. Thanks for any helpful suggestions! :)

Update 2015-05-18:

Seems like I misunderstood the error message. In fact, Weld does not even resolve the DemoApplication bean, which leads me to believe, that something's wrong with the bean discovery process. After updating my weld-se dependency to the freshly released 3.0.0.Alpha8 version (see the linked Github repo), I was able to get the application to work by manually telling Weld about my beans in Main.java:

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

Still, any suggestions as to why the beans are not automatically discovered despite having an empty META-INF/beans.xml in place are highly appreciated!

Update 2015-05-19:

The mystery is unraveled, see my own answer below. I changed the question title, in order to reflect the actual nature of the issue.


回答1:


Having spent more time than seems sane on a problem like this, I was finally able to hunt down the origin of my issue. It has to do neither with Weld nor Jandex, but rather the way Gradle structures its output directories:

The :build task creates two separate output folders for actual compilation results and and for additional resources (build/classes and build/resources). Only when you create a JAR archive these two folders get merged. The :run task however starts the application directly from the compilation output folders with two separate classpath entries for classes and resources.

Weld's bean discovery mechanism apparently only attempts to discover beans for the same classpath entry as the META-INF/beans.xml file, which in this case is the build/resources/main folder. In turn, no beans are ever discovered and are never eligible for injection anywhere.

My workaround for now (see the Git repository) is to create an additional Gradle task to copy the resources into the appropriate folder, so that bean discovery happens on the correct classpath entry:

task copyResources(type: Copy) {
    from "${projectDir}/src/main/resources"
    into "${buildDir}/classes/main"
}

processResources.dependsOn copyResources

The same issue with a similar has been described in the Gradle forums: 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

Thanks to everyone for your hints!




回答2:


There is a problem with static declaration. Could you do it in non-static way?

Regarding to https://issues.jboss.org/browse/WELD-1505 should be fixed in 2.2.0.Beta2




回答3:


As far as I could see from your GIT example, you have something like this in terms of hierarchy of objects:

Main -> DemoApplication -> EntityManager

If you're injecting an EntityManager onto your DemoApplication, you'll have to inject the DemoApplication onto your Main.java, otherwise you'll break the injection chain.

Try to annotate your DemoApplication and HibernateUtil class with @Singleton and then inject it onto your Main.java class:

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

That should help skip manual WELD bean detection.

Also, if you're about to produce a JAR, your beans.xml should be in META-INF, but if you're making a WAR out of your project, the beans.xml must be in WEB-INF.




回答4:


The solution could also be found by defining sourceSets in gradle.

Like this:

// 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'
    }
}

Looks cleaner in my opinion. And certainly better then declaring each class as a package.




回答5:


As an improvement: Gradle 4 uses a different output directory for the classes.

The task definition in the other answer is slightly wrong. Looking at gradle log for run task, here's the classpath used:

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.......  

So it uses /build/classes/java/main and then /build/resources/main. Here's my task:

task copyResources(type: Copy) {
    from "${projectDir}/src/main/resources/META-INF"
    into "${buildDir}/classes/java/main/META-INF"
}

processResources.dependsOn copyResources


来源:https://stackoverflow.com/questions/30255760/bean-discovery-problems-when-using-weld-se-with-gradle-application-plugin

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!