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

5

13

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.

Wildebeest answered 15/5, 2015 at 9:16 Comment(2)
What's the bean-discovery-mode in your beans.xml?Raleighraley
@MilanBaran I tried using an empty beans.xml, as well as one with bean-discovery-mode=ALL, both without any visible effect.Wildebeest
W
19

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!

Wildebeest answered 19/5, 2015 at 12:27 Comment(2)
Have you filed any WELD issue about this weird behaviour?Arequipa
Instead of copy you could add resources folder to run task's classpath. run { classpath += files("${buildDir}/resources") } Bittencourt
R
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

Raleighraley answered 19/5, 2015 at 8:24 Comment(1)
I've tried converting the static producer methods in HibernateUtil into instance methods, however to no avail. I have tried both Weld 2.2.9.Final and 3.0.0.Alpha8, so the issue you mentioned should be fixed in those releases. Thank you for the suggestion still!Wildebeest
P
1

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.

Parishioner answered 19/5, 2015 at 10:10 Comment(0)
C
1

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.

Cristobalcristobalite answered 30/12, 2015 at 11:49 Comment(1)
This is a hack for Gradle and IMHO should not be used. You can face side effects, e.g. longer build due to lack of outputs caching and other unknowns.Contrabass
Z
1

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
Zipper answered 23/8, 2017 at 23:13 Comment(1)
Improvement. Gradle 4 use different output directory for the classesZipper

© 2022 - 2024 — McMap. All rights reserved.