一、简介
从本章节开始进入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方法调用