0. Introducción
El famoso "jar hell" o infierno de los jars, se manifiesta aquí en todo su esplendor. En concreto la integración de Apache CXF ha corrido mas ríos de sangre y frustación que de tinta.
1. Shadow plugin con Apache CXF
Recordar crear el subproyecto dentro del módulo principal y añadirle la referencia al subproyecto en "settings.properties" del proyecto principal
El settings.gradle seria:
rootProject.name = 'Multiproject'
include('lib',
'A000-Basic', 'A003-Excel', 'A004-PDF',
'B001-Model-Base', 'B003-DAO',
'C002-Model-Control', 'C005-Model-CSV',
'D001-DAO-Imp', 'D002-Sedipualba-WS-Shdw',
'PH01', 'PH01-Simple')
Veamos el fichero build.gradle
Hay que prestar especial atención a las líneas que hacen referencia al transformer y a las reubicaciones que son muy truculentas ("tricky")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79 | plugins {
// Apply the java-library plugin for API and implementation separation.
id 'java-library'
//id 'application'
id 'com.github.johnrengelman.shadow' version '7.1.2'
}
import com.github.jengelman.gradle.plugins.shadow.transformers.AppendingTransformer
repositories {
jcenter()
mavenCentral()
maven { url "https://jitpack.io" } //@see: https://stackoverflow.com/questions/38905939/how-to-import-library-from-jitpack-io-using-gradle
}
dependencies {
// Use JUnit Jupiter for testing.
testImplementation 'org.junit.jupiter:junit-jupiter:5.7.2'
/***********************************************************************
*********************** LOMBOK DEFINITION ******************************/
compileOnly 'org.projectlombok:lombok:1.18.20'
annotationProcessor 'org.projectlombok:lombok:1.18.20'
testCompileOnly 'org.projectlombok:lombok:1.18.20'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.20'
/************************************************************************
*********************** END LOMBOK DEFINITION ***************************/
api(project(":A000-Basic"))
// Apache general //
implementation "commons-beanutils:commons-beanutils:1.9.4"
implementation "commons-io:commons-io:2.11.0"
implementation "org.apache.commons:commons-lang3:3.12.0"
/*Apache CXF*/
implementation 'org.apache.cxf:cxf-core:3.5.2'
implementation 'org.apache.cxf:cxf-rt-features-logging:3.5.2'
implementation 'org.apache.cxf:cxf-rt-ws-security:3.5.2' // WSS4JInterceptors, WSPasswordCallback
implementation 'org.apache.cxf:cxf-rt-frontend-jaxws:3.5.2' //Dynamic client
implementation 'org.apache.cxf:cxf-rt-transports-http:3.5.2'//Si no es fica, es queda penjat !!!
implementation 'com.sun.xml.ws:jaxws-ri:2.3.5' //FUNCIONA! Quita:com.sun.tools.internal.xjc.api.XJC
/* Jackson */
implementation "com.fasterxml.jackson.core:jackson-databind:2.13.3"
} //dependencies
shadowJar {
transform(AppendingTransformer) {
resource = 'META-INF/cxf/bus-extensions.txt'
}
//1. Name and location of jars
destinationDirectory = file("$rootDir/../mynewtargets")
archiveBaseName.set('d002-sedipualba-ws-shdw')
archiveClassifier.set('')
archiveVersion.set('2.1')
//mainClassName = 'openadmin.utils.sedipualba.SedipuAlbaUtilsWSSeres'
//2. Relocate (shadow) old problematic transitive dependency
relocate 'com.sun.xml.bind.v2', 'com.sun.xmlv2.internal.bind.v2'
relocate 'com.sun.xml' , 'com.sun.xmlv2'
relocate 'javax.xml.bind' , 'javax.xml.bindv2'
//4. Include sources
from sourceSets.main.allSource
}//shadowJar
tasks.named('test') {
// Use JUnit Platform for unit tests.
useJUnitPlatform()
}//tasks
|
Veamos las líneas importantes:
5: Se importa el gradle shadow plugin.
8: Se importa el transformer, pieza clave para que se pueda empaquetar apache cxf. Ver la contestación de petternordholm
40-44: Dependencias de Apache CXF
46-49: Dependencias que se requieren en apache CXF
54-56: Aplicación del transformer. Sin esto, da un montón de referencias nulas (Bindings...)
58-61: Se define nombre y ubicación del jar generado
66: Reubicación importante: Hay conflictos a partir de la versión 3, pues se cambia la referencia desde javax a jakarta. Por tanto hay que utilizar el shadow para que reubique estas dependencias antiguas que las llama apache cxf. (que internamente llaman a javax)
65: Volvemos atras una línea, para indicar que si se reubiva la línea 65, entonces el sistema se "inventa" una reubicación intermedia llamada "internal". Por tanto hay que tener cuidado
67: Reubicación del javax para que nlo lo machaque el jakarta
71. Incluimos las fuentes ".java" para que la depuración sea más fácil
2. Subproyecto que usa este como referencia.
Aquí la cosa tiene truco.
Para ello seguimos la entrada anterior del blog y creamos un subproyecto "PH01-Simple" que solo va a tener de dependencia al subproyecto anterior, y otro. Recordar de crear la referencia a settings.gradle del proyecto padre.
Veamos el build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 | plugins {
// Apply the java-library plugin for API and implementation separation.
id 'java-library'
id 'application'
}
repositories {
jcenter()
mavenCentral()
}
gradle.startParameter.showStacktrace = org.gradle.api.logging.configuration.ShowStacktrace.ALWAYS
project.jar.destinationDirectory = file("$rootDir/../mynewtargets")
project.archivesBaseName = 'PH01-Simple'
project.version = '2.0'
dependencies {
// Use JUnit Jupiter for testing.
testImplementation 'org.junit.jupiter:junit-jupiter:5.7.2'
//=============LOMBOK BEGIN ============================
// Not generated by the Vaadin starting-project
compileOnly 'org.projectlombok:lombok:1.18.20'
annotationProcessor 'org.projectlombok:lombok:1.18.20'
testCompileOnly 'org.projectlombok:lombok:1.18.20'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.20'
//=============LOMBOK END ==============================
api(project(":A000-Basic")) implementation project(path: ':D002-Sedipualba-WS-Shdw', configuration: 'shadow')
}
application {
mainClass = 'alba.utils.AlbaEdu'
}
jar {
duplicatesStrategy = 'exclude'
from sourceSets.main.allSource //Include java sources
}
|
Observar que:
- No utilizamos el shadow plugin
- Para referenciar a un proyecto hecho shadow plugin, ver la línea 32. En cambio un proyecto normal se referencia con la línea 31
Si le damos a la task de gradle application -run, parece ser que funciona
Pero si le damos a ejecutar una clase que hace referencia al proyecto anterior (AlbaEdu) que usamos el shadow plugin. da error de ejecución y no encuentra la referencia a apache cxf.
Exception in thread "main" java.lang.NoClassDefFoundError: org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory
at openadmin.utils.sedipualba.SedipualbaUtilsOther.getClientsAndClassLoadersIRunBO(SedipualbaUtilsOther.java:270)
at alba.utils.AlbaEdu.<init>(AlbaEdu.java:32)
at alba.utils.AlbaEdu.main(AlbaEdu.java:106)
Caused by: java.lang.ClassNotFoundException: org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:827)
at java.base/jdk.internal.loader.ClassLoaders$Aimplementation project(path: ':D002-Sedipualba-WS-Shdw', configuration: 'shadow')implementation project(path: ':D002-Sedipualba-WS-Shdw', configuration: 'shadow')ppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:1112)
... 3 more
3. 1ª Solución al problema
Para poder depurar esta clase en Eclipse, es importante que se pueda ejecutar. Para ello, como Eclipse no puede encontrar las referencias al proyecto anterior (del shadow plugin), hay que indicárselo en la configuración de ejecución.
Apretar el botón derecho del ratón sobre la clase en cuestión (en mi caso AlbaEdu) y Run As ->Run Configurations o también Debug As > Debug Configurations
Le indicamos Add External Jars, y buscamos donde está el jar generado en el subproyecto anterior y lo añadimos. Y a funcionar!!!
4. 2ª Solución al problema
Es evidente que la solución anterior no es precisamente muy elegante. Parece ser que el shadow plugin y la implementación multiproyecto de eclipse no se comunican muy bien.
Otra solución poco elegante pero no tanto consiste en realizar estos pasos:
1. Generar el jar del proyecto que contiene el shadow plugin (maven lo contempla)
2. En vez de hacer referencia a este proyecto, se haría referencia al jar generado. En concreto bastaría con cambiar esta línea en el build-gradle:
implementation project(path: ':D002-Sedipualba-WS-Shdw', configuration: 'shadow')
por esta otra
implementation files(
'../mynewtargets/d002-sedipualba-ws-shdw-2.1.jar',
)
Ahora me toca probar con un war!