User-Profile-Image
hankin
  • 5
  • Java
  • Kotlin
  • Spring
  • Web
  • SQL
  • MegaData
  • More
  • Experience
  • Enamiĝu al vi
  • 分类
    • Zuul
    • XML
    • WebSocket
    • Web Notes
    • Web
    • Vue
    • Thymeleaf
    • SQL Server
    • SQL Notes
    • SQL
    • SpringSecurity
    • SpringMVC
    • SpringJPA
    • SpringCloud
    • SpringBoot
    • Spring Notes
    • Spring
    • Servlet
    • Ribbon
    • Redis
    • RabbitMQ
    • PostgreSQL
    • OAuth2
    • NOSQL
    • Netty
    • MySQL
    • MyBatis
    • More
    • MegaData
    • Maven
    • LoadBalancer
    • Kotlin Notes
    • Kotlin
    • jQuery
    • JavaScript
    • Java Notes
    • Java
    • Hystrix
    • Git
    • Gateway
    • Freemarker
    • Feign
    • Eureka
    • Enamiĝu al vi
    • ElasticSearch
    • Docker
    • Consul
    • Ajax
    • ActiveMQ
  • 页面
    • 归档
  • 友链
    • Spring Cloud Alibaba
    • Spring Cloud
    • Nacos
    • Docker
    • ElasticSearch
    • Kotlin
    • Kotlin易百
    • KotlinWeb3
    • KotlinNhooo
    • 前端开源搜索
    • Ktorm ORM
    • Ktorm-KSP
    • Ebean ORM
    • Maven
    • 江南一点雨
    • 江南国际站
    • 设计模式
    • 熊猫大佬
    • java学习
    • kotlin函数查询
    • Istio 服务网格
    • istio
    • Ktor 异步 Web 框架
    • PostGis
    • kuangstudy
    • 源码地图
    • it教程吧
    • Arthas-JVM调优
    • Electron
    • bugstack虫洞栈
    • github大佬宝典
    • Sa-Token
    • 前端技术胖
    • bennyhuo-Kt大佬
    • Rickiyang博客
    • 李大辉大佬博客
    • KOIN
    • SQLDelight
    • Exposed-Kt-ORM
    • Javalin—Web 框架
    • http4k—HTTP包
    • 爱威尔大佬
    • 小土豆
    • 小胖哥安全框架
    • 负雪明烛刷题
    • Kotlin-FP-Arrow
    • Lua参考手册
    • 美团文章
    • 666资源站
    • Java 全栈知识体系
    • 尼恩架构师学习
Help?

Please contact us on our email for need any support

Support
    首页   ›   Enamiĝu al vi   ›   正文
Enamiĝu al vi

SpringMVC—Web九大组件之HandlerExceptionResolver异常处理器

2022-12-10 22:52:13
167  0 0

参考目录

  • Java异常体系简介
  • 为何需要全局异常处理?
  • 古老的异常处理方式
  • Spring MVC处理异常
  • HandlerExceptionResolver
  • AbstractHandlerExceptionResolver
  • SimpleMappingExceptionResolver
  • ResponseStatusExceptionResolver
  • DefaultHandlerExceptionResolver
  • DispatcherServlet对它的初始化和应用
  • 初始化
  • 应用流程
  • 自定义HandlerExceptionResolver处理异常
  • @ExceptionHandler
  • HandlerExceptionResolver如何返回JSON格式数据
  • @ExceptionHandler
  • AbstractHandlerMethodExceptionResolver
  • ExceptionHandlerMethodResolver(重要)
  • ExceptionHandlerExceptionResolver(重要)
  • 异常处理优先级
  • 全局异常示例
  • ResponseEntityExceptionHandler
  • 处理异常时又发生了异常怎么办呢
  • 如何优雅统一处理Filter异常
  • Spring Boot

阅读完需:约 35 分钟

Java异常体系简介

Java相较于其它大多数语言提供了一套非常完善的异常体系Throwable:分为Error和Exception两大分支:

  1. Error:错误,对于所有的编译时期的错误以及系统错误都是通过Error抛出的,比如NoClassDefFoundError、Virtual MachineError、ZipError、硬件问题等等。
  2. Exception:异常,是更为重要的一个分支,是程序员经常打交道的。异常定义为是程序的问题,程序本身是可以处理的。

Error和Exception最大的区别是:异常是可以被程序处理的,而错误是没法处理的。

错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况(比如类找不到NoClassDefFoundError)

当然,异常Exception它本身还分为两大重要的分支:Checked Exception(可检查异常,如IOException)和Unchecked Exception(不可检查异常,如RuntimeException)。

为何需要全局异常处理?

在web项目开发时,我们一般把业务代码(大量代码)写在Service层。作为面向返回的Controller层就需要关注一些异常情况了:如此一来,我们的Controller层就不得不进行try-catch,形如这样子:

@GetMapping("/test")
public String test() {
	try {
		... // 处理你的业务逻辑
		return "success";
	} catch (Exception e) {
		return "fail"; // 处理异常
	}
}

显然,这么处理至少有如下两大问题:

  1. Controller一般方法众多,那就需要写大量的try-catch代码,很难看也很难维护
  2. 在此处try-catch也只能捕获住Handler的异常,万一是view抛出异常了呢?

古老的异常处理方式

在还没有Spring,更无Spring Boot时,开发使用的是源生的Servlet + tomcat容器。其实它也是提供了通用的异常的处理配置方式的(自己控制response的方式不在本文讨论访问内)。如果你是“老”程序员,你应该在web.xml里看到过如下配置:

<!-- 根据状态码 -->
<error-page>
    <error-code>500</error-code>
    <location>/500.jsp</location>
</error-page>

<!-- 根据异常类型 -->
<error-page>
	<exception-type>java.lang.RuntimeException</exception-type>
	<location>/500.jsp</location>
</error-page>

配置上的效果很容易理解,这里就不赘述。但是显然这种做法已经完全落伍了,毕竟web.xml都已经被淘汰了嘛,所以我此处把它称为古老的异常处理方式。

Spring MVC处理异常

版本:Spring-mvcweb:5.3.7

Spring MVC作为现在the most known的Web框架产品,优雅异常处理这块它当然提供了完善的支持。Spring MVC提供处理异常的方式主要分为两种:

  1. 实现HandlerExceptionResolver方式
  2. @ExceptionHandler注解方式。注解方式也有两种用法:
    1. 使用在Controller内部
    2. 配置@ControllerAdvice一起使用实现全局处理

HandlerExceptionResolver

// @since 22.11.2003
public interface HandlerExceptionResolver {
	// 注意:handler是有可能为null的,比如404
	@Nullable
	ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}

继承树:

处理方法返回一个ModelAndView视图:既可以是json,也可以是页面。从接口参数上可以发现的是:它只能处理Exception,因为Error是程序处理不了的(注意:Error也是可以捕获的),因此入参类型若写成Throwable是不合适的。

