Why Kotlin receives such an UndeclaredThrowableException rather than a ParseException?

前端 未结 2 1502
生来不讨喜
生来不讨喜 2021-02-19 06:32

I have an extension method that converts string to Date in Kotlin.

fun String.convertToDate() : Date {
  var pattern: String = \"dd-mm-yyyy\"
  val         


        
相关标签:
2条回答
  • 2021-02-19 07:11

    The UndeclaredThrowableException is caused by kotlin. why? we know kotlin does not have checked exceptions.

    The documentation of UndeclaredThrowableException says:

    Thrown by a method invocation on a proxy instance if its invocation handler's invoke method throws a checked exception

    On the other hand, Kotlin can throw any exceptions but in java them will are: unchecked/checked exception and error. we know almost all of the popular frameworks is created base on java.reflect package includes java.reflect.Proxy.

    In short, when the kotlin function throw a java checked exception and don't declare the exception that it will maybe throwing. then call a java Proxy you maybe receive such a UndeclaredThrowableException.

    In java you can declare a checked-exception will be throwing as below:

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

    Thanks for @glee8e to points my mistake. you also can throws an exception in kotlin, since kotlin don't have throws keyword so you must using @Throws to declare an exception will be throwing:

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

    let's reproduce the UndeclaredThrowableException in kotlin:

    //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
    ));
    

    How to avoiding this problem?

    If the function is wrote by yourself the solution is so simpler. yes, declare the function will be throwing a checked exception. for example:

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

    OR write some gradle-plugin like as allopen, I named it allthrows here.

    But you also can make some compromises. If you are not sure what will be happens in the frameworks like as spring, you should wrap your invocation into a helper method. for example:

    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);
        }
    }
    
    0 讨论(0)
  • 2021-02-19 07:17

    It seems that your service is running in a different invocation context than your controller. As the service is throwing the exception you cannot catch it in the controller; it looks like you're calling the service directly, but due to code injection you really aren't. So what happens is that the invocation context (usually a thread) ends with an exception. This gets translated into a UndeclaredThrowableException with the original exception as cause.

    There are two ways of dealing with this:

    1. catch the exception locally in the service where the exception is generated;
    2. catch the UndeclaredThrowableException in a separate try/catch and then re-throw the cause.

    The first option should be preferred but requires you to handle the exception in the service. The second one looks too much like a hack to me, but it doesn't require setting up the exception handling in the service instead of the controller.

    0 讨论(0)
提交回复
热议问题