Spring MVC中DispatcherServlet工作原理探究

下面类图将主要的类及方法抽离出来,以便查看方便,根据类的结构来说明整个请求是如何工作的

主要使用到的技术有Spring的IOC容器和Servlet。

假如我们要实现一个请求home.htm然后返回home.jsp视图资源则

当home.htm请求到达时,我们需要DispatcherServlet来处理该请求,所以首先配置该Servlet

第一步需要在web.xml中配置DispatcherServlet,使该servlet来接收请求并做进一步处理。

  1. <servlet>  
  2.     <servlet-name>dispatch</servlet-name>  
  3.     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
  4.     <load-on-startup>1</load-on-startup>  
  5. </servlet>  
  6. <servlet-mapping>  
  7.     <servlet-name>dispatch</servlet-name>  
  8.     <url-pattern>*.htm</url-pattern>  
  9. </servlet-mapping>  
 <servlet>
 	<servlet-name>dispatch</servlet-name>
 	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
 	<load-on-startup>1</load-on-startup>
 </servlet>
 <servlet-mapping>
 	<servlet-name>dispatch</servlet-name>
 	<url-pattern>*.htm</url-pattern>
 </servlet-mapping>

这个部分很好理解,如果请求以.htm结尾则交给名为dispatch类为DispatcherServlet的Servlet处理。

从类图中很容易看出DispatcherServlet最终继承的是HttpServlet,也就是说它同样满足Servlet的工作原理

Servlet初始化时需要调用init方法,在HttpServletBean中实现,该init方法调用了initServletBean,该方法在FrameworkServlet中实现

initServletBean主要初始化关于配置文件的内容,比如{servlet-name}-servlet.xml


第二步,需要在/WebRoot/WEB-INF下新建名为{servlet-name}-servlet.xml的spring bean配置文件。(该示例中即为dispatch-servlet.xml)

在初始化过程中会去寻找该配置文件,当然我们也可以自己去设置参数来更改配置文件所在路径

比如我们如果在src下新建的该配置文件dispatch-servlet,在编译后会被复制到WEB-INF/classes文件夹下,

配置文件还是按照命名规范做吧(可以修改为其他名字)

  1. <servlet>  
  2.         <servlet-name>dispatch</servlet-name>  
  3.         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
  4.         <init-param>  
  5.             <param-name>namespace</param-name>  
  6.             <param-value>classes/dispatch-servlet</param-value>  
  7.         </init-param>  
  8.         <load-on-startup>1</load-on-startup>  
  9.     </servlet>  
<servlet>
		<servlet-name>dispatch</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>namespace</param-name>
			<param-value>classes/dispatch-servlet</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>

此时的配置就会去寻找/WEB-INF/classes/dispatch-servlet.xml

当请求到达后Servlet将调用service方法进行处理,由于我们是通过输入网址方式的get方法请求,Servlet将调用doGet方法

此处的doGet方法在FrameworkServlet中实现,doGet方法调用processRequest方法,processRequest则调用doService方法处理

而doService在DispatcherServlet中实现,doService再调用了DispatcherServlet的doDispatch方法,

该方法则会根据request找到转发对象,并进行请求转发操作,

下面是获取实际的视图资源部分

  1. public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)  
  2.             throws Exception {  
  3.   
  4.         return ((Controller) handler).handleRequest(request, response);  
  5.     }  
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		return ((Controller) handler).handleRequest(request, response);
	}

这里需要我们自己实现Controller接口并实现handleRequest方法,返回对应的ModelAndView对象。


下面是请求转发的部分

  1. /** 
  2.      * Render the internal resource given the specified model. 
  3.      * This includes setting the model as request attributes. 
  4.      */  
  5.     @Override  
  6.     protected void renderMergedOutputModel(  
  7.             Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {  
  8.   
  9.         // Determine which request handle to expose to the RequestDispatcher.  
  10.         HttpServletRequest requestToExpose = getRequestToExpose(request);  
  11.   
  12.         ...  
  13.                  exposeModelAsRequestAttributes(model, requestToExpose);//这个方法看下面源码,request.setAttribute操作  
  14.                   // Determine the path for the request dispatcher.  
  15.         String dispatcherPath = prepareForRendering(requestToExpose, response);  
  16.   
  17.             ...  
  18.   
  19.         // If already included or response already committed, perform include, else forward.  
  20.         if (useInclude(requestToExpose, response)) {  
  21.             ......  
  22.         }  
  23.   
  24.         else {//重点看这部分,在根据请求以及配置文件获取到RequestDispatcher 对象之后,使用该对象做转发处理  
  25.             // Note: The forwarded resource is supposed to determine the content type itself.  
  26.             exposeForwardRequestAttributes(requestToExpose);  
  27.             if (logger.isDebugEnabled()) {  
  28.                 logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");  
  29.             }  
  30.             rd.forward(requestToExpose, response);  
  31.         }  
  32.     }  
/**
	 * Render the internal resource given the specified model.
	 * This includes setting the model as request attributes.
	 */
	@Override
	protected void renderMergedOutputModel(
			Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

		// Determine which request handle to expose to the RequestDispatcher.
		HttpServletRequest requestToExpose = getRequestToExpose(request);

		...
                 exposeModelAsRequestAttributes(model, requestToExpose);//这个方法看下面源码,request.setAttribute操作
                  // Determine the path for the request dispatcher.
		String dispatcherPath = prepareForRendering(requestToExpose, response);

	        ...

		// If already included or response already committed, perform include, else forward.
		if (useInclude(requestToExpose, response)) {
			......
		}

		else {//重点看这部分,在根据请求以及配置文件获取到RequestDispatcher 对象之后,使用该对象做转发处理
			// Note: The forwarded resource is supposed to determine the content type itself.
			exposeForwardRequestAttributes(requestToExpose);
			if (logger.isDebugEnabled()) {
				logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
			}
			rd.forward(requestToExpose, response);
		}
	}
下面是设置model和modelValue
  1. /** 
  2.      * Expose the model objects in the given map as request attributes. 
  3.      * Names will be taken from the model Map. 
  4.      * This method is suitable for all resources reachable by {@link javax.servlet.RequestDispatcher}. 
  5.      * @param model Map of model objects to expose 
  6.      * @param request current HTTP request 
  7.      */  
  8.     protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {  
  9.         for (Map.Entry<String, Object> entry : model.entrySet()) {  
  10.             String modelName = entry.getKey();  
  11.             Object modelValue = entry.getValue();  
  12.             if (modelValue != null) {  
  13.                 request.setAttribute(modelName, modelValue);  
  14.                 if (logger.isDebugEnabled()) {  
  15.                     logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() +  
  16.                             "] to request in view with name '" + getBeanName() + "'");  
  17.                 }  
  18.             }  
  19.             else {  
  20.                 request.removeAttribute(modelName);  
  21.                 if (logger.isDebugEnabled()) {  
  22.                     logger.debug("Removed model object '" + modelName +  
  23.                             "' from request in view with name '" + getBeanName() + "'");  
  24.                 }  
  25.             }  
  26.         }  
  27.     }  
/**
	 * Expose the model objects in the given map as request attributes.
	 * Names will be taken from the model Map.
	 * This method is suitable for all resources reachable by {@link javax.servlet.RequestDispatcher}.
	 * @param model Map of model objects to expose
	 * @param request current HTTP request
	 */
	protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {
		for (Map.Entry<String, Object> entry : model.entrySet()) {
			String modelName = entry.getKey();
			Object modelValue = entry.getValue();
			if (modelValue != null) {
				request.setAttribute(modelName, modelValue);
				if (logger.isDebugEnabled()) {
					logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() +
							"] to request in view with name '" + getBeanName() + "'");
				}
			}
			else {
				request.removeAttribute(modelName);
				if (logger.isDebugEnabled()) {
					logger.debug("Removed model object '" + modelName +
							"' from request in view with name '" + getBeanName() + "'");
				}
			}
		}
	}



第三步,编写实现Controller的类

  1. public class HomeController implements Controller  
  2. {  
  3.     private String greeting;  
  4.   
  5.     public String getGreeting()  
  6.     {  
  7.         return greeting;  
  8.     }  
  9.   
  10.     public void setGreeting(String greeting)  
  11.     {  
  12.         this.greeting = greeting;  
  13.     }  
  14.   
  15.     public ModelAndView handleRequest(HttpServletRequest arg0,  
  16.             HttpServletResponse arg1) throws Exception  
  17.     {  
  18.         System.out.println(arg0.getRequestURI());//请求地址  
  19.         return new ModelAndView("home""message", greeting);  
  20. //返回一个视图资源对象,名为home,model为message的对象(即上面的exposeModelAsRequestAtrributes方法中使用的request.setAttribute  
  21.     }  
  22.   
  23. }  
public class HomeController implements Controller
{
	private String greeting;

	public String getGreeting()
	{
		return greeting;
	}

	public void setGreeting(String greeting)
	{
		this.greeting = greeting;
	}

	public ModelAndView handleRequest(HttpServletRequest arg0,
			HttpServletResponse arg1) throws Exception
	{
		System.out.println(arg0.getRequestURI());//请求地址
		return new ModelAndView("home", "message", greeting);
//返回一个视图资源对象,名为home,model为message的对象(即上面的exposeModelAsRequestAtrributes方法中使用的request.setAttribute
	}

}

第四步,在dispatch-servlet.xml中配置该bean提供给spring web使用。
  1. <bean name="/home.htm" class="com.iss.spring.web.HomeController">  
  2.     <property name="greeting"><value>Hello!This is Training!你好,这里是训练营!</value></property>  
  3. </bean>  
	<bean name="/home.htm" class="com.iss.spring.web.HomeController">
		<property name="greeting"><value>Hello!This is Training!你好,这里是训练营!</value></property>
	</bean>

这里name将用来匹配请求的资源(默认的使用BeanNameUrlHandlerMapping处理,由bean Name映射 URL),在home.htm请求到达时,

spring将使用实现了Controller接口的HomeController的handleRequest方法来返回映射的视图资源。

在得到MoldelAndView对象后,需要根据这个MoldelAndView对象得到View name然后来解析得到View对象

  1. /** 
  2.      * Resolve the given view name into a View object (to be rendered). 
  3.      * <p>The default implementations asks all ViewResolvers of this dispatcher. 
  4.      * Can be overridden for custom resolution strategies, potentially based on 
  5.      * specific model attributes or request parameters. 
  6.      * @param viewName the name of the view to resolve 
  7.      * @param model the model to be passed to the view 
  8.      * @param locale the current locale 
  9.      * @param request current HTTP servlet request 
  10.      * @return the View object, or <code>null</code> if none found 
  11.      * @throws Exception if the view cannot be resolved 
  12.      * (typically in case of problems creating an actual View object) 
  13.      * @see ViewResolver#resolveViewName 
  14.      */  
  15.     protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,  
  16.             HttpServletRequest request) throws Exception {  
  17.   
  18.         for (ViewResolver viewResolver : this.viewResolvers) {  
  19.             View view = viewResolver.resolveViewName(viewName, locale);  
  20.             if (view != null) {  
  21.                 return view;  
  22.             }  
  23.         }  
  24.         return null;  
  25.     }  
/**
	 * Resolve the given view name into a View object (to be rendered).
	 * <p>The default implementations asks all ViewResolvers of this dispatcher.
	 * Can be overridden for custom resolution strategies, potentially based on
	 * specific model attributes or request parameters.
	 * @param viewName the name of the view to resolve
	 * @param model the model to be passed to the view
	 * @param locale the current locale
	 * @param request current HTTP servlet request
	 * @return the View object, or <code>null</code> if none found
	 * @throws Exception if the view cannot be resolved
	 * (typically in case of problems creating an actual View object)
	 * @see ViewResolver#resolveViewName
	 */
	protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
			HttpServletRequest request) throws Exception {

		for (ViewResolver viewResolver : this.viewResolvers) {
			View view = viewResolver.resolveViewName(viewName, locale);
			if (view != null) {
				return view;
			}
		}
		return null;
	}

此处需要我们配置viewResolver bean给spring使用,指明使用哪个类充当viewResolver并具有什么属性

第五步,配置viewResolver bean

  1. <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">  
  2.         <property name="suffix"><value>.jsp</value></property>  
  3.     </bean>  
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="suffix"><value>.jsp</value></property>
    </bean>

中间可以加上prefix或者suffix

这些配置完成后,spring就会根据请求地址以及配置信息,找到视图资源并做请求转发操作

总结:整个流程分析下来,其实主要就是做两个操作,

首先请求信息到达DispatchServlet,Servlet中根据请求信息与配置文件找到映射的视图资源

然后使用RequestDispatch请求转发到该视图资源。

另外,可以分成多个bean配置文件,在web.xml中配置载入

  1. <listener>  
  2.     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
  3. </listener>  
  4. <context-param>  
  5.     <param-name>contextConfigLocation</param-name>  
  6.     <param-value>/WEB-INF/dispatch-data.xml,/WEB-INF/dispatch-service.xml</param-value>  
  7. </context-param>  
 <listener>
 	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>
 <context-param>
 	<param-name>contextConfigLocation</param-name>
 	<param-value>/WEB-INF/dispatch-data.xml,/WEB-INF/dispatch-service.xml</param-value>
 </context-param>

其中contextConfigLocation这个名字可能是匹配FrameworkServlet的setContextConfigLocation方法

也有可能是匹配ContextLoaderListener继承ContextLoader的CONFIG_LOCATION_PARAM

public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";
(不确定,不太了解context-param的用法,API上两个类关于这个变量的说明都类似,也分不太清楚,反正可以这么记- -||)

然后配置的viewResolver bean的id为什么要为viewResolver,下面的是DispatcherServlet中一个静态字符串说明了一切

public static final String VIEW_RESOLVER_BEAN_NAME = "viewResolver";

猜你喜欢

转载自adrianset.iteye.com/blog/1851177
今日推荐