为什么 Kotlin 会收到这样一个 UndeclaredThrowableException 而不是 ParseException?

2022-09-04 01:30:04

我有一个在Kotlin中将字符串转换为Date的方法。extension

fun String.convertToDate() : Date {
  var pattern: String = "dd-mm-yyyy"
  val dateFormatter = SimpleDateFormat(pattern)
  return dateFormatter.parse(this) // parse method throw ParseException
}

这就是我试图捕获可能的异常的代码。

    try {
        "22---2017".convertToDate()
    } catch (ex: ParseException) {
        // ParseException supposed to be caught in this block

        logger.error("Parse exception occur")      
    } catch (ex: Exception) {

        // ParseException caught in this block

        logger.error("Exception occur")            
    }

在最后一个块中捕获的,即被捕获的位置。但是它应该被捕获在ParseException块中吗?我在这里错过了什么?ParseExceptionException

===更新===

我正在开发一个Spring MVC项目。我已经在简单的独立kotlin程序中运行了代码,它的行为相应地。但在我的春季项目中,它的行为有所不同。我给出了完整的代码和层。ControllerService

控制器

@PostMapping
@PreAuthorize("hasAnyRole('USER','ROLE_USER','ROLE_ADMIN','ADMIN')")
fun postAttendance(@RequestBody attendanceJson: AttendanceJsonWrapper,
                   request: HttpServletRequest): ResponseEntity<*> {
    val organization = getOrganizationFromSession(request)
    try {
        val attendanceBook: AttendanceBook = attendanceService.post(attendanceJson, organization.id!!)
        logger.info("Post successfully attendance book {}", attendanceBook)
    } catch (ex: SameDateAttendanceException) {
        logger.error("Duplicate attendance entry found at date [{}]", attendanceJson.date, attendanceJson.classId)
        return responseConflict(attendanceJson)
    } catch (ex: java.text.ParseException) {
        ex.printStackTrace()
        logger.error("Parse exception occur")
        return responseError(ErrorObject(
                attendanceJson,
                "date",
                Constants.INVALID_DATE_FORMAT,
                Constants.EXPECTED_DATE_FORMAT))
    } catch (ex: Exception) {
        ex.printStackTrace()
        logger.error("Exception occur")
        if (ex.cause is ParseException) {
            logger.info("What the hell is happening")
        }
    return responseOK(attendanceJson)
}

服务

@Service
open class AttendanceService constructor(val attendanceRepository: AttendanceRepository) {
@Transactional
open fun post(attendanceJsonWrapper: AttendanceJsonWrapper, orgId: Long): AttendanceBook {
    // ParseException should thrown from this line.
    val _attendanceDate = attendanceJsonWrapper.date.convertToDate()
    // Other logic goes here
    return attendanceRepository.save(attendanceBook)
}
}

日志

对于从前端生成的日志中给定的输入。28--2017

2017-06-28 02:36:52.942 ERROR 4632 --- [nio-8080-exec-1] c.l.c.rest.AttendanceRestController      : Exception occur
2017-06-28 02:52:32.485  INFO 2796 --- [io-8080-exec-10] c.l.c.rest.AttendanceRestController      : What the hell is happening

例外

java.lang.reflect.UndeclaredThrowableException
at com.lynas.service.AttendanceService$$EnhancerBySpringCGLIB$$7b42c004.post(<generated>)
at com.lynas.controller.rest.AttendanceRestController.postAttendance(AttendanceRestController.kt:34)
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.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:661)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.boot.web.filter.ApplicationContextHeaderFilter.doFilterInternal(ApplicationContextHeaderFilter.java:55)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.boot.actuate.trace.WebRequestTraceFilter.doFilterInternal(WebRequestTraceFilter.java:110)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:317)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:127)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:91)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:114)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:170)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:200)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:124)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:214)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:177)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:105)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.boot.actuate.autoconfigure.MetricsFilter.doFilterInternal(MetricsFilter.java:106)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:478)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:80)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:799)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1455)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.text.ParseException: Unparseable date: "28--2017"
at java.text.DateFormat.parse(DateFormat.java:366)
at com.lynas.util.UtilKt.convertToDate(Util.kt:56)
at com.lynas.service.AttendanceService.post(AttendanceService.kt:23)
at com.lynas.service.AttendanceService$$FastClassBySpringCGLIB$$2b941a4b.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)
... 103 more