有人会问为何不捕获Error呢?此处简答一下:因为出现Error的情况会造成程序直接无法运行,所以捕获了也没有任何意义。

HandlerExceptionResolverComposite这种模式的类已经非常熟悉了,就不用再分析了,它实现的是短路效果:只要有一个Resolver返回了不为null的视图就截止了,否则继续处理。多个处理器的顺序可用Ordered控制(需要注意的是:若你是HandlerExceptionResolverComposite#add进来的,那order是不生效的请手动控制此ArrayList)

AbstractHandlerExceptionResolver

可以看到所有其它子类的实现都是此抽象类的子类,所以若我们自定义异常处理器,我也推荐从此处去继承,它是Spring3.0后才有的。它主要是提供了对异常更细粒度的控制:此Resolver可只处理指定类型的异常。

// @since 3.0
public abstract class AbstractHandlerExceptionResolver implements HandlerExceptionResolver, Ordered {
	...
	private int order = Ordered.LOWEST_PRECEDENCE;
	
	// 可以设置任何的handler,表示只作用于这些Handler们
	@Nullable
	private Set<?> mappedHandlers;
	// 表示只作用域这些Class类型的Handler们~~~
	@Nullable
	private Class<?>[] mappedHandlerClasses;
	// 以上两者若都为null,那就是匹配素有。但凡有一个有值,那就需要精确匹配(并集的关系)
	
	... // 省略所有的get/set方法

	@Override
	@Nullable
	public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {

		// 这个作用匹配逻辑很简答
		// 若mappedHandlers和mappedHandlerClasses都为null永远返回true
		// 但凡配置了一个就需要精确匹配(并集关系)
		// 需要注意的是:shouldApplyTo方法,子类AbstractHandlerMethodExceptionResolver是有复写的
		if (shouldApplyTo(request, handler)) {
			// 是否执行;response.addHeader(HEADER_CACHE_CONTROL, "no-store")  默认是不执行的
			prepareResponse(ex, response);
			// 此抽象方法留给子类去完成~~~~~
			ModelAndView result = doResolveException(request, response, handler, ex);
			return result;
		} else { // 若此处理器不处理,就返回null呗
			return null;
		}
	}
}

此抽象类主要是提供setMappedHandlers和setMappedHandlerClasses让此处理器可以作用在指定类型/处理器上,因此子类只要继承了它都将会有这种能力,这也是为何我推荐自定义实现也继承于它的原因。它提供了shouldApplyTo()方法用于匹配逻辑,子类若想定制化匹配规则,亦可复写此方法。

SimpleMappingExceptionResolver

顾名思义它就是通过简单映射关系来决定由哪个错误视图来处理当前的异常信息。它提供了多种映射关系可以使用:

  1. 通过异常类型Properties exceptionMappings;映射。它的key可以是全类名、短名称,同时还有继承效果:比如key是Exception那将匹配所有的异常。value是view name视图名称
  2. 若有需要,可以配合Class[] excludedExceptions来一起使用
    通过状态码Map statusCodes匹配。key是view name,value是http状态码

它的源码部分,我们只需要关心下面这一个方法就可以了:

SimpleMappingExceptionResolver:
	@Override
	@Nullable
	protected ModelAndView doResolveException(
			HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {

		// 根据异常类型去exceptionMappings匹配到一个viewName
		// 实在木有匹配到,就用的defaultErrorView(当然defaultErrorView也可能为null没配置,不过建议配置)
		String viewName = determineViewName(ex, request);
		if (viewName != null) {
			// 如果匹配上了一个视图后,再去使用视图匹配出一个statusCode
			// 若没匹配上就用defaultStatusCode(当然它也有可能为null)
			Integer statusCode = determineStatusCode(request, viewName);
			if (statusCode != null) {
				//	执行response.setStatus(statusCode)
				applyStatusCodeIfPossible(request, response, statusCode);
			}
			// new ModelAndView(viewName) 设置好viewName
			// 并且,并且,并且:mv.addObject(this.exceptionAttribute, ex)把异常信息放进去。exceptionAttribute的值默认为:exception
			return getModelAndView(viewName, ex, request);
		} else {
			return null;
		}
	}

此类是Spring首个版本就内置的,其它的均是Spring3.0+才出现。此简单映射功能还算强大,但使用起来有诸多不便,因此Spring MVC默认情况下并没有装配上它(它几乎处于一个被弃用的状态,基本可忽略)。

ResponseStatusExceptionResolver

若抛出的异常类型上有@ResponseStatus注解,那么此处理器就会处理,并且状态码会返给response。Spring5.0还能处理ResponseStatusException这个异常(此异常是5.0新增)。

// 实现了接口MessageSourceAware,方便拿到国际化资源,方便错误消息的国际化
// @since 3.0
public class ResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver implements MessageSourceAware {

	@Nullable
	private MessageSource messageSource;
	@Override
	public void setMessageSource(MessageSource messageSource) {
		this.messageSource = messageSource;
	}


	@Override
	@Nullable
	protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
		try {
			// 若异常类型是,那就处理这个异常
			// 处理很简单:response.sendError(statusCode, resolvedReason)
			// 当然会有国际化消息的处理。最终new一个空的new ModelAndView()供以返回
			if (ex instanceof ResponseStatusException) {
				return resolveResponseStatusException((ResponseStatusException) ex, request, response, handler);
			}

			// 若异常类型所在的类上标注了ResponseStatus注解,就处理这个状态码
			//(可见:异常类型优先于ResponseStatus)
			// 处理方式同上~~~~
			ResponseStatus status = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class);
			if (status != null) {
				return resolveResponseStatus(status, request, response, handler, ex);
			}

			// 这里有个递归:如果异常类型是Course里面的,也会继续处理,所以需要注意这里的递归处理
			if (ex.getCause() instanceof Exception) {
				return doResolveException(request, response, handler, (Exception) ex.getCause());
			}
		} catch (Exception resolveEx) { // 处理失败,就记录warn日志(非info哦~)
			if (logger.isWarnEnabled()) {
				logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", resolveEx);
			}
		}
		return null;
	}
}

这里有个处理的小细节:递归调用了doResolveException()方法,也就是说若有coouse原因也是异常,那就继续会尝试处理的。

另外请注意:@ResponseStatus标注在异常类上此处理器才会处理,而不是标注在处理方法上,或者所在类上哦,所以一般用于自定义异常时使用。

DefaultHandlerExceptionResolver

