SpringMVC源码(十):异常View视图的处理

1、调试示例代码

1.1、Controller

 1 import org.springframework.stereotype.Controller;
 2 import org.springframework.web.bind.annotation.ExceptionHandler;
 3 import org.springframework.web.bind.annotation.RequestMapping;
 4 import org.springframework.web.servlet.ModelAndView;
 5 
 6 @Controller
 7 public class ExceptionController {
 8     
 9     @RequestMapping("/exception")
10     public ModelAndView exceptionMethod(ModelAndView view) {
11        view.setViewName("success");
12        String str = null;
13        str.length();
14        return view;
15     }
16     
17     @ExceptionHandler(RuntimeException.class)
18     public ModelAndView handleError(RuntimeException error) {
19        ModelAndView mav = new ModelAndView();
20        mav.setViewName("error");
21        mav.addObject("msg", "Runtime error");
22        return mav;
23     }
24 }

2、success.jsp

1 <%@ page contentType="text/html;charset=UTF-8" language="java" %>
2 <html>
3 <head>
4     <title>Title</title>
5 </head>
6 <body>
7 <h1>success</h1>
8 </body>
9 </html>

3、error.jsp

 1 <%@ page contentType="text/html;charset=UTF-8" language="java" %>
 2 <html>
 3 <head>
 4     <title>Title</title>
 5 </head>
 6 <body>
 7 <h1>This is error page</h1><br/>
 8 ${msg}
 9 </body>
10 </html>

2、核心流程图

  

3、核心流程源码分析

  在执行ExceptionController控制器的exceptionMethod方法时会出现空指针异常NullPointerException,在源码(八):执行Controller控制器流程中提到在InvocableHandlerMethod的doInvoke() 方法中会进入到目标Controller控制器的逻辑中,下面重点来看doInvoke()中对异常的处理。

  InvocableHandlerMethod#doInvoke() 核心伪代码:

 1 // 调用执行
 2 protected Object doInvoke(Object... args) throws Exception {
 3    try {
 4       // 调用目标控制器Controller
 5       return getBridgedMethod().invoke(getBean(), args);
 6    }
 7    // 不合法的参数异常
 8    catch (IllegalArgumentException ex) {
 9       assertTargetBean(getBridgedMethod(), getBean(), args);
10       String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
11       throw new IllegalStateException(formatInvokeError(text, args), ex);
12    }
13    catch (InvocationTargetException ex) {
14       // 获取异常信息
15       Throwable targetException = ex.getTargetException();
16       // RuntimeException异常
17       if (targetException instanceof RuntimeException) {
18          throw (RuntimeException) targetException;
19       }
20       // Error错误
21       else if (targetException instanceof Error) {
22          throw (Error) targetException;
23       }
24       // Exception异常
25       else if (targetException instanceof Exception) {
26          throw (Exception) targetException;
27       }
28       // 报错非 RuntimeException、Error、Exception类型的异常,抛出IllegalStateException异常
29       else {
30          throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
31       }
32    }
33 }

  当前抛出的NullPointerException,空指针的类图关系如下图,继承自Exception、RuntimeException,会捕获RuntimeException异常并抛出。

 

   回到源码(五):MVC请求执行整体源码概览的doDispatch()流程处理主流程中,DispatcherServlet#doDispatch() 对异常的处理核心伪代码

 1 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
 2 
 3   // 处理请求的处理器链(包含处理器和对应的interceptor)
 4   HandlerExecutionChain mappedHandler = null;
 5 
 6   // 封装model和view的容器
 7   ModelAndView mv = null;
 8   // 处理请求过程中抛出的异常,但是不包含渲染过程中抛出的异常
 9   Exception dispatchException = null;
