前言
“@全体成员 数据库的一切异常都不能直接暴露给前端” springboot的全局异常处理 可通过
@ControllerAdivce/@RestControllerAdvice来实现。使用之前先看其原理。
今天就从从源码的角度分析 @ControllerAdvice 的实现全局异常捕捉的过程,帮助开发者更深入地理解其工作机制。
@ControllerAdvice 概述
@ControllerAdvice 是一个注解,用于定义全局的控制器建议。它可以用于处理所有控制器抛出的异常、进行全局数据绑定以及提供全局模型属性。
实际开发中基本就是用来全局的异常处理。现在一般用@RestControllerAdvice实现上基本一样,只是返回结果都以json数据格式返回,符合前后端开发规范。
使用demo
实际开发中用得比较多的就是捕获全局异常,当然
Controller层也可以定义自己的异常。Controller级别的异常处理优先级高于全局异常处理。
1.定义全局异常异常处理
/**
*
**/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 处理自定义异常
*/
@ExceptionHandler(BizDataException.class)
public ResponseVO<?> handleBizDataException(BizDataException e){
log.error(e.getMessage(), e);
return ResponseVO.error(e.getMessage());
}
}
- 定义
Controller级别的异常
@RestController
@RequestMapping("/admin/api")
public class CommController {
@GetMapping(value = "/query")
public ResponseVO<?> query(@RequestParam(required = false) String name) {
return ResponseVO.ok(service.query(name));
}
/**
* 处理自定义异常
*/
@ExceptionHandler(BizDataException.class)
public ResponseVO<?> handleBizDataException(BizDataException e){
log.error(e.getMessage(), e);
return ResponseVO.error(e.getMessage());
}
}
核心类与注解
在 Spring 的实现中,几个关键类和注解共同构成了 @ControllerAdvice 的功能:
@ControllerAdvice: 注解本身,标记类为全局控制器建议。@ExceptionHandler标记处理异常方法ExceptionHandlerExceptionResolver遍历@ControllerAdvice类 初始化异常处理器ExceptionHandlerMethodResolverExceptionHandlerMethodResolver: 负责从@ControllerAdvice注解的类中解析异常处理方法。
实现过程
实现过程主要分为两流程,异常处器的加载初始化。然后就是异常的捕捉,以及调用对应的处理器;
加载流程如下图:
异常捕获流程:
初始化异常解析器
1.当 Spring 容器启动时,它会扫描所有带有 @ControllerAdvice 注解的类。具体而言,AnnotationConfigServletWebServerApplicationContext 会通过 ClassPathBeanDefinitionScanner 扫描类路径,以发现这些注解。
2.遍历所有@ControllerAdvice标记的Bean,对每个Bean封装成ExceptionHandlerMethodResolver 放入exceptionHandlerAdviceCache Map 中。
exceptionHandlerAdviceCache 这个map非常重要,后面处理异常就会遍历该map 找到对应异常的处理方法
初始化 ExceptionHandlerMethodResolver的时候,就会去解析@ControllerAdvice标记的Bean里面的 异常处理方法封装,放到mappedMethods Map 中。
第2步的处理入口就是ExceptionHandlerExceptionResolver类,这个类实现了InitializingBean#afterPropertiesSet接口
public void afterPropertiesSet() {
// 将advice 标记类初始化成 异常处理器
this.initExceptionHandlerAdviceCache();
List handlers;
if (this.argumentResolvers == null) {
handlers = this.getDefaultArgumentResolvers();
this.argumentResolvers = (new HandlerMethodArgumentResolverComposite()).addResolvers(handlers);
}
if (this.returnValueHandlers == null) {
handlers = this.getDefaultReturnValueHandlers();
this.returnValueHandlers = (new HandlerMethodReturnValueHandlerComposite()).addHandlers(handlers);
}
}
initExceptionHandlerAdviceCache 实现
关键的代码都进行了注释,如下:
private void initExceptionHandlerAdviceCache() {
if (this.getApplicationContext() != null) {
// 通过上下文,找到ControllerAdviceBean
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(this.getApplicationContext());
Iterator var2 = adviceBeans.iterator();
while(var2.hasNext()) {
ControllerAdviceBean adviceBean = (ControllerAdviceBean)var2.next();
Class<?> beanType = adviceBean.getBeanType();
if (beanType == null) {
throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
}
// 将一全局 处理异常的bean 封装成 resovler;并且会把bean 中 方法 和 方法对应的异常进行映射。
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
if (resolver.hasExceptionMappings()) {
// 将 adviceBean 和对应的解析器 放到map中
this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
}
if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
// 放进list中
this.responseBodyAdvice.add(adviceBean);
}
}
......................
}
}
上面还有一个比较关键的代码 ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType); 具体实现如下
// 异常处理方法解析器 构造方法
public ExceptionHandlerMethodResolver(Class<?> handlerType) {
// 遍历 ControllerAdvice 类的异常处理 方法
for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
addExceptionMapping(exceptionType, method);
}
}
}
private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
// 将异常类型 和 对应处理异常的方法 放到HashMap 中
Method oldMethod = this.mappedMethods.put(exceptionType, method);
if (oldMethod != null && !oldMethod.equals(method)) {
throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" +
exceptionType + "]: {" + oldMethod + ", " + method + "}");
}
}
以上就是解析@ControllerAdvice 类,并初始化解析器
ExceptionHandlerMethodResolver的过程。
异常捕捉,反射调用
当控制器抛出异常时,DispatcherServlet 会捕捉到这些异常。在 Spring MVC 中,DispatcherServlet 实现了 HandlerExceptionResolver 接口,负责将异常传递给合适的异常处理方法。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...........
try {
........
try {
......................
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
.......................
}
catch (Exception ex) {
// 捕捉异常
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 处理返回结果 ,有异常就会处理异常,重新封装返回结果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
..................
}
}
中间步骤就省略了,看看堆栈吧截图吧,从上往下执行:
下面就主要分析如何找到对应的异常处理器
选择异常处理器
上图我们debug 到 ExceptionHandlerExceptionResolver#getExceptionHandlerMethod 这个就是找到对应异常的处理类已经处理方法
主要分为三步
- 查询Controller 本身是否有异常处理方法
- 处理代理类情况
- 查找全局 @ControllerAdvice 的异常处理方法 (重点落在这一步)
/**
* @param handlerMethod 抛出异常的 Controller 对应的method
* @param exception 捕捉到的异常
**/
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
@Nullable HandlerMethod handlerMethod, Exception exception) {
Class<?> handlerType = null;
if (handlerMethod != null) {
//1. 查询Controller 本身是否有异常处理方法
// Local exception handler methods on the controller class itself.
// To be invoked through the proxy, even in case of an interface-based proxy.
handlerType = handlerMethod.getBeanType();
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, this.applicationContext);
}
//2. 处理代理类情况
// For advice applicability check below (involving base packages, assignable types
// and annotation presence), use target class instead of interface-based proxy.
if (Proxy.isProxyClass(handlerType)) {
handlerType = AopUtils.getTargetClass(handlerMethod.getBean());
}
}
//3. 查找全局 @ControllerAdvice 的异常处理方法 (重点落在这一步)
for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
ControllerAdviceBean advice = entry.getKey();
if (advice.isApplicableToBeanType(handlerType)) {
// 获取到我们自定义的 @ControllerAdvice 标记类的处理器(初始化阶段完成的)
ExceptionHandlerMethodResolver resolver = entry.getValue();
// 获取到 解析异常的处理类
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(advice.resolveBean(), method, this.applicationContext);
}
}
}
return null;
}
// 获取到 解析异常的处理类 Method method = resolver.resolveMethod(exception); 获取异常的处理方法,核心处理逻辑如下:
private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
List<Class<? extends Throwable>> matches = new ArrayList<>();
// 从mapperdMethods Map 中获取对应的异常处理类,mapperdMethods 里面数据就是在初始化阶段完成的
for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
if (mappedException.isAssignableFrom(exceptionType)) {
matches.add(mappedException);
}
}
if (!matches.isEmpty()) {
if (matches.size() > 1) {
matches.sort(new ExceptionDepthComparator(exceptionType));
}
return this.mappedMethods.get(matches.get(0));
}
else {
return NO_MATCHING_EXCEPTION_HANDLER_METHOD;
}
}
反射调用异常处理类
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
// 获取到 处理异常对应的 Method,封装到ServletInvocableHandlerMethod
ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
.................
try {
.............
//反射调用
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, arguments);
}
//返回视图.............
..........
}
}
5. 总结
本篇文章分析了异常解析器的初始化过程(bean 初始化阶段),再到异常的捕捉,以及如何找到对应的异常处理方法,最后反射调用,返回结果。希望这篇文章对你有所帮助,欢迎各位老铁点赞收藏!!!