默认的异常处理器。它能够处理标准的Spring MVC异常们,并且把它转换为对应的HTTP status codes,一般作为兜底处理,Spring MVC默认也注册了此处理器。它能处理的异常非常之多,简单列出来如下:

  • MissingPathVariableException 500
  • ConversionNotSupportedException 500
  • HttpMessageNotWritableException 500
  • AsyncRequestTimeoutException 503
  • MissingServletRequestParameterException 400
  • ServletRequestBindingException 400
  • TypeMismatchException 400
  • HttpMessageNotReadableException 400
  • MethodArgumentNotValidException 400
  • MissingServletRequestPartException 400
  • BindException 400
  • NoHandlerFoundException 404
  • HttpRequestMethodNotSupportedException 405
  • HttpMediaTypeNotAcceptableException 406
  • HttpMediaTypeNotSupportedException 415
// @since 3.0
public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionResolver {
	public DefaultHandlerExceptionResolver() {
		setOrder(Ordered.LOWEST_PRECEDENCE);
		setWarnLogCategory(getClass().getName()); // 不同的日志采用不同的记录器是个很好的习惯
	}

	@Override
	@Nullable
	protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
		try {
			if (ex instanceof HttpRequestMethodNotSupportedException) {
				return handleHttpRequestMethodNotSupported(
						(HttpRequestMethodNotSupportedException) ex, request, response, handler);
			} else if (ex instanceof HttpMediaTypeNotSupportedException) {
				return handleHttpMediaTypeNotSupported(
						(HttpMediaTypeNotSupportedException) ex, request, response, handler);
			} ... // 省略其它的else if
			// 多有的handle方法几乎一样的,都是response.sendError()
			// 有的还会esponse.setHeader("Accept", MediaType.toString(mediaTypes));等等
	}
}

它对这些异常的处理,亦可参考内置的ResponseEntityExceptionHandler实现,它提供了基于@ExceptionHandler的很多异常类型的处理。

DispatcherServlet对它的初始化和应用

因为Spring MVC对请求的整个处理流程都是由DispatcherServlet来控制的,异常处理也属于请求的一部分,所以它的初始化和应用都在此处。

初始化

虽然异常处理非常重要,但绝大多数情况下你可能并不知道Spring MVC它内置就自动给我们配置好了一些异常处理器。DispatcherServlet初始化它的相关代码如下:

DispatcherServlet:
	protected void initStrategies(ApplicationContext context) {
		...
		initHandlerExceptionResolvers(context); // 第六步
		...
	}

	// 寻找逻辑(detectAllHandlerExceptionResolvers默认值是true表示回去容器里寻找):
	// 1、若detect = true(默认是true),去容器里找出所有`HandlerExceptionResolver`类型的Bean们,找到后排序
	// 2、若detect = false(可手动更改),那就拿名称为`handlerExceptionResolver`这单独的一个Bean(context.getBean())
	// 3、如果一个都木有找到,那就走默认策略getDefaultStrategies(),详见下面截图~~~
	private void initHandlerExceptionResolvers(ApplicationContext context) {
		this.handlerExceptionResolvers = null;

		if (this.detectAllHandlerExceptionResolvers) {
			// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
			Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
				// We keep HandlerExceptionResolvers in sorted order.
				AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
			}
		} else {
			try {
				HandlerExceptionResolver her = context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
				this.handlerExceptionResolvers = Collections.singletonList(her);
			} catch (NoSuchBeanDefinitionException ex) {
				// Ignore, no HandlerExceptionResolver is fine too.
			}
		}
		// Ensure we have at least some HandlerExceptionResolvers, by registering
		// default HandlerExceptionResolvers if no other resolvers are found.
		if (this.handlerExceptionResolvers == null) {
			this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
			if (logger.isTraceEnabled()) {
				logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() + "': using default strategies from DispatcherServlet.properties");
			}
		}
	}
  • 开启@EnableWebMvc后,使用的异常处理器是HandlerExceptionResolverComposite
  • 若不开启@EnableWebMvc,就执行默认策略

应用流程

请求交给Handler处理后得到返回结果Result,但result可能会有异常,因此DispatcherServlet会针对性对result做处理:

DispatcherServlet:

	// 处理request请求
	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		applyDefaultViewName(processedRequest, mv);
		mappedHandler.applyPostHandle(processedRequest, response, mv);
		... // 全部处理完成后,这中间可以是真正结果,也有可能有异常,交给结果处理器
		processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		... // 执行拦截器的AfterCompletion方法
	}

	private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
		boolean errorView = false;
		// 不等于null,说明有异常哦~~~~ 那就处理异常
		if (exception != null) {
			// 此种异常属于Spring MVC内部的异常
			if (exception instanceof ModelAndViewDefiningException) {
				mv = ((ModelAndViewDefiningException) exception).getModelAndView();
			} else {
				// 若是普通异常,就交给方法processHandlerException()去统一处理
				// 从而得到一个异常视图ModelAndView,并且标注errorView = true(若不为null的话)
				Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
				mv = processHandlerException(request, response, handler, exception);
				errorView = (mv != null);
			}
		}
		...
		// 渲染此错误视图(若不为null)
		render(mv, request, response)
		... 
	}

	protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception {
		...
		ModelAndView exMv = null;
		// 核心处理办法就在此处,exMv 只有有一个视图返回了,就立马停止(短路效果)
		if (this.handlerExceptionResolvers != null) {
			for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
				exMv = resolver.resolveException(request, response, handler, ex);
				if (exMv != null) {
					break;
				}
			}
		}	
		... // 后面处理viewName等等~~~~~~~
	}

从应用流程上看是比较简单的,但是了解了此处理流程对我们后续使用、定制会有很好的促进作用。

自定义HandlerExceptionResolver处理异常

上面两个案例都是使用Spring MVC内置的异常处理器,显然用户体验均非常不友好。所以在实际生产环境,必须是需要自己来处理异常(页面)的,下面采用自定义HandlerExceptionResolver方式给出Demo案例,仅供参考:

@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
        // 自定义异常处理器一般请放在首位
        exceptionResolvers.add(0, new AbstractHandlerExceptionResolver() {
            @Override
            protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
                // 若是自定义的业务异常,那就返回到单页面异常页面
                if (ex instanceof BusinessException) {
                    return new ModelAndView("/business.jsp");
                } else { // 否则统一到统一的错误页面
                    return new ModelAndView("/error.jsp");
                }
            }
        });
    }
}

@ExceptionHandler

我们可以通过自定义HandlerExceptionResolver实现来处理程序异常,当然Spring MVC也内置了一些实现来对异常处理进行支持。估计已经很少人知道HandlerExceptionResolver这个异常处理器接口(更有甚者连ModelAndView都没听说过也大有人在啊),虽然这不应该,但存在即合理。因此从现象上可以认为使用自定义HandlerExceptionResolver实现的方式去处理异常已经out了,它已经被新的方式所取代:@ExceptionHandler方式。

回忆HandlerExceptionResolver,你是否疑问过这个问题:通过HandlerExceptionResolver如何返回一个json串呢?其实这个问题雷同于:源生Servlet如何给前端返回一个json串呢?

HandlerExceptionResolver如何返回JSON格式数据