10 
11   try {
12 
13      // HandlerMapping处理器获取
14      mappedHandler = getHandler(processedRequest);
15     
16      //  HandlerAdapter适配器获取
17      HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
18 
19      // 真正的调用handler方法,调用Controller控制器,并返回视图
20      mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
21   }
22   // 异常处理
23   catch (Exception ex) {
24      // 记录异常
25      dispatchException = ex;
26   }
27   catch (Throwable err) {
28      // 初始化dispatchException异常
29      dispatchException = new NestedServletException("Handler dispatch failed", err);
30   }
31   // 处理返回结果,包括处理异常、渲染页面、触发Interceptor的afterCompletion
32   processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
33    
34 }

  Controller控制器中出现异常后,将异常对象设置给声明的Exception类型的dispatchException对象,并处理返回结果,在源码(九):无异常View视图解析中已经分析了返回正常的String类型的View视图解析渲染流程,下面我们来看看处理过程中出现异常,View视图如何进行处理。

  DispatcherServlet#processDispatchResult() 核心伪代码
 1 private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
 2       @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
 3       @Nullable Exception exception) throws Exception {
 4 
 5    // 在处理Controller控制器过程是否异常的标识
 6    boolean errorView = false;
 7 
 8    // 如果请求处理过程中有异常抛出则处理异常
 9    if (exception != null) {
10       // 从ModelAndViewDefiningException中获得ModelAndView对象
11       if (exception instanceof ModelAndViewDefiningException) {
12          mv = ((ModelAndViewDefiningException) exception).getModelAndView();
13       }
14       // 处理异常,生成ModelAndView对象
15       else {
16          Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
17          mv = processHandlerException(request, response, handler, exception);
18          errorView = (mv != null);
19       }
20    }
21 
22    // 是否进行页面渲染
23    if (mv != null && !mv.wasCleared()) {
24       // 渲染页面
25       render(mv, request, response);
26       // 清理请求中的错误消息属性
27       if (errorView) {
28          WebUtils.clearErrorRequestAttributes(request);
29       }
30    }
31 
32    // 发出请求处理完成通知,触发Interceptor的afterCompletion
33    if (mappedHandler != null) {
34       mappedHandler.triggerAfterCompletion(request, response, null);
35    }
36 }

1、声明异常视图标识,默认值为false

2、异常类型判断

2.1、ModelAndViewDefiningException类型

  异常为ModelAndViewDefiningException类型,直接从exception中获取异常的View视图。

2.2、非ModelAndViewDefiningException类型

1、声明异常视图对象

2、处理异常并获取ModelAndView对象

1、异常处理器的初始化

  在源码(三):MVC九大内置组件初始化中已提到HandlerExceptionResolver异常处理器组件的初始化过程,此处不再赘述。

2、处理异常获取MV对象

  处理异常并获取MV对象,DispatcherServlet#processHandlerException() 核心伪代码

 1 protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
 2       @Nullable Object handler, Exception ex) throws Exception {
 3 
 4    // 声明异常视图对象
 5    ModelAndView exMv = null;
 6    // 遍历 HandlerExceptionResolver 数组,解析异常,生成 ModelAndView 对象
 7    if (this.handlerExceptionResolvers != null) {
 8       // 遍历 HandlerExceptionResolver 数组
 9       for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
10          // 解析异常,生成 ModelAndView 对象
11          exMv = resolver.resolveException(request, response, handler, ex);
12          // 生成成功,结束循环
13          if (exMv != null) {
14             break;
15          }
16       }
17    }
18    // 生成了 ModelAndView 对象,进行返回
19    if (exMv != null) {
20       // ModelAndView 对象为空,则返回 null
21       if (exMv.isEmpty()) {
22          request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
23          return null;
24       }
25       // 没有视图则设置默认视图
26       if (!exMv.hasView()) {
27          String defaultViewName = getDefaultViewName(request);
28          if (defaultViewName != null) {
29             exMv.setViewName(defaultViewName);
30          }
31       }
32       
33       return exMv;
34    }
35    // 未生成 ModelAndView 对象,则抛出异常
36    throw ex;
37 }
2.1、遍历异常处理解析器,解析异常

  AbstractHandlerExceptionResolver#resolveException() 核心伪代码

 1 // 解析异常
 2 public ModelAndView resolveException(
 3       HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
 4 
 5    // 判断是否可以应用
 6    if (shouldApplyTo(request, handler)) {
 7       // 阻止缓存
 8       prepareResponse(ex, response);
 9       // 执行解析异常,返回modelAndView对象
10       ModelAndView result = doResolveException(request, response, handler, ex);
11       // 如果ModelAndView对象非空,则进行返回
12       return result;
13    }
14    // 不可应用,直接返回null
15    else {
16       return null;
17    }
18 }

  ExceptionHandlerExceptionResolver#doResolveHandlerMethodException() 核心伪代码

 1 protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
 2       HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
 3 
 4    // 获得异常对应的 ServletInvocableHandlerMethod 对象,handlerMethod是处理异常的方法
 5    ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
 6    if (exceptionHandlerMethod == null) {
 7       return null;
 8    }
 9 
