Указание предоставленных зависимостей с помощью Grails под maven

У меня есть приложение Grails 1.3.7. Я использую классы Spring JMS для настройки одной из моих служб Grails в качестве прослушивателя сообщений, устанавливая эти классы в приложении grails-app/conf/resources.groovy. Я использую maven 2.0.9 для сборки, используя grails-maven-plugin 1.3.7 и цель «maven-war» для создания файла войны.

У меня есть два сценария:

  1. Я хочу иметь возможность запускать приложение Grails локально из командной строки, используя «mvn grails: run-app». Я использую это во время разработки.
  2. Я хочу иметь возможность запускать приложение в JBoss 5.1.0 GA, развернув военный файл, созданный maven. Это то, что мы делаем в наших средах интеграции, тестирования и производства.

При работе внутри JBoss все зависимости, связанные с поставщиком JMS, доступны и предоставляются сервером приложений. Обычный способ справиться с этим с помощью maven - объявить эти зависимости в файле pom с областью действия «предоставлено». Это сделает эти зависимости доступными для компиляции и модульных тестов, но исключит их из файла войны.

Однако, когда я запускаю локально из командной строки с помощью «mvn grails:run-app», кажется, что эти зависимости недоступны для grails во время выполнения, о чем свидетельствуют многие исключения ClassNotFound и т. д. Изменение области действия на «компилировать» позволяет мне работать локально. Однако теперь эти зависимости упакованы в мой военный файл, который мне не нужен и имеет тенденцию ломать вещи при работе внутри JBoss.

Решение (или обходной путь), которое я нашел на данный момент, состоит в том, чтобы включить эти зависимости JMS с областью действия по умолчанию (компиляция) в моем pom и удалить эти jar-файлы (и все их транзитивные зависимости) из файла войны через некоторый код в BuildConfig.groovy ( увидеть ниже). Это работает, но беспорядочно и чревато ошибками, потому что мне приходится перечислять каждую переходную зависимость (которых много!).

Некоторые другие вещи, которые я пробовал:

Сначала я подумал, что, возможно, смогу добавить необходимые зависимости JMS в BuildConfig.groovy в разделе «grails.project.dependency.resolution/dependencies» и полностью исключить их из pom. Однако это не сработало, поскольку согласно эта ссылка, раздел зависимостей BuildConfig игнорируется при запуске Grails под maven.

Я также заметил опцию «pom true» (упомянутую в ссылке выше) и попытался ее использовать. Однако при попытке запустить grails:run-app grails выдает предупреждения о неразрешенных зависимостях и выдает ошибку tomcat:

:::: WARNINGS
::::::::::::::::::::::::::::::::::::::::::::::
::          UNRESOLVED DEPENDENCIES         ::
::::::::::::::::::::::::::::::::::::::::::::::
:: commons-collections#commons-collections;3.2.1: configuration not found in commons-collections#commons-collections;3.2.1: 'master'. It was required from org.grails.internal#load-manager-grails;1.2-SNAPSHOT compile
:: org.slf4j#slf4j-api;1.5.8: configuration not found in org.slf4j#slf4j-api;1.5.8: 'master'. It was required from org.grails.internal#load-manager-grails;1.2-SNAPSHOT runtime

...

java.lang.LinkageError: loader constraint violation: when resolving overridden method "org.apache.tomcat.util.digester.Digester.setDocumentLocator(Lorg/xml/sax/Locator;)V" the class loader (instance of org/codehaus/groovy/grails/cli/support/GrailsRootLoader) of the current class, org/apache/tomcat/util/digester/Digester, and its superclass loader (instance of <bootloader>), have different Class objects for the type org/xml/sax/Locator used in the signature
        at org.grails.tomcat.TomcatServer.start(TomcatServer.groovy:212)

Мой вопрос: есть ли лучший способ - с помощью параметров конфигурации grail и/или maven - выполнить то, что я хочу, то есть иметь возможность успешно запускать grail локально и в JBoss без необходимости вручную исключать все транзитивные зависимости из военный файл?

Примечание. Я не могу изменить используемую версию Grails, JBoss или maven.

Некоторые выдержки из соответствующих файлов:

BuildConfig.groovy:

grails.project.class.dir = "target/classes"
grails.project.test.class.dir = "target/test-classes"
grails.project.test.reports.dir = "target/test-reports"
grails.project.war.file = "target/${appName}-${appVersion}.war"

