首先说结论,使用@controlleradvice配合@exceptionhandler处理全局controller的异常时,如果想要正确匹配自己的自定义异常,需要在controller的方法上抛出相应的自定义异常,或者自定义异常继承runtimeexception类。

问题描述:

1、在使用@controlleradvice配合@exceptionhandler处理全局异常时,自定义了一个appexception(extends exception),由于有些全局的参数需要统一验证,所以在所有controller的方法上加一层aop校验,如果参数校验没通过也抛出appexception

2、在@controlleradvice标记的类上,主要有两个@exceptionhandler,分别匹配appexception.class和throwable.class。

3、在测试时,由于全局aop的参数校验没通过,抛出了appexception,但是发现这个appexception被throwable.class匹配到了,而不是我们想要的appexception.class匹配上。

分析过程:

一阶段

开始由于一直测试的两个不同的请求(一个通过swagger,一个通过游览器地址输入,两个请求比较相似,我以为是同一个请求),一个方法上抛出了appexception,一个没有,然后发现这个问题时现时不现,因为无法稳定复现问题,我猜测可能是appexception出了问题,所以我修改了appexception,将其父类改为了runtimeexception,然后发现问题解决了

二阶段

问题解决后,我又思考了下为啥会出现这种情况,根据java的异常体系来说,无论是继承exception还是runtimeexception,都不应该会匹配到throwable.class上去。

我再次跟踪了异常的执行过程,粗略的过了一遍,发现在下面这个位置出现了差别:

catch (invocationtargetexception ex) {
            // unwrap for handlerexceptionresolvers ...
            throwable targetexception = ex.gettargetexception();
            if (targetexception instanceof runtimeexception) {
                throw (runtimeexception) targetexception;
            }
            else if (targetexception instanceof error) {
                throw (error) targetexception;
            }
            else if (targetexception instanceof exception) {
                throw (exception) targetexception;
            }
            else {
                string text = getinvocationerrormessage("failed to invoke handler method", args);
                throw new illegalstateexception(text, targetexception);
            }
        }

成功的走的是exception,失败的走的是runtimeexception。

这时候到了@controlleradvice标记的类时就会出问题了,因为继承appexception是和runtimeexception是平级,所以如果走runtimeexception这个判断条件抛出去的异常注定就不会被appexception匹配上。

这时候再仔细对比下异常类型,可以发现正确的那个异常类型时appexception,而错误的那个异常类型时java.lang.reflect.undeclaredthrowableexception,内部包着appexception。

jdk的java doc是这么解释undeclaredthrowableexception的:如果代理实例的调用处理程序的 invoke 方法抛出一个经过检查的异常(不可分配给 runtimeexception 或 error 的 throwable),且该异常不可分配给该方法的throws子局声明的任何异常类,则由代理实例上的方法调用抛出此异常。

因为appexception继承于exception,所以代理抛出的异常就是包着appexception的undeclaredthrowableexception,在@controlleradvice匹配的时候自然就匹配不上了。

而当appexception继承于runtimeexception时,抛出的异常依旧是appexception,所以能够被匹配上。

结论:所以解决方法有两种:appexception继承runtimeexception或者controller的方法抛出appexception异常。

spring的@exceptionhandler和@controlleradvice统一处理异常

之前敲代码的时候,避免不了各种try…catch,如果业务复杂一点,就会发现全都是try…catch

try{
    ..........
}catch(exception1 e){
    ..........
}catch(exception2 e){
    ...........
}catch(exception3 e){
    ...........
}

这样其实代码既不简洁好看 ,我们敲着也烦, 一般我们可能想到用拦截器去处理, 但是既然现在spring这么火,aop大家也不陌生, 那么spring一定为我们想好了这个解决办法.果然:

@exceptionhandler

源码

//该注解作用对象为方法
@target({elementtype.method})
//在运行时有效
@retention(retentionpolicy.runtime)
@documented
public @interface exceptionhandler {
 //value()可以指定异常类
    class<? extends throwable>[] value() default {};
}

@controlleradvice

源码

@target({elementtype.type})
@retention(retentionpolicy.runtime)
@documented
//bean对象交给spring管理生成
@component
public @interface controlleradvice {
    @aliasfor("basepackages")
    string[] value() default {};
    @aliasfor("value")
    string[] basepackages() default {};
    class<?>[] basepackageclasses() default {};
    class<?>[] assignabletypes() default {};
    class<? extends annotation>[] annotations() default {};
}

从名字上可以看出大体意思是控制器增强

所以结合上面我们可以知道,使用@exceptionhandler,可以处理异常, 但是仅限于当前controller中处理异常,

@controlleradvice可以配置basepackage下的所有controller. 所以结合两者使用,就可以处理全局的异常了.

一、代码