10    // 设置 ServletInvocableHandlerMethod 对象的相关属性,主要是为了处理HandlerMethod中方法的参数以及handlerMethod的返回值
11    if (this.argumentResolvers != null) {
12       exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
13    }
14    if (this.returnValueHandlers != null) {
15       exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
16    }
17 
18    // 创建 ServletWebRequest 对象
19    ServletWebRequest webRequest = new ServletWebRequest(request, response);
20    // 创建 ModelAndViewContainer 对象
21    ModelAndViewContainer mavContainer = new ModelAndViewContainer();
22 
23    try {
24       // 执行处理该异常的方法 ServletInvocableHandlerMethod 的调用,主要是对参数和返回值及进行处理,通过ModelAndViewContainer作为中间变量
25       // 将一些视图名、参数放到ModelAndViewContainer中
26       Throwable cause = exception.getCause();
27       if (cause != null) {
28          // cause不为空,将cause也作为参数传入
29          exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);
30       }
31       else {
32          exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
33       }
34    }
35    catch (Throwable invocationEx) {
36       return null;
37    }
38 
39    // 如果 mavContainer 已处理,则返回 '空的' ModelAndView 对象。
40    if (mavContainer.isRequestHandled()) {
41       return new ModelAndView();
42    }
43    // 如果 mavContainer 未处,则基于mavContainer生成 ModelAndView 对象
44    else {
45       ModelMap model = mavContainer.getModel();
46       HttpStatus status = mavContainer.getStatus();
47       // 创建 ModelAndView 对象,并设置相关属性
48       ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);
49       mav.setViewName(mavContainer.getViewName());
50       // 若mavContainer的视图不是String类型,设置视图名称
51       if (!mavContainer.isViewReference()) {
52          mav.setView((View) mavContainer.getView());
53       }
54       // 重定向的处理
55       if (model instanceof RedirectAttributes) {
56          Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
57          // FlashMapManager组件重定向时的参数传递
58          RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
59       }
60       return mav;
61    }
62 }
1、获取InvocableHandlerMethod对象

  获取目标Controller控制器的相关信息,执行ExceptionController中的异常处理方法handlerError()。