答案 1

是由科特林引起的。为什么?我们知道 kotlin 没有检查异常。UndeclaredThrowableException

UndeclaredThrowableException的文档说:

如果代理实例上的方法调用的调用方法引发已检验的异常,则由代理实例上的方法调用引发

另一方面,Kotlin 可以抛出任何异常,但在 java 中,它们是:未选中/选中的异常和错误。我们知道几乎所有流行的框架都是基于包含java.reflect.Proxy的软件包创建的。java.reflect

简而言之,当 kotlin 函数抛出一个 java 检查异常,并且不声明它可能会引发的异常时。然后调用java代理,你可能会收到这样一个未声明的可清除异常

在java中,您可以声明将引发一个已检查的异常,如下所示:

//                v--- it is a checked exception in java
int read() throws IOException{/**/}

感谢您@glee8e指出我的错误。你也可以在kotlin中抛出一个异常,因为kotlin没有关键字,所以你必须用来声明一个异常将抛出:throws@Throws

@Throws(IOException::class)
fun read():Int{/**/}

让我们在 kotlin 中重现 UndeclaredThrowableException

//throws a UndeclaredThrowableException takes the original IOException as its cause
// because java.lang.Runnable don't declare any checked exception at all
//                                                      |
//                                                      v
Runnable::class.proxying(::throwsAJavaCheckedException).run()


// throws the original IOException directly because java.util.concurrent.Callable
// has declared that it will be throwing a checked Exception
//                                                      |
//                                                      v
Callable::class.proxying(::throwsAJavaCheckedException).call()

fun throwsAJavaCheckedException(proxy:Any, method:Method, args:Array<Any>?): Any? {
    throw IOException();
}

typealias Invocation = (Any, Method, Array<Any>?) -> Any?;

fun <T:Any> KClass<T>.proxying(handler:Invocation) = cast(Proxy.newProxyInstance(
        ClassLoader.getSystemClassLoader(),
        arrayOf(java),
        handler
));

如何避免这个问题?

如果函数是自己编写的,则解决方案非常简单。是的,声明该函数将引发已检查的异常。例如:

@Throws(ParseException::class)
fun convertToDate(){/**/}

或者写一些像,我在这里命名的 gradle 插件。allopenallthrows

但你也可以做出一些妥协。如果你不确定在像spring这样的框架中会发生什么,你应该把你的调用包装成一个帮助器方法。例如:

val task = Runnable::class.proxying(::throwsAJavaCheckedException)

//  v-- the result return by catch-block immediately if no exception occurs
val result : Unit = catch(task::run).by { actual: Throwable ->
    val exceptional: Unit = Unit;
    //    v--- you can choose return an exceptional value or rethrow the exception
    when (actual) {
        is RuntimeException -> exceptional
        is ParseException -> logger.info(acutal)
        else -> throw actual
    }
}


val result : Unit? = catch(task::run).only { actual:Throwable ->
// only handle the exception don't return the exceptional value
    logger.info(actual);
}

inline fun <T> catch(crossinline block: () -> T): () -> T {
    return { block(); };
}

inline fun <T> (() -> T).by(exceptionally: (Throwable) -> T): T {
    return only { exceptionally(it) }!!
}

inline fun <T : R, R> (() -> T).only(exceptionally: (Throwable) -> R): R? {
    try {
        return invoke();
    } catch(e: UndeclaredThrowableException) {
        return exceptionally(e.cause ?: e);
    } catch(e: Exception) {
        return exceptionally(e);
    }
}

答案 2

您的服务似乎在与控制器不同的调用上下文中运行。由于服务正在引发异常,因此您无法在控制器中捕获它;看起来你直接调用服务,但由于代码注入,你真的不是。因此,发生的事情是调用上下文(通常是线程)以异常结束。这被翻译成 a,以原始异常作为原因。UndeclaredThrowableException

有两种方法可以解决这个问题:

  1. 在生成异常的服务中本地捕获异常;
  2. 在单独的尝试/捕获中捕获,然后重新抛出原因。UndeclaredThrowableException

第一个选项应该是首选,但要求您处理服务中的异常。第二个对我来说太像一个黑客,但它不需要在服务中而不是控制器中设置异常处理。


推荐