自定义了一个异常处理器来处理Handler抛出的异常,示例中返回的是一个页面ModelAndView。但是通常情况下我们的应用都是REST应用,我们的接口返回的都是一个JSON串,那么若接口抛出异常的话我们处理好后也同样的返回一个JSON串比返回一个页面更为合适。

这时若你项目较老,使用的仍旧是HandlerExceptionResolver方式处理异常的话,我在本处提供两种处理方式,供以参考:

方式一:response直接输出json

@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
        // 自定义异常处理器一般请放在首位
        exceptionResolvers.add(0, new AbstractHandlerExceptionResolver() {
            @Override
            protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
                response.setContentType("application/json;charset=UTF-8");
                response.setCharacterEncoding("UTF-8");
                try {
                    String jsonStr = "";
                    if (ex instanceof BusinessException) {
                        response.setStatus(HttpStatus.OK.value());
                        jsonStr = "{'code':100001,'message':'业务异常,请联系客服处理'}";
                    } else {
                        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
                        jsonStr = "{'code':500,'message':'服务器未知异常'}";
                    }
                    response.getWriter().print(jsonStr);
                    response.getWriter().flush();
                    response.getWriter().close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return null;
            }
        });
    }
    
}

注意事项:

  • 因为return null,所以后面若还有处理器将继续执行。但因为本处已把response close了,因此请确保后面不会再使用此response
  • 若所有Resolver处理完后还是return null,那Spring MVC将直接throw ex,因此你看到的效果是:控制台上有异常栈,但是前段页面上显示是友好的json串。
  • 因为没有ModelAndView(值为null),所以不会有渲染步骤,因此后续步骤Spring MVC也不会再使用到response(自定义的拦截器除外)。

方式二:借助MappingJackson2JsonView

@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
        // 自定义异常处理器一般请放在首位
        exceptionResolvers.add(0, new AbstractHandlerExceptionResolver() {
            @Override
            protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
                ModelAndView mv = new ModelAndView();
                MappingJackson2JsonView view = new MappingJackson2JsonView();
                view.setJsonPrefix("fsxJson"); // 设置JSON前缀,有的时候很好用的哦
                //view.setModelKey(); // 让只序列化指定的key
                mv.setView(view);

                // 这样添加key value就非常方便
                mv.addObject("code", "100001");
                mv.addObject("message", "业务异常,请联系客服处理");
                return mv;
            }
        });
    }
    
}

显然这种使用JsonView的方式代码看起来更加舒服,使用起来更加的面向对象。

这两种方式都是基于自定义HandlerExceptionResolver实现类的方式来处理异常,最终给前端返回一个json串。

@ExceptionHandler

此注解是Spring 3.0后提供的处理异常的注解,整个Spring在3.0+中新增了大量的能力来对REST应用提供支持,此注解便是其中之一。

它(只能)标注在方法上,可以使得这个方法成为一个异常处理器,处理指定的异常类型。

// @since 3.0
@Target(ElementType.METHOD) // 只能标注在方法上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {
	// 指定异常类型,可以多个
	Class<? extends Throwable>[] value() default {};
}

上面讲解HandlerExceptionResolver的原理部分讲到了,DispatcherServlet对异常的处理最终都是无一例外的交给了HandlerExceptionResolver异常处理器,因此很容易想到@ExceptionHandler它的底层实现原理其实也是一个异常处理器,它便是:ExceptionHandlerExceptionResolver。

在分析它之前,需要先前置介绍两个类:AbstractHandlerMethodExceptionResolver和ExceptionHandlerMethodResolver

AbstractHandlerMethodExceptionResolver

它是ExceptionHandlerExceptionResolver的抽象父类,服务于处理器类型是HandlerMethod类型的抛出的异常,它并不规定实现方式必须是@ExceptionHandler。它复写了抽象父类AbstractHandlerExceptionResolver的shouldApplyTo方法:

// @since 3.1 专门处理HandlerMethod类型是HandlerMethod类型的异常
public abstract class AbstractHandlerMethodExceptionResolver extends AbstractHandlerExceptionResolver {
	
	// 只处理HandlerMethod这种类型的处理器抛出的异常~~~~~~
	@Override
	protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) {
		if (handler == null) {
			return super.shouldApplyTo(request, null);
		} else if (handler instanceof HandlerMethod) {
			HandlerMethod handlerMethod = (HandlerMethod) handler;
			// 可以看到最终getBean表示最终哪去验证的是它所在的Bean类,而不是方法本身
			// 所以异常的控制是针对于Controller这个类的~
			handler = handlerMethod.getBean(); 
			return super.shouldApplyTo(request, handler);
		} else {
			return false;
		}
	}

	@Override
	@Nullable
	protected final ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
		return doResolveHandlerMethodException(request, response, (HandlerMethod) handler, ex);
	}

	@Nullable
	protected abstract ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception ex);
}

此抽象类非常简单:规定了只处理HandlerMethod抛出的异常。

ExceptionHandlerMethodResolver(重要)

它是一个会在Class及Class的父类中找出带有@ExceptionHandler注解的类,该类带有key为Throwable,value为Method的缓存属性,提供匹配效率。

// @since 3.1
public class ExceptionHandlerMethodResolver {

