Создание автономного исполняемого JAR с OpenEJB

Я создаю инструмент CLI, который интегрируется с несколькими модулями EJB. По этой причине мне нужно построить fat jar, который затем выполняется как отдельное приложение.

Однако выполнение этого fat jar с помощью java -jar (Примечание: conf/openejb.xml находится в том же каталоге, что и fat jar), с помощью следующей команды stacktrace:

INFORMATION - PersistenceUnit(name=demo, provider=org.hibernate.jpa.HibernatePersistenceProvider) - provider time 2706ms
INFORMATION - Jndi(name="java:global/DemoMain/demo-shade-1.0-SNAPSHOT/MEJB!javax.management.j2ee.ManagementHome")
INFORMATION - Jndi(name="java:global/DemoMain/demo-shade-1.0-SNAPSHOT/MEJB")
INFORMATION - Jndi(name="java:global/DemoMain/demo-shade-1.0-SNAPSHOT/openejb/Deployer!org.apache.openejb.assembler.Deployer")
INFORMATION - Jndi(name="java:global/DemoMain/demo-shade-1.0-SNAPSHOT/openejb/Deployer")
INFORMATION - Jndi(name="java:global/DemoMain/demo-shade-1.0-SNAPSHOT/openejb/ConfigurationInfo!org.apache.openejb.assembler.classic.cmd.ConfigurationInfo")
INFORMATION - Jndi(name="java:global/DemoMain/demo-shade-1.0-SNAPSHOT/openejb/ConfigurationInfo")
INFORMATION - Jndi(name="java:global/DemoMain/demo-shade-1.0-SNAPSHOT/DemoServiceImpl!com.github.rzo1.service.DemoService")
INFORMATION - Jndi(name="java:global/DemoMain/demo-shade-1.0-SNAPSHOT/DemoServiceImpl")
INFORMATION - Existing thread singleton service in SystemInstance(): [email protected]
INFORMATION - Closing DataSource: demoDS
INFORMATION - Closing DataSource: demoDSNonJTA
Exception in thread "Thread-0" java.lang.RuntimeException: org.apache.openejb.OpenEjbContainer$AssembleApplicationException: javax.enterprise.inject.spi.DeploymentException: couldn't start owb context
       at com.github.rzo1.DemoMain.run(DemoMain.java:116)
       at java.lang.Thread.run(Thread.java:745)
Caused by: org.apache.openejb.OpenEjbContainer$AssembleApplicationException: javax.enterprise.inject.spi.DeploymentException: couldn't start owb context
       at org.apache.openejb.OpenEjbContainer$Provider.createEJBContainer(OpenEjbContainer.java:346)
       at javax.ejb.embeddable.EJBContainer.createEJBContainer(EJBContainer.java:56)
       at com.github.rzo1.DemoMain.run(DemoMain.java:90)
       ... 1 more
Caused by: javax.enterprise.inject.spi.DeploymentException: couldn't start owb context
       at org.apache.openejb.cdi.ThreadSingletonServiceImpl.initialize(ThreadSingletonServiceImpl.java:191)
       at org.apache.openejb.cdi.CdiBuilder.build(CdiBuilder.java:41)
       at org.apache.openejb.assembler.classic.Assembler.createApplication(Assembler.java:913)
       at org.apache.openejb.assembler.classic.Assembler.createApplication(Assembler.java:717)
       at org.apache.openejb.OpenEjbContainer$Provider.createEJBContainer(OpenEjbContainer.java:342)
       ... 3 more
Caused by: org.apache.webbeans.exception.WebBeansException: Wrong startup object.
       at org.apache.webbeans.web.lifecycle.WebContainerLifecycle.getServletContext(WebContainerLifecycle.java:227)
       at org.apache.webbeans.web.lifecycle.WebContainerLifecycle.startApplication(WebContainerLifecycle.java:86)
       at org.apache.openejb.cdi.ThreadSingletonServiceImpl.initialize(ThreadSingletonServiceImpl.java:189)
       ... 7 more

