diff --git a/README.md b/README.md index 201b9bd..366097b 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,12 @@ [10.第10章-SpringMVC中的HandlerAdapter源码解析](docs/spring-sourcecode-v1/10.第10章-SpringMVC中的HandlerAdapter源码解析.md) +[11.第11章-SpringMVC异常处理源码和@EnableWebMvc原理](docs/spring-sourcecode-v1/11.第11章-SpringMVC异常处理源码和@EnableWebMvc原理.md) + +[12.第12章-SpringBoot源码-自动配置原理和内嵌Tomcat启动原理](docs/spring-sourcecode-v1/12.第12章-SpringBoot源码-自动配置原理和内嵌Tomcat启动原理.md) + + + # Netty ## 入门 diff --git a/docs/spring-sourcecode-v1/11.第11章-SpringMVC异常处理源码和@EnableWebMvc原理.md b/docs/spring-sourcecode-v1/11.第11章-SpringMVC异常处理源码和@EnableWebMvc原理.md new file mode 100644 index 0000000..d807514 --- /dev/null +++ b/docs/spring-sourcecode-v1/11.第11章-SpringMVC异常处理源码和@EnableWebMvc原理.md @@ -0,0 +1,1671 @@ +# 第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() + + + + + +报了一个缺少参数的错误,下面看看怎么处理的 + +#### 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); + } + } +``` + + + + + +#### 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; //如果所有的异常解析器都不能解析就直接抛出这个异常 + } +``` + + + + + +##### 三个异常解析器概述 + + + +- 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 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 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 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标注的类,所以这里会返回空 + + + + + +接着返回 + +#### 返回到DispatcherServlet#processHandlerException() + +准备循环第二个异常解析器 + + + + + +接下来还是父类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() + + + + + + + + + +#### 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() + + + +看到这里返回了一个空的ModelAndView,并不是NULL + +#### 返回到DispatcherServlet#processDispatchResult() + + + +最后下面再处理下拦截器 + + + + + +最终结束 + +#### 页面效果 + + + + + + + +### 传参但报错的情况 + +我们这样写:http://localhost:8080/springmvc_source_test/hello?name=zhangsan&i=0 + +> 前面讲的不再重复 + +#### DispatcherServlet#doDispatch() + + + + + +#### DispatcherServlet#processHandlerException() + +到了第三个异常解析器也依然处理不了,于是出现了一个谁都处理不了的异常 + + + +然后就抛出此异常 + + + +#### 返回到DispatcherServlet#processDispatchResult()直接炸了 + + + + + +在这一步抛出了异常,整个方法直接炸了,后面的逻辑全都不走了 + + + + + +#### 返回到DispatcherServlet#doDispatch() + + + + + +#### 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 + + + +#### 页面效果 + + + +这就是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; + } +``` + + + + + + + + + +```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(); + } +``` + + + +页面效果 + + + + + + + + + + + +## 最常用的注解版异常解析器 @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() + + + +### 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 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 flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); + RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); + } + return mav; + } + } +``` + + + +### ExceptionHandlerExceptionResolver#getExceptionHandlerMethod() + + + +### ExceptionHandlerMethodResolver#getMappedMethod() + +最终调到这里 + +```java + private Method getMappedMethod(Class exceptionType) { + List> matches = new ArrayList<>(); + for (Class 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; + } + } +``` + + + + + + + +### 返回到ExceptionHandlerExceptionResolver#doResolveHandlerMethodException() + + + + + + + + + +### ServletInvocableHandlerMethod#invokeAndHandle() + + + + + +### 小总结 + +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 resolvers = getDefaultArgumentResolvers(); + this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); + } + if (this.returnValueHandlers == null) { + List handlers = getDefaultReturnValueHandlers(); + this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers); + } + } +``` + + + +还是咱们的老朋友InitializingBean,一样的逻辑,不讲了 + + + +#### ExceptionHandlerExceptionResolver#initExceptionHandlerAdviceCache() + +```java + private void initExceptionHandlerAdviceCache() { + if (getApplicationContext() == null) { + return; + } + //找到所有的 ControllerAdviceBean(标注了@ControllerAdvice注解的类) 、 + List 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 findAnnotatedBeans(ApplicationContext context) { + ListableBeanFactory beanFactory = context; + if (context instanceof ConfigurableApplicationContext) { + // Use internal BeanFactory for potential downcast to ConfigurableBeanFactory above + beanFactory = ((ConfigurableApplicationContext) context).getBeanFactory(); + } + List 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 exceptionType : detectExceptionMappings(method)) { + addExceptionMapping(exceptionType, method);// 每一个方法能处理什么异常类型,放入到map里 + } + } + } +``` + + + +#### ExceptionHandlerMethodResolver#addExceptionMapping() + +```java + //每一个方法能处理什么异常类型,缓存到Map中。 + private void addExceptionMapping(Class 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, Method> mappedMethods = new HashMap<>(16); +``` + + + + + + + +### 小扩展 + +你也可以自己写一个异常解析器,比如@YouthExceptionHandler,这个注解将异常信息存档,比如存到hdfs,实时看报错日志。下面是思路: + +1. 我们的@YouthExceptionHandler实现InitilazingBean接口,在PropertiesSet()时,分析所有标注了@YouthExceptionHandler注解的方法,在方法执行时进行hdfs存档 + + + + + + + +## 进阶版@EnableWebMvc+WebMvcConfigurer启动Web功能 + +### 概述 + +1. 在以前如果我们自定义自己的组件之后,DispatcherServlet就不再用内部提供的默认组件,导致我们失去了很多默认功能。 +2. 比如在前面我们没讲的视图解析器,我在自己测试自定义视图解析器的时候,我发现只有自定义的视图解析器了,springmvc提供的默认视图解析器就没了。看下图 + + + + + +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 configurers) { + if (!CollectionUtils.isEmpty(configurers)) { + this.configurers.addWebMvcConfigurers(configurers); + } + } + + + @Override + protected void configureViewResolvers(ViewResolverRegistry registry) { + this.configurers.configureViewResolvers(registry); + } + + @Override + protected void addArgumentResolvers(List argumentResolvers) { + this.configurers.addArgumentResolvers(argumentResolvers); + } + + @Override + protected void addReturnValueHandlers(List returnValueHandlers) { + this.configurers.addReturnValueHandlers(returnValueHandlers); + } + + @Override + protected void configureMessageConverters(List> converters) { + this.configurers.configureMessageConverters(converters); + } + + @Override + protected void extendMessageConverters(List> 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 argumentResolvers) { + for (WebMvcConfigurer delegate : this.delegates) { + delegate.addArgumentResolvers(argumentResolvers); + } + } + + @Override + public void addReturnValueHandlers(List 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最基本的功能,即使是以前自己也要配置默认视图解析器 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/spring-sourcecode-v1/12.第12章-SpringBoot源码-自动配置原理和内嵌Tomcat启动原理.md b/docs/spring-sourcecode-v1/12.第12章-SpringBoot源码-自动配置原理和内嵌Tomcat启动原理.md new file mode 100644 index 0000000..f707eff --- /dev/null +++ b/docs/spring-sourcecode-v1/12.第12章-SpringBoot源码-自动配置原理和内嵌Tomcat启动原理.md @@ -0,0 +1,1374 @@ + + +# 第12章-SpringBoot源码-自动配置原理和内嵌Tomcat启动原理 + +## 嵌入式Tomcat与Spring整合 + +### 测试类 + +#### 测试项目目录 + +```java +springboot-first +├── common_usetree.txt +├── pom.xml +├── springboot-first.iml +├── src/ +| ├── main/ +| | ├── java/ +| | | └── cn/ +| | | └── imlql/ +| | | └── boot/ +| | | ├── config/ +| | | | ├── SpringConfig.java +| | | | └── SpringMVCConfig.java +| | | ├── controller/ +| | | | └── HelloController.java +| | | ├── Main.java +| | | ├── QuickAppStarter.java +| | └── resources/ +| └── test/ +| └── java/ +└── work/ + └── Tomcat/ + └── localhost/ + └── boot/ +``` + +#### pom.xml + +```xml + + + 4.0.0 + + org.example + springboot-first + 1.0-SNAPSHOT + + + 8 + 8 + + + + + + org.springframework + spring-webmvc + 5.3.5 + + + + + org.apache.tomcat.embed + tomcat-embed-core + 8.5.64 + + + + + org.apache.tomcat.embed + tomcat-embed-jasper + 8.5.64 + + + + + + +``` + +#### Main + +```java +package cn.imlql.boot; + +import org.apache.catalina.Context; +import org.apache.catalina.LifecycleException; +import org.apache.catalina.startup.Tomcat; + +public class Main { + + public static void main(String[] args) throws LifecycleException { + //自己写Tomcat的启动源码 + Tomcat tomcat = new Tomcat(); + + tomcat.setPort(8888); + tomcat.setHostname("localhost"); + tomcat.setBaseDir("."); + // user.dir代表当前工作目录 + Context context = tomcat.addWebapp("/boot", System.getProperty("user.dir") + "/src/main"); + tomcat.start();//启动tomcat 注解版MVC利用Tomcat SPI机制 + + + tomcat.getServer().await(); //服务器等待 + + } + +} +``` + + + +#### QuickAppStarter + +```java +package cn.imlql.boot; + +import cn.imlql.boot.config.SpringConfig; + +import cn.imlql.boot.config.SpringMVCConfig; +import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; + +import javax.servlet.ServletRegistration; + + +/** + * 最快速的整合注解版SpringMVC和Spring的 + */ +public class QuickAppStarter extends AbstractAnnotationConfigDispatcherServletInitializer { + @Override //根容器的配置(Spring的配置文件===Spring的配置类) + protected Class[] getRootConfigClasses() { + return new Class[]{SpringConfig.class}; + } + + @Override //web容器的配置(SpringMVC的配置文件===SpringMVC的配置类) + protected Class[] getServletConfigClasses() { + return new Class[]{SpringMVCConfig.class}; + } + + @Override //Servlet的映射,DispatcherServlet的映射路径 + protected String[] getServletMappings() { + return new String[]{"/"}; + } + + @Override + protected void customizeRegistration(ServletRegistration.Dynamic registration) { +// super.customizeRegistration(registration); + +// registration.addMapping("");// + } +} +``` + +#### SpringConfig + +```java +@ComponentScan(value = "cn.imlql.boot",excludeFilters = { + @ComponentScan.Filter(type= FilterType.ANNOTATION,value = Controller.class) +}) +@Configuration +public class SpringConfig { + //Spring的父容器 + +} +``` + +#### SpringMVCConfig + +```java +@ComponentScan(value = "cn.imlql.boot",includeFilters = { + @ComponentScan.Filter(type= FilterType.ANNOTATION,value = Controller.class) +},useDefaultFilters = false) +public class SpringMVCConfig { + //SpringMVC的子容器,能扫描的Spring容器中的组件 + + +} +``` + +#### HelloController + +```java +package cn.imlql.boot.controller; + + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class HelloController { + + @GetMapping("/hello66") + public String hello(){ + + return "66666666~~~~~"; + } +} +``` + + + +### 测试效果 + + + + + + + +## 再来简单捋一下SPI如何启动的Web容器 + +1. 我们看到上面很神奇的效果,我们自己写代码做到了类似SpringBoot的效果,不需要配置本地tomcat,直接就把Web应用启动起来了。 +2. 这里只是简单的捋一下,详细过程在前面讲过 + +### META-INF/services + + + + + + + + + + + +### AbstractAnnotationConfigDispatcherServletInitializer继承树 + +我们的QuickAppStarter实现了 + + + + + + + + + + + +### SpringServletContainerInitializer + +利用Java的SPI加载META-INF/services下的实现类 + + + + + +```java + +@HandlesTypes(WebApplicationInitializer.class) +public class SpringServletContainerInitializer implements ServletContainerInitializer { + + /** + * Delegate the {@code ServletContext} to any {@link WebApplicationInitializer} + * implementations present on the application classpath. + *