	// A filter for selecting {@code @ExceptionHandler} methods.
	public static final MethodFilter EXCEPTION_HANDLER_METHODS = method -> AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class);

	// 两个缓存:key:异常类型   value:目标方法Method
	private final Map<Class<? extends Throwable>, Method> mappedMethods = new HashMap<>(16);
	private final Map<Class<? extends Throwable>, Method> exceptionLookupCache = new ConcurrentReferenceHashMap<>(16);

	// 唯一构造函数
	// detectExceptionMappings:传入method,找到这个Method可以处理的所有的异常类型们(注意此方法的逻辑)
	// addExceptionMapping:把异常类型和Method缓存进mappedMethods里
	public ExceptionHandlerMethodResolver(Class<?> handlerType) {
		for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
			for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
				addExceptionMapping(exceptionType, method);
			}
		}
	}

	// 找到此Method能够处理的所有的异常类型
	// 1、detectAnnotationExceptionMappings:本方法或者父类的方法上标注有ExceptionHandler注解,然后读取出其value值就是它能处理的异常们
	// 2、若value值木有指定,那所有的方法入参们的异常类型,就是此方法能够处理的所有异常们
	// 3、若最终还是空,那就抛出异常:No exception types mapped to " + method
	private List<Class<? extends Throwable>> detectExceptionMappings(Method method) {
		List<Class<? extends Throwable>> result = new ArrayList<>();
		detectAnnotationExceptionMappings(method, result);
		if (result.isEmpty()) {
			for (Class<?> paramType : method.getParameterTypes()) {
				if (Throwable.class.isAssignableFrom(paramType)) {
					result.add((Class<? extends Throwable>) paramType);
				}
			}
		}
		if (result.isEmpty()) {
			throw new IllegalStateException("No exception types mapped to " + method);
		}
		return result;
	}

	// 对于添加方法一样有一句值得说的:
	// 若不同的Method表示可以处理同一个异常,那是不行的:"Ambiguous @ExceptionHandler method mapped for [" 
	// 注意:此处必须是同一个异常(比如Exception和RuntimeException不属于同一个...)
	private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
		Method oldMethod = this.mappedMethods.put(exceptionType, method);
		if (oldMethod != null && !oldMethod.equals(method)) {
			throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" + exceptionType + "]: {" + oldMethod + ", " + method + "}");
		}
	}

	// 给指定的异常exception匹配上一个Method方法来处理
	// 若有多个匹配上的:使用ExceptionDepthComparator它来排序。若木有匹配的就返回null
	@Nullable
	public Method resolveMethod(Exception exception) {
		return resolveMethodByThrowable(exception);
	}
	// @since 5.0 递归到了couse异常类型 也会处理
	@Nullable
	public Method resolveMethodByThrowable(Throwable exception) {
		Method method = resolveMethodByExceptionType(exception.getClass());
		if (method == null) {
			Throwable cause = exception.getCause();
			if (cause != null) {
				method = resolveMethodByExceptionType(cause.getClass());
			}
		}
		return method;
	}

	//1、先去exceptionLookupCache找,若匹配上了直接返回
	// 2、再去mappedMethods这个缓存里找。很显然可能匹配上多个,那就用ExceptionDepthComparator排序匹配到一个最为合适的
	// 3、匹配上后放进缓存`exceptionLookupCache`,所以下次进来就不需要再次匹配了,这就是缓存的效果
	// ExceptionDepthComparator的基本理论上:精确匹配优先(按照深度比较)
	@Nullable
	public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
		Method method = this.exceptionLookupCache.get(exceptionType);
		if (method == null) {
			method = getMappedMethod(exceptionType);
			this.exceptionLookupCache.put(exceptionType, method);
		}
		return method;
	}
}

对于本类的功能,可总结如下:

  1. 找到指定Class类(可能是Controller本身,也可能是@ControllerAdvice)里面所有标注有@ExceptionHandler的方法们
  2. 同一个Class内,不能出现同一个(注意理解同一个的含义)异常类型被多个Method处理的情况,否则抛出异常:Ambiguous @ExceptionHandler method mapped for …,相同异常类型处在不同的Class内的方法上是可以的,比如常见的一个在Controller内,一个在@ControllerAdvice内~
    提供缓存:
  3. mappedMethods:每种异常对应的处理方法(直接映射代码上书写的异常-方法映射)
  4. exceptionLookupCache:经过按照深度逻辑精确匹配上的Method方法
    既能处理本身的异常,也能够处理getCause()导致的异常
    ExceptionDepthComparator的匹配逻辑是按照深度匹配。比如发生的是NullPointerException,但是声明的异常有Throwable和Exception,这是它会根据异常的最近继承关系找到继承深度最浅的那个异常,即Exception。

ExceptionHandlerExceptionResolver(重要)

该子类实现就是用于处理标注有@ExceptionHandler注解的HandlerMethod方法的,是@ExceptionHandler功能的实现部分。请注意命名上和ExceptionHandlerMethodResolver做区分

// @since 3.1
public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver implements ApplicationContextAware, InitializingBean {

	// 这个熟悉:用于处理方法入参的(比如支持入参里可写HttpServletRequest等等)
	@Nullable
	private List<HandlerMethodArgumentResolver> customArgumentResolvers;
	@Nullable
	private HandlerMethodArgumentResolverComposite argumentResolvers;
	
	// 用于处理方法返回值(ModelAndView、@ResponseBody、@ResponseStatus等)
	@Nullable
	private List<HandlerMethodReturnValueHandler> customReturnValueHandlers;
	@Nullable
	private HandlerMethodReturnValueHandlerComposite returnValueHandlers;

	// 消息处理器和内容协商管理器
	private List<HttpMessageConverter<?>> messageConverters;
	private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();

	// 通知(因为异常是可以做全局效果的)
	private final List<Object> responseBodyAdvice = new ArrayList<>();
	@Nullable
	private ApplicationContext applicationContext;

	// 缓存:异常类型对应的处理器
	// 它缓存着Controller本类,对应的异常处理器(多个@ExceptionHandler)~~~~
	private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache = new ConcurrentHashMap<>(64);
	// 它缓存ControllerAdviceBean对应的异常处理器(@ExceptionHandler)
	private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache = new LinkedHashMap<>();

	// 唯一构造函数:注册上默认的消息转换器
	public ExceptionHandlerExceptionResolver() {
		StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
		...
		this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
	}
	... // 省略所有的get/set方法

	@Override
	public void afterPropertiesSet() {
		// Do this first, it may add ResponseBodyAdvice beans
		// 这一步骤同RequestMappingHandlerAdapter#initControllerAdviceCache
		// 目的是找到项目中所有的`ResponseBodyAdvice`,然后缓存起来。
		// 并且把它里面所有的标注有@ExceptionHandler的方法都解析保存起来
		// exceptionHandlerAdviceCache:每个advice切面对应哪个ExceptionHandlerMethodResolver(含多个@ExceptionHandler处理方法)
		
		//并且,并且若此Advice还实现了接口:ResponseBodyAdvice。那就还可干预到异常处理器的返回值处理上(基于body)
		//可见:若你想干预到异常处理器的返回值body上,可通过ResponseBodyAdvice来实现哟~~~~~~~~~ 
		// 可见ResponseBodyAdvice连异常处理方法也是生效的,但是`RequestBodyAdvice`可就木有啦。
		initExceptionHandlerAdviceCache();

		// 注册默认的参数处理器。支持到了@SessionAttribute、@RequestAttribute
		// ServletRequest/ServletResponse/RedirectAttributes/ModelMethod等等(当然你还可以自定义)
		if (this.argumentResolvers == null) {
			List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
			this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
		}
		// 支持到了:ModelAndView/Model/View/HttpEntity/ModelAttribute/RequestResponseBody
		// ViewName/Map等等这些返回值 当然还可以自定义
		if (this.returnValueHandlers == null) {
			List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
			this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
		}
	}
	...

	// 处理HandlerMethod类型的异常。它的步骤是找到标注有@ExceptionHandler匹配的方法
	// 然后执行此方法来处理所抛出的异常
	@Override
	@Nullable
	protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {

		// 这个方法是精华,是关键。它最终返回的是一个ServletInvocableHandlerMethod可执行的方法处理器
		// 也就是说标注有@ExceptionHandler的方法最终会成为它

		// 1、本类能够找到处理方法,就在本类里找,找到就返回一个ServletInvocableHandlerMethod
		// 2、本类木有,就去ControllerAdviceBean切面里找,匹配上了也是欧克的
		//   显然此处会判断:advice.isApplicableToBeanType(handlerType) 看此advice是否匹配
		// 若两者都木有找到,那就返回null。这里的核心其实是ExceptionHandlerMethodResolver这个类
		ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
		if (exceptionHandlerMethod == null) {
			return null;
		}

		
		// 给该执行器设置一些值,方便它的指定(封装参数和处理返回值)
		if (this.argumentResolvers != null) {
			exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
		}
		if (this.returnValueHandlers != null) {
			exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
		}
	}

	...
	// 执行此方法的调用(比couse也传入进去了)
	exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);
	... // 下面处理model、ModelAndView、view等等。最终返回一个ModelAndView
	// 这样异常梳理完成。
}

