当前位置: 首页 > article >正文

Spring MVC源码分析のinit流程

文章目录

  • 前言
  • 一、 init
    • 1.1、createWebApplicationContext
    • 1.2、onRefresh
  • 二、请求处理器
    • 2.1、@RequestMapping
    • 2.2、Controller接口
    • 2.3、HttpRequestHandler接口
    • 2.4、HandlerFunction
  • 三、initHandlerMappings
    • 3.1、getDefaultStrategies
      • 3.1.1、RequestMappingHandlerMapping
      • 3.1.2、BeanNameUrlHandlerMapping
  • 四、initHandlerAdapters
  • 总结


前言

  本篇介绍Spring MVC 的初始化流程,源码的体现是HttpServletBean的init方法。通常Spring MVC的项目,需要打成war包,部署在Tomcat服务器上,也就是Spring MVC通常需要配合Tomcat使用,当然也可以使用Jetty、Undertow 等。
  Spring MVC的源码,主要是分为两部分,第一部分是Tomcat启动时,Spring MVC上下文和容器的初始化。第二部分则是请求到达Tomcat,进行路径映射,转发到DispatcherServlet然后进行处理并且返回的流程。

一、 init

  在Tomcat容器启动时,会进入到HttpServletBeaninit方法,在其中进行初始化的操作:
在这里插入图片描述  initServletBean最终会跳转到FrameworkServletinitWebApplicationContext方法,而在该方法中,最关键的代码是createWebApplicationContext
在这里插入图片描述  在执行完initWebApplicationContext方法刷新容器之后,会将容器对象赋值给webApplicationContext属性,并且执行initFrameworkServlet方法(是一个空实现)。
在这里插入图片描述

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的属性才有值:
在这里插入图片描述

1.2、onRefresh

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

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

  这行代码是添加了一个监听器,触发时机是Spring容器refresh完成后。
在这里插入图片描述  最终会跳转到DispatcherServletinitStrategies方法。
在这里插入图片描述在这里插入图片描述  在该方法中,会进行一些初始化操作,其中最重要的是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
在这里插入图片描述

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

三、initHandlerMappings

  HandlerMapping的作用,就是去记录请求路径上述Handler的对应关系。可以理解底层有一个map,key是请求的路径,value是与之匹配的Handler对象,在容器启动时就将其维护好,避免在请求到达时再去寻找对应的Handler。
  在initHandlerMappings中,首先会去寻找有没有用户自定义的HandlerMapping类型的bean,如果有,就会直接给DispatcherServlethandlerMappings属性赋值为自定义的那个处理器,不会再去用DispatcherServlet.properties中默认的那三个了。
在这里插入图片描述  如果没有自定义的,才会去调用getDefaultStrategiesDispatcherServlet.properties中的:
在这里插入图片描述

3.1、getDefaultStrategies

  在getDefaultStrategies中,首先会拿到DispatcherServlet.properties默认的三个处理器:
在这里插入图片描述  然后会按照顺序循环处理:
在这里插入图片描述  这里要区分不同的情况。

3.1.1、RequestMappingHandlerMapping

  RequestMappingHandlerMapping是针对@RequestMapping注解的情况,处理逻辑的代码在afterPropertiesSet中:
在这里插入图片描述  因为它间接实现了InitializingBean接口,所以处理时机是在bean生命周期中的初始化阶段。我们来看一下RequestMappingHandlerMapping重写的afterPropertiesSet的逻辑,最终调用的是父类AbstractHandlerMethodMappingafterPropertiesSet方法:
在这里插入图片描述  首先会拿到Spring容器中所有bean的名称,然后过滤掉"scopedTarget." 开头的 Bean,最后用当前的候选bean调用processCandidateBean方法:
在这里插入图片描述  在processCandidateBean方法中,会在isHandler中进行判断,当前的bean是否有@Controller注解@RequestMapping注解
在这里插入图片描述在这里插入图片描述如果仅仅在类上加@RequestMapping注解也是不可以的,因为没有@Component,该类就不会被扫描并且注册成bean,也不会进入这一步的逻辑

  对于符合要求的bean,则会进入detectHandlerMethods方法:
