9 June 2013

CodeGenerationException and proxies

Recently I saw one of tests failed with the message:
Exception of type java.lang.IllegalStateException expected but was not thrown. 
Instead an exception of type class org.mockito.cglib.core.CodeGenerationException 
with message 'java.lang.reflect.InvocationTargetException-->null' was thrown.
But let's start from the beginning. We have a spring web application that contains only 2 classes. First one is a standard session scoped component:
@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MySessionComponent {
 
    public void doError() {
        throw new IllegalStateException(); 
    }

    public void doNothing() {}
}
Nothing new, right? Second component is a standard singleton:
@Component
public class MySingleton {
 
    @Autowired MySessionComponent mySessionComponent;

    public void sampleAction() {
        mySessionComponent.doNothing();
    }
}
That's the whole application. Now let's test it.
import org.junit.Test;
import static com.googlecode.catchexception.apis.CatchExceptionBdd.*;

public class MySessionComponentTest {

    @Test
    public void test() {
        when(new MySessionComponent()).doError();
        thenThrown(IllegalStateException.class);
    }
}
I use catch exception v1.0.4 and test passes. Now, let's do an integration test:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class MySingletonTest {
 
    @Configuration @ComponentScan static class TestAppContext {} // just registers 2 components
 
    @Autowired MySingleton myController;
 
    @Test
    public void test() {
        myController.sampleAction();
    }
}
All tests pass. And now, let's suppose that we need, for whatever reason, to combine those tests:
@Test
public void test() {
    myController.sampleAction();

    when(new MySessionComponent()).doError();
    thenThrown(IllegalStateException.class);
}
And we get the exception. WTF? After some time spent with debugger I found InvocationTargetException thrown inside org.mockito.cglib.core.AbstractClassGenerator. The problem is there is no cause nor detailed message and therefore it's not propagated anywhere so you can't find the real reason in any logs. However this exception has target field and there we can find:
java.lang.LinkageError: loader (instance of  sun/misc/Launcher$AppClassLoader): 
attempted  duplicate class definition for name: "MySessionComponent$$FastClassByCGLIB$$441a78f3"
At first spring creates proxy for MySessionComponent in order to autowire beans with different scopes. Then catch-exception tries to create proxy for the same class. It seems that both frameworks generates the same name for the class and two classes with same name are not allowed within one classloader.

When we change the order of method invocations (the order of creating proxies)
@Test
public void deleteEbook() {
    when(new MySessionComponent()).doError();
    thenThrown(IllegalStateException.class);

    myController.sampleAction();
}
spring throws an exception but now you can see the real cause in the stacktrace:
org.springframework.cglib.core.CodeGenerationException: java.lang.reflect.InvocationTargetException-->null
 at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:237)
...
Caused by: java.lang.LinkageError: loader (instance of  sun/misc/Launcher$AppClassLoader): attempted  duplicate class definition for name: "MySessionComponent$$FastClassByCGLIB$$441a78f3"
Btw, this behavior is strange because cglib claims it can detect name clashes. Maybe it's about repackaging cglib in almost every framework?

1 komentarze :

Unknown said...

I've upload a fix for this case that has been recently uploaded in catch-exception version 1.1.0. :)

Post a comment