对它的功能,总结如下:

  1. @ExceptionHandler的处理和执行是由本类完成的,同一个Class上的所有@ExceptionHandler方法对应着同一个ExceptionHandlerExceptionResolver,不同Class上的对应着不同的~
  2. 标注有@ExceptionHandler的方法入参上可写:具体异常类型、ServletRequest/ServletResponse/RedirectAttributes/ModelMethod等等,注意:入参写具体异常类型时只能够写一个类型。(若有多种异常,请写公共父类,你再用instanceof来辨别,而不能直接写多个)
  3. 返回值可写:ModelAndView/Model/View/HttpEntity/ModelAttribute/RequestResponseBody/@ResponseStatus等等
  4. @ExceptionHandler只能标注在方法上。既能标注在Controller本类内的方法上(只对本类生效),也可配合@ControllerAdvice一起使用(对全局生效)
  5. 对步骤4的两种情况,执行时的匹配顺序如下:优先匹配本类(本Controller),再匹配全局的。
  6. 有必要再强调一句:@ExceptionHandler方式并不是只能返回JSON串,步骤4也说了,它返回一个ModelAndView也是ok的

异常处理优先级

在实际生成环境中,我们的项目中一般确实也会存在多个HandlerExceptionResolver异常处理器,那么对于抛出的一个异常,它的处理顺序到底是怎样的呢?

理解了DispatcherServlet默认注册的异常处理器们和它们的执行原理后,再去解答这个问题就易如反掌了。这是DispatcherServlet默认注册的异常处理器们:

所以在我们没有自定义HandlerExceptionResolver来干扰这种顺序的情况下(绝大部分情况下我们都不会干扰它),最最最最先执行的便是@ExceptionHandler方式的异常处理器,只有匹配不上才会继续执行其它的处理器。根据此规律,我从使用层面总结出一个结论:

  1. @Controller + @ExceptionHandler优先级最高
  2. @ControllerAdvice + @ExceptionHandler次之
  3. HandlerExceptionResolver最后(一般是DefaultHandlerExceptionResolver)

全局异常示例

在很多Spring MVC项目中你或许都可以看到一个名字叫GlobalExceptionHandler(名字大同小异)的类,它的作用一般被标注上了@ControllerAdvice/@RestControllerAdvice用于处理全局异常。

大多数项目对此类的设计是相当不完善的,它只做了一个通用处理:处理Exception类型。显然这种宽泛的处理是很不优雅的,理应做细分。

@Slf4j
@RestControllerAdvice // 全部返回JSON格式,因为大都是REST项目
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    // 处理所有不可知的异常,作为全局的兜底
    @ExceptionHandler(Exception.class)
    AppResponse handleException(Exception e){
        log.error(e.getMessage(), e);

        AppResponse response = new AppResponse();
        response.setFail("未知错误,操作失败!");
        return response;
    }

    // 处理所有业务异常(一般为手动抛出)
    @ExceptionHandler(BusinessException.class)
    AppResponse handleBusinessException(BusinessException e){
        log.error(e.getMessage(), e);

        AppResponse response = new AppResponse();
        response.setFail(e.getMessage());
        return response;
    }

    // 处理所有接口参数的数据验证异常(此处特殊处理了这个异常)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    AppResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
        log.warn(e.getMessage(), e); //此处我不建议使用error异常...

		// 关于校验的错误信息的返回,此处我知识简单处理,具体你可以加强
        AppResponse response = new AppResponse();
        response.setFail(e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
        return response;
    }


    // 自己定制化处理HttpRequestMethodNotSupportedException这个异常类型喽
    @Override
    protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        log.warn(ex.getMessage());

        String method = ex.getMethod();
        String[] supportedMethods = ex.getSupportedMethods();
        Map<String, Object> map = new HashMap<>();
        map.put("code", status.value());
        map.put("message", "不支持的请求类型:" + method + ",支持的请求类型:" + Arrays.toString(supportedMethods));

        return super.handleExceptionInternal(ex, map, headers, status, request);
    }
}

有人并不清楚为何我要继承ResponseEntityExceptionHandler这个类,下面我就简单介绍一下它。

ResponseEntityExceptionHandler

它是个抽象类,可谓是Spring 3.2后对REST应用异常支持的一个暖心举动。它包装了各种Spring MVC在处理请求时可能抛出的异常的处理,处理结果都是封装成一个ResponseEntity对象。通过ResponseEntity我们可以指定需要响应的状态码、header和body等信息

因为它是个抽象类,所以我们要使用它只需要定义一个标注有@ControllerAdvice的类继承于它便可(如上示例):
加上全局处理前(被DefaultHandlerExceptionResolver处理的结果):

若你是REST应用,可以在全局异常处理类上都设计为继承自此类,做兜底使用。它能处理的异常类型如下(同DefaultHandlerExceptionResolver处理的异常类型):

ResponseEntityExceptionHandler:
	@ExceptionHandler({
			HttpRequestMethodNotSupportedException.class,
			HttpMediaTypeNotSupportedException.class,
			HttpMediaTypeNotAcceptableException.class,
			MissingPathVariableException.class,
			MissingServletRequestParameterException.class,
			ServletRequestBindingException.class,
			ConversionNotSupportedException.class,
			TypeMismatchException.class,
			HttpMessageNotReadableException.class,
			HttpMessageNotWritableException.class,
			MethodArgumentNotValidException.class,
			MissingServletRequestPartException.class,
			BindException.class,
			NoHandlerFoundException.class,
			AsyncRequestTimeoutException.class
		})
	@Nullable
	public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) throws Exception { ... }

处理异常时又发生了异常怎么办呢

若处理器内部又抛出异常,一般就会交给tomcat处理把异常栈输出到前端,显示非常不友好的页面。因此:请务必保证你的异常处理程序中不要出现任何异常,保证健壮性。(当然最最最最为兜底的方案就是架构师统一设计一个HandlerExceptionResolver放在末位,用最简单、最不会出bug的代码来处理一切前面不能处理的异常)

如何优雅统一处理Filter异常