Because this class declares @{@code HandlesTypes(WebApplicationInitializer.class)}, + * Servlet 3.0+ containers will automatically scan the classpath for implementations + * of Spring's {@code WebApplicationInitializer} interface and provide the set of all + * such types to the {@code webAppInitializerClasses} parameter of this method. + *

If no {@code WebApplicationInitializer} implementations are found on the classpath, + * this method is effectively a no-op. An INFO-level log message will be issued notifying + * the user that the {@code ServletContainerInitializer} has indeed been invoked but that + * no {@code WebApplicationInitializer} implementations were found. + *

Assuming that one or more {@code WebApplicationInitializer} types are detected, + * they will be instantiated (and sorted if the @{@link + * org.springframework.core.annotation.Order @Order} annotation is present or + * the {@link org.springframework.core.Ordered Ordered} interface has been + * implemented). Then the {@link WebApplicationInitializer#onStartup(ServletContext)} + * method will be invoked on each instance, delegating the {@code ServletContext} such + * that each instance may register and configure servlets such as Spring's + * {@code DispatcherServlet}, listeners such as Spring's {@code ContextLoaderListener}, + * or any other Servlet API componentry such as filters. + * @param webAppInitializerClasses all implementations of + * {@link WebApplicationInitializer} found on the application classpath + * @param servletContext the servlet context to be initialized + * @see WebApplicationInitializer#onStartup(ServletContext) + * @see AnnotationAwareOrderComparator + */ + public void onStartup(@Nullable Set> webAppInitializerClasses, ServletContext servletContext) + throws ServletException { + + List initializers = Collections.emptyList(); + + if (webAppInitializerClasses != null) { + initializers = new ArrayList<>(webAppInitializerClasses.size()); + for (Class waiClass : webAppInitializerClasses) { + // Be defensive: Some servlet containers provide us with invalid classes, + // no matter what @HandlesTypes says... 所有的非接口非抽象的WebApplicationInitializer实现类 + if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && + WebApplicationInitializer.class.isAssignableFrom(waiClass)) { + try { + initializers.add((WebApplicationInitializer) //集合负责保存满足上面条件的类 + ReflectionUtils.accessibleConstructor(waiClass).newInstance()); + } + catch (Throwable ex) { + throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex); + } + } + } + } + + if (initializers.isEmpty()) { + servletContext.log("No Spring WebApplicationInitializer types detected on classpath"); + return; + } + //下面会遍历所有满足要求的WebApplicationInitializer,调用他们的onStartup + servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath"); + AnnotationAwareOrderComparator.sort(initializers); + for (WebApplicationInitializer initializer : initializers) { + initializer.onStartup(servletContext); //所有的 WebApplicationInitializer 的 onStartup + } + } + + +} +``` + + + +### @HandlesTypes + +1. 其中@HandlesTypes注解表示可以处理的类,在`onStartup` 方法中,可以通过`Set> webAppInitializerClasses` 获取得到。 +2. @HandlesTypes属于sun公司对Servlet定义的规范,包括tomcat,jetty等服务器都对它有不同的实现 +3. tomcat的具体实现咱们这里不深究,可以肯定的是一定用到了Java的SPI,如下。 + +```java +ServiceLoader load = ServiceLoader.load(WebApplicationInitializer.class); +``` + +4. tomcat具体对于@HandlesTypes一定是和上面类似甚至是一样的代码来加载WebApplicationInitializer的实现 + + + +因为咱们的QuickAppStarter继承的AbstractAnnotationConfigDispatcherServletInitializer也属于WebApplicationInitializer,所以它就会被加载 + + + +### Servlet相关规范 + +1. tomcat会遵循sun公司的规范给每一个Servlet创建对象 +2. 所以DispatcherServlet肯定也会创建对象 + +3. Servlet的规范 + 1. Servlet创建对象 + 2. Servlet调用Init初始化 + 3. 每次请求调用service处理 + 4. tomcat停止的时候调用destroy进行销毁 +4. Serlvet是被谁调用开始初始化的属于tomcat的源码,我们这里不研究 + + + + + +### DispatcherServlet + +1. spring-web中有一个叫DispatcherServlet的类,很明显他是一个Servlet,所以tomcat启动的时候就会加载它,加载它的话当然是从父类一层一层加载的 + + + +2. 也就是说是从Servlet最顶层开始一层一层往下面调用 +3. 最终我们发现FrameworkServlet里有一个核心方法 + + + +### FrameworkServlet + +```java + /** 追踪看web应用启动做了什么。 + * Overridden method of {@link HttpServletBean}, invoked after any bean properties + * have been set. Creates this servlet's WebApplicationContext. + */ + @Override + protected final void initServletBean() throws ServletException { + getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'"); + if (logger.isInfoEnabled()) { + logger.info("Initializing Servlet '" + getServletName() + "'"); + } + long startTime = System.currentTimeMillis(); + + try { + this.webApplicationContext = initWebApplicationContext(); //初始化WebIOC容器,那我们想一下大概率是在这里启动的IOC容器 + initFrameworkServlet(); //这又是留给子类的 + } + catch (ServletException | RuntimeException ex) { + logger.error("Context initialization failed", ex); + throw ex; + } + + if (logger.isDebugEnabled()) { + String value = this.enableLoggingRequestDetails ? + "shown which may lead to unsafe logging of potentially sensitive data" : + "masked to prevent unsafe logging of potentially sensitive data"; + logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails + + "': request parameters and headers will be " + value); + } + + if (logger.isInfoEnabled()) { + logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms"); + } + } +``` + + + +1. `this.webApplicationContext = initWebApplicationContext();`没错,看名字就知道是从这里开始启动Web容器的。 + +2. 然后我们就自己搭建了一个MySpringBoot项目,我们这个项目和SpringBoot官方的区别就是官方帮我们封装了很多自动配置类,帮我们给容器中放了很多组件,使得我们感觉开发更方便了。 + + + + + + + +## SpringBoot启动导入了很多自动配置类 + +为什么 @SpringBootApplication +SpringApplication.run(SpringbootSourceApplication.class, args);能把Spring+SpringMVC+Tomcat+其他场景都整合进来 + + + +```java +@SpringBootApplication +public class SpringbootSourceApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringbootSourceApplication.class, args); + } +} +``` + + + +### pom.xml + +```java + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.4 + + + com.atuigu.boot + springboot-source + 0.0.1-SNAPSHOT + springboot-source + Demo project for Spring Boot + + 1.8 + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +1. 首先是在Maven依赖上的支持,spring-boot-starter-xxx的这种依赖内部又导入了很多的依赖,包括上面说的嵌入式tomcat,以及Spring,SpringMVC + + + + + +### @SpringBootApplication原理 + +```java +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@SpringBootConfiguration +@EnableAutoConfiguration +@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), + @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) +public @interface SpringBootApplication { + + //...... +} +``` + + + +### @SpringBootConfiguration + +```java +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Configuration +public @interface SpringBootConfiguration { + + @AliasFor(annotation = Configuration.class) + boolean proxyBeanMethods() default true; + +} +``` + +这个注解的功能就相当于@Configuration + +### @EnableAutoConfiguration + +```java +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@AutoConfigurationPackage +@Import(AutoConfigurationImportSelector.class) +public @interface EnableAutoConfiguration { + + //...... +} +``` + +### @AutoConfigurationPackage + +```java +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@Import(AutoConfigurationPackages.Registrar.class) +public @interface AutoConfigurationPackage { + + //...... +} +``` + +## @AutoConfigurationPackage导入的AutoConfigurationPackages.Registrar类 + + + +### AutoConfigurationPackages.Registrar#registerBeanDefinitions() + +```java +static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { + + @Override + public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { + register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0])); + } + + @Override + public Set determineImports(AnnotationMetadata metadata) { + return Collections.singleton(new PackageImports(metadata)); + } + +} +``` + + + + + + + +这里就是得到要注册哪些包下的信息,F7进入此方法。从这里你也能知道SpringBoot默认导的包是SpringbootXXXApplication所在的那个包层级 + +### AutoConfigurationPackages#register() + +```java + public static void register(BeanDefinitionRegistry registry, String... packageNames) { + if (registry.containsBeanDefinition(BEAN)) { + BasePackagesBeanDefinition beanDefinition = (BasePackagesBeanDefinition) registry.getBeanDefinition(BEAN); + beanDefinition.addBasePackages(packageNames); + } + else { + registry.registerBeanDefinition(BEAN, new BasePackagesBeanDefinition(packageNames)); + } + } + + private static final String BEAN = AutoConfigurationPackages.class.getName(); +``` + + + +### AutoConfigurationPackages.BasePackagesBeanDefinition + +```java +static final class BasePackagesBeanDefinition extends GenericBeanDefinition { + + private final Set basePackages = new LinkedHashSet<>(); + + BasePackagesBeanDefinition(String... basePackages) { + setBeanClass(BasePackages.class); + setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + addBasePackages(basePackages);//就是要指定最终要扫哪些包 + } + + @Override + public Supplier getInstanceSupplier() { + return () -> new BasePackages(StringUtils.toStringArray(this.basePackages)); + } + + private void addBasePackages(String[] additionalBasePackages) { + this.basePackages.addAll(Arrays.asList(additionalBasePackages)); + } + +} +``` + + + +### BeanDefinitionMap里的数据 + + + +此时beanDefinitionMap已经有了AutoConfigurationPackages,当处理到这个Bean的时候,最终发现这是个包导入的组件,最终就会导入这个包里面的组件 + + + +## @EnableAutoConfiguration注解导入的AutoConfigurationImportSelector类 + + + +```java +public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, + ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered { + + //...... + +} +``` + +1. AutoConfigurationImportSelector是用@Import注解导进来的 +2. AutoConfigurationImportSelector根据它的名字很明显它是一个ImportSelector的实现类,了解ImportSelector的都应该知道它是通过`selectImports()`方法来实现导入哪些组件的 + + + +### AutoConfigurationImportSelector.AutoConfigurationGroup#process() + + + +F7进入此方法 + +### AutoConfigurationImportSelector#getAutoConfigurationEntry() + + + +F7进入此方法 + +### AutoConfigurationImportSelector#getCandidateConfigurations() + +```java + protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { + List configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), + getBeanClassLoader()); + Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + + "are using a custom packaging, make sure that file is correct."); + return configurations; + } + + protected Class getSpringFactoriesLoaderFactoryClass() { + return EnableAutoConfiguration.class; + } +``` + + + +### SpringFactoriesLoader#loadFactoryNames() + + + + + +### SpringFactoriesLoader#loadSpringFactories()加载类路径下META-INF/spring.factories的资源 + +```java + public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; + + private static Map> loadSpringFactories(ClassLoader classLoader) { + Map> result = cache.get(classLoader); + if (result != null) { + return result; + } + + result = new HashMap<>(); + try { + //加载类路径下META-INF/spring.factories的资源 + Enumeration urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION); + while (urls.hasMoreElements()) { + URL url = urls.nextElement(); + UrlResource resource = new UrlResource(url); + Properties properties = PropertiesLoaderUtils.loadProperties(resource); + for (Map.Entry entry : properties.entrySet()) { + String factoryTypeName = ((String) entry.getKey()).trim(); + String[] factoryImplementationNames = + StringUtils.commaDelimitedListToStringArray((String) entry.getValue()); + for (String factoryImplementationName : factoryImplementationNames) { + result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>()) + .add(factoryImplementationName.trim()); + } + } + } + + // Replace all lists with unmodifiable lists containing unique elements + result.replaceAll((factoryType, implementations) -> implementations.stream().distinct() + .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList))); + cache.put(classLoader, result); + } + catch (IOException ex) { + throw new IllegalArgumentException("Unable to load factories from location [" + + FACTORIES_RESOURCE_LOCATION + "]", ex); + } + return result; + } +``` + +然后咱们就要找类路径下META-INF/spring.factories,并且名字是`org.springframework.boot.autoconfigure.EnableAutoConfiguration`的资源,这有点类似于SPI机制 + +### spring.factories + +1. 不止这个包下有spring.factories文件,可能很多第三方的starter都有,这个包下的这些类只是Spring能想到的常用的组件。 + + + + + +### 返回到AutoConfigurationImportSelector#getAutoConfigurationEntry() + +1. 自此这130个组件会先被放到List里,但不一定全部导入 + + + +2. 然后这里会有一个过滤`configurations = getConfigurationClassFilter().filter(configurations);` + + + +3. 最终这里只会有23个组件被放到容器中,为什么这里要过滤?看下面的@ConditionalOnClass注解,当容器中有KafkaTemplate这个类时才会导入KafkaAutoConfiguration,而KafkaTemplate这个类只有导入了kafka相关jar包才会有。意思就是你只有在maven中导入了相关jar包,才会给你自动配置 + + + + + + + +还有下面这个SpringMvc的,当你有DispatcherServlet这个类的时候,才会给你自动配置web相关的东西。而有DispatcherServlet类就代表你导入了web的相关依赖 + + + + + + + + + +## 容器刷新在onRefresh步骤会启动Tomcat + + + +1. 在刚开始的时候我们自己实现的简易SpringBoot是利用SPI机制启动的Web容器 +2. 其实我们还要一个方法就是自己创建一个DispatcherServlet注册到Tomcat里,然后Tomcat就会调用Servlet相关初始化,最终调用到FrameworkServlet类里调用的`this.webApplicationContext = initWebApplicationContext();`,**进而启动Web容器。在SpringBoot里使用的就是这种方式启动Web容器** + + + +### DispatcherServletAutoConfiguration + +```java +package org.springframework.boot.autoconfigure.web.servlet; + + +@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) +@Configuration(proxyBeanMethods = false) +@ConditionalOnWebApplication(type = Type.SERVLET) +@ConditionalOnClass(DispatcherServlet.class) +//这里就是DispatcherServlet在自动配置之前,先自动配置ServletWebServerFactoryAutoConfiguration +@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class) +public class DispatcherServletAutoConfiguration { + + /** + * The bean name for a DispatcherServlet that will be mapped to the root URL "/". + */ + public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet"; + + /** + * The bean name for a ServletRegistrationBean for the DispatcherServlet "/". + */ + public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration"; + + @Configuration(proxyBeanMethods = false) + @Conditional(DefaultDispatcherServletCondition.class) + @ConditionalOnClass(ServletRegistration.class) + @EnableConfigurationProperties(WebMvcProperties.class) + protected static class DispatcherServletConfiguration { + + @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) + public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) { + DispatcherServlet dispatcherServlet = new DispatcherServlet(); + dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest()); + dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest()); + dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound()); + dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents()); + dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails()); + return dispatcherServlet; + } + + @Bean + @ConditionalOnBean(MultipartResolver.class) + @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) + public MultipartResolver multipartResolver(MultipartResolver resolver) { + // Detect if the user has created a MultipartResolver but named it incorrectly + return resolver; + } + + } + + @Configuration(proxyBeanMethods = false) + @Conditional(DispatcherServletRegistrationCondition.class) + @ConditionalOnClass(ServletRegistration.class) + @EnableConfigurationProperties(WebMvcProperties.class) + @Import(DispatcherServletConfiguration.class) + protected static class DispatcherServletRegistrationConfiguration { + + @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME) + @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) + public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet, + WebMvcProperties webMvcProperties, ObjectProvider multipartConfig) { + DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, + webMvcProperties.getServlet().getPath()); + registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME); + registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup()); + multipartConfig.ifAvailable(registration::setMultipartConfig); + return registration; + } + + } + + @Order(Ordered.LOWEST_PRECEDENCE - 10) + private static class DefaultDispatcherServletCondition extends SpringBootCondition { + // ...... + } + + @Order(Ordered.LOWEST_PRECEDENCE - 10) + private static class DispatcherServletRegistrationCondition extends SpringBootCondition { + // ...... + } + +} +``` + +1. `@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)` +2. 这里就是最关键的,@AutoConfigureAfter注解看名字就能大概明白是什么意思,这里就是DispatcherServlet在自动配置之前,先自动配置ServletWebServerFactoryAutoConfiguration + + + + + +### ServletWebServerFactoryAutoConfiguration + +```java +@Configuration(proxyBeanMethods = false) +@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) +@ConditionalOnClass(ServletRequest.class) +@ConditionalOnWebApplication(type = Type.SERVLET) +@EnableConfigurationProperties(ServerProperties.class) +@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, //这里是最核心的 + ServletWebServerFactoryConfiguration.EmbeddedTomcat.class, //这里都是嵌入式服务器 + ServletWebServerFactoryConfiguration.EmbeddedJetty.class, //这里都是嵌入式服务器 + ServletWebServerFactoryConfiguration.EmbeddedUndertow.class }) //这里都是嵌入式服务器 +public class ServletWebServerFactoryAutoConfiguration { + // ...... +} +``` + + + + + +### ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar + +```java + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, + BeanDefinitionRegistry registry) { + if (this.beanFactory == null) { + return; + } + //给容器中注册一个服务器的后置处理器 + registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor", + WebServerFactoryCustomizerBeanPostProcessor.class, + WebServerFactoryCustomizerBeanPostProcessor::new); + registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor", + ErrorPageRegistrarBeanPostProcessor.class, ErrorPageRegistrarBeanPostProcessor::new); + } +``` + + + + + +### ServletWebServerFactoryConfiguration + +```java +package org.springframework.boot.autoconfigure.web.servlet; + +@Configuration(proxyBeanMethods = false) +class ServletWebServerFactoryConfiguration { + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class }) + @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT) + static class EmbeddedTomcat { + + @Bean + TomcatServletWebServerFactory tomcatServletWebServerFactory( + ObjectProvider connectorCustomizers, + ObjectProvider contextCustomizers, + ObjectProvider> protocolHandlerCustomizers) { + TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(); + factory.getTomcatConnectorCustomizers() + .addAll(connectorCustomizers.orderedStream().collect(Collectors.toList())); + factory.getTomcatContextCustomizers() + .addAll(contextCustomizers.orderedStream().collect(Collectors.toList())); + factory.getTomcatProtocolHandlerCustomizers() + .addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList())); + return factory; + } + + } + + /** + * Nested configuration if Jetty is being used. + */ + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class }) + @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT) + static class EmbeddedJetty { + + @Bean + JettyServletWebServerFactory JettyServletWebServerFactory( + ObjectProvider serverCustomizers) { + JettyServletWebServerFactory factory = new JettyServletWebServerFactory(); + factory.getServerCustomizers().addAll(serverCustomizers.orderedStream().collect(Collectors.toList())); + return factory; + } + + } + + /** + * Nested configuration if Undertow is being used. + */ + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class }) + @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT) + static class EmbeddedUndertow { + + @Bean + UndertowServletWebServerFactory undertowServletWebServerFactory( + ObjectProvider deploymentInfoCustomizers, + ObjectProvider builderCustomizers) { + UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory(); + factory.getDeploymentInfoCustomizers() + .addAll(deploymentInfoCustomizers.orderedStream().collect(Collectors.toList())); + factory.getBuilderCustomizers().addAll(builderCustomizers.orderedStream().collect(Collectors.toList())); + return factory; + } + + @Bean + UndertowServletWebServerFactoryCustomizer undertowServletWebServerFactoryCustomizer( + ServerProperties serverProperties) { + return new UndertowServletWebServerFactoryCustomizer(serverProperties); + } + + } + +} +``` + +ServletWebServerFactory:服务器工厂,我们可以自己放Serlvet容器,我们自己放了就会用我们自己的 + + + + + +XXXProvider的意思就是这些方法的参数都是从容器中拿,如果你自定义了,就用自定义的 + + + + + + + +### TomcatServletWebServerFactory自己new Tomcat() + +```java + public WebServer getWebServer(ServletContextInitializer... initializers) { + if (this.disableMBeanRegistry) { + Registry.disableRegistry(); + } + Tomcat tomcat = new Tomcat(); + File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat"); + tomcat.setBaseDir(baseDir.getAbsolutePath()); + Connector connector = new Connector(this.protocol); + connector.setThrowOnFailure(true); + tomcat.getService().addConnector(connector); + customizeConnector(connector); + tomcat.setConnector(connector); + tomcat.getHost().setAutoDeploy(false); + configureEngine(tomcat.getEngine()); + for (Connector additionalConnector : this.additionalTomcatConnectors) { + tomcat.getService().addConnector(additionalConnector); + } + prepareContext(tomcat.getHost(), initializers); + return getTomcatWebServer(tomcat); + } +``` + +#### Debug调用栈 + + + + + +#### ServletWebServerApplicationContext + +```java + @Override + protected void onRefresh() { + super.onRefresh(); + try { + createWebServer(); + } + catch (Throwable ex) { + throw new ApplicationContextException("Unable to start web server", ex); + } + } + + + private void createWebServer() { + WebServer webServer = this.webServer; + ServletContext servletContext = getServletContext(); + if (webServer == null && servletContext == null) { + StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create"); + ServletWebServerFactory factory = getWebServerFactory(); + createWebServer.tag("factory", factory.getClass().toString()); + //最终在这里调用了TomcatServletWebServerFactory#getWebServer() + this.webServer = factory.getWebServer(getSelfInitializer()); + createWebServer.end(); + getBeanFactory().registerSingleton("webServerGracefulShutdown", + new WebServerGracefulShutdownLifecycle(this.webServer)); + getBeanFactory().registerSingleton("webServerStartStop", + new WebServerStartStopLifecycle(this, this.webServer)); + } + else if (servletContext != null) { + try { + getSelfInitializer().onStartup(servletContext); + } + catch (ServletException ex) { + throw new ApplicationContextException("Cannot initialize servlet context", ex); + } + } + initPropertySources(); + } +``` + + + + + + + +#### 返回到getWebServer并且调用prepareContext() + + + + + + + +注意看`ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);`这一步就是Tomcat启动加载DispatcherServlet的时机 + + + + + + + +## Tomcat启动加载DispatcherServlet的时机 + +```java +package org.springframework.boot.autoconfigure.web.servlet; + +@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) +@Configuration(proxyBeanMethods = false) +@ConditionalOnWebApplication(type = Type.SERVLET) +@ConditionalOnClass(DispatcherServlet.class) +@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class) +public class DispatcherServletAutoConfiguration { + + /** + * The bean name for a DispatcherServlet that will be mapped to the root URL "/". + */ + public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet"; + + /** + * The bean name for a ServletRegistrationBean for the DispatcherServlet "/". + */ + public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration"; + + @Configuration(proxyBeanMethods = false) + @Conditional(DefaultDispatcherServletCondition.class) + @ConditionalOnClass(ServletRegistration.class) + @EnableConfigurationProperties(WebMvcProperties.class) + protected static class DispatcherServletConfiguration { + + @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) + public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) { + DispatcherServlet dispatcherServlet = new DispatcherServlet(); + dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest()); + dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest()); + dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound()); + dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents()); + dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails()); + return dispatcherServlet; + } + + @Bean + @ConditionalOnBean(MultipartResolver.class) + @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) + public MultipartResolver multipartResolver(MultipartResolver resolver) { + // Detect if the user has created a MultipartResolver but named it incorrectly + return resolver; + } + + } + + @Configuration(proxyBeanMethods = false) + @Conditional(DispatcherServletRegistrationCondition.class) + @ConditionalOnClass(ServletRegistration.class) + @EnableConfigurationProperties(WebMvcProperties.class) + @Import(DispatcherServletConfiguration.class) + protected static class DispatcherServletRegistrationConfiguration { + + @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME) + @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) + //注意看DispatcherServletRegistrationBean + public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet, + WebMvcProperties webMvcProperties, ObjectProvider multipartConfig) { + DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, + webMvcProperties.getServlet().getPath()); + registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME); + registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup()); + multipartConfig.ifAvailable(registration::setMultipartConfig); + return registration; + } + + } + + // ...... +} +``` + + + +### DispatcherServletRegistrationBean继承树 + +我们发现DispatcherServletRegistrationBean它是一个ServletContextInitializer + + + + + +F7进入上面说的这个`configureContext(context, initializersToUse);` + + + + + + + + + +### TomcatServletWebServerFactory#configureContext() + +```java + protected void configureContext(Context context, ServletContextInitializer[] initializers) { + TomcatStarter starter = new TomcatStarter(initializers);//注意在这里把这些ServletContextInitializer给了TomcatStarter + // ...... + for (TomcatContextCustomizer customizer : this.tomcatContextCustomizers) { + customizer.customize(context); + } + } +``` + + + +### 返回到TomcatServletWebServerFactory#getWebServer() + +然后返回到getWebServer调用最后一步 + + + + + + + +### TomcatStarter#onStartup() + +最终初始化会调用到onStartup() + + + +```java + TomcatStarter(ServletContextInitializer[] initializers) { + this.initializers = initializers; + } + + @Override + public void onStartup(Set> classes, ServletContext servletContext) throws ServletException { + try { + for (ServletContextInitializer initializer : this.initializers) { + initializer.onStartup(servletContext); + } + } + catch (Exception ex) { + this.startUpException = ex; + // Prevent Tomcat from logging and re-throwing when we know we can + // deal with it in the main thread, but log for information here. + if (logger.isErrorEnabled()) { + logger.error("Error starting Tomcat context. Exception: " + ex.getClass().getName() + ". Message: " + + ex.getMessage()); + } + } + } +``` + + + +### RegistrationBean#onStartup() + + + +F7进入,省略到一些不重要的方法 + + + +### DynamicRegistrationBean#register() + +```java + protected final void register(String description, ServletContext servletContext) { + D registration = addRegistration(description, servletContext); + if (registration == null) { + logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)"); + return; + } + configure(registration); + } +``` + + + +### ServletRegistration.Dynamic#addRegistration()将dispatcherServlet放入Tomcat容器中 + + + +servletContext这个就是tomcat容器 + +1. ServletRegistration.Dynamic#addRegistration()将dispatcherServlet放入tomcat容器中 + +2. 然后Tomcat启动之后自然就调用Servelt初始化,进而调到了dispatcherServlet,然后就是之前讲过的初始化web容器。 + + `this.webApplicationContext = initWebApplicationContext();` + + + +得到下面的结论 + +```java +@SpringBootApplication +public class SpringbootSourceApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringbootSourceApplication.class, args); + } + + @Bean //所有的xxxRegistrationBean都是允许我们注册原生的Servlet组件进去, + //利用 ServletContextInitializer在Tomcat启动完成以后进行回调的机制 + ServletRegistrationBean registrationBean(){ + + ServletRegistrationBean registrationBean = new ServletRegistrationBean<>(new HelloServlet()); + registrationBean.addUrlMappings("/he66"); + return registrationBean; + } + +} +``` + + + +## SpringApplication的run方法 + +```java + public ConfigurableApplicationContext run(String... args) { + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + DefaultBootstrapContext bootstrapContext = createBootstrapContext(); + ConfigurableApplicationContext context = null; + configureHeadlessProperty(); + SpringApplicationRunListeners listeners = getRunListeners(args); + listeners.starting(bootstrapContext, this.mainApplicationClass); + try { + ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); + ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments); + configureIgnoreBeanInfo(environment); + Banner printedBanner = printBanner(environment); + //创建容器 + context = createApplicationContext(); + context.setApplicationStartup(this.applicationStartup); + prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); + //刷新容器 + refreshContext(context); + afterRefresh(context, applicationArguments); + stopWatch.stop(); + if (this.logStartupInfo) { + new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); + } + listeners.started(context); + callRunners(context, applicationArguments); + } + catch (Throwable ex) { + handleRunFailure(context, ex, listeners); + throw new IllegalStateException(ex); + } + + try { + listeners.running(context); + } + catch (Throwable ex) { + handleRunFailure(context, ex, null); + throw new IllegalStateException(ex); + } + return context; + } +``` + +就是这样很简单 + + + + + + + + + + + + +