这里需要声明的是,这个统一异常处理类,也是基于controlleradvice,也就是控制层切面的,如果是过滤器抛出的异常,不会被捕获!!!

在@controlleradvice注解下的类,里面的方法用@exceptionhandler注解修饰的方法,会将对应的异常交给对应的方法处理。

@exceptionhandler({ioexception.class})
public result handleexception(ioexceptione) {
    log.error("[handleexception] ", e);
    return resultutil.failuredefaulterror();
  }

比如这个,就是捕获io异常并处理。

废话不多说,代码:

package com.zgd.shop.core.exception;
import com.zgd.shop.core.error.errorcache;
import com.zgd.shop.core.result.result;
import com.zgd.shop.core.result.resultutil;
import lombok.extern.slf4j.slf4j;
import org.apache.commons.lang3.stringutils;
import org.springframework.http.httpstatus;
import org.springframework.http.converter.httpmessagenotreadableexception;
import org.springframework.validation.bindexception;
import org.springframework.validation.bindingresult;
import org.springframework.validation.fielderror;
import org.springframework.web.httpmediatypenotsupportedexception;
import org.springframework.web.httprequestmethodnotsupportedexception;
import org.springframework.web.bind.methodargumentnotvalidexception;
import org.springframework.web.bind.missingservletrequestparameterexception;
import org.springframework.web.bind.annotation.controlleradvice;
import org.springframework.web.bind.annotation.exceptionhandler;
import org.springframework.web.bind.annotation.responsebody;
import org.springframework.web.bind.annotation.responsestatus;
import org.springframework.web.method.annotation.methodargumenttypemismatchexception;
import javax.validation.constraintviolation;
import javax.validation.constraintviolationexception;
import javax.validation.validationexception;
import java.util.set;
/**
* globalexceptionhandle
* 全局的异常处理
*
* @author zgd
* @date 2019/7/19 11:01
*/
@controlleradvice
@responsebody
@slf4j
public class globalexceptionhandle {
/**
* 请求参数错误
*/
private final static string base_param_err_code = "base-param-01";
private final static string base_param_err_msg = "参数校验不通过";
/**
* 无效的请求
*/
private final static string base_bad_request_err_code = "base-param-02";
private final static string base_bad_request_err_msg = "无效的请求";
/**
* 顶级的异常处理
*
* @param e
* @return
*/
@responsestatus(httpstatus.ok)
@exceptionhandler({exception.class})
public result handleexception(exception e) {
log.error("[handleexception] ", e);
return resultutil.failuredefaulterror();
}
/**
* 自定义的异常处理
*
* @param ex
* @return
*/
@responsestatus(httpstatus.ok)
@exceptionhandler({bizserviceexception.class})
public result serviceexceptionhandler(bizserviceexception ex) {
string errorcode = ex.geterrcode();
string msg = ex.geterrmsg() == null ? "" : ex.geterrmsg();
string innererrmsg;
string outererrmsg;
if (base_param_err_code.equalsignorecase(errorcode)) {
innererrmsg = "参数校验不通过:" + msg;
outererrmsg = base_param_err_msg;
} else if (ex.isinnererror()) {
innererrmsg = errorcache.getinternalmsg(errorcode);
outererrmsg = errorcache.getmsg(errorcode);
if (stringutils.isnotblank(msg)) {
innererrmsg = innererrmsg + "," + msg;
outererrmsg = outererrmsg + "," + msg;
}
} else {
innererrmsg = msg;
outererrmsg = msg;
}
log.info("【错误码】:{},【错误码内部描述】:{},【错误码外部描述】:{}", errorcode, innererrmsg, outererrmsg);
return resultutil.failure(errorcode, outererrmsg);
}
/**
* 缺少servlet请求参数抛出的异常
*
* @param e
* @return
*/
@responsestatus(httpstatus.bad_request)
@exceptionhandler({missingservletrequestparameterexception.class})
public result handlemissingservletrequestparameterexception(missingservletrequestparameterexception e) {
log.warn("[handlemissingservletrequestparameterexception] 参数错误: " + e.getparametername());
return resultutil.failure(base_param_err_code, base_param_err_msg);
}
/**
* 请求参数不能正确读取解析时,抛出的异常,比如传入和接受的参数类型不一致
*
* @param e
* @return
*/
@responsestatus(httpstatus.ok)
@exceptionhandler({httpmessagenotreadableexception.class})
public result handlehttpmessagenotreadableexception(httpmessagenotreadableexception e) {
log.warn("[handlehttpmessagenotreadableexception] 参数解析失败:", e);
return resultutil.failure(base_param_err_code, base_param_err_msg);
}
/**
* 请求参数无效抛出的异常
*
* @param e
* @return
*/
@responsestatus(httpstatus.bad_request)
@exceptionhandler({methodargumentnotvalidexception.class})
public result handlemethodargumentnotvalidexception(methodargumentnotvalidexception e) {
bindingresult result = e.getbindingresult();
string message = getbindresultmessage(result);
log.warn("[handlemethodargumentnotvalidexception] 参数验证失败:" + message);
return resultutil.failure(base_param_err_code, base_param_err_msg);
}
private string getbindresultmessage(bindingresult result) {
fielderror error = result.getfielderror();
string field = error != null ? error.getfield() : "空";
string code = error != null ? error.getdefaultmessage() : "空";
return string.format("%s:%s", field, code);
}
/**
* 方法请求参数类型不匹配异常
*
* @param e
* @return
*/
@responsestatus(httpstatus.bad_request)
@exceptionhandler({methodargumenttypemismatchexception.class})
public result handlemethodargumenttypemismatchexception(methodargumenttypemismatchexception e) {
log.warn("[handlemethodargumenttypemismatchexception] 方法参数类型不匹配异常: ", e);
return resultutil.failure(base_param_err_code, base_param_err_msg);
}
/**
* 请求参数绑定到controller请求参数时的异常
*
* @param e
* @return
*/
@responsestatus(httpstatus.bad_request)
@exceptionhandler({bindexception.class})
public result handlehttpmessagenotreadableexception(bindexception e) {
bindingresult result = e.getbindingresult();
string message = getbindresultmessage(result);
log.warn("[handlehttpmessagenotreadableexception] 参数绑定失败:" + message);
return resultutil.failure(base_param_err_code, base_param_err_msg);
}
/**
* javax.validation:validation-api 校验参数抛出的异常
*
* @param e
* @return
*/
@responsestatus(httpstatus.bad_request)
@exceptionhandler({constraintviolationexception.class})
public result handleserviceexception(constraintviolationexception e) {
set<constraintviolation<?>> violations = e.getconstraintviolations();
constraintviolation<?> violation = violations.iterator().next();
string message = violation.getmessage();
log.warn("[handleserviceexception] 参数验证失败:" + message);
return resultutil.failure(base_param_err_code, base_param_err_msg);
}
/**
* javax.validation 下校验参数时抛出的异常
*
* @param e
* @return
*/
@responsestatus(httpstatus.bad_request)
@exceptionhandler({validationexception.class})
public result handlevalidationexception(validationexception e) {
log.warn("[handlevalidationexception] 参数验证失败:", e);
return resultutil.failure(base_param_err_code, base_param_err_msg);
}
/**
* 不支持该请求方法时抛出的异常
*
* @param e
* @return
*/
@responsestatus(httpstatus.method_not_allowed)
@exceptionhandler({httprequestmethodnotsupportedexception.class})
public result handlehttprequestmethodnotsupportedexception(httprequestmethodnotsupportedexception e) {
log.warn("[handlehttprequestmethodnotsupportedexception] 不支持当前请求方法: ", e);
return resultutil.failure(base_bad_request_err_code, base_bad_request_err_msg);
}
/**
* 不支持当前媒体类型抛出的异常
*
* @param e
* @return
*/
@responsestatus(httpstatus.unsupported_media_type)
@exceptionhandler({httpmediatypenotsupportedexception.class})
public result handlehttpmediatypenotsupportedexception(httpmediatypenotsupportedexception e) {
log.warn("[handlehttpmediatypenotsupportedexception] 不支持当前媒体类型: ", e);
return resultutil.failure(base_bad_request_err_code, base_bad_request_err_msg);
}
}

至于返回值,就可以理解为controller层方法的返回值,可以返回@responsebody,或者页面。我这里是一个@responsebody的result<>,前后端分离。

我们也可以自己根据需求,捕获更多的异常类型。

包括我们自定义的异常类型。比如:

package com.zgd.shop.core.exception;
import lombok.data;
/**
* bizserviceexception
* 业务抛出的异常
* @author zgd
* @date 2019/7/19 11:04
*/
@data
public class bizserviceexception extends runtimeexception{
private string errcode;
private string errmsg;
private boolean isinnererror;
public bizserviceexception(){
this.isinnererror=false;
}
public bizserviceexception(string errcode){
this.errcode =errcode;
this.isinnererror = false;
}
public bizserviceexception(string errcode,boolean isinnererror){
this.errcode =errcode;
this.isinnererror = isinnererror;
}
public bizserviceexception(string errcode,string errmsg){
this.errcode =errcode;
this.errmsg = errmsg;
this.isinnererror = false;
}
public bizserviceexception(string errcode,string errmsg,boolean isinnererror){
this.errcode =errcode;
this.errmsg = errmsg;
this.isinnererror = isinnererror;
}
}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持www.887551.com。