因为我们无法通过@ControllerAdvice+@ExceptionHandler的方式去处理Filter过滤器抛出的异常(理由希望读者自己能明白),所以此处我提供较为优雅的处理方式作为参考。

传统Spring MVC

  1. catch住Filter所有异常
  2. 把Exception放进请求attr属性里
  3. 把该请求forward转发到专门处理错误的Controller里
  4. 该Controller里拿出异常throw出去,从而便可交给全局异常统一处理了
@Component("helloFilter")
@WebFilter(urlPatterns = "/*")
public class HelloFilter extends OncePerRequestFilter {

    @Override
    protected void initFilterBean() throws ServletException {
        System.out.println("HelloFilter初始化...");
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            System.out.println(1 / 0);
            filterChain.doFilter(request, response);
        } catch (Exception e) { // 捕获所有异常做转发用
            request.setAttribute(ErrorController.EXCEPTION_ATTR, e);
            request.getRequestDispatcher(ErrorController.ERROR_URL).forward(request, response);
        }
    }
}

ErrorController

@Slf4j
@RestController
public class ErrorController {

    public static final String ERROR_URL = "/do/filter/errors";
    public static final String EXCEPTION_ATTR = ErrorController.class.getName() + ".error";

    /**
     * 把Filter里的异常同意交给全局异常处理
     */
    @GetMapping(value = "/do/filter/errors")
    public void doFilterErrors(HttpServletRequest request) throws Exception {
        throw Exception.class.cast(request.getAttribute(EXCEPTION_ATTR));
    }
}

GlobalExceptionHandler

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    // 处理所有不可知的异常,作为全局的兜底
    @ExceptionHandler(Exception.class)
    Object handleException(Exception e) {
        log.error(e.getMessage(), e);
        return "hello error";
    }
}

Spring Boot

本文针对性的特别提出了SpringBoot case下的解决方案。因为SpringBoot它会把所有的异常情况都转换为请求/error,所以扩展它还是容易些的:

Filter:没必要自己catch了,交给SpringBoot全局处理即可

@Component("helloFilter")
@WebFilter(urlPatterns = "/*")
public class HelloFilter extends OncePerRequestFilter {

    @Override
    protected void initFilterBean() throws ServletException {
        System.out.println("HelloFilter初始化...");
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        System.out.println(1 / 0);
        filterChain.doFilter(request, response);
    }
}
@RestController
public class MyErrorController extends BasicErrorController {

    // 最终使用的是此构造函数,所以魔方着只需要使用它即可
    // return new BasicErrorController(errorAttributes, this.serverProperties.getError(), this.errorViewResolvers);
    public MyErrorController(ErrorAttributes errorAttributes, ServerProperties serverProperties) {
        super(errorAttributes, serverProperties.getError());
    }

    @RequestMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Map<String, Object>> errorJson(HttpServletRequest request) {
        HttpStatus status = getStatus(request);
        if (status == HttpStatus.NO_CONTENT) {
            return new ResponseEntity<>(status);
        }
        Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
        body.put("myErrorType", "this is my diy error");
        return new ResponseEntity<>(body, status);
    }
}

若你在SpringBoot采用上面Spring MVC方式处理,优先级是更高的。

如本文“对您有用”,欢迎随意打赏作者,让我们坚持创作!

0 打赏
Enamiĝu al vi
一个人若有一百只羊,一只走迷了路,你们的意思如何?他岂不撇下这九十九只,往山里去找那只迷路的羊吗?
513文章 67评论 170点赞 294555浏览

默认版本~SpringBoot2.0~2.3
  • Redis—注解接口限流
  • SpringBoot—数据库读写分离
  • Netty—初探与核心
  • Netty—NIO基础
  • Spring—WebClient使用
  • SpringCloud—LoadBalanced负载均衡
  • Spring—ApplicationEvent事件驱动机制
随便看看
ActiveMQ (4) Ajax (13) Docker (7) ElasticSearch (13) Enamiĝu al vi (1) Eureka (2) Feign (6) Freemarker (5) Gateway (6) Git (5) Hystrix (7) Java (72) Java Notes (111) JavaScript (1) jQuery (2) Kotlin Notes (47) Maven (2) More (2) MyBatis (42) MySQL (5) Netty (2) NOSQL (1) OAuth2 (11) PostgreSQL (4) RabbitMQ (6) Redis (18) Ribbon (6) Servlet (3) Spring (70) SpringBoot (85) SpringCloud (14) SpringJPA (4) SpringMVC (46) Spring Notes (43) SpringSecurity (49) SQL (15) SQL Notes (9) SQL Server (2) Thymeleaf (4) Vue (9) Web (12) Web Notes (18) WebSocket (9) XML (1) Zuul (3)
随便看看
  • 2023年2月 (4)
  • 2023年1月 (3)
  • 2022年12月 (1)
  • 2022年11月 (3)
  • 2022年10月 (5)
  • 2022年9月 (8)
  • 2022年8月 (1)
  • 2022年7月 (2)
  • 2022年6月 (4)
  • 2022年5月 (5)
  • 2022年4月 (3)
  • 2022年3月 (7)
  • 2022年2月 (4)
  • 2022年1月 (15)
  • 2021年12月 (16)
  • 2021年11月 (3)
  • 2021年10月 (3)
  • 2021年9月 (3)
  • 2021年8月 (2)
  • 2021年7月 (4)
  • 2021年6月 (16)
  • 2021年5月 (3)
  • 2021年4月 (2)
  • 2021年3月 (13)
  • 2021年2月 (2)
  • 2021年1月 (33)
  • 2020年12月 (13)
  • 2020年11月 (6)
  • 2020年10月 (17)
  • 2020年9月 (26)
  • 2020年8月 (46)
  • 2020年7月 (28)
  • 2020年6月 (4)
  • 2020年5月 (16)
  • 2020年4月 (88)
  • 2020年3月 (104)
随机文章
SpringBoot—单元测试(补充)
3年前
SpringBoot—使用类型转换器
3年前
SpringSecurity—从子线程获取用户登录信息
2年前
Redis—实现消息队列
2年前
SpringBoot—配置拦截器(HandlerInterceptor)
3年前
Java—并发编程(六)JUC锁 – (1)总览
2年前
ENMAL摘要

1、Kotlin:

https://www.liuwj.me/

 

2、webflux:

https://www.cnblogs.com/lixinjie/p/a-brother-of-spring-mvc-is-spring-webflux.html

 

3、Java中的Unsafe

 

4、https://my.oschina.net/quanke/blog/1631990

 

5、https://blog.csdn.net/u013064109/article/details/78786646?ops_request_misc=%25257B%252522request%25255Fid%252522%25253A%252522161214257916780264022540%252522%25252C%252522scm%252522%25253A%25252220140713.130102334.pc%25255Fall.%252522%25257D&request_id=161214257916780264022540&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_v2~rank_v29-11-78786646.first_rank_v2_pc_rank_v29&utm_term=kotlin

 

