一、简介
从本章节开始进入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;
}
@Override
public 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);
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("DispatcherServlet的service方法调用");
}
// 组件初始化,Servlet的init方法调用
@Override
protected 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;
@Override
public void onStartup(ServletContext servletContext) {
// 创建父容器
final AbstractApplicationContext rootApplicationContext = createRootApplicationContext();
// 父容器放入servletContext
servletContext.setAttribute(WebApplicationContext.ROOT_NAME, rootApplicationContext);
// 刷新父容器(通过register配置类,所以需要手动刷新) -> 在源码中是通过事件进行refresh
rootApplicationContext.refresh();
final WebApplicationContext webApplicationContext = createWebApplicationContext();
// 创建DispatcherServlet
final 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 {
@Override
protected AbstractApplicationContext createRootApplicationContext() {
final Class<?> rootConfigClass = getRootConfigClass();
if (ObjectUtil.isNotNull(rootConfigClass)) {
final AnnotationConfigApplicationContext rootContext = new AnnotationConfigApplicationContext();
rootContext.register(rootConfigClass);
return rootContext;
}
return null;
}
@Override
protected 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调用
@Override
public 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 {
@Override
protected Filter[] getFilters() {
return new Filter[0];
}
@Override
protected Class<?> getRootConfigClass() {
return AppConfig.class;
}
@Override
protected 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方法调用