0

  获取处理异常的目标方法,ExceptionHandlerExceptionResolver#getExceptionHandlerMethod() 核心伪代码如下

 1 // 获取处理异常的目标方法
 2 protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
 3       @Nullable HandlerMethod handlerMethod, Exception exception) {
 4 
 5    // 处理器的类型
 6    Class<?> handlerType = null;
 7 
 8    // 如果 handlerMethod 非空,则优先获得 Controller 对应的 @ExceptionHandler 处理器对应的方法
 9    if (handlerMethod != null) {
10       // 获得 handlerType
11       handlerType = handlerMethod.getBeanType();
12       // 根据 handlerType 对应的 ExceptionHandlerMethodResolver 对象
13       ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
14       if (resolver == null) {
15          resolver = new ExceptionHandlerMethodResolver(handlerType);
16          this.exceptionHandlerCache.put(handlerType, resolver);
17       }
18       // 获得异常对应的 Method 处理方法
19       Method method = resolver.resolveMethod(exception);
20       // 如果获得该异常对应的 Method 处理方法,则创建 ServletInvocableHandlerMethod 对象,并返回
21       if (method != null) {
22          return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
23       }
24       // 获得 handlerType 的原始类, 有可能是代理对象
25       if (Proxy.isProxyClass(handlerType)) {
26          handlerType = AopUtils.getTargetClass(handlerMethod.getBean());
27       }
28    }
29 
30    // 再次使用 ControllerAdvice 对应的 @ExceptionHandler 处理器对应的方法
31    for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
32       ControllerAdviceBean advice = entry.getKey();
33       // 如果 ControllerAdvice 支持当前的 handlerType
34       if (advice.isApplicableToBeanType(handlerType)) {
35          // 获得 handlerType 对应的 ExceptionHandlerMethodResolver 对象
36          ExceptionHandlerMethodResolver resolver = entry.getValue();
37          // 获得异常对应的 Method 处理方法
38          Method method = resolver.resolveMethod(exception);
39          if (method != null) {
40             return new ServletInvocableHandlerMethod(advice.resolveBean(), method);
41          }
42       }
43    }
44    // 获取不到,返回null
45    return null;
46 }

1.1、异常方法解析器缓存exceptionHandlerAdviceCache初始化

  在MVC容器启动,创建ExceptionHandlerExceptionResolver的bean,初始化bean实例的时候,会初始化异常方法解析器缓存。

  initializeBean() -> invokeInitMethods() -> ExceptionHandlerExceptionResolver#afterPropertiesSet()

  初始化异常方法解析器,ExceptionHandlerMethodResolver#initExceptionHandlerAdviceCache() 核心伪代码:

 

 1 // ControllerAdviceBean与异常方法解析器映射缓存
 2 private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache =
 3       new LinkedHashMap<>();
 4 
 5 // 初始化ExceptionHandlerAdvice缓存
 6 private void initExceptionHandlerAdviceCache() {
 7    // 获取所有被@ControllerAdvice修饰的bean集合
 8    List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
 9    // 遍历bean集合
10    for (ControllerAdviceBean adviceBean : adviceBeans) {
11       // 获取Class对象
12       Class<?> beanType = adviceBean.getBeanType();
13       // Class对象为null,抛出异常
14       if (beanType == null) {
15          throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
16       }
17       // 根据Class对象创建异常方法解析器ExceptionHandlerMethodResolver对象
18       ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
19       // 缓存中不存在,添加进缓存
20       if (resolver.hasExceptionMappings()) {
21          this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
22       }
23       // ...
24    }
25 }

1.2、获取处理异常目标方法整理流程

  ·根据Controller的Class对象,优先从缓存exceptionHandlerCache中获取异常处理解析器ExceptionHandlerMethodResolver,缓存中获取不到,创建异常处理解析器并设置进缓存中;

  ·异常处理器解析器解析异常,获取处理异常的方法Method,若获取到当前Controller中的处理异常的方法,根据处理异常的方法及当前Controller的bean创建ServletInvocableHandlerMethod对象并返回。

  ·判断当前Controller是否为代理类,若未代理类,获取原始类型;

  ·若当前Controller中无处理异常的方法,在@ControllerAdvice注解修饰的类中查找对应的@ExceptionHandler 处理器对应的方法,若能找到,创建ServletInvocableHandlerMethod对象并返回,若无法找到,返回null,抛出原始异常。

