目录

Spring-MVC源码分析init流程

Spring MVC源码分析のinit流程


前言

本篇介绍Spring MVC 的初始化流程,源码的体现是 HttpServletBean的init方法 。通常Spring MVC的项目,需要打成war包,部署在Tomcat服务器上,也就是Spring MVC通常需要配合Tomcat使用,当然也可以使用Jetty、Undertow 等。

Spring MVC的源码,主要是分为两部分,第一部分是Tomcat启动时,Spring MVC上下文和容器的初始化。第二部分则是请求到达Tomcat,进行路径映射,转发到 DispatcherServlet 然后进行处理并且返回的流程。

一、 init

在Tomcat容器启动时,会进入到 HttpServletBeaninit方法 ,在其中进行初始化的操作:

https://i-blog.csdnimg.cn/direct/d694e5b3c40f430a9f55948f8c943a0b.png initServletBean 最终会跳转到 FrameworkServletinitWebApplicationContext 方法,而在该方法中,最关键的代码是 createWebApplicationContext

https://i-blog.csdnimg.cn/direct/09c8d875524943d2a22d73549232d4be.png 在执行完 initWebApplicationContext 方法刷新容器之后,会将容器对象赋值给 webApplicationContext 属性,并且执行 initFrameworkServlet 方法(是一个空实现)。

https://i-blog.csdnimg.cn/direct/17a59bf075ec4b38919a0bfe807d9b14.png

1.1、createWebApplicationContext

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
	//首先获取上下文,这里获取到的是XmlWebApplicationContext类型,即解析xml文件的模式
	Class<?> contextClass = getContextClass();
	if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
		throw new ApplicationContextException(
				"Fatal initialization error in servlet with name '" + getServletName() +
				"': custom WebApplicationContext class [" + contextClass.getName() +
				"] is not of type ConfigurableWebApplicationContext");
	}
	//尝试通过类型去实例化	XmlWebApplicationContext 容器 
	//实例化完成后的XmlWebApplicationContext是空的,属性都是默认值
	ConfigurableWebApplicationContext wac =
			(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
	//设置环境
	wac.setEnvironment(getEnvironment());
	//设置父容器
	wac.setParent(parent);
	//拿到WEB-INF下的spring.xml配置文件
	String configLocation = getContextConfigLocation();
	if (configLocation != null) {
		wac.setConfigLocation(configLocation);
	}
	//完成后续容器初始化与刷新操作(配置、监听器、refresh 等)
	configureAndRefreshWebApplicationContext(wac);
	return wac;
}