Выполняя код непосредственно из моей IDE (IntelliJ), создается автономный контейнер и ведет себя как ожидалось.

Резюме версии:

  • openejb в версии 1.7.0/openejb-server в версии 7.0.2
  • maven-shade-plugin в версии 2.4.3
  • Maven в версии 3.3.9
  • hibernate в версии 5.2.7

Основная настройка

Я смог воспроизвести свою проблему на простом рабочем примере, который я добавил как проект GitHub для дальнейшего изучения.

Основной макет проекта выглядит следующим образом:

  | # demo-shade
    | - demo-services (EJB-Module)          
    | - demo-main (Shading happens here)

Конфигурация maven-shade-plugin выглядит следующим образом:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>2.4.3</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
            <configuration>
                <finalName>demo-shade-${project.version}</finalName>
                <transformers>
                    <transformer
                            implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                        <manifestEntries>
                            <Main-Class>com.github.rzo1.DemoMain</Main-Class>
                        </manifestEntries>
                    </transformer>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                        <resource>META-INF/openwebbeans/openwebbeans.properties</resource>
                    </transformer>
                </transformers>
                <filters>
                    <filter> <!-- we don't want JSF to be activated -->
                        <artifact>*:*</artifact>
                        <excludes>
                            <exclude>META-INF/faces-config.xml</exclude>
                            <exclude>META-INF/*.SF</exclude>
                            <exclude>META-INF/*.DSA</exclude>
                            <exclude>META-INF/*.RSA</exclude>
                        </excludes>
                    </filter>
                </filters>
                <shadedClassifierName>dist</shadedClassifierName>
            </configuration>
        </execution>
    </executions>
</plugin>

Код для запуска контейнера:

EJBContainer ejbContainer = null;
    try {
        final Properties properties = new Properties();
        properties.setProperty(EJBContainer.APP_NAME, applicationName);
        properties.setProperty(EJBContainer.PROVIDER, OpenEjbContainer.class.getName());
        properties.setProperty(OpenEjbContainer.OPENEJB_EMBEDDED_REMOTABLE, "false");
        properties.setProperty("ejbd.disabled", "true");
        properties.setProperty("ejbds.disabled", "true");
        properties.setProperty("admin.disabled", "true");
        properties.setProperty("openejb.jaxrs.application", "false");

        Path launchPath = Paths.get(DemoMain.class.getProtectionDomain().getCodeSource().getLocation().toURI());
        properties.setProperty("openejb.configuration", launchPath.toAbsolutePath() + "/conf/openejb.xml");

        properties.put("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
        properties.put("hibernate.connection.driver_class", "org.hsqldb.jdbcDriver");

        // This is the line starting the EJB container
        ejbContainer = EJBContainer.createEJBContainer(properties);
        ejbContainer.getContext().bind("inject", this);

        ejbContainerReady = true;

        final CountDownLatch latch = new CountDownLatch(1);
        // Graceful shutdown
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                try {
                    logger.info("Shutting down..");
                    latch.countDown();
                    logger.info("Shutdown completed successfully.");
                } catch (final Exception e) {
                    logger.error("Graceful shutdown went wrong. SIGKILL (kill -9) if you want.", e);
                }
            }
        });
        try {
            latch.await();
        } catch (final InterruptedException e) {
            // ignored
        }
    } catch (final Exception e) {
        ejbContainerReady = false;
        throw new RuntimeException(e);
    } finally {
        if (ejbContainer != null) {
            ejbContainer.close();
        }
    }

}

Вопросы

  • Я что-то пропустил в конфигурации maven-shade-plugin?

  • Как я могу построить fat jar с помощью openejb автономно?

Пример проекта

ОБНОВЛЕНИЕ 1:

Я изменил пом в соответствии с ответом П. Меркле. Я нашел еще одну статью здесь, описывающую процесс затенения специально для TomEE.

pom изменен на

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>2.4.3</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
            <configuration>
                <finalName>demo-shade-${project.version}</finalName>
                <transformers>
                    <transformer
                            implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                        <manifestEntries>
                            <Main-Class>com.github.rzo1.DemoMain</Main-Class>
                        </manifestEntries>
                    </transformer>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
                    <transformer implementation="org.apache.openwebbeans.maven.shade.OpenWebBeansPropertiesTransformer"/>
                </transformers>
                <filters>
                    <filter> <!-- we don't want JSF to be activated -->
                        <artifact>*:*</artifact>
                        <excludes>
                            <exclude>META-INF/faces-config.xml</exclude>
                            <exclude>META-INF/*.SF</exclude>
                            <exclude>META-INF/*.DSA</exclude>
                            <exclude>META-INF/*.RSA</exclude>
                        </excludes>
                    </filter>
                </filters>
                <shadedClassifierName>dist</shadedClassifierName>
            </configuration>
        </execution>
    </executions>
    <dependencies>
        <dependency>
            <groupId>org.apache.openwebbeans</groupId>
            <artifactId>openwebbeans-maven</artifactId>
            <version>1.7.0</version>
        </dependency>
    </dependencies>
</plugin>

Выполнение этого fat jar приводит:

INFORMATION - OpenWebBeans Container is starting...
INFORMATION - Adding OpenWebBeansPlugin : [CdiPlugin]
SCHWERWIEGEND - CDI Beans module deployment failed
java.lang.NullPointerException
        at org.apache.openejb.cdi.CdiScanner.handleBda(CdiScanner.java:271)
        at org.apache.openejb.cdi.CdiScanner.init(CdiScanner.java:148)
        at org.apache.openejb.cdi.OpenEJBLifecycle.startApplication(OpenEJBLifecycle.java:179)
        at org.apache.openejb.cdi.ThreadSingletonServiceImpl.initialize(ThreadSingletonServiceImpl.java:189)
        at org.apache.openejb.cdi.CdiBuilder.build(CdiBuilder.java:41)
        at org.apache.openejb.assembler.classic.Assembler.createApplication(Assembler.java:913)
        at org.apache.openejb.assembler.classic.Assembler.createApplication(Assembler.java:717)
        at org.apache.openejb.OpenEjbContainer$Provider.createEJBContainer(OpenEjbContainer.java:342)
        at javax.ejb.embeddable.EJBContainer.createEJBContainer(EJBContainer.java:56)
        at com.github.rzo1.DemoMain.run(DemoMain.java:90)
        at java.lang.Thread.run(Unknown Source)
INFORMATION - Closing DataSource: demoDS
INFORMATION - Closing DataSource: demoDSNonJTA
Exception in thread "Thread-0" java.lang.RuntimeException: org.apache.openejb.OpenEjbContainer$AssembleApplicationException: javax.enterprise.inject.spi.DeploymentException: couldn't start owb context
        at com.github.rzo1.DemoMain.run(DemoMain.java:116)
        at java.lang.Thread.run(Unknown Source)
Caused by: org.apache.openejb.OpenEjbContainer$AssembleApplicationException: javax.enterprise.inject.spi.DeploymentException: couldn't start owb context
        at org.apache.openejb.OpenEjbContainer$Provider.createEJBContainer(OpenEjbContainer.java:346)
        at javax.ejb.embeddable.EJBContainer.createEJBContainer(EJBContainer.java:56)
        at com.github.rzo1.DemoMain.run(DemoMain.java:90)
        ... 1 more
Caused by: javax.enterprise.inject.spi.DeploymentException: couldn't start owb context
        at org.apache.openejb.cdi.ThreadSingletonServiceImpl.initialize(ThreadSingletonServiceImpl.java:191)
        at org.apache.openejb.cdi.CdiBuilder.build(CdiBuilder.java:41)
        at org.apache.openejb.assembler.classic.Assembler.createApplication(Assembler.java:913)
        at org.apache.openejb.assembler.classic.Assembler.createApplication(Assembler.java:717)
        at org.apache.openejb.OpenEjbContainer$Provider.createEJBContainer(OpenEjbContainer.java:342)
        ... 3 more
Caused by: org.apache.openejb.OpenEJBRuntimeException: java.lang.NullPointerException
        at org.apache.openejb.cdi.OpenEJBLifecycle.startApplication(OpenEJBLifecycle.java:200)
        at org.apache.openejb.cdi.ThreadSingletonServiceImpl.initialize(ThreadSingletonServiceImpl.java:189)
        ... 7 more
Caused by: java.lang.NullPointerException
        at org.apache.openejb.cdi.CdiScanner.handleBda(CdiScanner.java:271)
        at org.apache.openejb.cdi.CdiScanner.init(CdiScanner.java:148)
        at org.apache.openejb.cdi.OpenEJBLifecycle.startApplication(OpenEJBLifecycle.java:179)
        ... 8 more

Я добавил ветку с этими изменениями в Проект GitHub для дальнейшего изучения.

ОБНОВЛЕНИЕ 2

Я исключил javax.xml.* из тени:

<excludes>
    <exclude>META-INF/faces-config.xml</exclude>
    <exclude>META-INF/*.SF</exclude>
    <exclude>META-INF/*.DSA</exclude>
    <exclude>META-INF/*.RSA</exclude>
    <exclude>javax/xml/**</exclude>
</excludes> 

Однако исключение остается таким же, как в обновлении 1. Я нажал связанный ветвь на GitHub хранилище.

Итак, мой вопрос:

  • Что else необходимо исключить из тени?

С помощью других ответов я наконец смог найти рабочее решение для создания автономного fat jar, который работает для моего использования.

ОБНОВЛЕНИЕ 3:

Шагами (пока) являются:

  • Использование OpenWebBeansPropertiesTransformer вместо AppendingTransformer, как указано P. Merkle

  • Исключить java.xml.* в тени, как указано Romain Manni-Bucau:

    <excludes>
    <exclude>META-INF/faces-config.xml</exclude>
    <exclude>META-INF/*.SF</exclude>
    <exclude>META-INF/*.DSA</exclude>
    <exclude>META-INF/*.RSA</exclude>
    <exclude>javax/xml/**</exclude>
    

  • Добавьте scan.xml в META-INF, включая только пакеты/классы, которые необходимо отсканировать. Текущую рабочую версию можно найти здесь

Вопрос:

  • Это их официальный или лучший способ сделать это?

Ответы

Ответ 1

http://tomee.apache.org/advanced/shading/index.html и, возможно, http://tomee.apache.org/advanced/applicationcomposer/index.html также являются хорошими отправными точками.

Теперь кажется, что вы сканируете неожиданные классы, такие как xml, где classloader равен null. Вероятно, исключить javax.xml. * Из сканирования или даже тени, и он будет работать

Ответ 2

Исключение вызвано слиянием нескольких файлов openwebbeans.properties из разных модулей в один файл свойств через AppendingTransformer.

Это связано с тем, что openwebbeans.properties файлы структурированы особым образом:

Все эти файлы содержат одно свойство configuration.ordinal, которое определяет их "важность". Любая настройка из файла свойств с более высокой конфигурацией.ординарный будет перезаписывать настройки с одного с более низкой конфигурацией.

Теперь, если вы наивно объедините эти файлы - как AppendingTransformer делает--, вы получите несколько конкурирующих порядковых свойств в том же файле.

Решение состоит в замене AppendingTransformer на OpenWebBeansPropertiesTransformer, которое сохраняет приоритеты при слиянии.

Пример pom.xml доступен здесь: http://openwebbeans.apache.org/meecrowave/meecrowave-maven/index.html


Плохая новость заключается в том, что это решение раскрывает другое исключение:

java.lang.NullPointerException на org.apache.openejb.cdi.CdiScanner.handleBda(CdiScanner.java:271)

До сих пор я не мог определить причину этого.