From 618eb5f34362e90c74ddac6bfc518d5f12784762 Mon Sep 17 00:00:00 2001 From: youthlql <1826692270@qq.com> Date: Tue, 21 Jun 2022 23:54:47 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0spring=E6=BA=90=E7=A0=81-spri?= =?UTF-8?q?ngmvc=E7=9B=B8=E5=85=B3=E4=B8=A4=E7=AF=87=E6=BA=90=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +- ...-SpringMVC子容器和Spring父容器的启动原理.md | 1394 +++++++++++++++++ ...SpringMVC请求处理源码和HandlerMapping原理.md | 1245 +++++++++++++++ 3 files changed, 2644 insertions(+), 1 deletion(-) create mode 100644 docs/spring-sourcecode-v1/08.第8章-SpringMVC子容器和Spring父容器的启动原理.md create mode 100644 docs/spring-sourcecode-v1/09.第9章-SpringMVC请求处理源码和HandlerMapping原理.md diff --git a/README.md b/README.md index 3d3530f..64b61b9 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ -## 源码【04.17更新】 +## 源码【06.21更新】 @@ -158,6 +158,10 @@ [07.第7章-AOP的执行流程原理和监听器原理](docs/spring-sourcecode-v1/07.第7章-AOP的执行流程原理和监听器原理.md) +[08.第8章-SpringMVC子容器和Spring父容器的启动原理](docs/spring-sourcecode-v1/08.第8章-SpringMVC子容器和Spring父容器的启动原理.md) + +[09.第9章-SpringMVC请求处理源码和HandlerMapping原理](docs/spring-sourcecode-v1/09.第9章-SpringMVC请求处理源码和HandlerMapping原理.md) + # Netty ## 入门 diff --git a/docs/spring-sourcecode-v1/08.第8章-SpringMVC子容器和Spring父容器的启动原理.md b/docs/spring-sourcecode-v1/08.第8章-SpringMVC子容器和Spring父容器的启动原理.md new file mode 100644 index 0000000..c19b434 --- /dev/null +++ b/docs/spring-sourcecode-v1/08.第8章-SpringMVC子容器和Spring父容器的启动原理.md @@ -0,0 +1,1394 @@ +--- +title: Spring源码系列-第8章-SpringMVC子容器和Spring父容器的启动原理 +tags: + - Spring源码 +categories: + - Spring + - 源码V1 +keywords: Spring,框架,spring源码 +description: SpringMVC子容器和Spring父容器的启动原理 +cover: 'https://npm.elemecdn.com/lql_static@latest/logo/spring.png' +abbrlink: c8dd1418 +date: 2022-06-20 12:01:02 +--- + +# 第8章-SpringMVC子容器和Spring父容器的启动原理 + +## 测试类 + +根据[官方文档](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#spring-web)写咱们的测试类 + +### AppStarter + +```java +package cn.imlql.web; + +import cn.imlql.web.config.AppConfig; +import org.springframework.web.WebApplicationInitializer; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; +import org.springframework.web.servlet.DispatcherServlet; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRegistration; + + +/** + * 只要写了这个,相当于配置了SpringMVC的DispatcherServlet + * 1、Tomcat一启动就加载他 + * 1)、创建了容器、制定了配置类(所有ioc、aop等spring的功能就ok) + * 2)、注册一个Servlet; DispatcherServlet; + * 3)、以后所有的请求都交给了 DispatcherServlet; + * 效果,访问Tomcat部署的这个Web应用下的所有请求都会被 DispatcherServlet 处理 + * DispatcherServlet就会进入强大的基于注解的mvc处理流程(@GetMapping) + * 必须Servlet3.0以上才可以;Tomcat6.0以上才支持Servlet3.0规范 + * + * Servlet3.0是javaEE的Web的规范标准,Tomcat是Servlet3.0规范的一个实现; + */ +public class AppStarter implements WebApplicationInitializer { + @Override + public void onStartup(ServletContext servletContext) throws ServletException { //Spring会给我们传入servletContext + //创建ioc容器 + AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); + context.register(AppConfig.class); //传入一个配置类 + //以上截止,ioc容器都没有启动 + //自己newDispatcherServlet,并传入容器 + DispatcherServlet servlet = new DispatcherServlet(context); + //利用Servlet规范 + ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet); + registration.setLoadOnStartup(1); + registration.addMapping("/"); //映射路径,写/就是所有的请求都交给DispatcherServlet + + } +} +``` + + + +### HelloController + +```java +@RestController +public class HelloController { + + + @GetMapping("/hello") + public String sayHello(){ + return "Hello, SpringMVC!"; + } +} +``` + +### AppConfig + +```java +@ComponentScan("cn.imlql.web") +@Configuration +public class AppConfig { + + +} +``` + + + + + +根路径是在这里配的,tomcat的配置自己百度下,很简单 + + + +## Java的SPI机制 + +> 下面会用到SPI,这里先讲一下 + +### 项目目录 + +```java +spi-demo +├── api-db-impl-mysql/ +| ├── api-db-impl-mysql.iml +| ├── pom.xml +| ├── src/ +| | ├── main/ +| | | ├── java/ +| | | | └── com/ +| | | | └── imlql/ +| | | | └── mysql/ +| | | | └── MySQLSaveService.java +| | | └── resources/ +| | | └── META-INF/ +| | | └── services/ +| | | └── cn.imlql.data.DataSaveService +| | └── test/ +| | └── java/ +| └── target/ +| ├── classes/ +| | ├── com/ +| | | └── imlql/ +| | | └── mysql/ +| | | └── MySQLSaveService.class +| | └── META-INF/ +| | └── services/ +| | └── cn.imlql.data.DataSaveService +| └── generated-sources/ +| └── annotations/ +├── api-db-impl-redis/ +| ├── api-db-impl-redis.iml +| ├── pom.xml +| ├── src/ +| | ├── main/ +| | | ├── java/ +| | | | └── com/ +| | | | └── imlql/ +| | | | └── redis/ +| | | | └── RedisSaveService.java +| | | └── resources/ +| | | └── META-INF/ +| | | └── services/ +| | | └── cn.imlql.data.DataSaveService +| | └── test/ +| | └── java/ +| └── target/ +| ├── classes/ +| | ├── com/ +| | | └── imlql/ +| | | └── redis/ +| | | └── RedisSaveService.class +| | └── META-INF/ +| | └── services/ +| | └── cn.imlql.data.DataSaveService +| └── generated-sources/ +| └── annotations/ +├── api-db-interface/ +| ├── api-db-interface.iml +| ├── pom.xml +| ├── src/ +| | ├── main/ +| | | ├── java/ +| | | | └── com/ +| | | | └── imlql/ +| | | | └── data/ +| | | | └── DataSaveService.java +| | | └── resources/ +| | └── test/ +| | └── java/ +| └── target/ +| ├── classes/ +| | └── com/ +| | └── imlql/ +| | └── data/ +| | └── DataSaveService.class +| └── generated-sources/ +| └── annotations/ +├── app/ +| ├── app.iml +| ├── pom.xml +| ├── src/ +| | ├── main/ +| | | ├── java/ +| | | | └── com/ +| | | | └── imlql/ +| | | | └── redis/ +| | | | └── MainTest.java +| | | └── resources/ +| | └── test/ +| | └── java/ +| └── target/ +| ├── classes/ +| | └── com/ +| | └── imlql/ +| | └── redis/ +| | └── MainTest.class +| └── generated-sources/ +| └── annotations/ +├── pom.xml +└── spi-demo.iml + +``` + +### MainTest + +```java +import cn.imlql.data.DataSaveService; + +import java.util.ServiceLoader; + + +/** + * 1、 ServiceLoader:load()指定一个接口, + * 他就会加载当前系统里面所有的这个接口的【指定实现】 + * 2、SPI(Service Provider Interface) + * 接口工程---提供接口 + * ---- 实现工程1 : 实现接口 【META-INF/services 创建文件 接口名作为文件名 实现类全路径作为文件内容】 + * ---- 实现工程2 : 实现接口 + * + * + * 客户端----引用 工程1、或者 工程2 + * + * + * + */ +public class MainTest { + + public static void main(String[] args) { + + //1、加载 可用的接口实现 + ServiceLoader load = ServiceLoader.load(DataSaveService.class); + + //拿到实现进行调用 + for (DataSaveService service : load) { + service.saveData("你好...."); + } + + } +} +``` + +输出: + +```java +MySQL保存了数据.......你好.... +Redis保存了数据.......你好.... +``` + +Java的SPI机制会默认加载**类路径**下`META-INF/services`的东西 + +### DataSaveService + +``` +public interface DataSaveService { + + void saveData(String data); +} +``` + +### MySQLSaveService + +```java +public class MySQLSaveService implements DataSaveService { + @Override + public void saveData(String data) { + System.out.println("MySQL保存了数据......." + data); + } +} +``` + +### RedisSaveService + +```java +public class RedisSaveService implements DataSaveService { + @Override + public void saveData(String data) { + System.out.println("Redis保存了数据......."+data); + } +} +``` + +### SPI文件示例 + +api-db-impl-redis\src\main\resources\META-INF\services\cn.imlql.data.DataSaveService + +```txt +cn.imlql.redis.RedisSaveService +``` + + + +api-db-impl-mysql\src\main\resources\META-INF\services\cn.imlql.data.DataSaveService + +```txt +cn.imlql.mysql.MySQLSaveService +``` + +你没看错就是这么简单 + +### Java的SPI机制的作用 + +我只需要规定接口就可以开放给任何人实现 + +> META-INF\services下的文件,本文统称为**SPI文件** + + + +## 基于SPI启动Web容器 + + + +首先我们看到AppStarter实现了WebApplicationInitializer接口,官方文档是这样写的,那么WebApplicationInitializer肯定是能启动Web的核心 + + + +### WebApplicationInitializer + + + + + +```java +@HandlesTypes(WebApplicationInitializer.class) +public class SpringServletContainerInitializer implements ServletContainerInitializer {...} +``` + +### ServletContainerInitializer#onStartup() + +```java +public interface ServletContainerInitializer { + + public void onStartup(Set> c, ServletContext ctx) + throws ServletException; +} +``` + + + + + + + + + + + + + +相当于tomcat一启动会加载SpringServletContainerInitializer + +### SpringServletContainerInitializer#onStartup() + +```java + +/** + * Servlet 3.0 {@link ServletContainerInitializer} designed to support code-based + * configuration of the servlet container using Spring's {@link WebApplicationInitializer} + * SPI as opposed to (or possibly in combination with) the traditional + * {@code web.xml}-based approach. + * + *