configureAndRefreshWebApplicationContext 方法:

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
	// 如果当前容器的 id 还只是默认值(即内存地址),我们给它设置一个更有意义的 id
	if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
		// 如果用户自定义设置了 contextId,则使用用户提供的
		if (this.contextId != null) {
			wac.setId(this.contextId);
		} else {
			// 否则自动生成一个 id:一般是 application:/contextPath/servletName
			wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
					ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
		}
	}

	// 设置容器的 ServletContext,后续会用于获取资源、属性等
	wac.setServletContext(getServletContext());

	// 设置 ServletConfig(包含 servlet 的 init 参数等信息)
	wac.setServletConfig(getServletConfig());

	// 设置命名空间,一般用于区分不同的 DispatcherServlet 容器
	wac.setNamespace(getNamespace());

	// 添加一个监听器,用于监听容器的 refresh 事件(刷新完成时触发)
	wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

	// 获取当前环境对象,用于管理属性配置(如 application.properties)
	ConfigurableEnvironment env = wac.getEnvironment();
	if (env instanceof ConfigurableWebEnvironment) {
		// 初始化 servletContext 和 servletConfig 中的属性源(property sources)
		// 这样可以在后续的 Bean 初始化过程中使用 ${...} 占位符引用这些属性
		((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
	}

	// 钩子方法:可以在子类中覆写,对容器进一步自定义处理(例如注册额外 Bean 定义)
	postProcessWebApplicationContext(wac);

	// 应用 SpringApplicationContextInitializer(初始化器扩展点)
	applyInitializers(wac);

	// 最终刷新容器,触发 Bean 的加载、依赖注入、事件发布等流程
	wac.refresh();
}

最终调用的是 AbstractApplicationContextrefresh 方法,在 refresh 完成后, XmlWebApplicationContext 的属性才有值:

https://i-blog.csdnimg.cn/direct/8baaaf85cc9a449496da83760a1e8dd5.png

1.2、onRefresh

configureAndRefreshWebApplicationContext 方法中,还有一行关键的代码:

wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

这行代码是添加了一个监听器, 触发时机是Spring容器refresh完成后。

https://i-blog.csdnimg.cn/direct/d37215fa0b9042b0a5f41c6556305230.png 最终会跳转到 DispatcherServletinitStrategies 方法。

https://i-blog.csdnimg.cn/direct/22decb7ce187440fa6d2061bbabaeb01.png https://i-blog.csdnimg.cn/direct/6d9c7d22215247689b308b7b3270bcfd.png 在该方法中,会进行一些初始化操作,其中最重要的是 initHandlerMappingsinitHandlerAdapters 。在说明这两个方法的逻辑之前,有必要先说明下什么是Handler。

二、请求处理器

Handler是Spring MVC中的请求处理器,有四种实现:

2.1、@RequestMapping

RequestMapping 是最常见的请求处理器实现,可以加在方法也可以加在类上,如果加在类上,需要配合 @Component 注解:

@Controller
public class TestController {

    @Autowired
    private TestService testService;

    @RequestMapping(method = RequestMethod.GET, path = "/test")
    public void test(){
        System.out.println("test");
    }
}

2.2、Controller接口

后三种方式在项目开发中较为少见,第一种是自定义类,实现Controller接口,同时需要将自定义的类标记成bean,然后加上请求的路径(需要加/)

@Component("/test1")
public class MyHandler implements Controller {
    /**
     * @param request  current HTTP request 
     * @param response current HTTP response
     * @return
     * @throws Exception
     */
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        System.out.println("MyHandler implements Controller");
        return null;
    }
}

2.3、HttpRequestHandler接口

自定义一个类,实现HttpRequestHandler接口,和实现Controller接口的区别在于返回值为空。

@Component("/test2")
public class MyHandler1 implements HttpRequestHandler {
    /**
     * @param request  current HTTP request 
     * @param response current HTTP response
     * @throws ServletException
     * @throws IOException
     */
    @Override
    public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("MyHandler1 implements HttpRequestHandler");
    }
}

2.4、HandlerFunction

也可以在配置类中注册一个自定义的bean,返回值类型为 RouterFunction<ServerResponse>

@ComponentScan("com.zhouyu")
@Configuration
public class AppConfig {
		
		@Bean
		public RouterFunction<ServerResponse> person() {
			return route() .GET("/app/person", request ->ServerResponse.status(HttpStatus.OK).body("Hello GET"))
									.POST("/app/person", request ->ServerResponse.status(HttpStatus.OK).body("Hello POST")).build(); 
		 }
		 
}

上述这四种Handler,分别对应Spring MVC源码中 DispatcherServlet.properties

https://i-blog.csdnimg.cn/direct/ff9cd3701ff84deba8cb99b15753ba33.png

  • @RequestMapping对应 BeanNameUrlHandlerMapping
  • Controller接口和HttpRequestHandler接口对应 RequestMappingHandlerMapping
  • HandlerFunction对应 RouterFunctionMapping

三、initHandlerMappings

HandlerMapping 的作用,就是去记录 请求路径上述Handler 的对应关系。可以理解底层有一个map,key是请求的路径,value是与之匹配的Handler对象, 在容器启动时就将其维护好,避免在请求到达时再去寻找对应的Handler。

initHandlerMappings 中,首先会去寻找有没有用户自定义的 HandlerMapping 类型的bean,如果有,就会直接给 DispatcherServlethandlerMappings 属性赋值为自定义的那个处理器,不会再去用 DispatcherServlet.properties 中默认的那三个了。

https://i-blog.csdnimg.cn/direct/c64591af651d4d629de4f0420d89a665.png 如果没有自定义的,才会去调用 getDefaultStrategiesDispatcherServlet.properties 中的:

https://i-blog.csdnimg.cn/direct/98a5b80f4b0142d29d74411956415df7.png

3.1、getDefaultStrategies

getDefaultStrategies 中,首先会拿到 DispatcherServlet.properties 默认的三个处理器:

https://i-blog.csdnimg.cn/direct/15b7968f8079428f9b613cc66ac4a3a8.png 然后会按照顺序循环处理:

https://i-blog.csdnimg.cn/direct/390ef7280637457da7ab012b39690f72.png 这里要区分不同的情况。

3.1.1、RequestMappingHandlerMapping

RequestMappingHandlerMapping 是针对 @RequestMapping 注解的情况,处理逻辑的代码在 afterPropertiesSet 中:

https://i-blog.csdnimg.cn/direct/aee95e81d19f4e67ae8c7e174bb47ac1.png 因为它间接实现了 InitializingBean 接口,所以处理时机是在bean生命周期中的 初始化阶段 。我们来看一下 RequestMappingHandlerMapping 重写的 afterPropertiesSet 的逻辑,最终调用的是父类 AbstractHandlerMethodMappingafterPropertiesSet 方法:

https://i-blog.csdnimg.cn/direct/b3c03fb7b99c44808412192379799411.png 首先会拿到Spring容器中所有bean的名称,然后过滤掉"scopedTarget." 开头的 Bean,最后用当前的候选bean调用 processCandidateBean 方法:

https://i-blog.csdnimg.cn/direct/1ca9a2b97b464257ac11e5a596ea8748.pngprocessCandidateBean 方法中,会在 isHandler 中进行判断,当前的bean是否有 @Controller注解@RequestMapping注解

https://i-blog.csdnimg.cn/direct/933f26ba955b46218b4380d268bfd56a.png https://i-blog.csdnimg.cn/direct/d83a6019b8794d13ab9efb77718a3519.png 如果仅仅在类上加@RequestMapping注解也是不可以的,因为没有@Component,该类就不会被扫描并且注册成bean,也不会进入这一步的逻辑

对于符合要求的bean,则会进入 detectHandlerMethods 方法:

https://i-blog.csdnimg.cn/direct/732163d293584e47af06975ce3096b4a.png 其中的核心方法是 getMappingForMethod :

/**
 * 为指定的方法构建对应的 RequestMappingInfo(映射信息)。
 * 该方法会综合考虑方法级注解、类级注解,以及类路径前缀等信息。
 *
 * @param method       要处理的方法(如某个 @RequestMapping 方法)
 * @param handlerType  方法所属的处理器类
 * @return 方法对应的 RequestMappingInfo(包含路径、请求方式、参数条件等),如果该方法无映射,则返回 null
 */
@Override
@Nullable
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
    // 第一步:从方法上提取 @RequestMapping 注解信息(方法级映射)
    RequestMappingInfo info = createRequestMappingInfo(method);

    if (info != null) {
        // 第二步:从类上提取 @RequestMapping 注解信息(类级映射)
        RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);

        // 如果类级映射不为空,则与方法级映射合并,形成完整映射
        if (typeInfo != null) {
            info = typeInfo.combine(info);
        }

        // 第三步:获取路径前缀(比如配置了统一前缀 "/api"),追加到映射路径中
        String prefix = getPathPrefix(handlerType);
        if (prefix != null) {
            // 构造前缀映射信息,再与原映射 info 合并
            info = RequestMappingInfo.paths(prefix)
                    .options(this.config)  // 使用当前的 config(包含匹配规则等)
                    .build()
                    .combine(info);
        }
    }

    // 返回最终合并后的 RequestMappingInfo
    return info;
}

在该方法中会解析 @RequestMapping 注解信息:

https://i-blog.csdnimg.cn/direct/45410a28439645bb921d0318c35d3b3d.png 最终返回到 detectHandlerMethods 方法的是一个 Map<Method, T> methodskey是当前的方法对象,value是注解的信息

https://i-blog.csdnimg.cn/direct/ee550d149fbb4d82bfa45a02884bfd75.png 然后会遍历这个map:

https://i-blog.csdnimg.cn/direct/fe3f33dd9de5423f8454049d5f9155a3.png 从上图中的 registerHandlerMethod 进入 MappingRegistryregister 方法,在该方法中有两个重要的map:

  • pathLookup:key存放了请求路径,value存放了注解的信息。

https://i-blog.csdnimg.cn/direct/9783b593852f413f9980fea884b86b43.png

  • registry:key存放了注解的对象,value存放了方法的信息。

    https://i-blog.csdnimg.cn/direct/02182c16977f4175b6b13daaffaacdda.png

    到这一步为止就完成了路径与方法的映射关系

