javagradlejboss-weldweld-se

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


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.


Solution

  • 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!