6、待学习除Spring之外的Web框架 — Cloudopt Next、Javalin、jfinal

https://jfinal.com/doc

 

7、kotlin设计模式——https://github.com/AboutKotlin/Design-Patterns-In-Kotlin

 

8、kotlin–ktorm+SpringBoot——https://gitee.com/tianchaohongyu/Spring-Boot-Ktor-Starter

 

9、新技术 — CQRS、jasync-sql、play!framework、akka、asyncdb

 

10、Kotlin Tips

https://gitee.com/lesliekoma/kotlin_tips?_from=gitee_search#tip5–%E6%87%92%E5%88%9D%E5%A7%8B%E5%8C%96by-lazy-%E5%92%8C-%E5%BB%B6%E8%BF%9F%E5%88%9D%E5%A7%8B%E5%8C%96lateinit

 

11、mall项目电商系统

https://github.com/macrozheng/mall

 

12、POI大量读写

https://www.cnblogs.com/swordfall/p/8298386.html

 

13、Gitee

权限RBAC:

https://gitee.com/log4j/pig

 

14、freecodecamp、pf4j

 

15、https://javadoop.com/

 

16、https://www.cnblogs.com/skywang12345/

 

17、Flyway

 

18、https://github.com/kotlin-orm/ktorm/pull/296

 

kt实体类自动生成表

 

https://github.com/tursom/TursomServer/tree/master/ts-database/src/main/kotlin/cn/tursom/database

 

19、蓝狐、支付沙盒、虚拟币

 

20、r2dbc spring强推,vertx这边是quarkus强推 redhat认证

 

21、Keycloak为Web应用和Restful服务提供了一站式的单点登录解决方案。

 

22、RSQL 的形式为 Restful API 带来了 SQL 声明性的便利

https://github.com/vineey/archelix-rsql

https://github.com/ymind/rsql-ktorm

 

23、Kotlin依赖注入

https://github.com/InsertKoinIO/koin

 

24、Kotlin– Alpas

https://github.com/alpas/alpas

一个基于 Kotlin 的 Web 框架,可让您简单快速地创建 Web 应用程序和 API。

 

25、外网学习网站,文章

https://medium.com/nerd-for-tech

 

26、Compose Multiplatform 进入 Alpha 版,统一桌面、Web 和 Android UI

https://blog.jetbrains.com/kotlin/2021/08/compose-multiplatform-goes- alpha/

 

27、Sureness

面向REST API的高性能认证鉴权框架,致力于管理保护API安全

https://gitee.com/dromara/sureness

与Javalin结合

https://javalin.io/2021/04/16/javalin-sureness-example.html

 

28、Kotlin官网合集库

https://kotlinlang.org/lp/server-side/

https://kotlinlang.org/lp/server-side/

https://kotlinlang.org/lp/server-side/

 

29、CLI知识体系

https://juejin.cn/post/6966119324478079007

 

30、面向 Web、移动和 Flutter 开发人员的安全开源后端服务器

https://appwrite.io/

 

31、Java锁

https://blog.csdn.net/hancoder/article/details/120421993

 

32、java—简单的鉴权认证

介绍 – Sa-Token (dev33.cn)

 

33、Effective Kotlin 中文翻译

GitHub – MaxzMeng/Effective-Kotlin-zh-CN: Effective Kotlin 中文翻译

 

34、Nutz—国产Web开源框架

http://www.nutzam.com/core/nutz_preface.html

 

35、Quarkus 夸克—国外开源框架

https://quarkus.io/

 

36、目前6个框架

  1. Spring Reactive → 背靠 Pivotal → 归属 VMware → 归属戴尔
  2. Quarkus 和 Vert.x → 背靠 Eclipse 基金会 → 主要由 Red Hat 支持
  3. Helidon → 背靠 Oracle
  4. Micronaut → 背靠 Object Computing(Grails、OpenDDS)
  5. Lagom → 背靠 Lightbend(Akka)
  6. Ktor → 背靠 JetBrains

 

37、XXL-JOB—–分布式任务调度平台

https://www.xuxueli.com/xxl-job/#%E3%80%8A%E5%88%86%E5%B8%83%E5%BC%8F%E4%BB%BB%E5%8A%A1%E8%B0%83%E5%BA%A6%E5%B9%B3%E5%8F%B0XXL-JOB%E3%80%8B

 

38、领域设计驱动模版

https://myddd.org

 

39、BFF— Backend For Frontend

 

40、面试突击小册

https://snailclimb.gitee.io/javaguide-interview/#/

https://javaguide.cn/

 

41、JeecgBoot 是一款基于代码生成器的低代码开发平台

http://doc.jeecg.com/2043868

 

42、

IdentityServer4 是用于 ASP.NET Core 的 OpenID Connect 和 OAuth 2.0 框架。

https://identityserver4docs.readthedocs.io/zh_CN/latest/index.html

 

43、cn.novelweb 工具类的个人博客

https://blog.novelweb.cn

 

44、分布式链路追踪SkyWalking

 

45、刷题模版

https://blog.csdn.net/fuxuemingzhu/article/details/101900729

 

46、TS中文文档

https://ts.xcatliu.com/

 

47、Rust 中文文档

https://kaisery.github.io/trpl-zh-cn/ch00-00-introduction.html

 

48、Bean Searcher 只读 ORM

https://searcher.ejlchina.com/guide/latest/start.html

 

49、K8S的学习手册

https://kuboard.cn/learning/k8s-basics/kubernetes-basics.html#%E5%AD%A6%E4%B9%A0%E7%9B%AE%E6%A0%87

 

50、fluent-mybatis, mybatis语法增强框架(关键自动生成代码JavaPoet)

https://gitee.com/fluent-mybatis/fluent-mybatis?_from=gitee_search

 

51、程序猿博客

https://qicoder.com/categories/

https://blog.hhui.top/hexblog/

https://fangshixiang.blog.csdn.net/category_7941357_2.html

https://www.zhihu.com/people/zhuo-zi-yang-93

 

52、itxiaoshen大佬的分享

https://www.cnblogs.com/itxiaoshen/

 

53、MySQL实战学习

https://funnylog.gitee.io/mysql45/

 

54、八股文

https://www.javalearn.cn/#/

 

55、两个宣传很牛的IO框架

https://gitee.com/smartboot/smart-socket

https://www.tiocloud.com/doc/taixin/?pageNumber=1

Copyright © 2023 网站备案号: 浙ICP备20017730号
主页
页面
  • 归档
博主
Enamiĝu al vi
Enamiĝu al vi 管理员
If you get tired, learn to rest, not to quit.
513 文章 67 评论 294555 浏览
测试
测试
赞赏作者

请通过微信、支付宝 APP 扫一扫

感谢您对作者的支持!

 支付宝 微信支付