mirror of
https://github.com/youthlql/JavaYouth.git
synced 2026-04-15 23:03:39 +00:00
1672 lines
47 KiB
Markdown
1672 lines
47 KiB
Markdown
# 第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最基本的功能,即使是以前自己也要配置默认视图解析器
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|