在这里插入图片描述  其中的核心方法是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 注解信息:
在这里插入图片描述  最终返回到detectHandlerMethods方法的是一个Map<Method, T> methodskey是当前的方法对象,value是注解的信息
在这里插入图片描述  然后会遍历这个map:
在这里插入图片描述  从上图中的registerHandlerMethod进入MappingRegistryregister方法,在该方法中有两个重要的map:

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

在这里插入图片描述

  • registry:key存放了注解的对象,value存放了方法的信息。
    在这里插入图片描述
      到这一步为止就完成了路径与方法的映射关系

3.1.2、BeanNameUrlHandlerMapping

  BeanNameUrlHandlerMapping处理的逻辑在setApplicationContext方法中。
  因为BeanNameUrlHandlerMapping间接实现了ApplicationContextAware接口,所以处理时机是在bean生命周期中的初始化前。
  会调用initApplicationContext方法:
在这里插入图片描述  其核心方法是AbstractDetectingUrlHandlerMappingdetectHandlers,同样会先获取容器中所有的bean:
在这里插入图片描述  然后遍历这些bean的名称,找到以"/“开头的(之前说明过,自定义bean实现HttpRequestHandler接口或Controller接口,需要指定bean的名称,并且在最前面加上”/"代表路径)
在这里插入图片描述在这里插入图片描述在这一步注册映射关系

  调用到registerHandler方法,首先根据bean的名称获取到bean:
在这里插入图片描述

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

在这里插入图片描述  最终handlerMap存放了映射关系:
在这里插入图片描述

四、initHandlerAdapters

  initHandlerAdapters是用于初始化DispatcherServlethandlerAdapters属性的方法。initHandlerAdapters() 的作用就是准备好一组 能够适配各种Controller类型的执行器,让DispatcherServlet能够在处理请求时找到并调用合适的控制器逻辑。
  和initHandlerMappings一样,首先会去找是否有自定义的HandlerAdapter,如果有,就用自定义的,并且不会用默认的了

在这里插入图片描述getDefaultStrategies 同样是去DispatcherServlet.properties中找默认的策略

在这里插入图片描述四种策略,分别对应<二>中的四种方式

  和initHandlerMappings一样,最终同样会去遍历DispatcherServlet.properties中的策略,然后走创建各自bean的流程。
在这里插入图片描述  最终得到四个实例,因为在案例中四种请求方式都有。
在这里插入图片描述  这里也是适配器模式的体现,四种HandlerAdapter都实现了HandlerAdapter适配器接口,真正如何适配的,会在doDispatch(处理请求)的源码中有所体现。
在这里插入图片描述在这里插入图片描述

总结

  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中。


http://www.kler.cn/a/583279.html

相关文章:

  • 【心理课堂】学习软件的道路上若感到了困难和迷茫怎么办
  • ubuntu 24.04通过Flatpak安装迅雷
  • LeetCode2012
  • 【DNS系列】httpdns实现原理
  • Chrome 扩展开发 API实战:History(三)
  • 【蓝桥杯】3514字串简写
  • 豆包大模型 1.5 正式发布,全面上线火山方舟
  • 中国软件供应链安全技术指南|DevSecOps敏捷安全技术金字塔V3.0正式发布
  • MySQL启动报错解决
  • Linux》》Ubuntu22.04下Docker的安装 Docker
  • 【Java代码审计 | 第十一篇】SSRF漏洞成因及防范
  • C# Channel
  • springboot集成neo4j搭建知识图谱后端项目(一)
  • 深度学习基础:线性代数本质5——行列式
  • 【网络安全 | 漏洞挖掘】$15,000——通过持久token获取个人身份信息(PII)
  • 图像识别技术与应用-YOLO
  • 2025年渗透测试面试题总结-奇安信安全工程师(题目+回答)
  • 数字人分身开发指南:从概念到实战
  • 使用Nodejs基于DeepSeek加chromadb实现RAG检索增强生成 本地知识库
  • 树莓集团落子海南,如何重构数字产业生态体系​