为什么Kotlin收到这样的UndeclaredThrowableException而不是ParseException?

我有一个extension方法,将字符串转换为Kotlin中的日期。

 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") } 

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

=== 更新 ===

我正在开发一个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] clcrest.AttendanceRestController : Exception occur 2017-06-28 02:52:32.485 INFO 2796 --- [io-8080-exec-10] clcrest.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 

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

UndeclaredThrowableException的文档说:

如果其调用处理程序的invoke方法抛出检查的异常,则通过代理实例的方法调用来抛出

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

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

在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(){/**/} 

或者allopen一样写一些gradle-plugin,我把它命名为allthrows here。

但是你也可以做出一些妥协。 如果你不确定像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); } } 

看起来你的服务运行在与你的控制器不同的调用环境中。 由于该服务抛出异常,你不能在控制器中捕捉到它; 它看起来像你直接调用服务,但由于代码注入你真的不是。 所以会发生什么是调用上下文(通常是一个线程)以异常结束。 这被翻译成一个UndeclaredThrowableException与原始异常作为原因。

处理这个问题有两种方法:

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

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