About Yoav Abrahami
Problem:
We had an integration test which creates a spring ClassPathXmlApplicationContext and while doing so the test blew up with a NoSuchMethodError. It turned out that we had conflicting versions of dependencies on Spring artifacts. This in itself is not an unusual problem – such problems are addressed with the maven dependency plugin using the verbose option. However, what do you do when the maven plugin is wrong?
Investigation:
We started to dig in and we found that the AbstractAutowireCapableBeanFactory in its getTypeForFactoryMethod method tries to access the GenericTypeResolver resolveReturnTypeForGeneric method and fails on a java.lang.NoSuchMethodError: org.springframework.core.GenericTypeResolver.resolveReturnTypeForGenericMethod(Ljava/lang/reflect/Method;.
Initial investigation and googling we found that the method was added in 3.2.0 while we’re supposed to be running with 3.1.1. Further investigation determined that spring-data-mongodb depends on spring framework in range [3.0.7-4) 1 and because maven takes the latest available version given that range 2 it tried to take 3.2.2.
Note that the above changes a bit given a conflict between an explicit version dependency and a range dependency but, IINM, when determining the dependencies of spring mongo there is no conflict.
The problem was further masked by two symptoms:
- We have other projects that use this pattern and have no problem- This was explained by the fact that the conflict-resolving mechanism of maven chooses the nearest version it finds by default 3 and since all other projects which need spring-data-mongodb depend on this project they were lucky enough to grab the 3.1.1 version and not 3.2.2
- dependency:tree shows it brings 3.1.1 while bringing 3.2.2- Since the stack trace showed other results I wrote a test which checks from which jar each of the above classes comes from and verified that indeed the AbstractAutowireCapableBeanFactory class arrives from spring-beans 3.2.2 and not 3.1.1 as "mvn dependency:tree" showed (a big thanks to http://bit.ly/10zD1iV for the code snippet of finding the jar of a class in runtime).
Maven dependency:tree output showing spring-beans:3.1.1 is used in the artifact
>:mvn dependency:tree -Dverbose -Dincludes=org.springframework...(omitted for clarity)...[INFO] — maven-dependency-plugin:2.1:tree (default-cli) @ wix-feature-toggle-administration —[INFO] artifact org.springframework:spring-beans: checking for updates from central[INFO] artifact org.springframework:spring-beans: checking for updates from snapshots[INFO] artifact org.springframework:spring-expression: checking for updates from central[INFO] artifact org.springframework:spring-expression: checking for updates from snapshots[INFO] artifact org.springframework:spring-tx: checking for updates from central[INFO] artifact org.springframework:spring-tx: checking for updates from snapshots[INFO] com.wixpress.common:wix-feature-toggle-administration:jar:2.180.0-SNAPSHOT…[INFO] +- org.springframework.data:spring-data-mongodb:jar:1.0.1.RELEASE:compile[INFO] | +- org.springframework:spring-beans:jar:3.1.1.RELEASE:compile[INFO] | | \- (org.springframework:spring-core:jar:3.2.2.RELEASE:compile – omitted for conflict with 3.1.1.RELEASE)[INFO] | +- org.springframework:spring-expression:jar:3.1.1.RELEASE:compile[INFO] | | \- (org.springframework:spring-core:jar:3.2.2.RELEASE:compile – omitted for conflict with 3.1.1.RELEASE)[INFO] | \- org.springframework.data:spring-data-commons-core:jar:1.2.1.RELEASE:compile[INFO] | +- (org.springframework:spring-beans:jar:3.1.1.RELEASE:compile – omitted for duplicate)[INFO] | \- (org.springframework:spring-tx:jar:3.1.1.RELEASE:compile – omitted for duplicate)[INFO] +- com.wixpress.common:wix-framework:jar:2.180.0-SNAPSHOT:compile[INFO] | +- org.springframework:spring-core:jar:3.1.1.RELEASE:compile[INFO] | | \- org.springframework:spring-asm:jar:3.1.1.RELEASE:compile…I’ve removed additional outputs for clarity. The additional outputs were all 3.1.1 and were further down the tree (so irrelevant due to maven conflict resolving mechanism)
Test which proves spring-beans:3.2.2 is used in the artifact (asserting what the jvm in the error was saying)
package com.wixpress.springVersionBug;import org.junit.*;import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory;import org.springframework.core.GenericTypeResolver;import java.security.CodeSource;import static org.hamcrest.Matchers.endsWith;/** * @author ittaiz * @since 3/24/13 */public class SpringVersionTest { @Test public void verifySpringBeans311InClasspath(){ verifyCorrectSpringVersionInClasspathFor(AbstractAutowireCapableBeanFactory.class,"spring-beans-3.1.1.RELEASE.jar"); } @Test public void verifySpringCore311InClasspath(){ verifyCorrectSpringVersionInClasspathFor(GenericTypeResolver.class,"spring-core-3.1.1.RELEASE.jar"); } public void verifyCorrectSpringVersionInClasspathFor(Class springClass,String expectedJarFileName){ CodeSource springClassCodeSource = springClass.getProtectionDomain().getCodeSource(); Assert.assertNotNull("expecting "+expectedJarFileName+" to be loaded by non-system class loader",springClassCodeSource); Assert.assertThat(springClassCodeSource.getLocation().toString(),endsWith(expectedJarFileName)); }} The reason spring-core artifact came in 3.1.1 when spring-beans came as 3.2.2 is that our framework explicitly depends on spring-core and this artifact explicitly depends on the framework. This means spring-core 3.1.1 from framework is 2 hops which is shorter than the 3.2.2 from spring-data-mongodb.
Solution:
Depend on spring-data-mongodb while excluding spring-beans like so:
<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-mongodb</artifactId> <version>1.0.1.RELEASE</version> <exclusions> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> </exclusion> </exclusions></dependency>
The Open question mark:
Why dependency:tree (in verbose mode) did not show that it brings spring-beans in 3.2.2 but in 3.1.1 while explicitly specifying that spring-core 3.2.2 was removed due to conflict? I chalk this up to a bug in the dependency plugin.
- http://repo1.maven.org/maven2/org/springframework/data/spring-data-mongodb-parent/1.0.1.RELEASE/spring-data-mongodb-parent-1.0.1.RELEASE.pom ↩
- http://www.maestrodev.com/better-builds-with-maven/creating-applications-with-maven/resolving-dependency-conflicts-and-using-version-ranges/ ↩
- http://www.maestrodev.com/better-builds-with-maven/creating-applications-with-maven/resolving-dependency-conflicts-and-using-version-ranges/ ↩
Source : http://www.javacodegeeks.com/2013/04/when-maven-dependency-plugin-lies.html