grails.project.dependency.resolution = {
    // inherit Grails' default dependencies
    inherits("global") {
        // uncomment to disable ehcache
        // excludes 'ehcache'
    }
    log "warn" // log level of Ivy resolver, either 'error', 'warn', 'info', 'debug' or 'verbose'
    repositories {
        // only use our internal Archiva instance
        mavenRepo "http://my-company.com/archiva/repository/mirror"
        mavenLocal()
    }
    dependencies {
        // specify dependencies here under either 'build', 'compile', 'runtime', 'test' or 'provided' scopes eg.
    }

    //Remove own log4j and use the one supplied by JBoss instead
    grails.war.resources = {stagingDir ->
        delete file:"$stagingDir/WEB-INF/classes/log4j.properties" // logging conf done in JBoss only

        def files = fileScanner {
            fileset(dir:"$stagingDir/WEB-INF/lib"){
                [
                    // all of the following are jms-related dependencies supplied by JBoss
                    /* org.jboss.javaee */ "jboss-jms-api-*.jar",
                    /* org.jboss.naming */ "jnp-client-*.jar",
                    /*    org.jboss */ "jboss-common-core-*.jar",
                    /*    org.jboss.logging */ "jboss-logging-spi-*.jar",
                    /* jboss.messaging */ "jboss-messaging-*.jar",
                    /* org.jboss.aop */ "jboss-aop-*.jar",
                    /*    org.apache.ant */ "ant-*.jar",
                    /*        org.apache.ant */ "ant-launcher-*.jar",
                    /*    org.jboss */ "jboss-reflect-*.jar",
                    /*    org.jboss */ "jboss-mdr-*.jar",
                    /*    qdox */ "qdox-*.jar",
                    /*    trove */ "trove-*.jar",
                    /*    org.jboss.logging */ "jboss-logging-log4j-*.jar",
                    /* org.jboss.remoting */ "jboss-remoting-*.jar",
                    /* jboss */ "jboss-serialization-*.jar",
                    /* oswego-concurrent */ "concurrent-*.jar",
                    /* org.jboss.jbossas */ "jboss-as-cluster-*-jboss-ha-legacy-client.jar",
                    /*    commons-logging */ "commons-logging-*.jar",
                    /*    org.jboss.jbossas */ "jboss-as-server-*.jar",
                    /*       sun-jaxb */ "jaxb-api-*.jar",
                    /*       org.jboss.jbossas */ "jboss-as-deployment-*.jar",
                    /*          org.jboss.javaee */ "jboss-jad-api-*.jar",
                    /*          org.jboss.security */ "jboss-security-spi-*.jar",
                    . . . // and the other 74 transitive dependencies...
                ].each{
                    include(name:it)
                }
            }
        }
        files.each
        {
            delete(file: it)
        }

    }
}

пом.xml:

<?xml version="1.0" encoding="utf-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

    . . .

    <dependencies>

        . . .

        <dependency>
            <!-- already a dep of grails-crud; make it scope:compile for resources.groovy -->
            <groupId>org.springframework</groupId>
            <artifactId>spring-jms</artifactId>
            <version>3.0.5.RELEASE</version>
        </dependency>
        <!-- Note: all the remaining jms dependencies below should be 'provided' scope, but
             grails doesn't correctly pull them in when running locally, so we must leave
             them as compile scope and remove them (and their transitive dependencies) from
             the war file through BuildConfig.groovy
        -->
        <dependency>
            <groupId>org.jboss.javaee</groupId>
            <artifactId>jboss-jms-api</artifactId>
            <version>1.1.0.GA</version>
        </dependency>
        <dependency>
            <groupId>org.jboss.naming</groupId>
            <artifactId>jnp-client</artifactId>
            <version>5.0.3.GA</version>
        </dependency>
        <dependency>
            <groupId>jboss.messaging</groupId>
            <artifactId>jboss-messaging</artifactId>
            <version>1.4.3.GA</version>
        </dependency>
        <dependency>
            <groupId>org.jboss.aop</groupId>
            <artifactId>jboss-aop</artifactId>
            <version>2.1.1.GA</version>
            <classifier>client</classifier>
            <exclusions>
                <exclusion>
                    <!-- see http://jira.codehaus.org/browse/GROOVY-3356 -->
                    <groupId>apache-xerces</groupId>
                    <artifactId>xml-apis</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.jboss.remoting</groupId>
            <artifactId>jboss-remoting</artifactId>
            <version>2.5.3.SP1</version>
        </dependency>
        <dependency>
            <groupId>jboss</groupId>
            <artifactId>jboss-serialization</artifactId>
            <version>1.0.3.GA</version>
        </dependency>
        <dependency>
            <groupId>oswego-concurrent</groupId>
            <artifactId>concurrent</artifactId>
            <version>1.3.4-jboss-update1</version>
        </dependency>
        <!-- the following two are required in order to connect to HA-JNDI -->
        <dependency>
            <groupId>org.jboss.jbossas</groupId>
            <artifactId>jboss-as-cluster</artifactId>
            <classifier>jboss-ha-legacy-client</classifier>
            <version>5.1.0.GA</version>
        </dependency>
        <dependency> 
            <groupId>org.jboss.cluster</groupId>
            <artifactId>jboss-ha-client</artifactId>
            <version>1.1.1.GA</version>
        </dependency>

        <!-- End dependencies for connecting to JMS -->

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.grails</groupId>
                <artifactId>grails-maven-plugin</artifactId>
                <version>1.3.7</version>
                <extensions>true</extensions>
                <executions>
                    <execution>
                        <goals>
                            <goal>init</goal>
                            <goal>maven-clean</goal>
                            <goal>validate</goal>
                            <goal>config-directories</goal>
                            <goal>maven-compile</goal>
                            <goal>maven-test</goal>
                            <goal>maven-war</goal>
                            <goal>maven-functional-test</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

