问题
I'm using @SneakyThrows Lombok feature in my SpringBoot project.
I have problems with this feature when CGLIB proxies implementation
it throws
java.lang.Exception: Unexpected exception,
expected but was<java.lang.reflect.UndeclaredThrowableException>.
Can it be fixed somehow ?
Providing examples.
There is interface and two implementations.
public interface SneakyThrowsExample {
void testSneakyThrows();
}
Simple implementation
import lombok.SneakyThrows;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component(value = "simpleSneakyThrowsExample")
public class SimpleSneakyThrowsExample implements SneakyThrowsExample {
@Override
@SneakyThrows
public void testSneakyThrows() {
throw new IOException();
}
}
And @Transactional implementations. CGLIB will proxy this implementation.
import lombok.SneakyThrows;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
@Component(value = "transactionalSneakyThrowsExample")
public class TransactionalSneakyThrowsExample implements SneakyThrowsExample {
@Override
@SneakyThrows
@Transactional
public void testSneakyThrows() {
throw new IOException();
}
}
Create @SpringBootTest test and inject these 2 implementation
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.io.IOException;
@RunWith(SpringRunner.class)
@SpringBootTest
public class DefaultSneakyThrowsExampleTest {
@Autowired
@Qualifier(value = "transactionalSneakyThrowsExample")
SneakyThrowsExample transactionalSneakyThrowsExample;
@Autowired
@Qualifier(value = "simpleSneakyThrowsExample")
SneakyThrowsExample simpleSneakyThrowsExample;
@Test(expected = IOException.class)
public void testSneakyThrowsSimple() throws Exception {
this.simpleSneakyThrowsExample.testSneakyThrows();
}
@Test(expected = IOException.class)
public void testSneakyThrowsTransactional() throws Exception {
this.transactionalSneakyThrowsExample.testSneakyThrows();
}
}
Test testSneakyThrowsTransactional fails with error
java.lang.Exception: Unexpected exception, expected<java.io.IOException> but was<java.lang.reflect.UndeclaredThrowableException>
at org.junit.internal.runners.statements.ExpectException.evaluate(ExpectException.java:28)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: java.lang.reflect.UndeclaredThrowableException
at fine.project.TransactionalSneakyThrowsExample$$EnhancerBySpringCGLIB$$57df642e.testSneakyThrows(<generated>)
at fine.project.DefaultSneakyThrowsExampleTest.testSneakyThrowsTransactional(DefaultSneakyThrowsExampleTest.java:35)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.ExpectException.evaluate(ExpectException.java:19)
... 20 more
Caused by: java.io.IOException
at fine.project.TransactionalSneakyThrowsExample.testSneakyThrows(TransactionalSneakyThrowsExample.java:21)
at fine.project.TransactionalSneakyThrowsExample$$FastClassBySpringCGLIB$$e5429d83.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:738)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:673)
... 31 more
回答1:
When you use @Transactional then Spring will create a proxy for your bean via AOP proxies - Spring Framework’s declarative transaction
UndeclaredThrowableException cause:
Thrown by a method invocation on a proxy instance if its invocation handler's invoke method throws a checked exception (a Throwable that is not assignable to RuntimeException or Error) that is not assignable to any of the exception types declared in the throws clause of the method that was invoked on the proxy instance and dispatched to the invocation handler.
Lombock @SneakyThrows:
Can be used to sneakily throw checked exceptions without actually declaring this in your method's throws clause
It means that your TransactionalSneakyThrowsExample.testSneakyThrows()
throws checked exception (that undeclared in the throws
in method signature) it's illegal behavior when instance wrapped in proxy
In this case you can change expected exception to the Exception.class
:
@Test(expected = Exception.class)
public void testSneakyThrowsTransactional() throws Exception {
this.transactionalSneakyThrowsExample.testSneakyThrows();
}
or you can use ExpectedException.expectCause()
to cheack IOException.class
in your test, take a look at JUnit expect a wrapped exception
来源:https://stackoverflow.com/questions/47017259/spring-lombok-sneakythrows