Spring MVC源码解读之请求处理 博客分类: Spring Spring MVCSpring
自从接触Spring开始就对这个框架比较喜欢吧,所以我最近在抽时间深入研究这个框架,因为在项目中用Spring MVC比较多所以打算深入理解一下这个框架。
首先我们要从web.xml讲起,web.xml中配置着用来进行初始化的各种配置信息:欢迎页、servlet、servlet-mapping、filter、lister、启动的记载级别等等信息。但是Spring MVC不同与Struts2(使用Filter拦截请求FilterDispatcher),Spring MVC是使用Servlet进行请求的拦截(DispatcherServlet进行拦截)。
首先我们知道Java web的加载顺序是:content-param --> listener --> filter --> servlet。所以我像按照这个顺序对Spring mvc的加载和处理流出进行讲解。
1、在启动Web服务器的时候会对web.xml中的<listener></listener> 和 <context-param></context-param> 进行读取并加载。context-param,它用于向 ServletContext 提供键值对,即应用程序上下文信息。我们的 listener, filter 等在初始化时会用到这些上下文中的信息。
这里在Spring mvc中的体现如下:
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:config/core/spring-core.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
这里的contextConfigLocation了Spring的配置文件是非常重要的,它以context-param的方式注册并在ContextLoaderListener中监听读取,ContextLoaderListener的职责是负责Spring IOC容器在web上下文的初始化,也就是说这正是这两个参数是我们的Spring容器和我们的Web容器相结合。ContextLoaderListener在web容器启动的时候自动装配ApplicationContext的配置信息。它实现了ServletContextLister这个接口,使我们在为客户端提供请求服务之前向ServletContext中添加任意的对象,这个对象在ServletContext启动的时候被初始化,然后在ServletContext整个运行期间都是可见的。所以ContextLoaderListener的核心就是初始化WebApplication并将实例放到ServletContext中。
2、当然filter也是必须的我们可以用它来进行编码过滤,权限控制等等但是在Spring mvc这里不做深入的介绍。
3、配置Servlet,首先介绍一下Servlet,他是一个用Java编写的基于HTTP协议的在服务器端运行的Java类。它的生命周期由web容器进行控制,其生命周期包括:初始化、运行和销毁具体的过程如下:
(初始化)
- Servlet容器加载servlet类,把servlet类的class文件读入内存。
- servlet容器创建一个ServletConfig对象。ServletConfig对象包括了servlet的初始化配置信息。
- servlet容器创建一个Servle对象
- servlet容器调用servlet的init方法进行初始化。
(运行阶段)
- 当servlet容器接收到一个请求的时候,servlet会针对这个请求创建一个servletRequest和servletResponse对象,然后调用service方法,service方法通过servletRequest对象获得请求信息。并处理该请求。在通过servletResponse生成这个请求的响应。然后销毁servletRequest和servletResponse对象。
(销毁阶段)
- 当Web应用被终止的时候,servlet容器首先会调用servlet的destory方法,然后在销毁servlet对象,同时也会删除与之关联的servletConfig对象。
那么我们回到Spring MVC中,前面说过ContextLoaderListener只是辅助功能用于创建WebApplicationContext类型的实例,真正的逻辑其实是在DispatcherServlet中实现的。
public class DispatcherServlet extends FrameworkServlet public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware
所以我在HttpServletBean中找到了其init()方法
/** * Map config parameters onto bean properties of this servlet, and * invoke subclass initialization. * @throws ServletException if bean properties are invalid (or required * properties are missing), or if subclass initialization fails. */ @Override public final void init() throws ServletException { if (logger.isDebugEnabled()) { logger.debug("Initializing servlet '" + getServletName() + "'"); } // Set bean properties from init parameters. try { //解析init-param并封装到pvs中 PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); //将当前的这个Servlet类转化为一BeanWrapper以便于使用Spring的方式对init-param的值进行注入 BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); //注册自定义的属性编辑器,一旦遇到Resource类型的属性将会使用 bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); initBeanWrapper(bw); bw.setPropertyValues(pvs, true); } catch (BeansException ex) { logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex); throw ex; } // Let subclasses do whatever initialization they like. initServletBean(); if (logger.isDebugEnabled()) { logger.debug("Servlet '" + getServletName() + "' configured successfully"); } }
DispatcherServlet的初始化过程主要是通过将当前的servlet类型的实例转换为BeanWrapper类型的实例,以便于Spring对其进行对应属性的注入,这些属性我在FrameworkServlet 中看到的有:
/** ServletContext attribute to find the WebApplicationContext in */ private String contextAttribute; /** WebApplicationContext implementation class to create */ private Class<?> contextClass = DEFAULT_CONTEXT_CLASS; /** WebApplicationContext id to assign */ private String contextId; /** Namespace for this servlet */ private String namespace; /** Explicit context config location */ private String contextConfigLocation; /** Actual ApplicationContextInitializer instances to apply to the context */ private final ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>> contextInitializers = new ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>>(); /** Comma-delimited ApplicationContextInitializer class names set through init param */ private String contextInitializerClasses; /** Should we publish the context as a ServletContext attribute? */ private boolean publishContext = true; /** Should we publish a ServletRequestHandledEvent at the end of each request? */ private boolean publishEvents = true; /** Expose LocaleContext and RequestAttributes as inheritable for child threads? */ private boolean threadContextInheritable = false; /** Should we dispatch an HTTP OPTIONS request to {@link #doService}? */ private boolean dispatchOptionsRequest = false; /** Should we dispatch an HTTP TRACE request to {@link #doService}? */ private boolean dispatchTraceRequest = false; /** WebApplicationContext for this servlet */ private WebApplicationContext webApplicationContext; /** If the WebApplicationContext was injected via {@link #setApplicationContext} */ private boolean webApplicationContextInjected = false; /** Flag used to detect whether onRefresh has already been called */ private boolean refreshEventReceived = false;
其主要的步骤如下:
- 封装及初始化参数
- 将当前的servlet类型的实例转换为BeanWrapper类型的实例
- 注册相对于Resource属性编辑器,
- 属性注入
- servletBean初始化
在DispatcherServlet 中对各种方法初始化:
/** * Initialize the strategy objects that this servlet uses. * <p>May be overridden in subclasses in order to initialize further strategy objects. */ protected void initStrategies(ApplicationContext context) { //MultipartResolver用于文件上传 initMultipartResolver(context); //国际化配置 initLocaleResolver(context); //初始化ThemeResolver其通常用户处理不同用户对web不同风格的要求 initThemeResolver(context); //初始化HandlerMappings initHandlerMappings(context); //用于根据Hander的类型定义不同的处理规则 initHandlerAdapters(context); //处理Hander的错误 initHandlerExceptionResolvers(context); //给相应链接转化为自己想要的格式,比如在前后添加后缀 initRequestToViewNameTranslator(context); //用于将view解析成页面,可以制定不同的解析策略默认按照jsp进行解析,也可以按照velocity模板进行解析 initViewResolvers(context); initFlashMapManager(context); }
其中比较重要的是HandlerMappings:当客户端发出Request请求的时候DispatcherServlet将请求提交给HandlerMappings,然后HandlerMappings根据Web Application Context的配置来回传给DispatcherServle相应的Controller,源代码如下:
/** * Initialize the HandlerMappings used by this class. * <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace, * we default to BeanNameUrlHandlerMapping. */ private void initHandlerMappings(ApplicationContext context) { this.handlerMappings = null; if (this.detectAllHandlerMappings) { // Find all HandlerMappings in the ApplicationContext, including ancestor contexts. Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values()); // We keep HandlerMappings in sorted order. AnnotationAwareOrderComparator.sort(this.handlerMappings); } } else { try { HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class); this.handlerMappings = Collections.singletonList(hm); } catch (NoSuchBeanDefinitionException ex) { // Ignore, we'll add a default HandlerMapping later. } } // Ensure we have at least one HandlerMapping, by registering // a default HandlerMapping if no other mappings are found. if (this.handlerMappings == null) { this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class); if (logger.isDebugEnabled()) { logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default"); } } }
DispatcherServlet初始化的时序图如下:
----------------上面这些完成Spring mvc的大部分初始化工作下面来分析以下请求处理的流程------------------
1、web服务器接收到客户端发来的请求并将其封装成一个ServletRequest对象。
2、DispatcherServlet接收到这个请求后,并将请求的处理工作委托给具体的处理器Handler,后者负责具体的业务逻辑,DispatcherServlet通过一个或者多个处理程序映射,将每个请求映射到处理程序中。处理程序映射配置在web应用程序的上下文中,是实现了HandlerMapping接口的Bean,它负责根据请求的URL将请求映射到处理程序(Controller)。
3、将请求分派给处理器进行处理,处理器处理完请求后,会将模型和视图名ModelAndView返回给DispatcherServlet
4、DispatcherServlet 将得到的ModelAndView交给ViewResolver来进行逻辑视图名和真实视图对象的对应。
5、当得到真实的视图对象后,DispatcherServlet 将请求分派给这个View对象,由其完成Model数据的渲染工作。
6、客户端的得到返回的响应,结果可能是一个HTML、Excel、pdf文档等等。
但是在上面的步骤中,DispatherServlet接受到请求后是如何映射到Controller的呢?
在Spring MVC中,web请求是被Web应用程序上下文中声明的一个或者多个实现了HanderMapping接口的Bean映射到Controller的,在HanderMapping接口中了一个根据一个URL返回一个HandlerExecutionChain
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
下面是 HanderMapping的默认实现
1. BeanNameUrlHandlerMapping(默认情况),他根据Controller Bean名称中指定的URL模式将请求映射到处理程序上。
eg. <bean name="/welcome.htm" class="com.kevin.controller.WelcomeController">...</bean>当你访问http://******/welcome.htm这个URL时,DispatcherServlet通过BeanNameUrlHandlerMapping映射就找到了WelcomeController。
2. ControllerClassNameHandlerMapping,它是按控制器类名称映射请求。
3. SimpleUrlHandlerMapping,用定制的映射定义来映射请求。
推荐阅读
-
Spring MVC源码解读之请求处理 博客分类: Spring Spring MVCSpring
-
Spring MVC静态资源处理 博客分类: Spring springmvcstatic静态mapping
-
Spring MVC学习之DispatcherServlet请求处理详析
-
Spring MVC学习之DispatcherServlet请求处理详析
-
spring mvc 下载文件 博客分类: Java Web spring mvcspring mvc下载文件spring downloadspring mvc downloadspring下载文件
-
spring MVC 获取请求体 博客分类: spring MVC 请求参数请求要素request stringrequest bodyspringMVC获取请求体
-
spring-data-redis消息订阅RedisMessageListenerContainer源码解读 博客分类: 架构相关 redis源码
-
Spring MVC Controller与jquery ajax请求处理json 博客分类: 学习笔记 springmvcjqueryjson
-
spring源码学习系列3.4-spring mvc原理-codes 博客分类: spring spring
-
Spring的IOC源码解读&UML 博客分类: javaspringiocbean javaspringiocbean