警告
本文最后更新于 2020-12-15,文中内容可能已过时。
spring源码系列文章,示例代码的中文注释,均是 copy 自 https://gitee.com/wlizhi/spring-framework 。
链接中源码是作者从 github 下载,并以自身理解对核心流程及主要节点做了详细的中文注释。
1 入口
根据文章 web容器启动时ServletContainerInitializer的加载 中所述,创建一个 WebApplicationInitializer 的实现类,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
/** 根容器 父容器 */
@Override
protected Class<?>[] getRootConfigClasses() {
logger.info("===========spring根容器加载成功============");
return new Class[]{RootConfig.class};
}
/** web容器 子容器 */
@Override
protected Class<?>[] getServletConfigClasses() {
logger.info("===========springweb子容器加载成功============");
return new Class[]{WebConfig.class};
}
/** 获取前端控制器映射信息 dispatcherServlet */
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
|
上面代码中,共重写了三个方法,分别是spring父容器的配置类、mvc子容器的配置类、DispatcherServlet 的拦截路径的设置。
在 mvc 子容器中,使用注解 @EnableWebMvc 来激活mvc功能。
mvc 子容器配置类代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
@EnableWebMvc
@ComponentScan(value = {"top.wlz922.controller", "top.wlz922.interceptor", "top.wlz922.resolver"}
// , includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})}
// , useDefaultFilters = true
)
@EnableAspectJAutoProxy
public class WebConfig implements WebMvcConfigurer {
private static final Logger logger = LoggerFactory.getLogger(WebConfig.class);
@Autowired(required = false)
private List<HandlerInterceptor> interceptors;
/**
* 配置springmvc视图
*/
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
logger.info("============jsp解析器开始加载===============");
// registry.jsp();
registry.jsp("/WEB-INF/views/", ".jsp");
logger.info("============jsp解析器开始成功,开始渲染把===============");
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
ArrayList<MediaType> list = new ArrayList<>();
list.add(MediaType.APPLICATION_JSON);
list.add(MediaType.TEXT_PLAIN);
FastJsonHttpMessageConverter c = new FastJsonHttpMessageConverter();
c.setSupportedMediaTypes(list);
converters.add(c);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
if (interceptors != null) {
for (HandlerInterceptor interceptor : interceptors) {
InterceptorRegistration registration = registry.addInterceptor(interceptor);
if (interceptor instanceof HaldlerInterceptorPathPattern) {
String[] pathPatterns = ((HaldlerInterceptorPathPattern) interceptor).getPathPatterns();
registration.addPathPatterns(pathPatterns);
}
}
}
}
}
|
根据 Servlet3.0 规范,tomcat 容器在启动时,会根据 SPI 机制加载配置的 ServletContainerInitializer 实现类。springmvc中使用的 SpringServletContainerInitializer,在这个类声明中,引入了 WebApplicationInitializer。
在 WebApplicationInitializer 的 onStartup() 中,会在创建 DispatcherServlet 时,创建一个 WebApplicationContext,将其设置到 DispatcherServlet 的成员变量中。
而在创建这个 WebApplicationContext 时,会调用 WebApplicationInitializer 实现类的回调方法 getServletConfigClasses(),获取到 mvc 子容器的配置类。
接着,在 mvc 子容器核心方法 refresh() 调用的时候,就会扫描加载这个配置类中的信息。
大致流程是:
- SPI机制加载 ServletContainerInitializer 实现类。
- 执行 HandlerTypes 中声明的 WebApplicationInitializer 实现类。
- 调用 WebApplicationInitializer.onStartup()。
- 创建父容器 ApplicationContext,调用钩子方法 getRootConfigClasses(),将其设置为父容器 refresh() 的配置类,并将父容器缓存到 ServletContext 中。
- 创建 DispatcherServlet,并创建 WebApplicationContext 子容器,在创建时,调用钩子方法 getServletConfigClasses() 作为子容器 refresh() 的配置类,将子容器设置到 DispatcherServlet 成员变量中。
- 在父容器、子容器 refresh() 执行的时候,就会加载扫描各自配置类中的注解。
@EnableWebMvc 注解是对 mvc功能的支持,内部通过 @Bean 向 ioc 容器中注入了许多功能支持的类实例。这里是 mvc 功能支持的入口。
2 嵌入式 Tomcat
首先,引入依赖包:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<!-- 嵌入式tomcat依赖 -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>${tomcat.embed.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>${tomcat.embed.version}</version>
<!--<scope>provided</scope>-->
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-logging-juli</artifactId>
<version>${tomcat.embed.version}</version>
</dependency>
|
编写启动类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
public class SpringSourceMvcAnoApplication {
private static final int PORT = 8080;
private static final String CONTEXT_PATH = "";
private static final Logger LOG = LoggerFactory.getLogger(SpringSourceMvcAnoApplication.class);
public static void main(String[] args) throws Exception {
SpringSourceMvcAnoApplication starter = new SpringSourceMvcAnoApplication();
starter.start();
}
public void start() throws Exception {
LOG.info("=================开始加载内嵌tomcat=====================");
String userDir = System.getProperty("user.dir");
// 这种方式设置System.setProperty("tomcat.util.scan.StandardJarScanFilter.jarsToSkip", "\\,*");
// 可以加快启动速度,但是由于读取的字节码文件是maven编译的(maven编译使用的本地仓库的jar包),会导致gradle项目中的代码断点无效。
// 这里设置为直接读取src/main/webapp下的文件,自动扫描baseDir下的java代码。(启动慢,可在gradle项目代码中打断点调试。)
// String webappDir = userDir + File.separator + "/target/spring-source-mvc-ano-1.0-SNAPSHOT";
String webappDir = userDir + File.separator + "/src/main/webapp";
Tomcat tomcat = new Tomcat();
tomcat.setBaseDir(userDir);
tomcat.setPort(PORT);
tomcat.addWebapp(CONTEXT_PATH, webappDir);
tomcat.enableNaming();
LOG.info("BaseDir:{}", userDir);
LOG.info("port:{}", PORT);
LOG.info("context_path:{}", webappDir);
// System.setProperty("tomcat.util.scan.StandardJarScanFilter.jarsToSkip", "\\,*");
tomcat.start();
LOG.info("==================tomcat加载成功==========================");
LOG.info("测试地址:http://localhost:8080/index");
tomcat.getServer().await();
}
}
|
通过运行 main() 方法来启动一个嵌入式 tomcat,它会根据 上面 的步骤,加载对应的实现类。