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>

这里定义了一个 listenerservlet

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 初始化

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

RequestMappingHandlerAdapter 初始化

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

  1. 从 Spring 容器中找到加了 @ControllerAdviceBean 对象。
  2. 解析 Bean 对象中加了 @ModelAttribute@InitBinder 注解的 Method 对象。
  3. 获取实现了 RequestBodyAdviceResponseBodyAdvice 接口的 Bean 对象。
  4. 从 Spring 容器中获取用户定义的 HandlerMethodArgumentResolverHandlerMethodReturnValueHandler,整合为 HandlerMethodArgumentResolverCompositeHandlerMethodReturnValueHandlerComposite 对象。

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. 调用 MappingJackson2HttpMessageConverterwrite() 方法将 User 对象写入响应中。

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

总结

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

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

本文由mdnice多平台发布

相关推荐
惜.己15 分钟前
Jmeter中的配置原件(四)
java·前端·功能测试·jmeter·1024程序员节
yava_free39 分钟前
JVM这个工具的使用方法
java·jvm
不会编程的懒洋洋1 小时前
Spring Cloud Eureka 服务注册与发现
java·笔记·后端·学习·spring·spring cloud·eureka
赖龙1 小时前
java程序打包及执行 jar命令及运行jar文件
java·pycharm·jar
U12Euphoria1 小时前
java的runnable jar采用exe和.bat两种方式解决jre环境的问题
java·pycharm·jar
java小吕布2 小时前
Java Lambda表达式详解:函数式编程的简洁之道
java·开发语言
程序员劝退师_2 小时前
优惠券秒杀的背后原理
java·数据库
java小吕布2 小时前
Java集合框架之Collection集合遍历
java
一二小选手2 小时前
【Java Web】分页查询
java·开发语言
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ2 小时前
idea 弹窗 delete remote branch origin/develop-deploy
java·elasticsearch·intellij-idea