springboot 用多了,竟然都不会使用原生 spring 来做 web 项目了。然而,如果想要深入研究 spring mvc,还得从最原始的方式开始,我觉得是最能让人理解的方法。话不多说,本章就记录下我的研究过程
本章只介绍基于 Servlet3.0 规范来搭建 web 工程的研究,基于 web.xml 的方式见:juejin.cn/book/685791…
🤔 思考
在正式开始之前,我们来思考下面这个问题: 按照 springboot 的启动方式,是先启动 webApplication,再启动 tomcat,向 tomcat 注册 DispatcherServlet 来实现 handler 的查找、执行。但是在 spring 中正好相反,是先启动 tomcat,再启动 webApplication,tomcat 是怎样来启动 spring 容器的呢,启动的又是哪个 application 呢?
🚪 spring 容器启动入口 ServletContainerInitializer
ServletContainerInitializer 是 javax.servlet 包中提供的一个接口,用于在 tomcat 启动时做一些初始化操作,例如注册 Listener、Servlet、Filter 等操作
tomcat 会利用 spi 机制加载此接口的实现类,而 spring 正是利用这个机制,来达到启动 webApplication 的目的,来到 spring-mvc 源码目录,可以看到如下文件,这正是 spi 机制会加载的文件
文件中配置的实现类 SpringServletContainerInitializer,在 spring-web 模块中,让我们来一探究竟
首先该类上有一个注解 @HandlesTypes(WebApplicationInitializer.class),这个注解由 tomcat 内部进行解析,他会找到类路径下 WebApplicationInitializer 的所有子类(包括接口、抽象类),传入下面的 onStartup 方法中
onStartup 方法会在 tomcat 启动过程中调用,他的第二个参数是 tomcat 的上下文对象,方法中会实例化这些 Initializer 对象,并依次调用他们的 onStartup 方法,因此,我们只需要实现 WebApplicationInitializer 这个接口,在这里面启动 webApplication 容器即可
WebApplicationInitializer 这个接口在 spring 中有几个子类,但是并没有具体实现类,需要我们自己实现
其中 AbstractAnnotationConfigDispatcherServletInitializer 这个类已经帮我们实现了大部分逻辑,并且也写好了初始化 DispatchServlet、注册 DispatchServlet 的逻辑,他有两个抽象方法,如下:
// 根配置类
protected abstract Class<?>[] getRootConfigClasses();
// servlet 配置类
protected abstract Class<?>[] getServletConfigClasses();
// dispatchServlet 的匹配路径
protected abstract String[] getServletMappings();
为什么需要分两个配置类呢?这是因为他会初始化两个 webApplication,ServletWebApplication 用来装配 Controller,RootWebApplication 用来装配其他 bean,这俩是父子关系,这样在 Controller 能注入其他的 bean,但是其他 bean 中不能注入 Controller,形成一种隔离
核心方法在 AbstractDispatcherServletInitializer 的 onStartup 方法中
这里面核心逻辑是创建两个 web 容器,注册 DispatchServlet,这里面的逻辑比较简单,不一一解析
✍ 自己动手实现 AbstractAnnotationConfigDispatcherServletInitializer
public class DispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{RootConfiguration.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{ServletConfiguration.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
@Configuration
@ComponentScan(value = "org.springframework.webmvc",
excludeFilters = {@ComponentScan.Filter(classes = RestController.class)})
public class RootConfiguration {
}
@Configuration
@ComponentScan("org.springframework.webmvc.controller")
public class ServletConfiguration {
}
Root 配置文件里需要排除 @Controller 类的扫描,否则俩容器重复了 Servlet 配置文件只需要扫描 @Controller 类 再写个 Controller 类
@RestController
public class UserController {
@GetMapping
public String hello(){
return "hello";
}
}
🐱 tomcat 环境配置
如果要将 spring 运行在 web 环境,那 tomcat 是必须的,在 mac 电脑中,下载安装 tomcat 非常方便,只需要一行命令: :::info brew install tomcat@9 :::
打开 idea,添加一个 tomcat 运行/调试 配置,里面需要用到 tomcat 的主目录:/opt/homebrew/opt/tomcat@9/libexec
接着配置 tomcat 的部署配置
启动 tomcat,如果没有报错,则说明启动成功
访问对应的 controller 路径,看是否能正确解析