你好,我是柳岸花明。
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 容器(父容器)。流程如下:
-
Tomcat启动,解析web.xml时
-
发现定义了一个ContextLoaderListener,Tomcat就会执行该listener中的contextInitialized()方法,该方法就会去创建要给Spring容器
-
从ServletContext中获取contextClass参数值,该参数表示所要创建的Spring容器的类型
-
如果没有配置该参数,那么则会从ContextLoader.properties文件中读取org.springframework.web.context.WebApplicationContext配置项的值,SpringMVC默认提供了一个ContextLoader.properties文件,内容为org.springframework.web.context.support.XmlWebApplicationContext
-
所以XmlWebApplicationContext就是要创建的Spring容器类型
-
确定好类型后,就用反射调用无参构造方法创建出来一个XmlWebApplicationContext对象
-
然后继续从ServletContext中获取contextConfigLocation参数的值,也就是一个spring配置文件的路径
-
把spring配置文件路径设置给Spring容器,然后调用refresh(),从而启动Spring容器,从而解析spring配置文件,从而扫描生成Bean对象等
-
这样Spring容器就创建出来了
-
有了Spring容器后,就会把XmlWebApplicationContext对象作为attribute设置到ServletContext中去,key为WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
-
把Spring容器存到ServletContext中的原因,是为了给Servlet创建出来的子容器来作为父容器的
子容器的创建
Tomcat启动过程中,执行完ContextLoaderListener的contextInitialized()之后,就会创建DispatcherServlet了,web.xml中定义DispatcherServlet时,load-on-startup为1,表示在Tomcat启动过程中要把这个DispatcherServlet创建并初始化出来,而这个过程是比较费时间的,所以要把load-on-startup设置为1,如果不为1,会在servlet接收到请求时才来创建和初始化,这样会导致请求处理比较慢。
-
Tomcat启动,解析web.xml时 -
创建DispatcherServlet对象 -
调用DispatcherServlet的init() -
从而调用initServletBean() -
从而调用initWebApplicationContext(),这个方法也会去创建一个Spring容器(就是子容器) -
initWebApplicationContext()执行过程中,会先从ServletContext拿出ContextLoaderListener所创建的Spring容器(父容器),记为rootContext -
然后读取contextClass参数值,可以在servlet中的 标签来定义想要创建的Spring容器类型,默认为XmlWebApplicationContext -
然后创建一个Spring容器对象,也就是子容器 -
将rootContext作为parent设置给子容器(父子关系的绑定) -
然后读取contextConfigLocation参数值,得到所配置的Spring配置文件路径 -
然后就是调用Spring容器的refresh()方法 -
从而完成了子容器的创建
SpringMVC 初始化
![alt](https://img-blog.csdnimg.cn/img_convert/70245e8650ebefad88359fe1ba237c44.png)
子容器创建完后,还会调用 DispatcherServlet
的 onRefresh()
方法,从 Spring 容器中获取一些特殊类型的 Bean
对象,并设置给 DispatcherServlet
对象中对应的属性,比如 HandlerMapping
、HandlerAdapter
等。
RequestMappingHandlerAdapter 初始化
RequestMappingHandlerAdapter
的作用是在收到请求时调用请求对应的方法。初始化逻辑如下:
-
从 Spring 容器中找到加了 @ControllerAdvice
的Bean
对象。 -
解析 Bean
对象中加了@ModelAttribute
和@InitBinder
注解的Method
对象。 -
获取实现了 RequestBodyAdvice
和ResponseBodyAdvice
接口的Bean
对象。 -
从 Spring 容器中获取用户定义的 HandlerMethodArgumentResolver
和HandlerMethodReturnValueHandler
,整合为HandlerMethodArgumentResolverComposite
和HandlerMethodReturnValueHandlerComposite
对象。
RequestMappingHandlerMapping 初始化
![alt](https://img-blog.csdnimg.cn/img_convert/1fd2b092a8ae3b6379a98a63c3fc1bb9.png)
RequestMappingHandlerMapping
的作用是保存我们定义了哪些 @RequestMapping
方法及对应的访问路径。初始化逻辑如下:
-
找出容器中定义的所有的beanName -
根据beanName找出beanType -
判断beanType上是否有@Controller注解或@RequestMapping注解,如果有那么就表示这个Bean对象是一个Handler -
如果是一个Handler,就通过反射找出加了@RequestMapping注解的Method,并解析@RequestMapping注解上定义的参数信息,得到一个对应的RequestMappingInfo对象,然后结合beanType上@RequestMapping注解所定义的path,以及当前Method上@RequestMapping注解所定义的path,进行整合,则得到了当前这个Method所对应的访问路径,并设置到RequestMappingInfo对象中去 -
所以,一个RequestMappingInfo对象就对应了一个加了@RequestMapping注解的Method,并且请求返回路径也记录在了RequestMappingInfo对象中 -
把当前Handler,也就是beanType中的所有RequestMappingInfo都找到后,就会存到MappingRegistry对象中 -
在存到MappingRegistry对象过程中,会像把Handler,也就是beanType,以及Method,生成一个HandlerMethod对象,其实就是表示一个方法 -
然后获取RequestMappingInfo对象中的path -
把path和HandlerMethod对象存在一个Map中,属性叫做pathLookup -
这样在处理请求时,就可以同请求路径找到HandlerMethod,然后找到Method,然后执行了
方法参数解析
在 RequestMappingHandlerAdapter
的初始化逻辑中会设置一些默认的 HandlerMethodArgumentResolver
,用于解析各种类型的方法参数。例如:
-
RequestParamMethodArgumentResolver
解析@RequestParam
注解的参数。 -
PathVariableMethodArgumentResolver
解析@PathVariable
注解的参数。 -
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
,用于解析各种类型的方法返回值。例如:
-
ModelAndViewMethodReturnValueHandler
处理返回值为ModelAndView
的情况。 -
RequestResponseBodyMethodProcessor
处理方法或类上加了@ResponseBody
的情况。 -
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
处理逻辑:
-
先从方法返回值中获取对象(如 User
)。 -
从 BeanFactory
中获取HttpMessageConverter
(如MappingJackson2HttpMessageConverter
)。 -
调用 MappingJackson2HttpMessageConverter
的write()
方法将User
对象写入响应中。
MappingJackson2HttpMessageConverter
是 SpringMVC 默认提供的一个 HttpMessageConverter
,用于将对象转换为 JSON。
总结
通过以上流程图和详细解析,我们可以清晰地了解SpringMVC的启动流程和父子容器机制。理解这些核心原理,不仅有助于我们更好地使用SpringMVC框架,也为我们在实际开发中解决问题提供了坚实的理论基础。
👇关注我,下期了解👇 MyBatis源码 回复 222,获取Java面试题合集 关于我 一枚爱折腾的Java程序猿,专注Spring干货。把路上的问题记录下来,帮助那些和我一样的人。 好奇心强,喜欢并深入研究古天文。 崇尚 个人系统创建,做一些时间越长越有价值的事情。思考 把时间留下来 又 每刻都是新的。
本文由 mdnice 多平台发布