JUnit, @ControllerAdvice и отсутствие проверенных исключений в Kotlin
Я написал советник по валидации в Котлине, который бросает EntityValidationException
когда проверка не проходит:
@Aspect
@Named
class ValidationAdvisor
@Inject constructor(val validator: EntityValidator) {
@Around(EVERY_SAVE_AND_UPDATE_TO_DATABASE)
fun validate(point: ProceedingJoinPoint): Any {
val result: List<ConstraintViolation<Any>> = validator.validate(getEntity(point))
if (isEntityValid(result))
return point.proceed()
throw EntityValidationException(
violationInfos = result as List<ConstraintViolationInfo>
)
}
private fun getEntity(point: ProceedingJoinPoint): Any {
return point.args[0]
}
private fun isEntityValid(result: List<ConstraintViolation<Any>>): Boolean {
return result.isEmpty()
}
companion object {
const val EVERY_SAVE_AND_UPDATE_TO_DATABASE = "execution(* beans.repositories.BaseRepository.save(..))"
}
}
У меня есть тест JUnit (я пишу некоторые части на Kotlin и некоторые на Java для изучения Kotlin в существующем частном проекте), который проверяет, было ли выброшено исключение:
@Test(expected = EntityValidationException.class)
public void test_WhenNameIsNotPresent_ThenExceptionIsThrown() throws Exception {
repository.save(/* entity to save */);
}
Но потом я увидел, что тест не пройден, потому что он генерирует UndeclaredThrowableException вместо EntityValidationException
, Далее я прочитал, что у Котлина нет проверенного исключения. Я думаю, что не только у меня есть эта проблема - я уверен, что способ обработки исключений с помощью @ControllerAdvice в Spring и Java также может быть проблематичным, когда мы решим переключиться на Kotlin в производственных проектах - потому что он опирается на класс, который выдается, и бросание UndeclaredThrowableException
отними у нас эту возможность. Есть ли способ обойти это поведение, чтобы убедиться, что возможно переключиться на Kotlin и старый Spring способ обработки исключений (и модульные тесты для ValidationAdvisor
) останется без изменений? Спасибо заранее за любую помощь.
PS Github репо для проекта доступно здесь: Playgroud для Kotlin, Spring Data и т. Д.
PS2 Stacktrace:
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:51)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:237)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
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:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
Caused by: java.lang.reflect.UndeclaredThrowableException
at com.sun.proxy.$Proxy65.save(Unknown Source)
at ActorValidationTest.test_WhenNameIsNotPresent_ThenExceptionIsThrown(ActorValidationTest.java:26)
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:497)
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)
... 26 more
Caused by: beans.validator.exceptions.EntityValidationException
at beans.aop.validation.ValidationAdvisor.validate(ValidationAdvisor.kt:23)
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:497)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:629)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:618)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
... 37 more
Для меня выглядит EntityValidationException
(который я хочу бросить) причины UndeclaredThrowableException
, EntityValidationException
Код класса Kotlin:
package beans.validator.exceptions
import beans.validator.constraintViolation.ConstraintViolationInfo
class EntityValidationException (val violationInfos: List<ConstraintViolationInfo>) : Exception() {
}
PS3 это тоже не сработало:
@Around(EVERY_SAVE_AND_UPDATE_TO_DATABASE)
@Throws(EntityValidationException::class)
fun validate(point: ProceedingJoinPoint): Any /* rest of code*/
PS4 Может проблема в том что @Aspect
является @Around
Spring Data репозиторий?
companion object {
const val EVERY_SAVE_AND_UPDATE_TO_DATABASE = "execution(* beans.repositories.BaseRepository.save(..))"
}
....
@NoRepositoryBean
public interface BaseRepository<ENTITY, IDENTIFIER extends Serializable>
extends JpaRepository<ENTITY, IDENTIFIER>, JpaSpecificationExecutor<ENTITY> {
}
1 ответ
Вы пытаетесь сделать метод save()
или ваш репозиторий генерирует проверенное исключение, которое он не объявляет. Поэтому, даже если аспект заявляет об этом, это не может работать. Вы должны расширить свое исключение RuntimeException
,