Spring @Autowired fails in case of Cobertura instrumented class
Asked Answered
B

3

12

Question

Cobertura instrumentation is breaking springs autowiring in a specific case. Does anyone know how to resolve this?

Scenario

  • I am running MVN 3.0.4 with the cobertura-maven-plugin version 2.5.1.
  • mvn test runs without issues
  • mvn compile, package etc also runs without issues.
  • mvn cobertura:cobertura also ran without any issues up until the addition of 2 new features which introduced a number of new classes, including two new com.mycompany.executor executor classes. (Example: MyHappyExecutor and MySadExecutor was added in addition to the existing MyExecutor)
  • Excluding MyExecutor from the cobertura instrumentation process seems to fix autowiring
  • Checking the spring autowiring output confirms that the correct beans are getting autowired.

Point of failure

Autowiring fails when attempting to autowire the instrumented version of myExecutor in myService. This worked fine before adding MyHappyExecutor and MySadExecutor. MyHappyExecutor and MySadExecutor are autowired and used in MyExecutor exclusively.

I have attached the exception output below. Please not that class and package names have been edited.

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'myService': Injection of autowired dependencies failed; 
nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.mycompany.executor.MyExecutor com.mycompany.service.impl.MyServiceImpl.myExecutor; 
nested exception is java.lang.IllegalArgumentException: Can not set com.mycompany.executor.MyExecutor field com.mycompany.service.impl.MyServiceImpl.myExecutor to $Proxy20

Conclusion

Something in the Cobertura instrumentation process messes up Springs autowiring.

Update 1

Forcing CGLIB class proxies changes the error type to a "java.lang.NoClassDefFoundError" error. This is affects the standard test goal as well as the Cobertura goal.

<aop:config proxy-target-class="true"/>

Update 2

Here is the output from springs startup process for the 3 classes in question.

2012-11-01 16:21:51 INFO  [main] Overriding bean definition for bean 'myExecutor': replacing [Generic bean: class [com.mycompany.executor.MyExecutor]; scope=; abstract=false; lazyInit=false; autowireMode=1; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [configuration.xml]] with [Generic bean: class [com.mycompany.executor.MyExecutor]; scope=; abstract=false; lazyInit=false; autowireMode=1; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [configuration.xml]] - (DefaultListableBeanFactory.java:623)
2012-11-01 16:21:51 INFO  [main] Overriding bean definition for bean 'happyExecutor': replacing [Generic bean: class [com.mycompany.executor.HappyExecutor]; scope=; abstract=false; lazyInit=false; autowireMode=1; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [configuration.xml]] with [Generic bean: class [com.mycompany.executor.HappyExecutor]; scope=; abstract=false; lazyInit=false; autowireMode=1; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [configuration.xml]] - (DefaultListableBeanFactory.java:623)
2012-11-01 16:21:51 INFO  [main] Overriding bean definition for bean 'sadExecutor': replacing [Generic bean: class [com.mycompany.executor.SadExecutor]; scope=; abstract=false; lazyInit=false; autowireMode=1; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [configuration.xml]] with [Generic bean: class [com.mycompany.executor.SadExecutor]; scope=; abstract=false; lazyInit=false; autowireMode=1; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [configuration.xml]] - (DefaultListableBeanFactory.java:623)
Bhayani answered 26/10, 2012 at 2:58 Comment(3)
possible duplicate of Getting Spring Error "Bean named 'x' must be of type [y], but was actually of type [$Proxy]" in JenkinsSpheroidicity
@TomaszNurkiewicz it certainly seems to be a related issue. The AOP proxy approach doesn't seem to be working in this case though. Any suggestions for some more advanced debugging approaches? (I am using the explicit <bean name="myExecutor" class="com.mycompany.executor.MyExecutor"/> syntax to set up my beans in the applicationContext.)Bhayani
I should mention that the executors do not implement any interfaces.Bhayani
G
1

Another option is to have the executors implement an interface (let's say Executor) and inject using interface in MyService. Spring will know how to build the proxy so that it implements the interface. Most of the times I prefer this approach to proxyTargetClass.

Garcon answered 23/7, 2015 at 21:2 Comment(0)
K
0

For your tests, you will need to set proxyTargetClass=true

@EnableTransactionManagement(mode=AdviceMode.ASPECTJ, proxyTargetClass=true)

If this works for your tests, but when you run your app it is failing, then you need separate configs for tests and for your app. Test config sets proxyTargetClass=true and the App config sets proxyTargetClass=false

For your NoClassDefFoundError error, we need to see the stacktrace. You probably haven't included the spring-aop library

Kingsize answered 7/11, 2012 at 2:55 Comment(0)
T
0

We had a similar issue concerning an Aspect and package-private classes. Our fix was pretty easy, since we could just make our package-private class public.

After that and more issues with cobertura, we moved to JaCoCo and are very happy with the result. By default, a maven build with cobertura is configured this way:

  • Run the tests
  • Instrument the code via cobertura
  • Run the tests again and measure the coverage

Since JaCoCo uses an agent instead of bytecode instrumentation for measuring the code coverage, the tests are only running once, making the build way faster.

See this link if you need more information about setting up JaCoCo.

Thatch answered 16/2, 2017 at 16:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.