在springboot多模块中, common模块有全局异常处理, A模块引用了common模块, 且A模块中有自己的全局异常处理, 在有些服务中是A中的全局异常处理生效, 有些服务中是common模块中的全局异常处理生效. 非常疑惑, 了解后写下此篇.
先加载的
@ControllerAdvice
类里如果存在
@ExceptionHandler(xxException.class)
是需要捕获的异常或其父类,则将使用先加载的类中的异常处理方式。如果没有,则看后面的
@ControllerAdvice
类里是否有。
可以使用
@Order
来决定加载优先级,网上也有说法可以使用
@Primary
,暂未自测,个人觉得应该也是可行的。
举例:
A类和B类都有
@ControllerAdvice
注解,要捕获的异常为自定义异常
CustomException
。
场景一
:A中有
@ExceptionHandler(CustomException.class)
,B中没有,但B中有
@ExceptionHandler(Exception.class)
,
-
若B加载顺序优先于A,则
CustomException
异常会被B处理,因为
Exception
是
CustomException
的父类。
-
若A加载顺序优先于B,则
CustomException
异常会被A处理。
场景二
:A中有
@ExceptionHandler(CustomException.class)
,B中没有,且B中没有任何
@ExceptionHandler()
是
CustomException
的父类
部分源码理解及分析
入口是
ExceptionHandlerExceptionResolver.doResolveHandlerMethodException
,这里面主要看
ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
方法,源码如下:
-
getExceptionHandlerMethod:
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
Class<?> handlerType = (handlerMethod != null ? handlerMethod.getBeanType() : null);
if (handlerMethod != null) {
ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
if (resolver == null) {
resolver = new ExceptionHandlerMethodResolver(handlerType);
this.exceptionHandlerCache.put(handlerType, resolver);
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
// 第一遍exceptionHandlerCache不会有值,会走到这个遍历里来
// exceptionHandlerAdviceCache, 结构为:LinkedHashMap<ControllerAdviceBean,ExceptionHandlerMethodResolver>(),注意是有序的
for (Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
if (entry.getKey().isApplicableToBeanType(handlerType)) {
ExceptionHandlerMethodResolver resolver = entry.getValue();
// 重点是这个方法,找到处理异常的方法返回,由上面入口执行异常处理
// 注意是调用的resolver.resolveMethod(),也就是resolver中的属性都能获取到
// 有个属性是后面要用到的,存储了@ControllerAdvice注解类的所有@ExceptionHandler方法:private final Map<Class<? extends Throwable>, Method> mappedMethods = new ConcurrentHashMap<Class<? extends Throwable>, Method>(16);
Method method = resolver.resolveMethod(exception);
// 这里只要method不为空就会返回,可以和前面例子中的顺序问题结合理解
if (method != null) {
return new ServletInvocableHandlerMethod(entry.getKey().resolveBean(), method);
return null;
public Method resolveMethod(Exception exception) {
// 这个方法进去看
Method method = resolveMethodByExceptionType(exception.getClass());
if (method == null) {
Throwable cause = exception.getCause();
if (cause != null) {
method = resolveMethodByExceptionType(cause.getClass());
return method;
- resolveMethodByExceptionType:
public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
Method method = this.exceptionLookupCache.get(exceptionType);
if (method == null) {
// 这个方法进去看
method = getMappedMethod(exceptionType);
this.exceptionLookupCache.put(exceptionType, (method != null ? method : NO_METHOD_FOUND));
return (method != NO_METHOD_FOUND ? method : null);
private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
List<Class<? extends Throwable>> matches = new ArrayList<Class<? extends Throwable>>();
// 遍历this.mappedMethods.keySet(),是上文中提到的存储了@ControllerAdvice注解类的所有@ExceptionHandler方法
for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
// class1.isAssignableFrom(class2):一个类Class1和另一个类Class2是否相同 或 Class1是否是Class2的超类或接口
if (mappedException.isAssignableFrom(exceptionType)) {
matches.add(mappedException);
// 如果存在则不会返空,也就是第一遍遍历如果有能处理异常的方法就会返回,不管是相同的异常类处理方法还是对其父类的处理方法
if (!matches.isEmpty()) {
// 排序, 如果当前@ControllerAdvice注解类中既存在相同异常类处理又存在父类异常处理,则会将相同异常类处理排在前面。这个排序没有深究,多个父类排序规则不清楚。
Collections.sort(matches, new ExceptionDepthComparator(exceptionType));
return this.mappedMethods.get(matches.get(0));
else {
return null;
- 总结:如果有多个
@ControllerAdvice
注解类,当第一个加载的注解类里有对需要捕获异常的相同类/父类有方法处理,就会使用第一个处理方法。可使用@Order
控制加载顺序。
上面处理流程是遍历exceptionHandlerAdviceCache
,故来看这个数据来源。
private void initExceptionHandlerAdviceCache() {
if (getApplicationContext() == null) {
return;
if (logger.isDebugEnabled()) {
logger.debug("Looking for exception mappings: " + getApplicationContext());
// 找到有@ControllerAdvice注解的类并排序,可以看到这里决定了上面的处理顺序,但加载排序未仔细看
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
AnnotationAwareOrderComparator.sort(adviceBeans);
for (ControllerAdviceBean adviceBean : adviceBeans) {
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(adviceBean.getBeanType());
if (resolver.hasExceptionMappings()) {
this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
if (logger.isInfoEnabled()) {
logger.info("Detected @ExceptionHandler methods in " + adviceBean);
if (ResponseBodyAdvice.class.isAssignableFrom(adviceBean.getBeanType())) {
this.responseBodyAdvice.add(adviceBean);
if (logger.isInfoEnabled()) {
logger.info("Detected ResponseBodyAdvice implementation in " + adviceBean);
-
个人理解哪个生效就是一个加载顺序的问题。启动类如果有@ComponentScan
注解,那么还有其他方法,但没有上述使用@Order
方法优雅。
- 使用
basePackageClasses
属性,将想要优先加载的包写在前面; - 使用
excludeFilters
属性排除不想要加载的类,该属性使用方式多样,可自行搜索查看。(例:@ComponentScan(excludeFilters = @ComponentScan.Filter( type = FilterType.ASSIGNABLE_TYPE, classes = xx.class))
)
-
关于全局异常处理的运用
全局异常处理代码:
@RestController
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public Result handle(Exception e) {
log.error("全局异常处理:", e);
return new Result<>(HttpStatus.INTERNAL_SERVER_ERROR.value(), "服务异常", null);
逻辑代码:
@Service
public class xxxServiceImpl {
private void xxxFunction(int xxId) {
// 省略逻辑代码, CustomException为自定义异常, 继承Exception
throw new CustomException("参数校验未通过");
背景在springboot多模块中, common模块有全局异常处理, A模块引用了common模块, 且A模块中有自己的全局异常处理, 在有些服务中是A中的全局异常处理生效, 有些服务中是common模块中的全局异常处理生效. 非常疑惑, 了解后写下此篇.简单描述先加载的@ControllerAdvice类里如果存在@ExceptionHandler(xxException.class)是需要捕获的异常或其父类,则将使用先加载的类中的异常处理方式。如果没有,则看后面的@ControllerAd
SpringBoot实现自定义异常+全局异常处理(多个异常处理类catch顺序)【详细步骤+图解】
Java虽然提供了丰富的异常处理类,但是在某些情况下还是不能满足实际需求。
这个时候就必须使用到自定义异常来进行处理,使用自定义异常处理程序错误对用户更加友好,同时对我们自己来说,也可以更加准确地定位到错误,更快地解决。
项目中难免会有被忽略错误,严重的可能会导致系统宕机,所以就需要全局异常处理器来捕获所有异常。
本文将通过几个简单步骤教大家如何实现自定义异常+全局异常处理,废话不多说,直接上步骤。
@Nullable
ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
HandlerExceptionResolverComposite组合了3个异常解析器
ExceptionH...
1、使用aop进行切面拦截异常
2、controller每个方法都用try-catch捕获异常
3、增加一个@RestControllerAdvice标注的类,负责处理我们项目的异常
一般放在一个类中就不会有这种情况了,而我用了两个类全局异常处理类和接口参数校验处理类
还有一种情况是一用力别人的模块,模块中是用了@RestControllerAdvice的类,
多个加了@RestControllerAdvice的类它们会按照类名依次加载,如果前面的类有能处
要在 Spring Boot 中实现全局异常处理,你可以这样做:
1. 定义一个异常处理类,并实现 Spring 的 `HandlerExceptionResolver` 接口或者使用 `@ControllerAdvice` 和 `@ExceptionHandler` 注解。
2. 在你的异常处理类中,通过使用 `@ExceptionHandler` 注解来声明你要处理的异常类型。
例如,假设你要处理所有类型的异常,你可以这样写:
```java
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(Exception ex) {
// 处理异常并返回响应
ErrorResponse error = new ErrorResponse();
error.setMessage(ex.getMessage());
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
在这个例子中,我们通过使用 `@ControllerAdvice` 注解声明了这个类是一个全局异常处理类,并使用 `@ExceptionHandler` 注解声明了要处理的异常类型是 `Exception`。你也可以声明多个 `@ExceptionHandler` 注解来处理不同类型的异常。
然后,你可以在你的应用程序中抛出异常,Spring 会自动调用你定义的异常处理方法来处理异常。
注意:如果你使用的是 Spring Boot 2.x 版本,你可以通过实现 `ErrorController` 接口来实现全局异常处理。