spring MVC工做機制與設計模式(《深刻分析java Web》技術內幕-14)-讀後小結(一/二/三)

讀《深刻分析java web技術內幕》 spring MVC一章,發現一篇好的讀後總結,轉載與此作記錄:

重點:java

1. Spring MVC中的dispatcherServletweb

2. 8個組件(handleMapping & handleAdaper & viewResolver)spring

3. MVC的關係,理解URL與handler的對應關係存放,handlerExecutionChain對象,modelAndMap對象數組

 

---------------------------- 原文 -----------------------------spring-mvc

地址:https://my.oschina.net/bosscheng/blog/129282session

Spring MVC的整體設計

在一個工程中若是想要使用 spring MVC的話,只須要兩個步驟mvc

  • 在web.xml中配置一個DispatcherServlet。

須要配置一個org.springframework.web.servlet.DispatcherServlet的servlet。app

  • 再定義一個dispatcherServlet-servlet.xml配置文件。(通常配置spring-mvc.xml)

在這個配置文件裏面咱們只須要擴展一個路徑映射關係,定義一個視圖解析器,再定義一個業務邏輯的處理流程規則。框架

這樣就能夠搞定一個最基本的Spring MVC的應用了。jsp

 

對於DispatcherServlet初始化的時候初始了哪些東西,這些能夠在initStrategies中看到。

 

/**
	 * 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);
                //用於定義一個主題
		initThemeResolver(context);
                //用於定義用戶設置的請求映射關係
		initHandlerMappings(context);
                //用於根據Handler的類型定義不一樣的處理規則
		initHandlerAdapters(context);
                //當Handler處理錯誤的時候,經過這個handler來作統一的處理
		initHandlerExceptionResolvers(context);
                //將指定的ViewName按照定義的RequestToViewNameTranslator替換成想要的格式。
		initRequestToViewNameTranslator(context);
                //用於將view解析成頁面
		initViewResolvers(context);
                //用於映射flash管理的。
		initFlashMapManager(context);
	}

 

 

小結: 對於spring MVC框架中,有三個組件是用戶必須定義和擴展的:

  • 定義URL映射規則:handlerMapping
  • 實現業務邏輯的handler實例對象:handlerAdapter
  • 渲染模版資源:ViewResolver

 

看看DispatcherServlet啓用的時候作了哪些工做?

  • 調用HttpServletBean的init()方法。在init()方法中,建立了BeanWrapper對象,而且執行了initServletBean()方法;
/**
	 * 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 {
			PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
			BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
			ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
			bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment));
			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");
		}
	}
  • 調用FrameworkServlet的createWebApplicationContext方法,初始化spring容器。
  • 調用FrameworkServlet的onRefresh方法完成配置文件的加載,配置文件的加載一樣是先查找servlet的init-param參數中設置的路徑,若是沒有,就會根據namespace+Servlet的名稱來查找XML文件。
  • Spring容器加載的時候會調用DispatcherServlet的initStrategies方法來初始化springMVC框架所須要的八個組件,這八個組件對應的八個bean對應都保存在DispatcherServlet類中。

 

這樣:spring MVC的初始化工做就完成了。這樣就能夠接受你的http請求了。

 

Control設計

Spring MVC 的Control主要是由HandlerMapping(interface)和HandlerAdapter(interface)兩個組件提供。

  • org.springframework.web.servlet.HandlerMapping
  • org.springframework.web.servlet.HandlerAdapter

對於HandlerMapping:主要負責映射用戶的URL和對應的處理類,HandlerMapping並無規定這個URL與應用的處理類如何映射,HandlerMapping接口中只定義了根據一個URL必須返回一個由HandlerExceptionChain表明的處理鏈,咱們能夠在這個處理鏈中添加任意的HandlerAdapter實例來處理這個URL對應的請求,這個設計思路和Servlet規範中的Filter處理是相似的。

 

/**
	 * Return a handler and any interceptors for this request. The choice may be made
	 * on request URL, session state, or any factor the implementing class chooses.
	 * <p>The returned HandlerExecutionChain contains a handler Object, rather than
	 * even a tag interface, so that handlers are not constrained in any way.
	 * For example, a HandlerAdapter could be written to allow another framework's
	 * handler objects to be used.
	 * <p>Returns <code>null</code> if no match was found. This is not an error.
	 * The DispatcherServlet will query all registered HandlerMapping beans to find
	 * a match, and only decide there is an error if none can find a handler.
	 * @param request current HTTP request
	 * @return a HandlerExecutionChain instance containing handler object and
	 * any interceptors, or <code>null</code> if no mapping found
	 * @throws Exception if there is an internal error
	 */
	HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

------------------------------------邪惡的分割線-------------------------------------------

HandlerMapping初始化

Spring MVC自己也提供了不少HandlerMapping 的實現,默認使用的是BeanNameUrlHandlerMapping,也能夠根據Bean的name屬性映射到URL中。

 

public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {

	/**
	 * Checks name and aliases of the given bean for URLs, starting with "/".
	 */
	@Override
	protected String[] determineUrlsForHandler(String beanName) {
		List<String> urls = new ArrayList<String>();
		if (beanName.startsWith("/")) {
			urls.add(beanName);
		}
		String[] aliases = getApplicationContext().getAliases(beanName);
		for (String alias : aliases) {
			if (alias.startsWith("/")) {
				urls.add(alias);
			}
		}
		return StringUtils.toStringArray(urls);
	}

}

 

 

對於HandlerMapping,能夠幫助咱們管理URL和處理類的映射關係,簡單的說就是能夠幫助咱們將一個或者多個URL映射到一個或者多個spring Bean中。

下面總結下spring MVC是如何將請求的URL映射到咱們定義的bean中的。

    對於HandlerMapping是如何初始化的。spring MVC提供了一個HandlerMapping的抽象類 AbstractHandlerMapping。

 

public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
		implements HandlerMapping, Ordered {
}

能夠經過讓HandlerMapping設置setOrder方法提升優先級和經過覆蓋initApplicationContext方法實現初始化的工做。

 

@Override
	protected void initApplicationContext() throws BeansException {
		extendInterceptors(this.interceptors);
		detectMappedInterceptors(this.mappedInterceptors);
		initInterceptors();
	}

 

對於SimpleUrlHandlerMapping類是如何初始化的:

  • 首先調用ApplicationObjectSupport的setApplicationContext()方法。
public final void setApplicationContext(ApplicationContext context) throws BeansException {
		if (context == null && !isContextRequired()) {
			// Reset internal context state.
			this.applicationContext = null;
			this.messageSourceAccessor = null;
		}
		else if (this.applicationContext == null) {
			// Initialize with passed-in context.
			if (!requiredContextClass().isInstance(context)) {
				throw new ApplicationContextException(
						"Invalid application context: needs to be of type [" + requiredContextClass().getName() + "]");
			}
			this.applicationContext = context;
			this.messageSourceAccessor = new MessageSourceAccessor(context);
			initApplicationContext(context);
		}
		else {
			// Ignore reinitialization if same context passed in.
			if (this.applicationContext != context) {
				throw new ApplicationContextException(
						"Cannot reinitialize with different application context: current one is [" +
						this.applicationContext + "], passed-in one is [" + context + "]");
			}
		}
	}

 

  • 調用SimpleUrlHandlerMapping的initApplicationContext()方法

 

/**
	 * Calls the {@link #registerHandlers} method in addition to the
	 * superclass's initialization.
	 */
	@Override
	public void initApplicationContext() throws BeansException {
		super.initApplicationContext();
		registerHandlers(this.urlMap);
	}

 

  • 調用super.initApplicationContext(),調用了AbstractHandlerMapping的initApplicationContext()方法

 

/**
	 * Initializes the interceptors.
	 * @see #extendInterceptors(java.util.List)
	 * @see #initInterceptors()
	 */
	@Override
	protected void initApplicationContext() throws BeansException {
		extendInterceptors(this.interceptors);
		detectMappedInterceptors(this.mappedInterceptors);
		initInterceptors();
	}

初始化initInterceptors()方法:將SimpleUrlHandlerMapping中定義的interceptors包裝成handlerInterceptor對象保存在adaptedInterceptors數組中。

 

  • SimpleUrlHandlerMapping繼續執行registerHandlers(this.urlMap)方法;

在方法registerHandlers中,調用了AbstractUrlHandlerMapping的registerHandler(url, handler)方法;

在方法registerHandler(url, handler)中,將在SimpleUrlHandlerMapping中定義的mappings註冊到handlerMap集合中。

 

protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
		if (urlMap.isEmpty()) {
			logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
		}
		else {
			for (Map.Entry<String, Object> entry : urlMap.entrySet()) {
				String url = entry.getKey();
				Object handler = entry.getValue();
				// Prepend with slash if not already present.
				if (!url.startsWith("/")) {
					url = "/" + url;
				}
				// Remove whitespace from handler bean name.
				if (handler instanceof String) {
					handler = ((String) handler).trim();
				}
				registerHandler(url, handler);
			}
		}
	}

在registerHandler方法中; 

 

/**
	 * Register the specified handler for the given URL path.
	 * @param urlPath the URL the bean should be mapped to
	 * @param handler the handler instance or handler bean name String
	 * (a bean name will automatically be resolved into the corresponding handler bean)
	 * @throws BeansException if the handler couldn't be registered
	 * @throws IllegalStateException if there is a conflicting handler registered
	 */

----------------------------------邪惡的分割線-----------------------------------

小結: HandlerMapping初始化工做完成的兩個最重要的工做就是:

  1. 將URL與Handler的對應關係保存在HandlerMapping集合中,並將全部的interceptors對象保存在adaptedInterceptors數組中,等請求到來的時候執行全部的adaptedIntercoptors數組中的interceptor對象。
  2. 全部的interceptor必須實現HandlerInterceptor接口。

 

HandlerAdapter初始化

HandlerMapping 能夠完成URL與Handler的映射關係,那麼HandlerAdapter就能夠幫助自定義各類handler了。由於SpringMVC首先幫助咱們把特別的URL對應到一個Handler,那麼這個Handler一定要符合某種規則,最多見的方法就是咱們的全部handler都繼承某個接口,而後SpringMVC 天然就調用這個接口中定義的特性方法。

對於spring MVC提供了三種典型的handlerAdapter實現類。

 

  1. SimpleServletHandlerAdapter:能夠直接繼承Servlet接口。
  2. SimpleControllerHandlerAdapter:能夠繼承Controller接口。
  3. SimpleRequestHandlerAdapter:能夠繼承HttpRequsetHandler接口。

對於handlerAdapter的初始化沒有什麼特別之處,只是簡單的建立一個handlerAdapter對象,將這個對象保存在DispatcherServlet的HandlerAdapters集合中。當Spring MVC將某個URL對應到某個Handler時候,在handlerAdapters集合中查詢那個handlerAdapter對象supports這個Handler,handlerAdapter對象將會被返回,用了調用這個handlerAdapter接口對應的方法。

 

------------------------------------邪惡的分割線-------------------------------------------

Control的調用邏輯

整個Spring MVC的調用是從DispatcherServlet的doService方法開始的,在doService方法中會將ApplicationContext、localeResolver、themeResolver等對象添加到request中便於在後面使用,接着就調用doDispatch方法,這個方法是主要的處理用戶請求的地方。

 

對於control的處理關鍵就是:DispatcherServlet的handlerMappings集合中根據請求的URL匹配每個handlerMapping對象中的某個handler,匹配成功以後將會返回這個handler的處理鏈接handlerExecutionChain對象。而這個handlerExecutionChain對象中將會包含用戶自定義的多個handlerInterceptor對象。

 

/**
	 * Return the HandlerExecutionChain for this request.
	 * <p>Tries all handler mappings in order.
	 * @param request current HTTP request
	 * @return the HandlerExecutionChain, or <code>null</code> if no handler could be found
	 */
	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		for (HandlerMapping hm : this.handlerMappings) {
			if (logger.isTraceEnabled()) {
				logger.trace(
						"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
			}
			HandlerExecutionChain handler = hm.getHandler(request);
			if (handler != null) {
				return handler;
			}
		}
		return null;
	}

而對於handlerInterceptor接口中定義的三個方法中,preHandler和postHandler分別在handler的執行前和執行後執行,afterCompletion在view渲染完成、在DispatcherServlet返回以前執行。

 

PS:這麼咱們須要注意的是:當preHandler返回false時,當前的請求將在執行完afterCompletion後直接返回,handler也將不會執行。

在類HandlerExecutionChain中的getHandler()方法是返回object對象的;

 

/**
	 * Return the handler object to execute.
	 * @return the handler object
	 */
	public Object getHandler() {
		return this.handler;
	}

這裏的handler是沒有類型的,handler的類型是由handlerAdapter決定的。dispatcherServlet會根據handler對象在其handlerAdapters集合中匹配哪一個HandlerAdapter實例支持該對象。接下來去執行handler對象的相應方法了,若是該handler對象的相應方法返回一個ModelAndView對象接下來就是去執行View渲染了。

 

/**
	 * Return the handler object to execute.
	 * @return the handler object
	 */
	public Object getHandler() {
		return this.handler;
	}

 

Model設計

若是handler兌現返回了ModelAndView對象,那麼說明Handler須要傳一個Model實例給view去渲染模版。除了渲染頁面須要model實例,在業務邏輯層一般也有Model實例。

 

ModelAndView對象是鏈接業務邏輯層與view展現層的橋樑,對spring MVC來講它也是鏈接Handler與view的橋樑。ModelAndView對象顧名思義會持有一個ModelMap對象和一個View對象或者View的名稱。ModelMap對象就是執行模版渲染時候所須要的變量對應的實例,如jsp的經過request.getAttribute(String)獲取的JSTL標籤名對應的對象。velocity中context.get(String)獲取$foo對應的變量實例。

 

public class ModelAndView {

/** View instance or view name String */
	private Object view;

	/** Model Map */
	private ModelMap model;

	/** Indicates whether or not this instance has been cleared with a call to {@link #clear()} */
	private boolean cleared = false;

.....

}

 

ModelMap其實也是一個Map,Handler中將模版中須要的對象存在這個Map中,而後傳遞到view對應的ViewResolver中。

 

public interface ViewResolver {
	View resolveViewName(String viewName, Locale locale) throws Exception;

}

 

不一樣的ViewResolver會對這個Map中的對象有不一樣的處理方式;

  • velocity中將這個Map保存到VelocityContext中。
  • JSP中將每個ModelMap中的元素分別設置到request.setAttribute(modelName,modelValue);

-----------------------邪惡的分割線-----------------------------------------------

view設計

在spring MVC中,view模塊須要兩個組件來支持:RequestToViewNameTranslator和ViewResolver

 

public interface RequestToViewNameTranslator {

	/**
	 * Translate the given {@link HttpServletRequest} into a view name.
	 * @param request the incoming {@link HttpServletRequest} providing
	 * the context from which a view name is to be resolved
	 * @return the view name (or <code>null</code> if no default found)
	 * @throws Exception if view name translation fails
	 */
	String getViewName(HttpServletRequest request) throws Exception;

}

對於 ViewResolver,前面有寫到了,就不寫了;

 

RequestToViewNameTranslator:主要支持用戶自定義對viewName的解析,如將請求的ViewName加上前綴或者後綴,或者替換成特定的字符串等。

ViewResolver:主要是根據用戶請求的viewName建立適合的模版引擎來渲染最終的頁面,ViewResolver會根據viewName建立一個view對象,調用view對象的Void render方法渲染出頁面;

 

public interface View {
void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
}

下面來總結下 Spring MVC解析View的邏輯:

  • dispatcherServlet方法調用getDefaultViewName()方法;
/**
	 * Translate the supplied request into a default view name.
	 * @param request current HTTP servlet request
	 * @return the view name (or <code>null</code> if no default found)
	 * @throws Exception if view name translation failed
	 */
	protected String getDefaultViewName(HttpServletRequest request) throws Exception {
		return this.viewNameTranslator.getViewName(request);
	}
  • 調用了RequestToViewNameTranslator的getViewName方法;

 

public interface RequestToViewNameTranslator {

	/**
	 * Translate the given {@link HttpServletRequest} into a view name.
	 * @param request the incoming {@link HttpServletRequest} providing
	 * the context from which a view name is to be resolved
	 * @return the view name (or <code>null</code> if no default found)
	 * @throws Exception if view name translation fails
	 */
	String getViewName(HttpServletRequest request) throws Exception;

}

 

  • 調用LocaleResolver接口的resolveLocale方法;

 

Locale resolveLocale(HttpServletRequest request);
  • 調用ViewResolver接口的resolveViewName方法,返回view對象

 

View resolveViewName(String viewName, Locale locale) throws Exception;
  • 調用render方法渲染出頁面
相關文章
相關標籤/搜索