ресурсы.groovy:

import org.codehaus.groovy.grails.commons.ConfigurationHolder
import org.springframework.jms.connection.UserCredentialsConnectionFactoryAdapter
import org.springframework.jms.listener.DefaultMessageListenerContainer
import org.springframework.jms.listener.adapter.MessageListenerAdapter
import org.springframework.jms.support.destination.JndiDestinationResolver
import org.springframework.jndi.JndiObjectFactoryBean
import org.springframework.jndi.JndiTemplate

beans = {

    def config = ConfigurationHolder.config

    jndiTemplate(JndiTemplate) {
        environment = config.myQueue.ctx.toProperties() // flattens a{b{c}} to 'a.b.c'
    }

    jmsFactory(JndiObjectFactoryBean) {
        jndiTemplate = jndiTemplate
        jndiName = config.myQueue.connectionFactory as String
        lookupOnStartup = false // need this?
        proxyInterface = "javax.jms.ConnectionFactory"
    }

    authJmsFactory(UserCredentialsConnectionFactoryAdapter) {
        targetConnectionFactory = jmsFactory
        username = config.app.auth.username as String
        password = config.app.auth.password as String
    }

    destinationResolver(JndiDestinationResolver) {
        jndiTemplate = jndiTemplate
    }

    jmsMessageListener(MessageListenerAdapter, ref("myMessageDrivenService")) {
        defaultListenerMethod = "onEventMessage"
    }

    jmsContainer(DefaultMessageListenerContainer) {
        connectionFactory = authJmsFactory
        destinationResolver = destinationResolver
        destinationName = config.eventQueue.queueName as String
        messageListener = jmsMessageListener
        transactionManager = ref("transactionManager") // grails' txn mgr
        cacheLevel = DefaultMessageListenerContainer.CACHE_CONNECTION
        autoStartup = false // started up in Bootstrap.groovy
    }
}

person GreenGiant    schedule 21.02.2012    source источник
comment
Эта проблема была решена в Grails 2.0. См. мой ответ ниже.   -  person GreenGiant    schedule 11.04.2013


Ответы (2)


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

Пример:

<?xml version="1.0" encoding="utf-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

. . .
<properties>
   <jms.deps.scope>compile</jms.deps.scope>
</properties>

<profile>
   <id>build</id>
   <properties>
       <jms.deps.scope>provided</jms.deps.scope>
   </properties>
</profile>

<dependencies>
   <dependency>
      <groupId>whatever</groupId>
      <artifactId>whatever</artifactId>
      <scope>${jms.deps.scope}</scope>
   </dependency>
</dependencies>

. . .

Затем, когда ваша командная строка укажет профиль:

mvn clean install war -P build

Надеюсь это поможет.

person Daniel Woods    schedule 26.04.2012
comment
В основном это работало. Несколько зависимостей все же пришлось удалить из файла war с помощью BuildConfig.groovy, потому что Grails, по-видимому, игнорирует предоставленные зависимости при выполнении шага компиляции (в данном случае jboss-jms-api). Мне также пришлось удалить банки, загруженные плагинами Grails (например, спящий режим и его зависимости). Наконец, интеграционные тесты Grails не будут выполняться правильно, потому что Grails не использует необходимые предоставленные зависимости. Но в результате получается примерно на 101 строку кода меньше. - person GreenGiant; 07.05.2012

Это было «исправлено» в Grails 2.0. Подключаемый модуль maven для Grails был обновлен таким образом, что «предоставленная» область означает, что зависимость доступна при локальном запуске, но не включена в файл войны пакетов.

person GreenGiant    schedule 02.10.2012