3.1.2、BeanNameUrlHandlerMapping

BeanNameUrlHandlerMapping 处理的逻辑在 setApplicationContext 方法中。

因为 BeanNameUrlHandlerMapping 间接实现了 ApplicationContextAware 接口,所以处理时机是在bean生命周期中的 初始化 前。

会调用 initApplicationContext 方法:

https://i-blog.csdnimg.cn/direct/116d102e786e43e08b7373f185028318.png 其核心方法是 AbstractDetectingUrlHandlerMappingdetectHandlers ,同样会先获取容器中所有的bean:

https://i-blog.csdnimg.cn/direct/0b1ed7c953eb487684ba3886bae38dd0.png 然后遍历这些bean的名称,找到以"/“开头的(之前说明过,自定义bean实现HttpRequestHandler接口或Controller接口,需要指定bean的名称,并且在最前面加上”/“代表路径)

https://i-blog.csdnimg.cn/direct/0959c5364e3740799071af57ab3c4e20.png https://i-blog.csdnimg.cn/direct/f1bb3408fb124da2a62555f4eb2b61e2.png 在这一步注册映射关系

调用到 registerHandler 方法,首先根据bean的名称获取到bean:

https://i-blog.csdnimg.cn/direct/2a2f7bf73e3543dd9907814bf2a90e21.png

  • handlerMap:key存放访问路径,value存放类对象。

https://i-blog.csdnimg.cn/direct/8be6b4a0ddd14180ad11c35b29f5d5b0.png 最终handlerMap存放了映射关系:

https://i-blog.csdnimg.cn/direct/469ad63b94194ffb8be285eb8f99b21f.png

四、initHandlerAdapters

initHandlerAdapters 是用于初始化 DispatcherServlethandlerAdapters 属性的方法。initHandlerAdapters() 的作用就是准备好一组 能够适配各种Controller类型的执行器,让DispatcherServlet能够在处理请求时找到并调用合适的控制器逻辑。

https://i-blog.csdnimg.cn/direct/8811ae4d58b546acab0fc206ef4950db.png 和initHandlerMappings一样,首先会去找是否有自定义的HandlerAdapter,如果有,就用自定义的,并且不会用默认的了

https://i-blog.csdnimg.cn/direct/5a753ada62cc49cb93489af1cd7580e2.png getDefaultStrategies 同样是去DispatcherServlet.properties中找默认的策略

https://i-blog.csdnimg.cn/direct/84503b224397400abe3c2808ff5d8016.png 四种策略,分别对应<二>中的四种方式

initHandlerMappings 一样,最终同样会去遍历 DispatcherServlet.properties 中的策略,然后走创建各自bean的流程。

https://i-blog.csdnimg.cn/direct/8e37b308bfea48b9b4764138aea93433.png 最终得到四个实例,因为在案例中四种请求方式都有。

https://i-blog.csdnimg.cn/direct/01761ee691a4462c80833517e8ce52fd.png 这里也是 适配器模式 的体现,四种 HandlerAdapter 都实现了 HandlerAdapter 适配器接口,真正如何适配的,会在 doDispatch (处理请求)的源码中有所体现。

https://i-blog.csdnimg.cn/direct/f12f96de549448ee85691d35b0af90e7.png https://i-blog.csdnimg.cn/direct/b10169b51b1c478699d546c6d71594ee.png

总结

Spring MVC在解析web.xml,调用 DispatcherServletinit 方法时,完成的主要工作可以分为两部分:

  • 添加一个监听器,用于监听容器的 refresh 事件(刷新完成时触发)。
  • 创建Spring容器,调用refresh方法。

其中Spring容器refresh 完成后,最终会被监听到,并且调用 DispatcherServletinitStrategies 方法,在该方法中会完成HandlerMappings和HandlerAdapters的初始化。HandlerMappings用于保存路径和方法的映射关系,而HandlerAdapters让DispatcherServlet能够在处理请求时找到并调用合适的控制器逻辑。

HandlerMappings和HandlerAdapters的初始化,本质都是实例化 DispatcherServlet.properties 配置文件中定义的bean名称。在各自的生命周期中进行方法回调。 BeanNameUrlHandlerMapping 处理的逻辑在 setApplicationContext 方法中, RequestMappingHandlerMapping 处理逻辑的代码在 afterPropertiesSet 中。