2、设置参数解析器argumentResolvers、返回值处理器returnValueHandlers

  异常处理解析器ExceptionHandlerExceptionResolver中的argumentResolvers、returnValueHandlers与与源码(六):Handler处理器获取中AbstractHandlerMethodMapping#mappingRegistry属性的初始化过程类似。

  在MVC容器启动,创建MVC容器中单例的bean对象,创建ExceptionHandlerExceptionResolver的bean实例过程中,在getBean() -> doGetBean() -> createBean() -> doCreateBean() -> initializeBean() -> invokeInitMethods() -> ExceptionHandlerExceptionResolver#afterPropertiesSet()

  ExceptionHandlerExceptionResolver#afterPropertiesSet(),调用bean的afterPropertiesSet方法核心伪代码

 1 public void afterPropertiesSet() {
 2    // 初始化ControllerAdvice中定义的ExceptionHandlerAdvice处理
 3    initExceptionHandlerAdviceCache(); 
 4    // 参数值解析器为空
 5    if (this.argumentResolvers == null) {
 6        // 获取默认的参数值解析器
 7       List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
 8       // 填充HandlerMethodArgumentResolverComposite、ExceptionHandlerExceptionResolver中的argumentResolvers属性
 9       this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
10    }
11    if (this.returnValueHandlers == null) {
12       // 获取默认的返回值解析器
13       List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
14       // 填充HandlerMethodReturnValueHandlerComposite、ExceptionHandlerExceptionResolver中的returnValueHandlers属性
15       this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
16    }
17 }

  获取默认的参数值解析器,ExceptionHandlerExceptionResolver#getDefaultArgumentResolvers() 核心代码

 1 // 获取默认的参数值解析器
 2 protected List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
 3    List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
 4 
 5    // 基于注解的参数值解析器
 6    resolvers.add(new SessionAttributeMethodArgumentResolver());
 7    resolvers.add(new RequestAttributeMethodArgumentResolver());
 8 
 9    // 基于类型的参数值解析器
10    resolvers.add(new ServletRequestMethodArgumentResolver());
11    resolvers.add(new ServletResponseMethodArgumentResolver());
12    resolvers.add(new RedirectAttributesMethodArgumentResolver());
13    resolvers.add(new ModelMethodProcessor());
14 
15    // 自定义的参数值解析器
16    if (getCustomArgumentResolvers() != null) {
17       resolvers.addAll(getCustomArgumentResolvers());
18    }
19 
20    return resolvers;
21 }

  获取默认的返回值处理器,ExceptionHandlerExceptionResolver#getDefaultArgumentResolvers() 核心代码

 1 // 获取默认的返回值处理器
 2 protected List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
 3    List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>();
 4 
 5    // 返回单一值的返回值处理器
 6    handlers.add(new ModelAndViewMethodReturnValueHandler());
 7    handlers.add(new ModelMethodProcessor());
 8    handlers.add(new ViewMethodReturnValueHandler());
 9    handlers.add(new HttpEntityMethodProcessor(
10          getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice));
11 
12    // 基于注解的返回值类型
13    handlers.add(new ModelAttributeMethodProcessor(false));
14    handlers.add(new RequestResponseBodyMethodProcessor(
15          getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice));
16 
17    // 返回多个值的返回值处理器
18    handlers.add(new ViewNameMethodReturnValueHandler());
19    handlers.add(new MapMethodProcessor());
20 
21    // 自定义返回值处理器
22    if (getCustomReturnValueHandlers() != null) {
23       handlers.addAll(getCustomReturnValueHandlers());
24    }
25    
26    handlers.add(new ModelAttributeMethodProcessor(true));
27 
28    return handlers;
29 }
3、创建WebRequest对象

  对象中请求的访问路径uri。

0

4、创建 ModelAndViewContainer 对象

  创建的 ModelAndViewContainer 对象,model属性和view属性后续填充。

0

5、调用异常处理并获取ModelAndView对象

  exceptionHandlerMethod.invokeAndHandle(...)的执行,实际上是执行ServletInvocableHandlerMethod#invokeAndHandle(...)方法,流程与源码(八):执行Controller控制器流程中,根据请求路径获取到正常的Controller控制器一样,需要执行的目标方法不同,异常时执行的目标方法handlerError(),此处不再赘述。