See Also

+ * See {@link WebApplicationInitializer} Javadoc for examples and detailed usage + * recommendations.

+ * + * @author Chris Beams + * @author Juergen Hoeller + * @author Rossen Stoyanchev + * @since 3.1 + * @see #onStartup(Set, ServletContext) + * @see WebApplicationInitializer + */ +@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对于@HandlesTypes的具体实现咱们这里不深究,可以肯定的是一定用到了Java的SPI,如下。 + +```java +ServiceLoader load = ServiceLoader.load(WebApplicationInitializer.class); +``` + +4. tomcat具体对于@HandlesTypes一定是和上面类似甚至是一样的代码来加载WebApplicationInitializer的实现 + + + + + + + +咱们给它的方法onStartup打上断点 + + + +下面就是所有实现了WebApplicationInitializer的类 + + + +接着在最底下的for循环执行所有实现了WebApplicationInitializer的类的onStartup(),然后就走到了我们的AppStarter + + + +到这一步,ioc容器都没有创建,我们给refresh()打个断点,看什么时候启动的ioc + + + + + +放行 + +我看的时候debug断点没有看到从AppStarter的哪一步跳到refresh()的。然后我一步一步走的时候发现不是在这个方法里调用的,注意看下面的图,DispatcherServlet已经new完了,但是debug依然没有跳到refresh(),说明不是在new DispatcherServlet()的时候创建的容器 + + + +不过我凭经验猜测Springmvc里最重要的是DispatcherServlet,会不会是DispatcherServlet的那一步启动了IOC,我们开始进行下面的尝试 + + + +### Servlet相关 + +1. tomcat会遵循sun公司的规范给每一个Servlet创建对象 +2. 所以DispatcherServlet肯定也会创建对象 + +3. Servlet的规范 + 1. Servlet创建对象 + 2. Servlet调用Init初始化 + 3. 每次请求调用service处理 + 4. tomcat停止的时候调用destroy进行销毁 + + + +4. Serlvet是被谁调用开始初始化的属于tomcat的源码,我们这里不研究,我们这里只需要知道,每一个Servlet都会被初始化就可以了。 + +### DispatcherServlet + +spring-web中有一个叫DispatcherServlet的类,很明显他是一个Servlet,所以tomcat启动的时候就会加载它,加载它的话当然是从父类一层一层加载的 + + + + + +1. 想要启动IOC容器,只可能是创建DispatcherServlet对象或者调用init()的时候来搞。上面我们也看到了,创建DispatcherServlet对象的时候debug调用栈并没有显示跳到了refresh方法,所以显然不是创建对象的时候 +2. 那就只有可能是调用init()的时候开始启动的IOC容器 + + + +Servlet规范的init我看了下都是空的,从Spring的HttpServletBean才开始有东西,HttpServletBean的父类和接口对于init()都是空实现,下面我们就从HttpServletBean开始分析。 + + + +### HttpServletBean#init() + +```java + public final void init() throws ServletException { + + // Set bean properties from init parameters. + PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); + if (!pvs.isEmpty()) { + try { + BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); + ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); + bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); + initBeanWrapper(bw); + bw.setPropertyValues(pvs, true); + } + catch (BeansException ex) { + if (logger.isErrorEnabled()) { + logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex); + } + throw ex; + } + } + + //模板方法模式。给子类留的喜欢干的事 Let subclasses do whatever initialization they like. + initServletBean(); + } +``` + + + +### FrameworkServlet#initServletBean() + +```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"); + } + } +``` + + + +DispatcherServlet没有重写initFrameworkServlet() + +最后得到结论 + +```java + this.webApplicationContext = initWebApplicationContext(); //初始化WebIOC容器,那我们想一下大概率是在这里启动的IOC容器 +``` + +下面开始具体分析 + + + +## 小补充 + +### XML版如何配置父子容器关系? + +#### web.xml + +```xml + + + org.springframework.web.context.ContextLoaderListener + + + + contextConfigLocation + /WEB-INF/app-context.xml + + + + app + org.springframework.web.servlet.DispatcherServlet + + contextConfigLocation + + + 1 + + + + app + /app/* + + + +``` + + + +1. 在web.xmI中配置C ontextl oaderListener,指定Spring配置文件的位置 +2. 在web.xml中配置 DispatcherServlet,指定SpringMVC配置文件位置 +3. 以上会产生父子容器 + +父容器(Spring配置文件进行包扫描并保存所有组件的容器) + +子容器(SpringMVC配置文件进行包扫描并保存所有组件的容器) + +webloc.setParent(springloc)。类似于双亲委派,容器隔离。先看当前容器有没有这个组件,当前容器没有再去父容器找有没有这个组件 + + + +### AbstractAnnotationConfigDispatcherServletInitializer能更快的整合Spring和SpringMVC + + + +AbstractAnnotationConfigDispatcherServletInitializer能更快的整合Spring和SpringMVC + +> 后面的讲解都用这个测试类 + + + +#### 测试类 + +##### QuickAppStarter + +```java +/** + * 最快速的整合注解版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("");// + } +} +``` + +##### SpringMVCConfig + +```java +/** + * SpringMVC只扫描controller组件,可以不指定父容器类,让MVC扫所有。@Component+@RequestMapping就生效了 + */ +@ComponentScan(value = "cn.imlql.web", includeFilters = { + @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class) +}, useDefaultFilters = false) +public class SpringMVCConfig { + //SpringMVC的子容器,能扫描的Spring容器中的组件 + +} +``` + +##### SpringConfig + +```java +/** + * Spring不扫描controller组件 + */ +@ComponentScan(value = "cn.imlql.web",excludeFilters = { + @ComponentScan.Filter(type= FilterType.ANNOTATION,value = Controller.class) +}) +public class SpringConfig { + //Spring的父容器 + +} +``` + +父子容器隔离,因为有这句话webloc.setParent(springloc),springmvc这个子容器可以扫描到父容器Spring里面的组件,反之Spring父容器扫描不到springmvc这个子容器的组件。 + + + +##### HelloController + +```java +//@Controller; 如果有父子容器 @Component,默认是在父容器中,还找不到 +//@Component+@RequestMapping +@RestController +public class HelloController { + + public HelloController(){ + System.out.println("HelloController....."); + } + + @Autowired + HelloService helloService; + + @GetMapping("/hello") // 所有的xxxMapping都是RequestMapping + public String sayHello(){ + + String mvc = helloService.say("MVC"); + return mvc; + } + +} +``` + +##### HelloService + +```java +@Service +public class HelloService { + + public HelloService(){ + System.out.println("HelloService....."); + } + + public String say(String name){ + return "Hello,"+name; + } +} +``` + + + + + +```java +public interface ServletContextListener extends EventListener { + + /** + * Receives notification that the web application initialization + * process is starting.【翻译:在当前web应用启动以后(Tomcat把web应用加载了以后),调用contextInitialized方法】 + * + *

