SpringMvc有几个上下文

你好,我是柳岸花明。

SpringMVC作为Spring框架的重要组成部分,其启动流程和父子容器机制是理解整个框架运行机制的关键。本文将通过一系列详细的流程图,深入剖析SpringMVC的启动原理与父子容器的源码结构。

SpringMVC 父子容器

父容器的创建

在 web.xml 文件中,可以定义如下内容:

复制代码
<web-app>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring.xml</param-value>
    </context-param>

    <servlet>
        <servlet-name>app</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>app</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>
</web-app>

这里定义了一个 listener 和 servlet。

ContextLoaderListener 的作用是创建一个 Spring 容器(父容器)。流程如下:

  1. Tomcat启动,解析web.xml时
  2. 发现定义了一个ContextLoaderListener,Tomcat就会执行该listener中的contextInitialized()方法,该方法就会去创建要给Spring容器
  3. 从ServletContext中获取contextClass参数值,该参数表示所要创建的Spring容器的类型
  4. 如果没有配置该参数,那么则会从ContextLoader.properties文件中读取org.springframework.web.context.WebApplicationContext配置项的值,SpringMVC默认提供了一个ContextLoader.properties文件,内容为org.springframework.web.context.support.XmlWebApplicationContext
  5. 所以XmlWebApplicationContext就是要创建的Spring容器类型
  6. 确定好类型后,就用反射调用无参构造方法创建出来一个XmlWebApplicationContext对象
  7. 然后继续从ServletContext中获取contextConfigLocation参数的值,也就是一个spring配置文件的路径
  8. 把spring配置文件路径设置给Spring容器,然后调用refresh(),从而启动Spring容器,从而解析spring配置文件,从而扫描生成Bean对象等
  9. 这样Spring容器就创建出来了
  10. 有了Spring容器后,就会把XmlWebApplicationContext对象作为attribute设置到ServletContext中去,key为WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
  11. 把Spring容器存到ServletContext中的原因,是为了给Servlet创建出来的子容器来作为父容器的

子容器的创建

Tomcat启动过程中,执行完ContextLoaderListener的contextInitialized()之后,就会创建DispatcherServlet了,web.xml中定义DispatcherServlet时,load-on-startup为1,表示在Tomcat启动过程中要把这个DispatcherServlet创建并初始化出来,而这个过程是比较费时间的,所以要把load-on-startup设置为1,如果不为1,会在servlet接收到请求时才来创建和初始化,这样会导致请求处理比较慢。

  1. Tomcat启动,解析web.xml时
  2. 创建DispatcherServlet对象
  3. 调用DispatcherServlet的init()
  4. 从而调用initServletBean()
  5. 从而调用initWebApplicationContext(),这个方法也会去创建一个Spring容器(就是子容器)
  6. initWebApplicationContext()执行过程中,会先从ServletContext拿出ContextLoaderListener所创建的Spring容器(父容器),记为rootContext
  7. 然后读取contextClass参数值,可以在servlet中的 标签来定义想要创建的Spring容器类型,默认为XmlWebApplicationContext
  8. 然后创建一个Spring容器对象,也就是子容器
  9. 将rootContext作为parent设置给子容器(父子关系的绑定)
  10. 然后读取contextConfigLocation参数值,得到所配置的Spring配置文件路径
  11. 然后就是调用Spring容器的refresh()方法
  12. 从而完成了子容器的创建

SpringMVC 初始化

子容器创建完后,还会调用 DispatcherServlet 的 onRefresh() 方法,从 Spring 容器中获取一些特殊类型的 Bean 对象,并设置给 DispatcherServlet 对象中对应的属性,比如 HandlerMapping、HandlerAdapter 等。

RequestMappingHandlerAdapter 初始化

RequestMappingHandlerAdapter 的作用是在收到请求时调用请求对应的方法。初始化逻辑如下:

  1. 从 Spring 容器中找到加了 @ControllerAdvice 的 Bean 对象。
  2. 解析 Bean 对象中加了 @ModelAttribute 和 @InitBinder 注解的 Method 对象。
  3. 获取实现了 RequestBodyAdvice 和 ResponseBodyAdvice 接口的 Bean 对象。
  4. 从 Spring 容器中获取用户定义的 HandlerMethodArgumentResolver 和 HandlerMethodReturnValueHandler,整合为 HandlerMethodArgumentResolverComposite 和 HandlerMethodReturnValueHandlerComposite 对象。

RequestMappingHandlerMapping 初始化

RequestMappingHandlerMapping 的作用是保存我们定义了哪些 @RequestMapping 方法及对应的访问路径。初始化逻辑如下:

  1. 找出容器中定义的所有的beanName
  2. 根据beanName找出beanType
  3. 判断beanType上是否有@Controller注解或@RequestMapping注解,如果有那么就表示这个Bean对象是一个Handler
  4. 如果是一个Handler,就通过反射找出加了@RequestMapping注解的Method,并解析@RequestMapping注解上定义的参数信息,得到一个对应的RequestMappingInfo对象,然后结合beanType上@RequestMapping注解所定义的path,以及当前Method上@RequestMapping注解所定义的path,进行整合,则得到了当前这个Method所对应的访问路径,并设置到RequestMappingInfo对象中去
  5. 所以,一个RequestMappingInfo对象就对应了一个加了@RequestMapping注解的Method,并且请求返回路径也记录在了RequestMappingInfo对象中
  6. 把当前Handler,也就是beanType中的所有RequestMappingInfo都找到后,就会存到MappingRegistry对象中
  7. 在存到MappingRegistry对象过程中,会像把Handler,也就是beanType,以及Method,生成一个HandlerMethod对象,其实就是表示一个方法
  8. 然后获取RequestMappingInfo对象中的path
  9. 把path和HandlerMethod对象存在一个Map中,属性叫做pathLookup
  10. 这样在处理请求时,就可以同请求路径找到HandlerMethod,然后找到Method,然后执行了

方法参数解析

在 RequestMappingHandlerAdapter 的初始化逻辑中会设置一些默认的 HandlerMethodArgumentResolver,用于解析各种类型的方法参数。例如:

  1. RequestParamMethodArgumentResolver 解析 @RequestParam 注解的参数。
  2. PathVariableMethodArgumentResolver 解析 @PathVariable 注解的参数。
  3. RequestHeaderMethodArgumentResolver 解析 @RequestHeader 注解的参数。

例如,RequestParamMethodArgumentResolver 的解析逻辑如下:

复制代码
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
    HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);

    if (arg == null) {
        String[] paramValues = request.getParameterValues(name);
        if (paramValues != null) {
            arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
        }
    }
    return arg;
}

这种方法会从请求中获取对应的参数值,并作为方法参数传递。

方法返回值解析

在 RequestMappingHandlerAdapter 的初始化逻辑中会设置一些默认的 HandlerMethodReturnValueHandler,用于解析各种类型的方法返回值。例如:

  1. ModelAndViewMethodReturnValueHandler 处理返回值为 ModelAndView 的情况。
  2. RequestResponseBodyMethodProcessor 处理方法或类上加了 @ResponseBody 的情况。
  3. ViewNameMethodReturnValueHandler 处理返回值为字符串的情况(无 @ResponseBody)。

我们重点看 RequestResponseBodyMethodProcessor 的逻辑。假如代码如下:

复制代码
@Controller
public class MController {

    @RequestMapping(method = RequestMethod.GET, path = "/test

")
    @ResponseBody
    public User test() {
        User user = new User();
        user.setName("zhangsan");
        return user;
    }
}

RequestResponseBodyMethodProcessor 处理逻辑:

  1. 先从方法返回值中获取对象(如 User)。
  2. 从 BeanFactory 中获取 HttpMessageConverter(如 MappingJackson2HttpMessageConverter)。
  3. 调用 MappingJackson2HttpMessageConverter 的 write() 方法将 User 对象写入响应中。

MappingJackson2HttpMessageConverter 是 SpringMVC 默认提供的一个 HttpMessageConverter,用于将对象转换为 JSON。

总结

通过以上流程图和详细解析,我们可以清晰地了解SpringMVC的启动流程和父子容器机制。理解这些核心原理,不仅有助于我们更好地使用SpringMVC框架,也为我们在实际开发中解决问题提供了坚实的理论基础。

👇关注我,下期了解👇 ​ MyBatis源码 ​ ​ ​ ​ 回复 222,获取Java面试题合集 ​ 关于我 ​ 一枚爱折腾的Java程序猿,专注Spring干货。把路上的问题记录下来,帮助那些和我一样的人。 ​ 好奇心强,喜欢并深入研究古天文。 ​ 崇尚 个人系统创建,做一些时间越长越有价值的事情。思考 把时间留下来 又 每刻都是新的。

本文由mdnice多平台发布

相关推荐
阿俊-全栈开发5 小时前
LikeShop 单商户SaaS商城分销模块二开:佣金计算与结算逻辑实现
java·开发语言·微服务·likeshop·likeshop开源商城·likeshop多商户
wuminyu8 小时前
C++协程实现接收端的零拷贝Buffer管理原理剖析
java·linux·c语言·jvm·c++
蜗牛互联网10 小时前
WSL Containers GA:本地AI容器的生命周期、网络与治理验收
java·网络·人工智能·后端
龙亘川11 小时前
铭记英烈守初心,数字强基促服务:亘川智城退役军人服务系统的实践与价值
java·数据库·人工智能
OxYGC11 小时前
[AI工程] Spring AI 第廿一篇:存量 REST 接口的 MCP 化改造实录——工具从哪条路径注册、参数描述怎么写、身份怎么过去
java·人工智能·spring
Zhou14113611 小时前
SpringBoot_02_自动配置原理
java·spring boot·后端
FYKJ_201011 小时前
SSM校园失物招领系统41452-计算机课程设计、毕业设计
java·vue.js·spring boot·python·mysql·typescript·spark
kv11011 小时前
Android studio国内开发有关
java·数据库·android studio
卓怡学长12 小时前
w198基于springboot始于足下健康打卡平台的设计与实现
java·spring boot·spring·intellij-idea
阿狗童鞋12 小时前
Java并发编程实战指南
java·开发语言