Проблемы с обнаружением компонентов при использовании сварки с плагином Gradle.

Я создаю приложение Java SE на базе Gradle, построенное на основе Hibernate, в качестве своего ORM. Мой план заключается в использовании weld-se чтобы иметь возможность использовать аннотации CDI для инъекций EntityManagers на протяжении всего приложения.

На основании общего HibernateUtil вспомогательный класс, найденный в документации Hibernate, я перешел к интерфейсам 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: невозможно разрешить любые bean-компоненты для типа: class app.DemoApplication; Квалификаторы: [@ @ 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 боб, что заставляет меня верить, что что-то не так с процессом обнаружения бобов. После обновления моей зависимости 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 на месте высоко ценятся!

Обновление 2015-05-19:

Тайна разгадана, см. Мой собственный ответ ниже. Я изменил название вопроса, чтобы отразить реальный характер проблемы.

5 ответов

Решение

Потратив на эту проблему больше времени, чем кажется нормальным, я наконец-то смог найти причину своей проблемы. Это не имеет отношения ни к Weld, ни к Jandex, а скорее к тому, как Gradle структурирует свои выходные каталоги:

:build задача создает две отдельные папки вывода для фактических результатов компиляции и и для дополнительных ресурсов (build/classes а также build/resources). Только когда вы создаете JAR-архив, эти две папки объединяются. :run Однако задача запускает приложение непосредственно из выходных папок компиляции с двумя отдельными записями пути к классам для классов и ресурсов.

Механизм обнаружения bean-компонентов Weld, по-видимому, только пытается обнаружить bean-объекты для той же записи пути к классу, META-INF/beans.xml файл, который в этом случае является build/resources/main папка. В свою очередь, бобы никогда не обнаруживаются и никогда не имеют права на инъекцию нигде.

Мой обходной путь на данный момент (см. Git-репозиторий) заключается в создании дополнительной задачи Gradle для копирования ресурсов в соответствующую папку, чтобы обнаружение bean-компонентов происходило в правильной записи classpath:

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

Спасибо всем за ваши советы!

Есть проблема с static декларация. Не могли бы вы сделать это нестатичным способом?

Относительно https://issues.jboss.org/browse/WELD-1505 должен быть исправлен в 2.2.0.Beta2

В качестве улучшения: Gradle 4 использует другой выходной каталог для классов.

Определение задачи в другом ответе немного неверно. Глядя на журнал Gradle для запуска задачи, вот используемый classpath:

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

Насколько я мог видеть из вашего примера GIT, у вас есть что-то вроде этого с точки зрения иерархии объектов:

Main -> DemoApplication -> EntityManager

Если вы внедряете EntityManager в свое DemoApplication, вам придется внедрить DemoApplication в свой Main.java, иначе вы нарушите цепочку внедрения.

Попробуйте аннотировать классы DemoApplication и HibernateUtil с помощью @Singleton а затем вставьте его в свой класс Main.java:

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

Это должно помочь пропустить ручное обнаружение бобов.

Кроме того, если вы собираетесь создать JAR, ваш файл beans.xml должен находиться в META-INF, но если вы делаете WAR из своего проекта, файл beans.xml должен быть в WEB-INF.

Решение также может быть найдено путем определения sourceSets в Gradle.

Как это:

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

Выглядит чище на мой взгляд. И, конечно, лучше, чем объявлять каждый класс как пакет.

Другие вопросы по тегам