一、簡介
從本章節開始進入SpringMVC的學習,SpringMVC最重要的類就是DispatcherServlet
DispatcherServlet的本質是一個Servlet,回顧一下Servlet
- JavaWeb就是基于Servlet的
- Servlet接口有5個方法
- Servlet實現類是HttpServlet,自定義的Servlet需要繼承HttpServlet,重寫service方法
- 使用SpringMVC后,DispatcherServlet就是唯一的Servlet,所有的請求由他分發
二、目標
- 手寫SpringMVC的核心類DispatcherServlet
- 通過SCI與Tomcat對接
三、手寫DispatcherServlet
新建抽象類FrameworkServlet,繼承HttpServlet,它的功能是維護WebApplicationContext容器
- 父容器在Spring對接SCI的時候刷新
- 子容器在在DispatcherServlet的init方法刷新
/*** Spring 對 Servlet 的抽象實現類,負責管理 Web ioc 容器** @Author 孤風雪影* @Email gitee.com/efairy520* @Date 2025/4/15 3:24* @Version 1.0*/
public abstract class FrameworkServlet extends HttpServlet {// 子容器private ApplicationContext webApplicationContext;public FrameworkServlet(ApplicationContext webApplicationContext) {this.webApplicationContext = webApplicationContext;}@Overridepublic void init() {initServletContext();}private void initServletContext() {ApplicationContext rootContext = (ApplicationContext) getServletContext().getAttribute(WebApplicationContext.ROOT_NAME);AbstractRefreshableWebApplicationContext cwc = null;// 在springboot場景下會根據當前存在類創建不同ioc,在boot下直接不管if (this.webApplicationContext != null) {if (!(this.webApplicationContext instanceof AnnotationConfigApplicationContext)) {cwc = (AbstractRefreshableWebApplicationContext) this.webApplicationContext;if (cwc.getParent() == null) {cwc.setParent(rootContext);}if (!cwc.isActive()) {cwc.refresh();}cwc.setServletConfig(getServletConfig());cwc.setServletContext(getServletContext());}onRefresh(webApplicationContext);}}protected abstract void onRefresh(ApplicationContext applicationContext);
}
新建DispatcherServlet
- 作為前置處理器
- 實現service方法
public class DispatcherServlet extends FrameworkServlet {public DispatcherServlet(WebApplicationContext webApplicationContext) {super(webApplicationContext);}@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("DispatcherServlet的service方法調用");}// 組件初始化,Servlet的init方法調用@Overrideprotected void onRefresh(ApplicationContext applicationContext) {}
}
四、通過SCI與Tomcat對接
對接SCI,需要新建一個接口WebApplicationInitializer,所有實現了這個接口的類,容器啟動的時候會自動調用其onStartup方法
public interface WebApplicationInitializer {/*** 所有實現了這個接口的類,容器啟動的時候會自動調用其 onStartup 方法* @param servletContext Tomcat會傳入servletContext*/void onStartup(ServletContext servletContext);
}
新建一個抽象類AbstractDispatcherServletInitializer,實現WebApplicationInitializer接口,實現onStartup方法
/*** 對接SCI,實現onStartup方法** @Author 孤風雪影* @Email gitee.com/efairy520* @Date 2025/4/15 3:29* @Version 1.0*/
public abstract class AbstractDispatcherServletInitializer implements WebApplicationInitializer {public static final String DEFAULT_SERVLET_NAME = "dispatcher";public static final String DEFAULT_FILTER_NAME = "filters";public static final int M = 1024 * 1024;@Overridepublic void onStartup(ServletContext servletContext) {// 創建父容器final AbstractApplicationContext rootApplicationContext = createRootApplicationContext();// 父容器放入servletContextservletContext.setAttribute(WebApplicationContext.ROOT_NAME, rootApplicationContext);// 刷新父容器(通過register配置類,所以需要手動刷新) -> 在源碼中是通過事件進行refreshrootApplicationContext.refresh();final WebApplicationContext webApplicationContext = createWebApplicationContext();// 創建DispatcherServletfinal DispatcherServlet dispatcherServlet = new DispatcherServlet(webApplicationContext);ServletRegistration.Dynamic dynamic = servletContext.addServlet(DEFAULT_SERVLET_NAME, dispatcherServlet);// 配置文件信息dynamic.setLoadOnStartup(1);final MultipartConfigElement configElement = new MultipartConfigElement(null, 5 * M, 5 * M, 5);dynamic.setMultipartConfig(configElement);dynamic.addMapping(getMappings());final Filter[] filters = getFilters();if (!ObjectUtil.isEmpty(filters)) {for (Filter filter : filters) {servletContext.addFilter(DEFAULT_FILTER_NAME, filter);}}}// 過濾器protected abstract Filter[] getFilters();// 映射器protected String[] getMappings() {return new String[]{"/"};}// 創建父容器,管理Service,Dao對象protected abstract AbstractApplicationContext createRootApplicationContext();// 創建子容器,管理Controller對象protected abstract WebApplicationContext createWebApplicationContext();}
建抽象類AbstractAnnotationConfigDispatcherServletInitializer,繼承AbstractDispatcherServletInitializer,實現創建父子容器的方法
- 用戶需要實現這個類,提供配置類
/*** 對接SCI,實現創建父子容器的方法** @Author 孤風雪影* @Email gitee.com/efairy520* @Date 2025/4/15 4:01* @Version 1.0*/
public abstract class AbstractAnnotationConfigDispatcherServletInitializer extends AbstractDispatcherServletInitializer {@Overrideprotected AbstractApplicationContext createRootApplicationContext() {final Class<?> rootConfigClass = getRootConfigClass();if (ObjectUtil.isNotNull(rootConfigClass)) {final AnnotationConfigApplicationContext rootContext = new AnnotationConfigApplicationContext();rootContext.register(rootConfigClass);return rootContext;}return null;}@Overrideprotected WebApplicationContext createWebApplicationContext() {final Class<?> webConfigClass = getWebConfigClass();if (ObjectUtil.isNotNull(webConfigClass)) {final AnnotationConfigWebApplicationContext webContext = new AnnotationConfigWebApplicationContext();webContext.register(webConfigClass);return webContext;}return null;}// 下面兩個方法,由用戶實現protected abstract Class<?> getRootConfigClass();protected abstract Class<?> getWebConfigClass();
}
新建SpringServletContainerInitializer類, 它負責spring與SCI的對接
- 它的onStartup方法由Tomcat調用
- 最終調用用戶實現的WebApplicationInitializer類的onStartup
/*** Spring與SCI對接的類* 1.需要實現ServletContainerInitializer* 2.掃描 @HandlesTypes 指定的類** @Author 孤風雪影* @Email gitee.com/efairy520* @Date 2025/4/15 4:13* @Version 1.0*/
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {// 此方法由Tomcat調用@Overridepublic void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {if (webAppInitializerClasses.size() != 0) {final List<WebApplicationInitializer> initializers = new ArrayList<>(webAppInitializerClasses.size());// 排除接口和抽象類for (Class<?> webAppInitializerClass : webAppInitializerClasses) {if (!webAppInitializerClass.isInterface() && !Modifier.isAbstract(webAppInitializerClass.getModifiers()) &&WebApplicationInitializer.class.isAssignableFrom(webAppInitializerClass)) {try {initializers.add((WebApplicationInitializer)ReflectUtil.getConstructor(webAppInitializerClass).newInstance());} catch (Throwable e) {e.printStackTrace();}}}for (WebApplicationInitializer initializer : initializers) {initializer.onStartup(servletContext);}}}
}
配置SPI
五、測試
install一下chapter32模塊
在tomcat9源碼中導入pom依賴
<dependency><groupId>cn.shopifymall</groupId><artifactId>splendid-spring-chapter-32</artifactId><version>1.0-SNAPSHOT</version>
</dependency>
提供配置類
/*** @Author 孤風雪影* @Email gitee.com/efairy520* @Date 2025/4/15 5:16* @Version 1.0*/
@Configuration
@ComponentScan("cn.shopifymall.tomcat")
public class AppConfig {
}
提供用戶類
/*** 此類為用戶類,實現了 WebApplicationInitializer** @Author 孤風雪影* @Email gitee.com/efairy520* @Date 2025/4/15 5:17* @Version 1.0*/
public class QuickStart extends AbstractAnnotationConfigDispatcherServletInitializer {@Overrideprotected Filter[] getFilters() {return new Filter[0];}@Overrideprotected Class<?> getRootConfigClass() {return AppConfig.class;}@Overrideprotected Class<?> getWebConfigClass() {return AppConfig.class;}
}
運行Tomcat9的Bootstrap里面的main方法
- 首先啟動Tomcat
- 識別到Pom依賴里面通過SPI注冊的SpringServletContainerInitializer類,發現它是一個SCI
- 掃描到所有的WebApplicationInitializer類型的用戶類
- 調用里面的onStartup方法,根據用戶類提供的配置類,創建父子容器,并初始化DispatcherServlet
- Tomcat啟動完成,開始接收用戶請求
啟動后,訪問任意接口
- http://localhost:18080/
四月 16, 2025 3:02:26 上午 org.apache.catalina.startup.Catalina start
DispatcherServlet的service方法調用