6、判断mavContainer是否已经被处理

·若mavContainer已经被处理,返回空的ModelAndView对象

·若mavContainer未被处理

  1、ModelAndViewContainer中的view属性是否为String类型,若为String类型,填充ModelAndView的View属性;

  2、ModelAndViewContainer中的model属性若为重定参数,重定向的参数传递处理。

7、返回ModelAndView对象
2.2、判断异常视图exMv返回对应的ModelAndView对象
1、异常视图exMv对象为空,抛出异常
2、异常视图exMv对象中view属性、model属性为null,返回null

  ModelAndView是否为空判断,ModelAndView#isEmpty() 核心伪代码

 

1 // ModelAndView中的model、view属性为空
2 public boolean isEmpty() {
3    return (this.view == null && CollectionUtils.isEmpty(this.model));
4 }

 

3、若异常视图exMv对象中的view属性为空,为ModelAndView设置默认视图

  设置默认视图,DispatcherServlet#getDefaultViewName() 核心代码

1 // 视图名称转换器
2 private RequestToViewNameTranslator viewNameTranslator;
3 
4 // 为请求提供默认的视图
5 protected String getDefaultViewName(HttpServletRequest request) throws Exception {
6    // 获取默认视图
7    return (this.viewNameTranslator != null ? this.viewNameTranslator.getViewName(request) : null);
8 }
  源码(三):MVC九大内置组件初始化中提到了视图名称转换器RequestToViewNameTranslator的初始化,默认的视图名称转换器为DefaultRequestToViewNameTranslator。
获取根据request请求路径URI转换成基于配置参数的视图名称,DefaultRequestToViewNameTranslator#getViewName() 核心代码
 1 // 前缀
 2 private String prefix = "";
 3 
 4 // 后缀
 5 private String suffix = "";
 6 
 7 // 获取根据request请求路径URI转换成基于配置参数的视图名称
 8 public String getViewName(HttpServletRequest request) {
 9    // 获得请求路径
10    String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, HandlerMapping.LOOKUP_PATH);
11    // 获得视图名
12    return (this.prefix + transformPath(lookupPath) + this.suffix);
13 }

  根据请求路径转换视图名称,DefaultRequestToViewNameTranslator#transformPath() 核心代码:

 1 private static final String SLASH = "/";
 2 // 前缀
 3 private String prefix = "";
 4 // 后缀
 5 private String suffix = "";
 6 // 分隔符
 7 private String separator = SLASH;
 8 // 是否移除开头 SLASH
 9 private boolean stripLeadingSlash = true;
10 // 是否移除末尾 SLASH
11 private boolean stripTrailingSlash = true;
12 // 是否移除拓展名
13 private boolean stripExtension = true;
14 
15 // 转换当前请求路径
16 protected String transformPath(String lookupPath) {
17    String path = lookupPath;
18    // 移除头部 /
19    if (this.stripLeadingSlash && path.startsWith(SLASH)) {
20       path = path.substring(1);
21    }
22    // 移除尾部 /
23    if (this.stripTrailingSlash && path.endsWith(SLASH)) {
24       path = path.substring(0, path.length() - 1);
25    }
26    // 移除拓展名
27    if (this.stripExtension) {
28       path = StringUtils.stripFilenameExtension(path);
29    }
30    // 替换分隔符
31    if (!SLASH.equals(this.separator)) {
32       path = StringUtils.replace(path, SLASH, this.separator);
33    }
34    return path;
35 }
4、为Request设置异常相关属性。
  为Request设置异常属性,WebUtils#exposeErrorRequestAttributes 核心伪代码:
 1 //响应码
 2 public static final String ERROR_STATUS_CODE_ATTRIBUTE = "javax.servlet.error.status_code";
 3 // 异常类型
 4 public static final String ERROR_EXCEPTION_TYPE_ATTRIBUTE = "javax.servlet.error.exception_type";
 5 // 错误消息
 6 public static final String ERROR_MESSAGE_ATTRIBUTE = "javax.servlet.error.message";
 7 // 异常信息
 8 public static final String ERROR_EXCEPTION_ATTRIBUTE = "javax.servlet.error.exception";
 9 // 处理失败的访问路径