All ServletContextListeners are notified of context + * initialization before any filters or servlets in the web + * application are initialized. + * + * @param sce the ServletContextEvent containing the ServletContext + * that is being initialized + * + * @implSpec + * The default implementation takes no action. + */ + default public void contextInitialized(ServletContextEvent sce) {} + + /** + * Receives notification that the ServletContext is about to be + * shut down. + * + * ...... + */ + default public void contextDestroyed(ServletContextEvent sce) {} +} +``` + +## SpringMVC父子容器的启动过程 + +### 保存父子容器相关信息 + +#### SpringServletContainerInitializer#onStartup() + + + + + +```java + 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 + } + } + +} +``` + +当initializer是咱们的QuickAppStarter时,F7进入方法。 + +#### AbstractDispatcherServletInitializer#onStartup() + + + +因为咱们的QuickAppStarter没有onStarup()所以就调用了父类AbstractDispatcherServletInitializer的,没想到AbstractDispatcherServletInitializer也是继续调用父类的 + + + +```java + public void onStartup(ServletContext servletContext) throws ServletException { + super.onStartup(servletContext); + registerDispatcherServlet(servletContext); + } +``` + + + +#### AbstractContextLoaderInitializer#onStartup() + + + + + +```java +public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer { + + /** Logger available to subclasses. */ + protected final Log logger = LogFactory.getLog(getClass()); + + + @Override //注册ContextLoaderListener;contextInitialized + public void onStartup(ServletContext servletContext) throws ServletException { + registerContextLoaderListener(servletContext); + } + + protected void registerContextLoaderListener(ServletContext servletContext) { + WebApplicationContext rootAppContext = createRootApplicationContext(); //创建一个根容器 + if (rootAppContext != null) { + ContextLoaderListener listener = new ContextLoaderListener(rootAppContext); + listener.setContextInitializers(getRootApplicationContextInitializers()); + servletContext.addListener(listener); + } + else { + logger.debug("No ContextLoaderListener registered, as " + + "createRootApplicationContext() did not return an application context"); + } + } + +} +``` + + + + + + + +#### AbstractAnnotationConfigDispatcherServletInitializer#createRootApplicationContext()创建Spring-IOC父容器 + +```java + @Nullable //重写了爷爷类的创建根容器方法 + protected WebApplicationContext createRootApplicationContext() { + Class[] configClasses = getRootConfigClasses(); //获取根配置 + if (!ObjectUtils.isEmpty(configClasses)) { + AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); + context.register(configClasses); //创建了一个IOC容器并把配置类注册进来 + return context; + } + else { + return null; + } + } +``` + + + +getRootConfigClasses()正好是咱们QuickAppStarter这个子类重写的,debug F7进入 + +#### QuickAppStarter#getRootConfigClasses()拿自定义的配置类 + +果不其然,调用了QuickAppStarter#getRootConfigClasses() + + + + + +继续往下走创建Web容器,这是Spring父容器,因为你看它getRootConfigClasses()获取的是父容器配置 + + + + + +然后返回 + + + + + +图片上面写错了。应该改为:**保存了上面创建的Spring-IOC父容器**。Web子容器的创建在下面 + +#### ContextLoaderListener创建一个监听器准备后续启动容器进行回调 + +```java +public class ContextLoaderListener extends ContextLoader implements ServletContextListener { + //在当前web应用启动以后(Tomcat把web应用加载了以后),调用contextInitialized方法 + public ContextLoaderListener(WebApplicationContext context) { + super(context); + } + + /** + * Initialize the root web application context. + */ + @Override + public void contextInitialized(ServletContextEvent event) { //根容器初始化 + initWebApplicationContext(event.getServletContext()); //初始化webioc容器 + } +} +``` + +上面是利用Servlet标准 + + + +#### 返回到AbstractDispatcherServletInitializer#onStartup() + +接着就继续返回 + + + + + +下面才是创建Web子容器(也叫做Servlet容器) + +```java + protected void registerDispatcherServlet(ServletContext servletContext) { + String servletName = getServletName(); + Assert.hasLength(servletName, "getServletName() must not return null or empty"); + + WebApplicationContext servletAppContext = createServletApplicationContext(); //创建Servlet容器 + Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null"); + + FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext); + Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null"); + dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers()); + + ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet); + if (registration == null) { + throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " + + "Check if there is another servlet registered under the same name."); + } + + registration.setLoadOnStartup(1); + registration.addMapping(getServletMappings()); //根据我们指定的DispatcherServlet的路径进行注册 + registration.setAsyncSupported(isAsyncSupported()); + + Filter[] filters = getServletFilters(); + if (!ObjectUtils.isEmpty(filters)) { + for (Filter filter : filters) { + registerServletFilter(servletContext, filter); + } + } + // 这个是留给我们自定义的方法,模板模式 + customizeRegistration(registration); + } +``` + + + + + +#### AbstractAnnotationConfigDispatcherServletInitializer#createServletApplicationContext()创建Web子容器(Servelt容器) + +```java + protected WebApplicationContext createServletApplicationContext() { + // + AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); + Class[] configClasses = getServletConfigClasses(); //获取web应用的配置 + if (!ObjectUtils.isEmpty(configClasses)) { + context.register(configClasses); + } + return context; + } +``` + + + +这里又new了一个容器,和上面那个容器一样都没有初始化。这里也是调用咱们QuickAppStarter重写的方法,因为这里调用的是getServletConfigClasses(),所以很明显这里的容器是Web子容器 + + + +然后就一路往回返,走到这里 + + + +继续F7进入 + + + +这里就是保存咱们上面刚创建的Web子容器,然后再返回 + +#### 返回到SpringServletContainerInitializer#onStartup() + + + +1. 这里应用就加载完了,接下来干嘛呢? +2. 你往前看看,咱们的Spring容器和Web子容器都是只是创建完了,都还没有初始化,甚至都没有webloc.setParent(springloc)这样产生父子容器的关系 +3. 回想下前面讲的,是不是有一个监听器,那你说把容器保存到监听器里,它能是干嘛的?猜想一下不就是应用加载完之后,发送一个什么事件,然后根据这个事件触发监听器初始化两大容器等等。 + +咱们就把那个监听器的初始化方法打个断点,然后F8放行试试呗 + + + +### ContextLoaderListener回调-开始refresh刷新Spring的根容器(父容器) + +#### ContextLoaderListener#contextInitialized() + + + + + +#### 简单看一下Tomcat里的调用源码 + +然后真的走到了这里。tomcat里的代码位置是乱的,乱的意思就是比如说上面写的是4766行的调用,但实际上那里是个`}`大括号。也不知道是什么问题,所以我们就大致看下tomcat的代码,不细究。 + + + +这里的调用还是对的 + + + + + +应该就是类似这样的调用 + + + + + +走的应该是第一个if + + + + + +大致就是tomcat里的一个监听器基于事件回调的原理,咱们就不深究了。 + +#### ContextLoader#initWebApplicationContext() + + + + + +F7进入,这里因为我重新启动了一次,所以你看到根容器是@3661 + + + +终于要调用refresh了 + + + +这里直接放行到容器refresh完毕看下父容器 + + + +父容器只扫描了,springconfig和helloService,我们继续放行看下Web子容器. + + + +### Servlet初始化-进而开始refresh刷新Spring的web子容器 + +#### HttpServletBean#init() + +跳到了这里,为什么会跳到这里呢?记不记得之前我们用DispatcherServlet保存了Web子容器,这里就要调用DispatcherServlet的相关初始化方法 + + + + + +#### FrameworkServlet#initWebApplicationContext() + +一路放行 + + + + + +上面父子容器关系形成了,并且父容器已经refresh完毕 + +```java + protected WebApplicationContext initWebApplicationContext() { + WebApplicationContext rootContext = + WebApplicationContextUtils.getWebApplicationContext(getServletContext()); //父容器 + WebApplicationContext wac = null; //先会获取之前的 WebApplicationContext(构建父子容器) + + if (this.webApplicationContext != null) { + // A context instance was injected at construction time -> use it + wac = this.webApplicationContext; //当前的web-ioc容器 + if (wac instanceof ConfigurableWebApplicationContext) { + ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac; + if (!cwac.isActive()) { + // The context has not yet been refreshed -> provide services such as + // setting the parent context, setting the application context id, etc + if (cwac.getParent() == null) { + // The context instance was injected without an explicit parent -> set + // the root application context (if any; may be null) as the parent + cwac.setParent(rootContext); //父子容器的体现, + } + configureAndRefreshWebApplicationContext(cwac); //配置并且刷新容器 + } + } + } + if (wac == null) { + // No context instance was injected at construction time -> see if one + // has been registered in the servlet context. If one exists, it is assumed + // that the parent context (if any) has already been set and that the + // user has performed any initialization such as setting the context id + wac = findWebApplicationContext(); + } + if (wac == null) { + // No context instance is defined for this servlet -> create a local one + wac = createWebApplicationContext(rootContext); + } + + if (!this.refreshEventReceived) { + // Either the context is not a ConfigurableApplicationContext with refresh + // support or the context injected at construction time had already been + // refreshed -> trigger initial onRefresh manually here. + synchronized (this.onRefreshMonitor) { + onRefresh(wac); + } + } + + if (this.publishContext) { + // Publish the context as a servlet context attribute. + String attrName = getServletContextAttributeName(); + getServletContext().setAttribute(attrName, wac); + } + + return wac; + } +``` + + + +#### FrameworkServlet#createWebApplicationContext() + + + +再次来到Web子容器的刷新 + + + +1. 然后我们看到子容器只有它自己的东西 +2. 虽然子容器只有controller,但是因为它保存了父容器。所以它是可以拿到HelloService的,也就是我们可以在HelloController里装配HelloService + +```java +public class HelloController { + + @Autowired + HelloService helloService; + +} +``` + +3. 但是如果想在父容器里装配HelloController就会报错,父容器没有保存子容器。 + +```java +public class HelloService { + + @Autowired + HelloController helloController; + +} +``` + +4. 这样就很好的实现容器隔离,避免Service乱引用Controller +5. 实现父子容器隔离的前提就是前面写的SpringConfig不扫描controller,交给Web子容器 + +```java +/** + * Spring不扫描controller组件 + */ +@ComponentScan(value = "cn.imlql.web",excludeFilters = { + @ComponentScan.Filter(type= FilterType.ANNOTATION,value = Controller.class) +}) +public class SpringConfig { + //Spring的父容器 + +} +``` + + + +```java +/** + * SpringMVC只扫描controller组件,可以不指定父容器类,让MVC扫所有。@Component+@RequestMapping就生效了 + */ +@ComponentScan(value = "cn.imlql.web", includeFilters = { + @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class) +}, useDefaultFilters = false) +public class SpringMVCConfig { + //SpringMVC的子容器,能扫描的Spring容器中的组件 + +} +``` + +注意找组件的过程是先找自己的容器,自己没有再到父容器里找 + + + +> 1. 总的来说Spring父容器和SpringMVC子容器启动过程是来自于tomcat的两个回调 +> 2. Serlvet应用启动,监听器回调刷新Spring父容器 +> 3. Servlet初始化,回调刷新SpringMVC子容器 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/spring-sourcecode-v1/09.第9章-SpringMVC请求处理源码和HandlerMapping原理.md b/docs/spring-sourcecode-v1/09.第9章-SpringMVC请求处理源码和HandlerMapping原理.md new file mode 100644 index 0000000..d9cd564 --- /dev/null +++ b/docs/spring-sourcecode-v1/09.第9章-SpringMVC请求处理源码和HandlerMapping原理.md @@ -0,0 +1,1245 @@ +--- +title: Spring源码系列-第9章-SpringMVC请求处理源码和HandlerMapping原理 +tags: + - Spring源码 +categories: + - Spring + - 源码V1 +keywords: Spring,框架,spring源码 +description: SpringMVC请求处理源码和HandlerMapping原理 +cover: 'https://npm.elemecdn.com/lql_static@latest/logo/spring.png' +abbrlink: 6f2cef28 +date: 2022-06-21 12:01:02 +--- + +# 第9章-SpringMVC请求处理源码和HandlerMapping原理 + +## 请求的处理链路 + + + +1. tomcat里面可以部署多个项目应用。/abc_test和mvc_test这种就是项目路径,用于区分多个项目 +2. 在以前的Servlet开发中,每一个路径都需要有一个Servlet来处理。比如上图所画 +3. 有了SpringMVC,整个Tomcat下面就不会有很多Servlet了,只会有一个DispatcherServlet来统一处理 + + + +### Servlet继承树 + + + + + +1. Servlet里是Service()方法处理请求 +2. GenericServlet也是Service()方法处理请求 +3. HttpServlet开始根据请求类型将处理方法分为doGet,doPost,doPut,doDelete等等 +4. HttpServletBean没有处理方法 +5. FrameworkServlet也是有处理方法分别为doGet,doPost,doPut,doDelete等等,但是这些方法最终调用的都是`processRequest(HttpServletRequest request, HttpServletResponse response)` +6. DispatcherServlet处理方法是doService() + + + +### Debug调用栈 + + + +### DispatcherServlet#doService() + +```java + @Override + protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { + logRequest(request); + + // Keep a snapshot of the request attributes in case of an include, + // to be able to restore the original attributes after the include. + Map attributesSnapshot = null; //把request域的所有属性提前保存 + if (WebUtils.isIncludeRequest(request)) { + attributesSnapshot = new HashMap<>(); + Enumeration attrNames = request.getAttributeNames(); + while (attrNames.hasMoreElements()) { + String attrName = (String) attrNames.nextElement(); + if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) { + attributesSnapshot.put(attrName, request.getAttribute(attrName)); //快照所有属性 + } + } + } + + //基本的东西保存到request域中方便处理 Make framework objects available to handlers and view objects. + request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); + request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); //国际化解析器 + request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); //主题解析器 + request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); + + if (this.flashMapManager != null) { //闪存管理器(重定向携带数据) + FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response); + if (inputFlashMap != null) { + request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap)); + } + request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); + request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager); + } + + RequestPath previousRequestPath = null; + if (this.parseRequestPath) { + previousRequestPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE); + ServletRequestPathUtils.parseAndCache(request); + } + + try { + doDispatch(request, response); //处理派发功能 + } + finally { + if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { + // Restore the original attribute snapshot, in case of an include. + if (attributesSnapshot != null) { + restoreAttributesAfterInclude(request, attributesSnapshot); + } + } + ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request); + } + } +``` + +### DispatcherServlet#doDispatch()请求派发 + +```java + //SpringMVC处理请求的核心流程 + protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { + HttpServletRequest processedRequest = request; + HandlerExecutionChain mappedHandler = null; //handler(目标方法)的执行链 + boolean multipartRequestParsed = false; //文件上传标志 + //对异步请求的支持(Servlet3.0以后才有的,Webflux) + WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); + + try { + ModelAndView mv = null; + Exception dispatchException = null; + + try { + processedRequest = checkMultipart(request); //检查当前是否文件上传请求 + multipartRequestParsed = (processedRequest != request); + + //构造出了【目标方法+拦截器整个链路】决定使用哪个Handler处理当前请求 Determine handler for the current request. + mappedHandler = getHandler(processedRequest); + if (mappedHandler == null) { //如果找不到人处理,就send 404 + noHandlerFound(processedRequest, response); + return; + } + + //适配器怎么找的、 Determine handler adapter for the current request. + HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); + + // Process last-modified header, if supported by the handler. + String method = request.getMethod(); + boolean isGet = "GET".equals(method); + if (isGet || "HEAD".equals(method)) { + long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); + if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { + return; + } + } + + if (!mappedHandler.applyPreHandle(processedRequest, response)) { + return; + } + + // Actually invoke the handler. + mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); + + if (asyncManager.isConcurrentHandlingStarted()) { + return; + } + + applyDefaultViewName(processedRequest, mv); + mappedHandler.applyPostHandle(processedRequest, response, mv); + } + catch (Exception ex) { + dispatchException = ex; + } + catch (Throwable err) { + // As of 4.3, we're processing Errors thrown from handler methods as well, + // making them available for @ExceptionHandler methods and other scenarios. + dispatchException = new NestedServletException("Handler dispatch failed", err); + } + processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); + } + catch (Exception ex) { + triggerAfterCompletion(processedRequest, response, mappedHandler, ex); + } + catch (Throwable err) { + triggerAfterCompletion(processedRequest, response, mappedHandler, + new NestedServletException("Handler processing failed", err)); + } + finally { + if (asyncManager.isConcurrentHandlingStarted()) { + // Instead of postHandle and afterCompletion + if (mappedHandler != null) { + mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); + } + } + else { + // Clean up any resources used by a multipart request. + if (multipartRequestParsed) { + cleanupMultipart(processedRequest); + } + } + } + } +``` + + + +### doDispatch处理大流程图 + + + + + + + +### DispatcherServlet#checkMultipart() + +```java +protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException { + //使用文件上传解析器来判断是否文件上传请求 + if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) { + // ...... + } + // If not returned before: return original request. + return request; +} +``` + + + +咱们这里目前连解析器都没有,所以就直接返回了 + +### StandardServletMultipartResolver#isMultipart() + +```java +public boolean isMultipart(HttpServletRequest request) { + //所有文件上传请求头Content-Type都会有这个 + return StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/"); +} +``` + + + +### DispatcherServlet九大组件 + +```java + //DispatcherServlet中的九大组件、全是接口,我们完全可以自定义实现。Spring默认也都准备好了这些组件的实现 + /** MultipartResolver used by this servlet. */ + @Nullable //文件上传解析器 + private MultipartResolver multipartResolver; + + /** LocaleResolver used by this servlet. */ + @Nullable //国际化解析器 Locale(区域信息) + private LocaleResolver localeResolver; + + /** ThemeResolver used by this servlet. */ + @Nullable //主题解析器 + private ThemeResolver themeResolver; + + /** List of HandlerMappings used by this servlet. */ + @Nullable //Handler(处理器、能处理请求的人(Controller))的映射:【保存的就是所有请求都由谁来处理的映射关系】 + private List handlerMappings; + + /** List of HandlerAdapters used by this servlet. */ + @Nullable //Handler处理器的适配器;是一个超级反射工具,帮我们解决参数呀,返回值这些,不需要我们很麻烦的写 + private List handlerAdapters; + + /** List of HandlerExceptionResolvers used by this servlet. */ + @Nullable //Handler的异常解析器,异常处理功能 + private List handlerExceptionResolvers; + + /** RequestToViewNameTranslator used by this servlet. */ + @Nullable //把请求转成视图名(我们要跳转的页面地址)的翻译器【没啥用】 + private RequestToViewNameTranslator viewNameTranslator; + + /** FlashMapManager used by this servlet. */ + @Nullable //闪存管理器 + private FlashMapManager flashMapManager; + + /** List of ViewResolvers used by this servlet. */ + @Nullable //视图解析器(我们去哪些页面,怎么过去?) + private List viewResolvers; +``` + +[官网介绍](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-servlet-special-bean-types) + + + + + + + +## 九大组件利用Spring事件机制进行初始化 + +上面的图我们看到九大组件有八个已经有值了,我们现在看下他们是何时有值的。怎么找就很简单,你就找这些属性的setXXX方法,最后我找到了这里 + +### DispatcherServlet#initStrategies() + +```java + protected void initStrategies(ApplicationContext context) { //初始化所有策略,九大组件在这里进行了初始化 + initMultipartResolver(context); //容器中有就用,没有就是null + initLocaleResolver(context); //从容器中获取,没有用默认 + initThemeResolver(context); //从容器中获取,没有用默认 + initHandlerMappings(context); //从容器中获取,没有用默认 + initHandlerAdapters(context); //从容器中获取,没有用默认 + initHandlerExceptionResolvers(context); //从容器中获取,没有用默认 + initRequestToViewNameTranslator(context); //Spring没有浓重说他,//从容器中获取,没有用默认 + initViewResolvers(context); //从容器中获取,没有用默认 + initFlashMapManager(context); //从容器中获取,没有用默认 + } +``` + +代码几乎都是一样的逻辑,咱们就只看一个举个例子 + + + +### DispatcherServlet#initLocaleResolver() + +```java + private void initLocaleResolver(ApplicationContext context) { + try { //容器中先来获取 + this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class); + if (logger.isTraceEnabled()) { + logger.trace("Detected " + this.localeResolver); + } + else if (logger.isDebugEnabled()) { + logger.debug("Detected " + this.localeResolver.getClass().getSimpleName()); + } + } + catch (NoSuchBeanDefinitionException ex) { + // We need to use the default. 容器中没有,读取默认配置文件进行加载 + this.localeResolver = getDefaultStrategy(context, LocaleResolver.class); //获取默认策略 + if (logger.isTraceEnabled()) { + logger.trace("No LocaleResolver '" + LOCALE_RESOLVER_BEAN_NAME + + "': using default [" + this.localeResolver.getClass().getSimpleName() + "]"); + } + } + } +``` + +只要不满意九大组件的任何一个,自己可以实现指定的接口,这样就会放在容器中,SpringMVC + + + +### Debug调用栈 + + + + + +1. Tomcat启动 +2. 触发DispatcherServlet的初始化 +3. DispatcherServlet初始化全部结束,容器会发送Spring的相关事件. +4. 感知到容器准备好了的事件--初始化九大组件(底层是SourceFilteringListener, 把事件回调到DispatcherServlet的onRefresh方法) + +### DispatcherServlet#onRefresh()接收到事件开始准备初始化九大组件 + +```java + protected void onRefresh(ApplicationContext context) { + initStrategies(context); //初始化九大组件 + } +``` + + + +### DispatcherServlet#getDefaultStrategy() 获取默认初始化策略 + +```java + protected T getDefaultStrategy(ApplicationContext context, Class strategyInterface) { + List strategies = getDefaultStrategies(context, strategyInterface); + if (strategies.size() != 1) { + throw new BeanInitializationException( + "DispatcherServlet needs exactly 1 strategy for interface [" + strategyInterface.getName() + "]"); + } + return strategies.get(0); + } + + protected List getDefaultStrategies(ApplicationContext context, Class strategyInterface) { + if (defaultStrategies == null) { + try { + // Load default strategy implementations from properties file. + // This is currently strictly internal and not meant to be customized + // by application developers. 去DispatcherServlet所在的类路径下找一个 DispatcherServlet.properties 资源 + ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class); + defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); //读取properties文件 + } + catch (IOException ex) { + throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage()); + } + } + + // ...... + } + + private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties"; +``` + + + + + + + +#### DispatcherServlet.properties + +下面这些就是九大组件默认组件 + +```properties +# Default implementation classes for DispatcherServlet's strategy interfaces. +# Used as fallback when no matching beans are found in the DispatcherServlet context. +# Not meant to be customized by application developers. + +org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver + +org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver + +org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\ + org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\ + org.springframework.web.servlet.function.support.RouterFunctionMapping + +org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\ + org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\ + org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\ + org.springframework.web.servlet.function.support.HandlerFunctionAdapter + + +org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\ + org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\ + org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver + +org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator + +org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver + +org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager +``` + + + +## HandlerMapping处理URL和Controller的映射关系 + + + +### DispatcherServlet#doDispatch()请求派发 + +```java + //SpringMVC处理请求的核心流程 + protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { + HttpServletRequest processedRequest = request; + HandlerExecutionChain mappedHandler = null; //handler(目标方法)的执行链 + boolean multipartRequestParsed = false; //文件上传标志 + //对异步请求的支持(Servlet3.0以后才有的,Webflux) + WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); + + try { + ModelAndView mv = null; + Exception dispatchException = null; + + try { + processedRequest = checkMultipart(request); //检查当前是否文件上传请求 + multipartRequestParsed = (processedRequest != request); + + //构造出了【目标方法+拦截器整个链路】决定使用哪个Handler处理当前请求 Determine handler for the current request. + mappedHandler = getHandler(processedRequest); + if (mappedHandler == null) { //如果找不到人处理,就send 404 + noHandlerFound(processedRequest, response); + return; + } + + //适配器怎么找的、 Determine handler adapter for the current request. + HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); + + // Process last-modified header, if supported by the handler. + String method = request.getMethod(); + boolean isGet = "GET".equals(method); + if (isGet || "HEAD".equals(method)) { + long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); + if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { + return; + } + } + + if (!mappedHandler.applyPreHandle(processedRequest, response)) { + return; + } + + // Actually invoke the handler. + mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); + + if (asyncManager.isConcurrentHandlingStarted()) { + return; + } + + applyDefaultViewName(processedRequest, mv); + mappedHandler.applyPostHandle(processedRequest, response, mv); + } + processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); + } + + // ...... + } +``` + + + + + +### DispatcherServlet#getHandler()根据请求拿Controller + + + + + +这里会有三个默认的HandlerMapping,就是在DispatcherServlet.properties写的那三个,在之前说九大组件初始化的时候如果我们自己没有写,就获取默认的 + + + +1. BeanNameUrlHandlerMapping:bean的名字作为URL路径,进行映射 +2. RequestMappingHandlerMapping:@RequestMapping注解作为URL地址进行映射 + 1. 默认用它,而且它里面保存了所有请求映射信息 +3. RouterFunctionMapping: 支持函数式处理以及WebFlux相关功能,未来可能这个用的多 + + + + + +BeanNameUrlHandlerMapping里找不到映射关系,就直接下一个循环了。咱们主要看RequestMappingHandlerMapping怎么处理的 + +## RequestMappingHandlerMapping处理流程 + + + + + +RequestMappingHandlerMapping的父类有一个MappingRegistry属性保存了请求路径 ==> 请求Controller+方法的映射 + +F7进入`mapping.getHandler(request)` + + + +### AbstractHandlerMapping#getHandler()开始根据请求URL查哪个Controler能处理 + +```java + public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { + Object handler = getHandlerInternal(request); //HandlerMapping的registry中找映射,返回HandlerMethod,真正执行当前请求的方法 + if (handler == null) { + handler = getDefaultHandler(); + } + if (handler == null) { + return null; + } + // Bean name or resolved handler? + if (handler instanceof String) { + String handlerName = (String) handler; + handler = obtainApplicationContext().getBean(handlerName); + } + + // Ensure presence of cached lookupPath for interceptors and others + if (!ServletRequestPathUtils.hasCachedPath(request)) { + initLookupPath(request); + } + //找到前面的目标方法以后,还要构造一个处理器链; + HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); + + if (logger.isTraceEnabled()) { + logger.trace("Mapped to " + handler); + } + else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) { + logger.debug("Mapped to " + executionChain.getHandler()); + } + + if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) { + CorsConfiguration config = getCorsConfiguration(handler, request); + if (getCorsConfigurationSource() != null) { + CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request); + config = (globalConfig != null ? globalConfig.combine(config) : config); + } + if (config != null) { + config.validateAllowCredentials(); + } + executionChain = getCorsHandlerExecutionChain(request, executionChain, config); + } + + return executionChain; + } +``` + + + +F7进入`getHandlerInternal(request)` + + + +### RequestMappingInfoHandlerMapping#getHandlerInternal() + +```java + protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { + request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE); + try { + return super.getHandlerInternal(request); + } + finally { + ProducesRequestCondition.clearMediaTypesAttribute(request); + } + } +``` + +继续F7进`super.getHandlerInternal(request)` + +### AbstractHandlerMethodMapping#getHandlerInternal() + +```java + protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { + String lookupPath = initLookupPath(request); + this.mappingRegistry.acquireReadLock(); + try { + HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); //寻找当前请求谁能处理 + return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); + } + finally { + this.mappingRegistry.releaseReadLock(); + } + } +``` + + + + + +F7进入`lookupHandlerMethod(lookupPath, request)` + + + +### AbstractHandlerMethodMapping#lookupHandlerMethod()真正根据URL查Controller + + + + + +可以看到还是从mappingRegistry里获取。我们后面再看mappingRegistry是什么时候被放入东西的,这里我们接着返回 + + + +### 返回到AbstractHandlerMethodMapping#getHandlerInternal() + + + +这个时候是已经找到了由哪个处理器处理,接着返回 + +### 返回到AbstractHandlerMapping#getHandler() + +返回到这一步,准备执行`getHandlerExecutionChain(handler, request)` + + + + + +### AbstractHandlerMapping#getHandlerExecutionChain()开始构造处理器链 + +```java + protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) { + HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ? + (HandlerExecutionChain) handler : new HandlerExecutionChain(handler)); + //把系统中所有的拦截器拿过来 + for (HandlerInterceptor interceptor : this.adaptedInterceptors) { + if (interceptor instanceof MappedInterceptor) { + MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor; + if (mappedInterceptor.matches(request)) { + chain.addInterceptor(mappedInterceptor.getInterceptor()); + } + } + else { + chain.addInterceptor(interceptor); //所有拦截器加进去 + } + } + return chain; + } +``` + + + +- 咱们没写拦截器,就没有。 +- 继续往回返 + +### 返回到DispatcherServlet#getHandler() + + + +这里就是责任链模式,有能处理的handler就直接返回 + +### 返回到DispatcherServlet#doDispatch() + + + +自此RequestMappingHandlerMapping处理结束 + +## RequestMappingHandlerMapping如何保存映射关系 + +上面我们看到了实际上是从MappingRegistry里拿到URL-->XXXController的映射关系的,那么下面就来演讲MappingRegistry是何时保存的这映射关系,又是怎样保存的 + +### AbstractHandlerMethodMapping#registerMapping() + +我们这里还是靠猜测,猜它调用哪个方法。找到了如下方法,给第一行打断点,重启。 + +```java + //分析所有的Controller;里面的每一个@RequestMapping 注解才能知道这个事情 + public void registerMapping(T mapping, Object handler, Method method) { //哪个请求由哪个方法处理会通过这个进行注册 + if (logger.isTraceEnabled()) { + logger.trace("Register \"" + mapping + "\" to " + method.toGenericString()); + } + this.mappingRegistry.register(mapping, handler, method); + } +``` + +重启的时候没有发现没跑到这里,然后我就看了下这个MappingRegistry,发现它是个AbstractHandlerMethodMapping的内部类 + + + + + +#### MappingRegistry(URL=>XXXController的映射中心) + +```java +class MappingRegistry { + + private final Map> registry = new HashMap<>(); + + private final MultiValueMap pathLookup = new LinkedMultiValueMap<>(); + + private final Map> nameLookup = new ConcurrentHashMap<>(); + + private final Map corsLookup = new ConcurrentHashMap<>(); + + private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); + + public void register(T mapping, Object handler, Method method) { + this.readWriteLock.writeLock().lock(); + try { + HandlerMethod handlerMethod = createHandlerMethod(handler, method); + validateMethodMapping(handlerMethod, mapping); + + Set directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping); + for (String path : directPaths) { + this.pathLookup.add(path, mapping); + } + + String name = null; + if (getNamingStrategy() != null) { + name = getNamingStrategy().getName(handlerMethod, mapping); + addMappingName(name, handlerMethod); + } + + CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping); + if (corsConfig != null) { + corsConfig.validateAllowCredentials(); + this.corsLookup.put(handlerMethod, corsConfig); + } + // 在这里put的 + this.registry.put(mapping, + new MappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig != null)); + } + finally { + this.readWriteLock.writeLock().unlock(); + } + } +} +``` + +我发现上面有一个registry是个Map类型,很容易想到这应该就是保存映射的,然后我就在它的put方法那里打断点,只找到了这一个put。重启应用 + +### Debug调用栈 + + + + + +意料之中,启动应用的时候从init初始化那里调用过来了 + +#### 流程概述 + +1. `DispatcherServlet#onRefresh()`开始初始化九大组件,就会开始初始化HandlerMapping +2. 首先是创建`DispatcherServlet.properties`里指定的三个HandlerMapping实现类的对象。还是用createBean来创建HandlerMapping的 +3. 其中RequestMappingHandlerMapping创建完对象后,因为它实现了InitializingBean,所以会调用RequestMappingHandlerMapping#afterPropertiesSet() +4. 接着就是拿到Web子容器的所有组件,for循环处理。看是不是有@Controller注解或者@RequestMapping注解 +5. 最后把分析到的RequestMapping信息放到HandlerMapping的registry中 + + + +### RequestMappingHandlerMapping#afterPropertiesSet() + +```java +public void afterPropertiesSet() { + + this.config = new RequestMappingInfo.BuilderConfiguration(); + this.config.setTrailingSlashMatch(useTrailingSlashMatch()); + this.config.setContentNegotiationManager(getContentNegotiationManager()); + + if (getPatternParser() != null) { + this.config.setPatternParser(getPatternParser()); + Assert.isTrue(!this.useSuffixPatternMatch && !this.useRegisteredSuffixPatternMatch, + "Suffix pattern matching not supported with PathPatternParser."); + } + else { + this.config.setSuffixPatternMatch(useSuffixPatternMatch()); + this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch()); + this.config.setPathMatcher(getPathMatcher()); + } + + super.afterPropertiesSet(); +} +``` + + + +### AbstractHandlerMethodMapping#afterPropertiesSet() + +```java +public void afterPropertiesSet() { + initHandlerMethods(); //初始化HandlerMethods。 +} +``` + + + +### AbstractHandlerMethodMapping#initHandlerMethods()初始化HandlerMethods + + + + + +可以看到这里只拿了Web子容器的所有组件进行for循环 + +### AbstractHandlerMethodMapping#processCandidateBean() + +```java + protected void processCandidateBean(String beanName) { + Class beanType = null; + try { + beanType = obtainApplicationContext().getType(beanName); + } + catch (Throwable ex) { + // An unresolvable bean type, probably from a lazy bean - let's ignore it. + if (logger.isTraceEnabled()) { + logger.trace("Could not resolve type for bean '" + beanName + "'", ex); + } + } + if (beanType != null && isHandler(beanType)) { + detectHandlerMethods(beanName); //分析当前bean的HandlerMethods + } + } +``` + + + +### RequestMappingHandlerMapping#isHandler()判断当前bean是不是Handler + +```java + protected boolean isHandler(Class beanType) { + return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || + AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class)); + } +``` + +1. 这里首先会过滤Bean,只有你Bean也就是类上标注了@Controller注解或者@RequestMapping注解,才会给这个类走下面的流程 +2. 那这个是什么意思呢? +3. 平常我们写XXXController就是写一个@Controller注解就完事了【注意@RestController值是@Controller的复合注解,包含了@Controller】 +4. 实际上我们可以不写@Controller注解,直接@Component+@RequestMapping也是一样的效果 + +### AbstractHandlerMethodMapping#detectHandlerMethods()探查容器里满足条件的方法 + +1. 然后那个detectHandlerMethods就是探查容器里满足条件的方法 +2. 下面看下怎么探索的 + +```java + protected void detectHandlerMethods(Object handler) { + Class handlerType = (handler instanceof String ? + obtainApplicationContext().getType((String) handler) : handler.getClass()); + + if (handlerType != null) { + Class userType = ClassUtils.getUserClass(handlerType); + Map methods = MethodIntrospector.selectMethods(userType, + (MethodIntrospector.MetadataLookup) method -> { + try { + return getMappingForMethod(method, userType); //探索当前类里面所有满足的方法,所有标注了 RequestMapping + } + catch (Throwable ex) { + throw new IllegalStateException("Invalid mapping on handler class [" + + userType.getName() + "]: " + method, ex); + } + }); + if (logger.isTraceEnabled()) { + logger.trace(formatMappings(userType, methods)); + } + else if (mappingsLogger.isDebugEnabled()) { + mappingsLogger.debug(formatMappings(userType, methods)); + } + methods.forEach((method, mapping) -> { + Method invocableMethod = AopUtils.selectInvocableMethod(method, userType); + registerHandlerMethod(handler, invocableMethod, mapping); + }); + } + } +``` + + + + + + + + + +### RequestMappingHandlerMapping#getMappingForMethod() + +```java + protected RequestMappingInfo getMappingForMethod(Method method, Class handlerType) { + RequestMappingInfo info = createRequestMappingInfo(method); //为每一个方法尝试创建 RequestMappingInfo + if (info != null) { + RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType); + if (typeInfo != null) { + info = typeInfo.combine(info); + } + String prefix = getPathPrefix(handlerType); + if (prefix != null) { + info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info); + } + } + return info; + } +``` + +### RequestMappingHandlerMapping#createRequestMappingInfo(AnnotatedElement ) + +```java + @Nullable + private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) { + // 找每一个标有@RequestMapping注解的方法 + RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class); + RequestCondition condition = (element instanceof Class ? + getCustomTypeCondition((Class) element) : getCustomMethodCondition((Method) element)); + return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null); + } +``` + +自此一切就明了了,原理就是上面写的流程概述。for循环组件,找@RequestMapping注解 + + + +### 小总结 + +Q:功能增强的时候什么时候用后置处理器BeanPostProcessor,什么时候用生命周期InitializingBean呢? + +A: + +1. 如果是所有组件都可能会用到的增强功能,那就实现后置处理器BeanPostProcessor来增强 +2. 如果是单组件增强,最好用InitializingBean。可以看到mvc的RequestMappingHandlerMapping这里用的就是InitializingBean + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +