Spécifiant les dépendances «fournies» avec des grails sous maven
-
28-10-2019 - |
Question
J'ai une application Graals 1.3.7. J'utilise les classes JMS de Spring pour configurer l'un de mes services Graals en tant qu'auditeur de messages, configurant ces classes dans Graals-App / conf / Resources.groovy. J'utilise Maven 2.0.9 pour les builds, en utilisant le Graals-Maven-Plugin 1.3.7 et l'objectif "Maven-War" pour créer un fichier de guerre.
J'ai deux scénarios:
- Je veux pouvoir exécuter mon application Graals localement, à partir de la ligne de commande, en utilisant "MVN Graals: Run-App". J'utilise cela pendant le développement.
- Je veux pouvoir exécuter l'application dans JBOSS 5.1.0 GA, en déploiement du fichier de guerre créé par Maven. C'est ce que nous faisons dans nos environnements d'intégration, de test et de production.
Lors de l'exécution à l'intérieur de JBoss, toutes les dépendances liées à JMS-Provider sont disponibles et fournies par le serveur d'applications. La manière normale de gérer cela avec Maven est de déclarer ces dépendances dans le fichier POM, avec une portée de "fournie". Cela mettra ces dépendances disponibles pour les tests de compilation et d'unité, mais les exclura du fichier de guerre.
Cependant, lorsque j'exécute localement à partir de la ligne de commande à l'aide de "MVN Graals: Run-App", il semble que ces dépendances ne soient pas disponibles pour Graals au moment de l'exécution, comme en témoignent de nombreuses exceptions ClassNotFound, etc. La modification de la portée en «compiler» me permet d'exécuter localement. Cependant, ces dépendances sont maintenant emballées dans mon fichier de guerre, ce que je ne veux pas et a tendance à casser les choses lors de l'exécution à l'intérieur de JBoss.
La solution (ou la solution de contournement) que j'ai trouvée pour l'instant consiste à inclure ces dépendances JMS avec une portée par défaut (compilation) dans mon POM, et supprimer ces pots (et toutes leurs dépendances transitives) du fichier de guerre via un code dans BuildConfig.groovy ( voir ci-dessous). Cela fonctionne, mais c'est désordonné et sujette à des erreurs parce que je dois énumérer chaque dépendance transitive (dont il y en a beaucoup!).
Quelques autres choses que j'ai essayées:
Au début, j'ai pensé que je pourrais peut-être ajouter les dépendances JMS requises à BuildConfig.groovy, dans la section "grails.project.dependency.resolution / dépendances", et les laisser complètement hors du pom. Cependant, cela n'a pas fonctionné parce que, selon ce lien, la section BuildConfig Dependances est ignorée lors de l'exécution de Graals sous Maven.
J'ai également remarqué l'option "POM True" (mentionnée dans le lien ci-dessus) et j'ai essayé de l'utiliser. Cependant, lorsque vous essayez d'exécuter Graals: Run-App, Graals jette des avertissements sur les dépendances non résolues et donne une erreur 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)
Ma question: Existe-t-il un meilleur moyen - à travers des options de configuration de Graals et / ou Maven - pour accomplir ce que je veux - c'est-à-dire pouvoir exécuter Graals avec succès localement et dans JBoss, sans avoir à exclure manuellement toutes les dépendances transitives du fichier de guerre?
Remarque: Je ne peux pas modifier la version de Graals, Jboss ou Maven que j'utilise.
Quelques extraits de fichiers pertinents:
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)
}
}
}
pom.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>
Ressources.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
}
}
La solution
La solution pour cela consiste à créer une portée dynamique pour votre dépendance via un profil.
Exemple:
<?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>
. . .
Ensuite, lorsque votre ligne de commande spécifie le profil:
mvn clean install war -P build
J'espère que cela t'aides.
Autres conseils
Cela a été "fixe" dans Graals 2.0. Le plugin Maven pour Graals a été mis à jour de sorte que la portée "fournie" signifie que la dépendance est disponible lors de l'exécution locale, mais n'est pas incluse dans le fichier de guerre du package.