10 public static final String ERROR_REQUEST_URI_ATTRIBUTE = "javax.servlet.error.request_uri";
11 // servlet名称
12 public static final String ERROR_SERVLET_NAME_ATTRIBUTE = "javax.servlet.error.servlet_name";
13 
14 // Request设置异常属性
15 public static void exposeErrorRequestAttributes(HttpServletRequest request, Throwable ex,
16       @Nullable String servletName) {
17    // 设置异常处理的响应码
18    exposeRequestAttributeIfNotPresent(request, ERROR_STATUS_CODE_ATTRIBUTE, HttpServletResponse.SC_OK);
19    // 设置异常类型
20    exposeRequestAttributeIfNotPresent(request, ERROR_EXCEPTION_TYPE_ATTRIBUTE, ex.getClass());
21    // 设置错误消息
22    exposeRequestAttributeIfNotPresent(request, ERROR_MESSAGE_ATTRIBUTE, ex.getMessage());
23    // 设置异常信息
24    exposeRequestAttributeIfNotPresent(request, ERROR_EXCEPTION_ATTRIBUTE, ex);
25    // 设置处理失败的访问路径
26    exposeRequestAttributeIfNotPresent(request, ERROR_REQUEST_URI_ATTRIBUTE, request.getRequestURI());
27     // 设置异常的servlet名称
28    if (servletName != null) {
29       exposeRequestAttributeIfNotPresent(request, ERROR_SERVLET_NAME_ATTRIBUTE, servletName);
30    }
31 }

  属性设置详情如下:

3、设置异常视图标识

  若获取到了异常视图exMv,设置异常视图标识为true,便于在完成视图渲染后,清除request中相关的异常属性.

  请求request相关异常属性,WebUtils#clearErrorRequestAttributes() 核心伪代码:

 1 // 响应码
 2 public static final String ERROR_STATUS_CODE_ATTRIBUTE = "javax.servlet.error.status_code";
 3 // 异常类型
 4 public static final String ERROR_EXCEPTION_TYPE_ATTRIBUTE = "javax.servlet.error.exception_type";
 5 // 错误消息
 6 public static final String ERROR_MESSAGE_ATTRIBUTE = "javax.servlet.error.message";
 7 // 异常信息
 8 public static final String ERROR_EXCEPTION_ATTRIBUTE = "javax.servlet.error.exception";
 9 // 处理失败的访问路径
10 public static final String ERROR_REQUEST_URI_ATTRIBUTE = "javax.servlet.error.request_uri";
11 // servlet名称
12 public static final String ERROR_SERVLET_NAME_ATTRIBUTE = "javax.servlet.error.servlet_name";
13 
14 public static void clearErrorRequestAttributes(HttpServletRequest request) {
15    // 清除响应码
16    request.removeAttribute(ERROR_STATUS_CODE_ATTRIBUTE);
17    // 清除异常类型
18    request.removeAttribute(ERROR_EXCEPTION_TYPE_ATTRIBUTE);
19    // 清除错误消息
20    request.removeAttribute(ERROR_MESSAGE_ATTRIBUTE);
21    // 清除异常信息
22    request.removeAttribute(ERROR_EXCEPTION_ATTRIBUTE);
23    // 清除处理失败的访问路径
24    request.removeAttribute(ERROR_REQUEST_URI_ATTRIBUTE);
25    // 清除servlet名称
26    request.removeAttribute(ERROR_SERVLET_NAME_ATTRIBUTE);
27 }

4、渲染异常视图

  渲染View视图,在源码(九):无异常View视图解析中已提及,此处不再赘述。

posted @ 2023-02-23 20:34  无虑的小猪  阅读(190)  评论(0编辑  收藏  举报