警告
      
      
        本文最后更新于 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,它会根据 上面 的步骤,加载对应的实现类。