Files
JavaYouth/docs/spring-sourcecode-v1/11.第11章-SpringMVC异常处理源码和@EnableWebMvc原理.md

1672 lines
47 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 第11章-SpringMVC异常处理源码和@EnableWebMvc原理
## 视图解析器不重要了,不细述了
因为现在都是前后端分离的架构,不太需要视图解析器了,有兴趣的可以自己研究。
## 异常处理流程
### 测试类
#### HelloController
```java
package cn.imlql.web.controller;
@Controller
public class HelloController {
public HelloController(){
System.out.println("HelloController.....");
}
@GetMapping("/hello")
public String sayHello(@RequestParam(name = "name",required = true) String name, Integer i) {
int x = 10 / i;
return "index.jsp";
}
}
```
### 什么参数都不传
什么参数都不传,肯定是会报异常的,因为@RequestParam那里我加了个required
#### DispatcherServlet#doDispatch()
<img src="https://npm.elemecdn.com/youthlql@1.0.6/spring-sourcecode-v1/chapter_11/image-20211021143709604.png"/>
报了一个缺少参数的错误,下面看看怎么处理的
#### DispatcherServlet#processDispatchResult()
```java
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
//如果有异常处理异常以下if内全是异常处理环节
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else { //定义无数种异常解析器就会得到不同的异常解析效果
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception); //处理异常,所有的异常解析器都不能干活,这个异常就抛出去了
errorView = (mv != null);
}
}
//上面所有的异常解析器都没能处理这个异常,下面直接炸....
// 动态策略。 Did the handler return a view to render? 为啥?@ResponseBody提前在解析返回值的时候就已经把数据写出去了所以这一步就没有了
if (mv != null && !mv.wasCleared()) {
render(mv, request, response); //渲染ModeAndView来解析模型和视图最终决定响应效果
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..
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
```
<img src="https://npm.elemecdn.com/youthlql@1.0.6/spring-sourcecode-v1/chapter_11/image-20211021144337185.png"/>
#### DispatcherServlet#processHandlerException()准备处理异常
```java
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) {
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
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; //如果所有的异常解析器都不能解析就直接抛出这个异常
}
```
<img src="https://npm.elemecdn.com/youthlql@1.0.6/spring-sourcecode-v1/chapter_11/image-20211021150026618.png"/>
##### 三个异常解析器概述
- ExceptionHandlerExceptionResolver所有@ExceptionHandler注解方式的异常处理由他来做,启动扫描了容器中所有标了@ControllerAdvice的类以及这个类里面所有@Exceptionhandler标注的方法,并且缓存这个方法能处理什么异常
- ResponseStatusExceptionResolver找异常类上有没有@ResponseStatus注解
- DefaultHandlerExceptionResolver异常是否是spring内部指定的异常如果是直接响应错误页sendError以及错误代码 并返回new的空的ModelAndView注意这里返回的是空不是null
#### AbstractHandlerExceptionResolver#resolveException()解析异常
```java
@Override
@Nullable //父类抽象类规定的模板
public ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
if (shouldApplyTo(request, handler)) {
prepareResponse(ex, response);
ModelAndView result = doResolveException(request, response, handler, ex); //留给子类的模板方法
if (result != null) {
// Print debug message when warn logger is not enabled.
if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {
logger.debug("Resolved [" + ex + "]" + (result.isEmpty() ? "" : " to " + result));
}
// Explicitly configured warn logger in logException method.
logException(ex, request);
}
return result;
}
else {
return null;
}
}
```
#### AbstractHandlerMethodExceptionResolver#doResolveException()
```java
protected final ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
HandlerMethod handlerMethod = (handler instanceof HandlerMethod ? (HandlerMethod) handler : null);
return doResolveHandlerMethodException(request, response, handlerMethod, ex);
}
```
下面开始进入实现类因为index=0的是ExceptionHandlerExceptionResolver就会先进入这个异常解析器
#### ExceptionHandlerExceptionResolver#doResolveHandlerMethodException()寻找@ExceptionHandler注解标注的方法
```java
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
//为当前异常找一个处理方法??? @ExceptionHandler注解标注的方法
ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
if (exceptionHandlerMethod == null) {
return null;
}
//异常解析器里面 还是利用了以前的 argumentResolvers和 returnValueHandlers扩展了异常解析的功能
if (this.argumentResolvers != null) {
exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
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.toArray(arguments); // efficient arraycopy call in ArrayList
arguments[arguments.length - 1] = handlerMethod;
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;
}
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;
}
}
```
#### ExceptionHandlerExceptionResolver#getExceptionHandlerMethod()遍历所有的@ControllerAdvice看哪个类的方法能处理这个异常
```java
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
@Nullable HandlerMethod handlerMethod, Exception exception) {
Class<?> handlerType = null;
if (handlerMethod != null) {
// 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);
}
// 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());
}
}
//遍历所有的@ControllerAdvice看哪个类的方法能处理这个异常
for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
ControllerAdviceBean advice = entry.getKey();
if (advice.isApplicableToBeanType(handlerType)) {
ExceptionHandlerMethodResolver resolver = entry.getValue();
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(advice.resolveBean(), method);
}
}
}
return null;
}
```
#### 返回到ExceptionHandlerExceptionResolver#doResolveHandlerMethodException()
因为咱们没有@ControllerAdvice标注的类,所以这里会返回空
<img src="https://npm.elemecdn.com/youthlql@1.0.6/spring-sourcecode-v1/chapter_11/image-20211021151804023.png"/>
接着返回
#### 返回到DispatcherServlet#processHandlerException()
准备循环第二个异常解析器
<img src="https://npm.elemecdn.com/youthlql@1.0.6/spring-sourcecode-v1/chapter_11/image-20211021151955136.png"/>
接下来还是父类AbstractHandlerMethodExceptionResolver那个模板方法前面写了这里直接跳过
#### ResponseStatusExceptionResolver#doResolveException()处理@ResponseStatus注解标注的相关异常
```java
protected ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
try {
if (ex instanceof ResponseStatusException) {
return resolveResponseStatusException((ResponseStatusException) ex, request, response, handler);
}
//拿到 ResponseStatus 注解
ResponseStatus status = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class);
if (status != null) {
return resolveResponseStatus(status, request, response, handler, ex);
}
if (ex.getCause() instanceof Exception) {
return doResolveException(request, response, handler, (Exception) ex.getCause());
}
}
catch (Exception resolveEx) {
if (logger.isWarnEnabled()) {
logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", resolveEx);
}
}
return null;
}
```
因为咱们也没有标注@ResponseStatus注解,所以也是空,直接来到第三个异常解析器
#### 返回到DispatcherServlet#processHandlerException()
<img src="https://npm.elemecdn.com/youthlql@1.0.6/spring-sourcecode-v1/chapter_11/image-20211021152505056.png"/>
#### DefaultHandlerExceptionResolver#doResolveException()处理SpringMVC底层的异常
```java
@Override
@Nullable
protected ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
try { //处理SpringMVC底层的异常
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 (ex instanceof HttpMediaTypeNotAcceptableException) {
return handleHttpMediaTypeNotAcceptable(
(HttpMediaTypeNotAcceptableException) ex, request, response, handler);
}
else if (ex instanceof MissingPathVariableException) {
return handleMissingPathVariable(
(MissingPathVariableException) ex, request, response, handler);
}
else if (ex instanceof MissingServletRequestParameterException) {
return handleMissingServletRequestParameter(
(MissingServletRequestParameterException) ex, request, response, handler);
}
else if (ex instanceof ServletRequestBindingException) {
return handleServletRequestBindingException(
(ServletRequestBindingException) ex, request, response, handler);
}
else if (ex instanceof ConversionNotSupportedException) {
return handleConversionNotSupported(
(ConversionNotSupportedException) ex, request, response, handler);
}
else if (ex instanceof TypeMismatchException) {
return handleTypeMismatch(
(TypeMismatchException) ex, request, response, handler);
}
else if (ex instanceof HttpMessageNotReadableException) {
return handleHttpMessageNotReadable(
(HttpMessageNotReadableException) ex, request, response, handler);
}
else if (ex instanceof HttpMessageNotWritableException) {
return handleHttpMessageNotWritable(
(HttpMessageNotWritableException) ex, request, response, handler);
}
else if (ex instanceof MethodArgumentNotValidException) {
return handleMethodArgumentNotValidException(
(MethodArgumentNotValidException) ex, request, response, handler);
}
else if (ex instanceof MissingServletRequestPartException) {
return handleMissingServletRequestPartException(
(MissingServletRequestPartException) ex, request, response, handler);
}
else if (ex instanceof BindException) {
return handleBindException((BindException) ex, request, response, handler);
}
else if (ex instanceof NoHandlerFoundException) {
return handleNoHandlerFoundException(
(NoHandlerFoundException) ex, request, response, handler);
}
else if (ex instanceof AsyncRequestTimeoutException) {
return handleAsyncRequestTimeoutException(
(AsyncRequestTimeoutException) ex, request, response, handler);
}
}
catch (Exception handlerEx) {
if (logger.isWarnEnabled()) {
logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", handlerEx);
}
}
return null;
}
```
#### DefaultHandlerExceptionResolver#handleMissingServletRequestParameter()展示tomcat默认错误页
```java
protected ModelAndView handleMissingServletRequestParameter(MissingServletRequestParameterException ex,
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws IOException {
//直接 sendError tomcat展示错误页
response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
return new ModelAndView();
}
```
这就是咱们刚刚报的错,缺少参数
#### 返回到DispatcherServlet#processHandlerException()
<img src="https://npm.elemecdn.com/youthlql@1.0.6/spring-sourcecode-v1/chapter_11/image-20211021153055017.png"/>
看到这里返回了一个空的ModelAndView并不是NULL
#### 返回到DispatcherServlet#processDispatchResult()
<img src="https://npm.elemecdn.com/youthlql@1.0.6/spring-sourcecode-v1/chapter_11/image-20211021153346708.png">
最后下面再处理下拦截器
<img src="https://npm.elemecdn.com/youthlql@1.0.6/spring-sourcecode-v1/chapter_11/image-20211021153518821.png" >
最终结束
#### 页面效果
<img src="https://npm.elemecdn.com/youthlql@1.0.6/spring-sourcecode-v1/chapter_11/image-20211021153618095.png"/>
### 传参但报错的情况
我们这样写http://localhost:8080/springmvc_source_test/hello?name=zhangsan&i=0
> 前面讲的不再重复
#### DispatcherServlet#doDispatch()
<img src="https://npm.elemecdn.com/youthlql@1.0.6/spring-sourcecode-v1/chapter_11/image-20211021154000555.png"/>
#### DispatcherServlet#processHandlerException()
到了第三个异常解析器也依然处理不了,于是出现了一个谁都处理不了的异常
<img src="https://npm.elemecdn.com/youthlql@1.0.6/spring-sourcecode-v1/chapter_11/image-20211021154346171.png">
然后就抛出此异常
<img src="https://npm.elemecdn.com/youthlql@1.0.6/spring-sourcecode-v1/chapter_11/image-20211021154530583.png"/>
#### 返回到DispatcherServlet#processDispatchResult()直接炸了
<img src="https://npm.elemecdn.com/youthlql@1.0.6/spring-sourcecode-v1/chapter_11/image-20211021154728408.png" />
在这一步抛出了异常,整个方法直接炸了,后面的逻辑全都不走了
#### 返回到DispatcherServlet#doDispatch()
<img src="https://npm.elemecdn.com/youthlql@1.0.6/spring-sourcecode-v1/chapter_11/image-20211021155153026.png"/>
#### DispatcherServlet#triggerAfterCompletion()抛异常
```java
private void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, Exception ex) throws Exception {
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, ex);
}
throw ex; //抛出去
}
```
执行完拦截器异常继续往上抛一层一层往上抛最终抛给了tomcat
#### 页面效果
<img src="https://npm.elemecdn.com/youthlql@1.0.6/spring-sourcecode-v1/chapter_11/image-20211021155354597.png">
这就是tomcat的默认错误页+堆栈页
## 自定义异常处理
### 测试类
#### InvalidUserException
```java
@ResponseStatus(value = HttpStatus.CONFLICT, reason = "非法用户")
public class InvalidUserException extends RuntimeException {
private static final long serialVersionUID = -7034897190745766222L;
}
```
#### HelloController
```java
package cn.imlql.web.controller;
@Controller
public class HelloController {
public HelloController(){
System.out.println("HelloController.....");
}
@GetMapping("/hello")
public String sayHello(@RequestParam(name = "name",required = true) String name {
if ("abc".equals(name)) {
//非法的用户信息
throw new InvalidUserException();
}
return "index.jsp";
}
}
```
### ResponseStatusExceptionResolver处理
```java
protected ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
try {
if (ex instanceof ResponseStatusException) {
return resolveResponseStatusException((ResponseStatusException) ex, request, response, handler);
}
//拿到 ResponseStatus 注解
ResponseStatus status = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class);
if (status != null) {
return resolveResponseStatus(status, request, response, handler, ex);
}
if (ex.getCause() instanceof Exception) {
return doResolveException(request, response, handler, (Exception) ex.getCause());
}
}
catch (Exception resolveEx) {
if (logger.isWarnEnabled()) {
logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", resolveEx);
}
}
return null;
}
```
<img src="https://npm.elemecdn.com/youthlql@1.0.6/spring-sourcecode-v1/chapter_11/image-20211021160217163.png"/>
```java
protected ModelAndView resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request,
HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception {
//获取注解指定的响应状态码和错误原因
int statusCode = responseStatus.code().value();
String reason = responseStatus.reason();
return applyStatusAndReason(statusCode, reason, response);
}
```
```java
protected ModelAndView applyStatusAndReason(int statusCode, @Nullable String reason, HttpServletResponse response)
throws IOException {
if (!StringUtils.hasLength(reason)) {
response.sendError(statusCode); //返回默认错误页
}
else {
String resolvedReason = (this.messageSource != null ?
this.messageSource.getMessage(reason, null, reason, LocaleContextHolder.getLocale()) :
reason);
response.sendError(statusCode, resolvedReason);
}
return new ModelAndView();
}
```
页面效果
<img src="https://npm.elemecdn.com/youthlql@1.0.6/spring-sourcecode-v1/chapter_11/image-20211021160409095.png">
## 最常用的注解版异常解析器 @ExceptionHandler
### 测试类
#### MyExceptionHandler
```java
@ControllerAdvice //专门处理所有controller异常的它是一个复合注解里面有@Component所以默认加在容器中
public class MyExceptionHandler {
@ResponseBody
@ExceptionHandler(value = {ArithmeticException.class})
public String handleZeroException(Exception exception){
//参数位置 https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-exceptionhandler-args
//返回值 https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-exceptionhandler-return-values
return "Error";
}
}
```
异常处理器能写这么多参数和返回值,也是用了参数解析器,返回值处理器
#### HelloController
```java
package cn.imlql.web.controller;
@Controller
public class HelloController {
public HelloController(){
System.out.println("HelloController.....");
}
@GetMapping("/hello")
public String sayHello(Integer i) {
int x = 10 / i;
return "index.jsp";
}
}
```
### DispatcherServlet#doDispatch()
<img src="https://npm.elemecdn.com/youthlql@1.0.6/spring-sourcecode-v1/chapter_11/image-20211021162737192.png"/>
### ExceptionHandlerExceptionResolver#doResolveHandlerMethodException()寻找@ExceptionHandler注解标注的方法
```java
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
//为当前异常找一个处理方法??? @ExceptionHandler注解标注的方法
ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
if (exceptionHandlerMethod == null) {
return null;
}
//异常解析器里面 还是利用了以前的 argumentResolvers和 returnValueHandlers扩展了异常解析的功能
if (this.argumentResolvers != null) {
exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
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.toArray(arguments); // efficient arraycopy call in ArrayList
arguments[arguments.length - 1] = handlerMethod;
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;
}
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;
}
}
```
### ExceptionHandlerExceptionResolver#getExceptionHandlerMethod()
<img src="https://npm.elemecdn.com/youthlql@1.0.6/spring-sourcecode-v1/chapter_11/image-20211021164615158.png"/>
### ExceptionHandlerMethodResolver#getMappedMethod()
最终调到这里
```java
private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
List<Class<? extends Throwable>> matches = new ArrayList<>();
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;
}
}
```
<img src="https://npm.elemecdn.com/youthlql@1.0.6/spring-sourcecode-v1/chapter_11/image-20211021165124914.png"/>
### 返回到ExceptionHandlerExceptionResolver#doResolveHandlerMethodException()
<img src="https://npm.elemecdn.com/youthlql@1.0.6/spring-sourcecode-v1/chapter_11/image-20211021165347422.png">
### ServletInvocableHandlerMethod#invokeAndHandle()
<img src="https://npm.elemecdn.com/youthlql@1.0.6/spring-sourcecode-v1/chapter_11/image-20211021165520356.png"/>
### 小总结
1. 可以看到异常处理的方法和springmvc的普通Controller方法最终走到了相同的反射执行逻辑
2. 这也是为什么叫@ControllerAdvice仅仅是Controller的增强
### ExceptionHandlerExceptionResolver里的参数解析器和返回值解析器何时赋值
#### ExceptionHandlerExceptionResolver#afterPropertiesSet()
```java
@Override //实现了 InitilazingBean 的组件,在容器创建完对象以后,会初始化调用 InitilazingBean
public void afterPropertiesSet() {
//初始化@ExceptionHandler 增强的缓存 Do this first, it may add ResponseBodyAdvice beans
initExceptionHandlerAdviceCache();
//准备好异常解析用的参数解析器
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
```
还是咱们的老朋友InitializingBean一样的逻辑不讲了
#### ExceptionHandlerExceptionResolver#initExceptionHandlerAdviceCache()
```java
private void initExceptionHandlerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
//找到所有的 ControllerAdviceBean(标注了@ControllerAdvice注解的类) 、
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 resolver = new ExceptionHandlerMethodResolver(beanType);
if (resolver.hasExceptionMappings()) {
this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
}
if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
this.responseBodyAdvice.add(adviceBean);
}
}
if (logger.isDebugEnabled()) {
int handlerSize = this.exceptionHandlerAdviceCache.size();
int adviceSize = this.responseBodyAdvice.size();
if (handlerSize == 0 && adviceSize == 0) {
logger.debug("ControllerAdvice beans: none");
}
else {
logger.debug("ControllerAdvice beans: " +
handlerSize + " @ExceptionHandler, " + adviceSize + " ResponseBodyAdvice");
}
}
}
```
#### ControllerAdviceBean#findAnnotatedBeans()拿到所有组件看谁标注了 @ControllerAdvice
```java
public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext context) {
ListableBeanFactory beanFactory = context;
if (context instanceof ConfigurableApplicationContext) {
// Use internal BeanFactory for potential downcast to ConfigurableBeanFactory above
beanFactory = ((ConfigurableApplicationContext) context).getBeanFactory();
}
List<ControllerAdviceBean> adviceBeans = new ArrayList<>();
for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, Object.class)) {
if (!ScopedProxyUtils.isScopedTarget(name)) { //拿到所有组件看谁标注了 @ControllerAdvice
ControllerAdvice controllerAdvice = beanFactory.findAnnotationOnBean(name, ControllerAdvice.class);
if (controllerAdvice != null) {
// Use the @ControllerAdvice annotation found by findAnnotationOnBean()
// in order to avoid a subsequent lookup of the same annotation.
adviceBeans.add(new ControllerAdviceBean(name, beanFactory, controllerAdvice));
}
}
}
OrderComparator.sort(adviceBeans);
return adviceBeans;
}
```
#### ExceptionHandlerMethodResolver#ExceptionHandlerMethodResolver()
```java
public static final MethodFilter EXCEPTION_HANDLER_METHODS = method ->
AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class);
public ExceptionHandlerMethodResolver(Class<?> handlerType) { //扫描当前这个ControllerAdvice中所有标注了@ExceptionHandler的方法
for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
addExceptionMapping(exceptionType, method);// 每一个方法能处理什么异常类型放入到map里
}
}
}
```
#### ExceptionHandlerMethodResolver#addExceptionMapping()
```java
//每一个方法能处理什么异常类型缓存到Map中。
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 + "}");
}
}
private final Map<Class<? extends Throwable>, Method> mappedMethods = new HashMap<>(16);
```
### 小扩展
你也可以自己写一个异常解析器,比如@YouthExceptionHandler这个注解将异常信息存档比如存到hdfs实时看报错日志。下面是思路
1. 我们的@YouthExceptionHandler实现InitilazingBean接口在PropertiesSet()时,分析所有标注了@YouthExceptionHandler注解的方法在方法执行时进行hdfs存档
## 进阶版@EnableWebMvc+WebMvcConfigurer启动Web功能
### 概述
1. 在以前如果我们自定义自己的组件之后DispatcherServlet就不再用内部提供的默认组件导致我们失去了很多默认功能。
2. 比如在前面我们没讲的视图解析器我在自己测试自定义视图解析器的时候我发现只有自定义的视图解析器了springmvc提供的默认视图解析器就没了。看下图
<img src="https://npm.elemecdn.com/youthlql@1.0.6/spring-sourcecode-v1/chapter_11/image-20211021174122938.png"/>
<img src="https://npm.elemecdn.com/youthlql@1.0.6/spring-sourcecode-v1/chapter_11/image-20211021174320909.png"/>
3. 我们最想要的结果就是既有我们自定义的组件,也有springmvc默认提供的组件。spring也提供了解决方法@EnableWebMvc+WebMvcConfigurer
4. 同时@EnableWebMvc+WebMvcConfigurer也使得扩展自定义组件变的很方便
### 测试类-MvcExtendConfiguration
```java
@EnableWebMvc //启用SpringMVC功能
@Configuration //
public class MvcExtendConfiguration implements WebMvcConfigurer {
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.viewResolver(new MeiNvViewResolver());
//不改源码就如下操作
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("");
viewResolver.setSuffix(".jsp"); //controller的返回值就不用写jsp
registry.viewResolver(viewResolver);
}
}
```
WebMvcConfigurer就是给我们定制的@EnableWebMvc就是启用默认的,下面讲讲原理
### @EnableWebMvc+WebMvcConfigurer如何导入自定义组件
#### 注解@EnableWebMvc
```java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
```
#### DelegatingWebMvcConfiguration
```java
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
//这里是个组合关系
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
@Autowired(required = false) //拿到容器中所有的 WebMvcConfigurer
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
@Override
protected void configureViewResolvers(ViewResolverRegistry registry) {
this.configurers.configureViewResolvers(registry);
}
@Override
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
this.configurers.addArgumentResolvers(argumentResolvers);
}
@Override
protected void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
this.configurers.addReturnValueHandlers(returnValueHandlers);
}
@Override
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
this.configurers.configureMessageConverters(converters);
}
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
this.configurers.extendMessageConverters(converters);
}
//...下面还有很多跟上面三个几乎一模一样的代码
}
```
我们以ViewResolvers为例
#### WebMvcConfigurerComposite
```java
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.configureViewResolvers(registry); //最终这里会调到我们自定义的那个
}
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.addArgumentResolvers(argumentResolvers);
}
}
@Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.addReturnValueHandlers(returnValueHandlers);
}
}
//......
```
1. 这里最终都会调用子类的实现
2. 那么@EnableWebMvc是如何导入很多默认组件。提供默认功能的呢核心就是下面的WebMvcConfigurationSupport
### @EnableWebMvc是如何导入很多默认组件
#### WebMvcConfigurationSupport
WebMvcConfigurationSupport是DelegatingWebMvcConfiguration的父类
```java
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
@Bean
public BeanNameUrlHandlerMapping beanNameHandlerMapping(
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
//......
}
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcValidator") Validator validator) {
//......
}
@Bean
public ViewResolver mvcViewResolver(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
//......
}
@Bean
@Lazy
public HandlerMappingIntrospector mvcHandlerMappingIntrospector() {
return new HandlerMappingIntrospector();
}
@Bean
public LocaleResolver localeResolver() {
return new AcceptHeaderLocaleResolver();
}
@Bean
public ThemeResolver themeResolver() {
return new FixedThemeResolver();
}
@Bean
public FlashMapManager flashMapManager() {
return new SessionFlashMapManager();
}
@Bean
public RequestToViewNameTranslator viewNameTranslator() {
return new DefaultRequestToViewNameTranslator();
}
@Bean
@SuppressWarnings("deprecation")
public RequestMappingHandlerMapping requestMappingHandlerMapping(@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider){
RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
mapping.setOrder(0);
mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
mapping.setContentNegotiationManager(contentNegotiationManager);
mapping.setCorsConfigurations(getCorsConfigurations());
//...
}
//......
}
```
1. 此类里面放了很多默认组件,包括九大组件还有很多我没列举出来的
#### WebMvcConfigurationSupport#getInterceptors()
```java
protected final Object[] getInterceptors(
FormattingConversionService mvcConversionService,
ResourceUrlProvider mvcResourceUrlProvider) {
if (this.interceptors == null) {
InterceptorRegistry registry = new InterceptorRegistry();
addInterceptors(registry); //子类模板先来修改 registry
registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService));
registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider));
this.interceptors = registry.getInterceptors();
}
return this.interceptors.toArray();
}
```
2. 导入了这么多组件这些组件在关键时刻会调用子类重写的方法比如上面面的addInterceptors(registry);会先调用
子类`DelegatingWebMvcConfiguration#addInterceptors()`
#### DelegatingWebMvcConfiguration#addInterceptors()
```java
@Override
protected void addInterceptors(InterceptorRegistry registry) {
this.configurers.addInterceptors(registry); //如果我们继承了WebMvcConfigurer并且重写了addInterceptors这里就会加进来
}
```
3. 也就是每一个组件的关键核心位置都留给了子类来重写
### 为什么自定义视图解析器会覆盖默认的视图解析器?
#### WebMvcConfigurationSupport#mvcViewResolver()
```java
@Bean //给容器中放了视图解析器容器中一开始就有mvcViewResolver这个bean的定义信息
public ViewResolver mvcViewResolver(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
ViewResolverRegistry registry =
new ViewResolverRegistry(contentNegotiationManager, this.applicationContext);
configureViewResolvers(registry); //前面自己扩展配置视图解析器,
//只有不自定义视图解析器,才会给容器放入默认视图解析器
if (registry.getViewResolvers().isEmpty() || this.applicationContext != null) {
String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors( //容器中只有一个组件的定义
this.applicationContext, ViewResolver.class, true, false);
if (true || names.length == 1) { //容器中有视图解析器,会把 InternalResourceViewResolver 放在容器中
registry.getViewResolvers().add(new InternalResourceViewResolver()); //总是把默认的加入进去
}
}
//我们如果扩展配置了完全按照我们configureViewResolvers如果没有配置用默认
ViewResolverComposite composite = new ViewResolverComposite();
composite.setOrder(registry.getOrder());
composite.setViewResolvers(registry.getViewResolvers());
if (this.applicationContext != null) {
composite.setApplicationContext(this.applicationContext);
}
if (this.servletContext != null) {
composite.setServletContext(this.servletContext);
}
return composite;
}
```
所以如果即想要自定义视图解析器,又想要默认的,就下面这样写
```java
@EnableWebMvc //启用SpringMVC功能
@Configuration //
public class MvcExtendConfiguration implements WebMvcConfigurer {
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.viewResolver(new MeiNvViewResolver());
//不改源码就如下操作
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("");
viewResolver.setSuffix(".jsp"); //controller的返回值就不用写jsp
registry.viewResolver(viewResolver);
}
}
```
其它组件可能也会有默认组件失效的情况,可以触类旁通看下源码
1. @EnableWebMvc导入的类会加入SpringMVC的很多核心默认组件,拥有默认功能
2. 这些默认组件在扩展的时候都是留给接口 WebMvcConfigurer访问者模式拿到真正的内容比如上面的registry进行修改
4、MeiNvViewResolver+InternalResourceViewResolver
5、@EnableWebMvc只是开启了SpringMVC最基本的功能,即使是以前自己也要配置默认视图解析器