又是美好的一天呀~
个人博客地址: huanghong.top
本文预估阅读时长为20分钟左右~
ExceptionHanlder的使用
handler定义
package com.huang.handler;
import com.huang.controller.demo1.DemoController;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* @Time 2023-02-28 17:25
* Created by Huang
* className: GlobalExceptionHandler
* Description:
*/
//@RestControllerAdvice
//@RestControllerAdvice(basePackages = "com.huang.controller.demo1")
@RestControllerAdvice(basePackageClasses = DemoController.class)
public class GlobalExceptionHandler {
@ExceptionHandler(value = RuntimeException.class)
public String runtimeExceptionHandler(HttpServletRequest request, RuntimeException exception) {
return exception.getMessage();
}
@ExceptionHandler(value = IOException.class)
public String IOExceptionHandler(HttpServletRequest request, IOException exception) {
return exception.getMessage();
}
@ExceptionHandler(value = Exception.class)
public String exceptionHandler(HttpServletRequest request, Exception exception) {
return exception.getMessage();
}
@ExceptionHandler(value = NullPointerException.class)
public String nullPointerExceptionHandler(HttpServletRequest request, NullPointerException exception) {
return exception.getMessage();
}
}
- @RestControllerAdvice是一个组合注解,由@ControllerAdvice和@ResponseBody组成,相同于**@RestController与@Controller之间的区别,增加@ResponseBody**注解则返回的结果直接写入 HTTP response body 中,反之直接返回view。
- 通过指定@RestControllerAdvice中的basePackages和basePackageClasses的属性可以扫描指定包路径下或类的异常。
controller定义
package com.huang.controller.demo1;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
/**
* @Time 2023-02-28 17:20
* Created by Huang
* className: DemoController
* Description:
*/
@RestController("demo1")
@RequestMapping
public class DemoController {
@GetMapping("/demo1/normal")
public String normal(){
return "normal";
}
@GetMapping("/demo1/NullPointerException")
public String NullPointerException(){
throw new NullPointerException("demo1 NullPointerException");
}
@GetMapping("/demo1/RuntimeException")
public String RuntimeException(){
throw new RuntimeException("demo1 RuntimeException");
}
@GetMapping("/demo1/IOException")
public String IOException() throws IOException {
throw new IOException("demo1 IOException");
}
}
package com.huang.controller.demo2;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
/**
* @Time 2023-02-28 17:20
* Created by Huang
* className: DemoController
* Description:
*/
@RestController("demo2")
@RequestMapping
public class DemoController {
@GetMapping("/demo2/normal")
public String normal(){
return "normal";
}
@GetMapping("/demo2/NullPointerException")
public String NullPointerException(){
throw new NullPointerException("demo2 NullPointerException");
}
@GetMapping("/demo2/RuntimeException")
public String RuntimeException(){
throw new RuntimeException("demo2 RuntimeException");
}
@GetMapping("/demo2/IOException")
public String IOException() throws IOException {
throw new IOException("demo2 IOException");
}
}
结论
- 指定basePackages则只扫描处理对应路径下的异常内容。
- 指定basePackageClasses则只扫描处理对应类中的异常内容。
- 异常捕获由最下级异常至最高级,比如抛出NullPointerException:handler处理顺序为nullPointerExceptionHandler->runtimeExceptionHandler->exceptionHandler。
- 单个ExceptionHandler可指定多个异常,比如**@ExceptionHandler(value = {NullPointerException.class,IOException.class})**
全局异常处理源码分析
异常处理配置
方式一:配置默认视图
package com.huang.handler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import java.util.Properties;
/**
* @Time 2023-02-28 21:22
* Created by Huang
* className: GlobalExceptionViewHandler
* Description:
*/
@Configuration
public class GlobalExceptionViewHandler {
@Bean
public SimpleMappingExceptionResolver getSimpleMappingExceptionResolver(){
SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver();
simpleMappingExceptionResolver.setDefaultErrorView("defaultErrorView");
Properties properties = new Properties();
properties.setProperty("java.lang.Exception", "ExceptionErrorView");
properties.setProperty("java.io.IOException", "IOExceptionErrorView");
properties.setProperty("java.lang.NullPointerException", "NullPointerExceptionErrorView");
properties.setProperty("java.lang.RuntimeException", "RuntimeExceptionErrorView");
properties.setProperty("java.lang.IllegalArgumentException", "IllegalArgumentExceptionErrorView");
simpleMappingExceptionResolver.setExceptionMappings(properties);
return simpleMappingExceptionResolver;
}
}
方式二:配置ControllerAdvice
package com.huang.handler;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* @Time 2023-02-28 17:25
* Created by Huang
* className: GlobalExceptionHandler
* Description:
*/
@RestControllerAdvice
//@RestControllerAdvice(basePackages = "com.huang.controller.demo1")
//@RestControllerAdvice(basePackageClasses = DemoController.class)
public class GlobalExceptionHandler {
@ExceptionHandler(value = RuntimeException.class)
public String exceptionHandler(HttpServletRequest request, RuntimeException exception) {
return exception.getMessage();
}
@ExceptionHandler(value = IOException.class)
public String exceptionHandler(HttpServletRequest request, IOException exception) {
return exception.getMessage();
}
@ExceptionHandler(value = Exception.class)
public String exceptionHandler(HttpServletRequest request, Exception exception) {
return exception.getMessage();
}
@ExceptionHandler(value = NullPointerException.class)
public String exceptionHandler(HttpServletRequest request, NullPointerException exception) {
return exception.getMessage();
}
}
注: 如果两者同时配置,则在请求访问的过程中,DispatcherServlet进行初始化执行initStrategies方法,在执行initHandlerExceptionResolvers中会获取所有异常解析器HandlerExceptionResolver然后加以排序操作**(AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers))**
以上述代码为例,根据ExceptionResolvers的PriorityOrdered、Ordered来进行排序
-
org.springframework.boot.web.servlet.error.DefaultErrorAttributes(@Order(Ordered.HIGHEST_PRECEDENCE))默认处理返回null。
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { storeErrorAttributes(request, ex); return null; } private void storeErrorAttributes(HttpServletRequest request, Exception ex) { request.setAttribute(ERROR_ATTRIBUTE, ex); }
-
org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver(Ordered.LOWEST_PRECEDENCE)子类异常视图处理器org.springframework.web.servlet.handler.SimpleMappingExceptionResolver
-
HandlerExceptionResolverComposite(Ordered=0)中的ExceptionHandlerExceptionResolver在初始化会解析ControllerAdvice中的异常处理方案,存在对应处理NullPointerException的处理器则进行处理返回,直接跳过GlobalExceptionViewHandler处理器。
//org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport
@Bean
public HandlerExceptionResolver handlerExceptionResolver(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();
configureHandlerExceptionResolvers(exceptionResolvers);
if (exceptionResolvers.isEmpty()) {
addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);
}
extendHandlerExceptionResolvers(exceptionResolvers);
HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
//设置Order=0
composite.setOrder(0);
composite.setExceptionResolvers(exceptionResolvers);
return composite;
}
总结:异常解析处理器执行顺序DefaultErrorAttributes->HandlerExceptionResolverComposite->SimpleMappingExceptionResolver,同时配置异常视图处理器和ControllerAdvice,ControllerAdvice会优先处理异常。
构建HandlerExceptionResolverComposite
org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport
@Bean
public HandlerExceptionResolver handlerExceptionResolver(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();
//自动注入WebMvcConfigurerComposite中扩展resolvers,不存在
configureHandlerExceptionResolvers(exceptionResolvers);
if (exceptionResolvers.isEmpty()) {
//添加异常解析器
addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);
}
extendHandlerExceptionResolvers(exceptionResolvers);
HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
//order设置为0
composite.setOrder(0);
//设置异常解析器
composite.setExceptionResolvers(exceptionResolvers);
return composite;
}
addDefaultHandlerExceptionResolvers
org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#addDefaultHandlerExceptionResolvers
protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers,
ContentNegotiationManager mvcContentNegotiationManager) {
//创建org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver实例
ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver();
exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager);
exceptionHandlerResolver.setMessageConverters(getMessageConverters());
exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers());
exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers());
if (jackson2Present) {
exceptionHandlerResolver.setResponseBodyAdvice(
Collections.singletonList(new JsonViewResponseBodyAdvice()));
}
if (this.applicationContext != null) {
exceptionHandlerResolver.setApplicationContext(this.applicationContext);
}
//执行afterPropertiesSet
exceptionHandlerResolver.afterPropertiesSet();
exceptionResolvers.add(exceptionHandlerResolver);
//添加ResponseStatusExceptionResolver
ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver();
responseStatusResolver.setMessageSource(this.applicationContext);
exceptionResolvers.add(responseStatusResolver);
//添加DefaultHandlerExceptionResolver
exceptionResolvers.add(new DefaultHandlerExceptionResolver());
}
//小结:HandlerExceptionResolverComposite中ExceptionResolvers包含上述三个实例(ExceptionHandlerExceptionResolver/ResponseStatusExceptionResolver/DefaultHandlerExceptionResolver)
initExceptionHandlerAdviceCache
org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#initExceptionHandlerAdviceCache
private void initExceptionHandlerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
//获取Spring容器中所有ControllerAdvice Bean
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
for (ControllerAdviceBean adviceBean : adviceBeans) {
Class<?> beanType = adviceBean.getBeanType();
if (beanType == null) {
throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
}
//构建ExceptionHandlerMethodResolver对象,解析ControllerAdvice中的ExceptionHandler
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
if (resolver.hasExceptionMappings()) {
//如果存在@ExceptionHandler,放入本地缓存中
this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
}
// 如果该 beanType 类型是 ResponseBodyAdvice 子类,则添加到 responseBodyAdvice 中
if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
this.responseBodyAdvice.add(adviceBean);
}
}
}
//ExceptionHandlerMethodResolver初始化
//mappedMethods key:异常类型 value: ControllerAdvice中的ExceptionHandler方法
private final Map<Class<? extends Throwable>, Method> mappedMethods = new HashMap<>(16);
public ExceptionHandlerMethodResolver(Class<?> handlerType) {
//遍历@ExceptionHandler的方法
for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
//遍历处理的异常集合
for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
//添加到mappedMethods中
addExceptionMapping(exceptionType, method);
}
}
}
DispatcherServlet初始化
//org.springframework.web.servlet.DispatcherServlet
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
...
initHandlerExceptionResolvers(context);
...
}
private void initHandlerExceptionResolvers(ApplicationContext context) {
this.handlerExceptionResolvers = null;
//默认检测所有异常解析器
if (this.detectAllHandlerExceptionResolvers) {
//查询上下文所有异常解析器
Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
//解析器不为空,根据ExceptionResolvers的PriorityOrdered、Ordered来进行排序
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");
}
}
}
Spring容器自动注入异常解析器
//org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
//org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport
@Bean
public HandlerExceptionResolver handlerExceptionResolver(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();
//自动注入WebMvcConfigurerComposite中扩展resolvers,不存在
configureHandlerExceptionResolvers(exceptionResolvers);
if (exceptionResolvers.isEmpty()) {
//添加异常解析器
addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);
}
extendHandlerExceptionResolvers(exceptionResolvers);
HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
//order设置为0
composite.setOrder(0);
//设置异常解析器
composite.setExceptionResolvers(exceptionResolvers);
return composite;
}
processDispatchResult
org.springframework.web.servlet.DispatcherServlet#processDispatchResult
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
//如果是ModelAndViewDefiningException,则获取对应的ModelAndView
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
//获取对应mappedHandler进行异常处理
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
//mv不为空则返回错误页面
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
//视图数据渲染
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
// Exception (if any) is already handled..
//异常处理完成后执行HandlerInterceptor的afterCompletion方法
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
processHandlerException
org.springframework.web.servlet.DispatcherServlet#processHandlerException
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {
// Success and error responses may use different content types
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
//遍历handlerExceptionResolvers,使用逐个handlerExceptionResolver处理异常,如果exMv不为空则跳过后续处理
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
//DefaultErrorAttributes 处理返回null
//HandlerExceptionResolver 自主定义ControllerAdvice
//如果仍定义了SimpleMappingExceptionResolver则会被跳过
exMv = resolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}
if (exMv != null) {
if (exMv.isEmpty()) {
request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
return null;
}
// We might still need view name translation for a plain error model...
if (!exMv.hasView()) {
String defaultViewName = getDefaultViewName(request);
if (defaultViewName != null) {
exMv.setViewName(defaultViewName);
}
}
if (logger.isTraceEnabled()) {
logger.trace("Using resolved error view: " + exMv, ex);
}
else if (logger.isDebugEnabled()) {
logger.debug("Using resolved error view: " + exMv);
}
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv;
}
throw ex;
}
resolveException
ControllerAdvice对应的异常处理逻辑
org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver#doResolveException
protected final ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
//对应controller
HandlerMethod handlerMethod = (handler instanceof HandlerMethod ? (HandlerMethod) handler : null);
return doResolveHandlerMethodException(request, response, handlerMethod, ex);
}
doResolveHandlerMethodException
org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#doResolveHandlerMethodException
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
//根据controller和异常类型匹配到对应的ExceptionHandler方法
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);
}
//根据原生request和response构建一个新的webRequest
ServletWebRequest webRequest = new ServletWebRequest(request, response);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
ArrayList<Throwable> exceptions = new ArrayList<>();
try {
if (logger.isDebugEnabled()) {
logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);
}
// Expose causes as provided arguments as well
Throwable exToExpose = exception;
while (exToExpose != null) {
exceptions.add(exToExpose);
Throwable cause = exToExpose.getCause();
exToExpose = (cause != exToExpose ? cause : null);
}
Object[] arguments = new Object[exceptions.size() + 1];
//构建参数 exceptions + handlerMethod
exceptions.toArray(arguments); // efficient arraycopy call in ArrayList
arguments[arguments.length - 1] = handlerMethod;
//通过ExceptionHandler的异常处理逻辑来处理异常
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, arguments);
}
catch (Throwable invocationEx) {
// Any other than the original exception (or a cause) is unintended here,
// probably an accident (e.g. failed assertion or the like).
if (!exceptions.contains(invocationEx) && logger.isWarnEnabled()) {
logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);
}
// Continue with default processing of the original exception...
return null;
}
//通过RequestResponseBodyMethodProcessor中handleReturnValue的方法设置mavContainer.setRequestHandled(true);
//直接返回new ModelAndView();
if (mavContainer.isRequestHandled()) {
return new ModelAndView();
}
else {
ModelMap model = mavContainer.getModel();
HttpStatus status = mavContainer.getStatus();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);
mav.setViewName(mavContainer.getViewName());
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
if (model instanceof RedirectAttributes) {
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
return mav;
}
}
感谢阅读完本篇文章!!!
个人博客地址: huanghong.top