1. 程式人生 > >SpringMVC原始碼剖析(一)SpringMVC整體架構分析和建立

SpringMVC原始碼剖析(一)SpringMVC整體架構分析和建立

先看一下Servlet的繼承結

前面的Servlet體系我都有講過HttpServlet實現了根據動作分發請求

其他結構重要的類為HttpServletBean,FrameworkServlet ,DispatcherServlet

在Spring中實現了XXXAware表示對XXX感知,及EnvironmentCaple

在HttpServletBean中Environment使用t的Standard-servlet-environment  在creatEnvironment中建立   這裡封裝了環境變數在propertySource屬性下

儲存了servletcontext

JNdiPropertySource   MapPropertySource存放虛擬機器屬性

SystemEnvironmentPropertySource存放環境變數

我們開始對HttpServletBean分析

定義

public abstract class HttpServletBean extends HttpServlet
        implements EnvironmentCapable, EnvironmentAware {

先看EnvironmentCaple介面

public interface EnvironmentCapable {

	/**
	 * Return the {@link Environment} associated with this component.
	 */
	Environment getEnvironment();

}

功能 獲取環境  功能很單一
public interface EnvironmentAware extends Aware {

	/**
	 * Set the {@code Environment} that this object runs in.
	 */
	void setEnvironment(Environment environment);

}

設定環境       Aware是個空介面 具體含義

繼承HttpServlet  實現了EnvironmentCaple 介面和EnvironmentAware介面  作用獲取和設定環境

這裡的環境實際是指StanardServletEnvironment

public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment {

	/** Servlet context init parameters property source name: {@value} */
	public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams";

	/** Servlet config init parameters property source name: {@value} */
	public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams";

	/** JNDI property source name: {@value} */
	public static final String JNDI_PROPERTY_SOURCE_NAME = "jndiProperties";

	@Override
	protected void customizePropertySources(MutablePropertySources propertySources) {
		propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
		propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
		if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
			propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
		}
		super.customizePropertySources(propertySources);
	}

	public void initPropertySources(ServletContext servletContext, ServletConfig servletConfig) {
		WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);
	}
} 
HttpServletBean中重要的屬性
public abstract class HttpServletBean extends HttpServlet
		implements EnvironmentCapable, EnvironmentAware {

	//日誌
	protected final Log logger = LogFactory.getLog(getClass());

	/**
	 * Set of required properties (Strings) that must be supplied as
	 * config parameters to this servlet.
	 */
	private final Set<String> requiredProperties = new HashSet<String>();

	private ConfigurableEnvironment environment;


	
	protected final void addRequiredProperty(String property) {
		this.requiredProperties.add(property);
	}

	/**
	 * 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, 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");
		}
	}

	/**
	 * Initialize the BeanWrapper for this HttpServletBean,
	 * possibly with custom editors.
	 * <p>This default implementation is empty.
	 * @param bw the BeanWrapper to initialize
	 * @throws BeansException if thrown by BeanWrapper methods
	 * @see org.springframework.beans.BeanWrapper#registerCustomEditor
	 */
	protected void initBeanWrapper(BeanWrapper bw) throws BeansException {
	}


	/**
	 * Overridden method that simply returns {@code null} when no
	 * ServletConfig set yet.
	 * @see #getServletConfig()
	 */
	@Override
	public final String getServletName() {
		return (getServletConfig() != null ? getServletConfig().getServletName() : null);
	}

	/**
	 * Overridden method that simply returns {@code null} when no
	 * ServletConfig set yet.
	 * @see #getServletConfig()
	 */
	@Override
	public final ServletContext getServletContext() {
		return (getServletConfig() != null ? getServletConfig().getServletContext() : null);
	}


	/**
	 * Subclasses may override this to perform custom initialization.
	 * All bean properties of this servlet will have been set before this
	 * method is invoked.
	 * <p>This default implementation is empty.
	 * @throws ServletException if subclass initialization fails
	 */
	protected void initServletBean() throws ServletException {
	}

	/**
	 * {@inheritDoc}
	 * @throws IllegalArgumentException if environment is not assignable to
	 * {@code ConfigurableEnvironment}.
	 */
	public void setEnvironment(Environment environment) {
		Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
		this.environment = (ConfigurableEnvironment) environment;
	}

	/**
	 * {@inheritDoc}
	 * <p>If {@code null}, a new environment will be initialized via
	 * {@link #createEnvironment()}.
	 */
	public ConfigurableEnvironment getEnvironment() {
		if (this.environment == null) {
			this.environment = this.createEnvironment();
		}
		return this.environment;
	}

	/**
	 * Create and return a new {@link StandardServletEnvironment}. Subclasses may override
	 * in order to configure the environment or specialize the environment type returned.
	 */
	protected ConfigurableEnvironment createEnvironment() {
		return new StandardServletEnvironment();
	}


	/**
	 * PropertyValues implementation created from ServletConfig init parameters.
	 */
	private static class ServletConfigPropertyValues extends MutablePropertyValues {

		/**
		 * Create new ServletConfigPropertyValues.
		 * @param config ServletConfig we'll use to take PropertyValues from
		 * @param requiredProperties set of property names we need, where
		 * we can't accept default values
		 * @throws ServletException if any required properties are missing
		 */
		public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
			throws ServletException {

			Set<String> missingProps = (requiredProperties != null && !requiredProperties.isEmpty()) ?
					new HashSet<String>(requiredProperties) : null;

			Enumeration en = config.getInitParameterNames();
			while (en.hasMoreElements()) {
				String property = (String) en.nextElement();
				Object value = config.getInitParameter(property);
				addPropertyValue(new PropertyValue(property, value));
				if (missingProps != null) {
					missingProps.remove(property);
				}
			}

			// Fail if we are still missing properties.
			if (missingProps != null && missingProps.size() > 0) {
				throw new ServletException(
					"Initialization from ServletConfig for servlet '" + config.getServletName() +
					"' failed; the following required properties were missing: " +
					StringUtils.collectionToDelimitedString(missingProps, ", "));
			}
		}
	}
} 

我們知道Servlet建立時直接呼叫了無參的init()

為了方便原始碼分析直接去除了日誌   具體省略了哪裡有興趣可以自己去看

public final void init() throws ServletException {
		//這裡的日誌記錄  省略了

		// Set bean properties from init parameters.
		//省略了異常的處理
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
		BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
		ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
		bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
		initBeanWrapper(bw);//模板方法 子類呼叫實現  做初始化工作
                //將配置中的初始化引數如contentConfigLocation設定到DispatcherServlet中
               bw.setPropertyValues(pvs, true);
		

		// Let subclasses do whatever initialization they like.
		initServletBean();

		//這裡的日誌省略了
	}
init做的幾件事

1.將Servlet中的配置引數封裝到pvs 變數中,requiredProperties為必要引數

bw代表的是DispatchServlet  將Servlet中的配置引數設定使用BeanWrapper設定到DispatcherServlet中的相關屬性然後最後呼叫模板方法initServletBean

這會我們會對BeanWrapper是什麼感興趣

BeanWrapper是Spring提供的一個操作JavaBean屬性的工作,使用它可以直接修改一個物件的屬性

關於它的使用也是固定套路

首先建立一個物件   然後用PropertyAcessorFactory封裝成一個BeanWrapper物件  這樣就可以使用BeanWrapper物件對其屬性進行操作

關於例子可以自己去百度

關於該類其他的

在在HttpServletBean中Environment使用t的Standard-servlet-environment  在creatEnvironment中建立 

還有一個靜態內部類

private static class ServletConfigPropertyValues extends MutablePropertyValues {

		/**
		 * Create new ServletConfigPropertyValues.
		 * @param config ServletConfig we'll use to take PropertyValues from
		 * @param requiredProperties set of property names we need, where
		 * we can't accept default values
		 * @throws ServletException if any required properties are missing
		 */
		public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
			throws ServletException {

			Set<String> missingProps = (requiredProperties != null && !requiredProperties.isEmpty()) ?
					new HashSet<String>(requiredProperties) : null;

			Enumeration en = config.getInitParameterNames();
			while (en.hasMoreElements()) {
				String property = (String) en.nextElement();
				Object value = config.getInitParameter(property);
				addPropertyValue(new PropertyValue(property, value));
				if (missingProps != null) {
					missingProps.remove(property);
				}
			}

			// Fail if we are still missing properties.
			if (missingProps != null && missingProps.size() > 0) {
				throw new ServletException(
					"Initialization from ServletConfig for servlet '" + config.getServletName() +
					"' failed; the following required properties were missing: " +
					StringUtils.collectionToDelimitedString(missingProps, ", "));
			}
		}
	}

作用是

然後我們開始分析FrameworkServlet

該類原始碼優點多

先分析它的一部分屬性

//預設字尾         
public static final String DEFAULT_NAMESPACE_SUFFIX = "-servlet";

//預設的contentclass
public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;

//預設webcontent字首
public static final String SERVLET_CONTEXT_PREFIX = FrameworkServlet.class.getName() + ".CONTEXT.";

//分隔符	
private static final String INIT_PARAM_DELIMITERS = ",; \t\n";

private String contextAttribute;

private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;

private String contextId;

//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;

	/** Flag used to detect whether onRefresh has already been called */
	private boolean refreshEventReceived = false;

拿重點的來說

看一些它的initServletBean方法

protected final void initServletBean() throws ServletException {
		
		//這裡省略了日誌
		
		//這裡省略了異常
		this.webApplicationContext = initWebApplicationContext();
		initFrameworkServlet();//模板方法  但子類並沒有實現它		
	}

核心程式碼只有兩句

初始化webApplicationContext,初始化FrameworkServlet

我們在來看一下initWebApplicationContext   這個方法有點複雜

protected WebApplicationContext initWebApplicationContext() {
		//獲取rootContext
                 WebApplicationContext rootContext =WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;
                //如果已經通過構造設定了webApplicationContext
		if (this.webApplicationContext != null) {
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				if (!cwac.isActive()) {
					if (cwac.getParent() == null) {
						cwac.setParent(rootContext);
					}
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		if (wac == null) {
			wac = findWebApplicationContext();
		}
		if (wac == null) {//如果webApplicationContext還沒有建立測建立一個
			wac = createWebApplicationContext(rootContext);
		}

		if (!this.refreshEventReceived) {
			onRefresh(wac);
		}

		if (this.publishContext) {//將ApplicationContext儲存到ServletContext中
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
                        //這裡省略了日誌
                }

		return wac;
	}

initWebApplicationContext做了3件事

1.獲取Spring的rootContext

2.設定webApplicationContext並根據情況呼叫onRefresh方法

3.將webApplicationContext設定到ServletContext中

獲取rootContext的原理是

預設情況下spring將自己的容器設定成ServletContext的屬性

設定webApplicationContext根據情況呼叫onRefresh方法

設定webApplicationContext有3種方法

1.在構造方法中傳遞引數   FrameworkServlet有2種構造一種無參一種傳遞webApplicationContext引數

這種方法用於Servlet3.0以後的環境中  Servlet3.0之後可以用woServletContext.addServlet方式註冊Servlet  所以在建立FrameworkServlet和其子類可以傳遞準備好的webContext

2.webContext已經在ServletContext中  只需要將name配置到contextAttribute屬性就可以  比如在xml中的配置

3.前兩種無效自己建立一個  呼叫方法createWebApplicationContext

protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
		獲取建立型別
                 Class<?> contextClass = getContextClass();
		//這裡省略日誌
		//檢查建立型別,這裡有一個如果型別不匹配丟擲異常
		//具體建立
                ConfigurableWebApplicationContext wac =
				(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

		wac.setEnvironment(getEnvironment());
		wac.setParent(parent);
		//將設定的contextConfigLocation引數傳遞給wac  預設WEB-INFO/[SERVLETNAME]-Servlet.xml
                 wac.setConfigLocation(getContextConfigLocation());

		configureAndRefreshWebApplicationContext(wac);

		return wac;
	}

我們發現它呼叫configAndRefreshWebApplicationContext方法   這個方法有點複雜
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
		if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
			if (this.contextId != null) {
				wac.setId(this.contextId);
			}
			else {
				// Generate default id...
				ServletContext sc = getServletContext();
				if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
					// Servlet <= 2.4: resort to name specified in web.xml, if any.
					String servletContextName = sc.getServletContextName();
					if (servletContextName != null) {
						wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + servletContextName +
								"." + getServletName());
					}
					else {
						wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + getServletName());
					}
				}
				else {
					// Servlet 2.5's getContextPath available!
					wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
							ObjectUtils.getDisplayString(sc.getContextPath()) + "/" + getServletName());
				}
			}
		}

		wac.setServletContext(getServletContext());
		wac.setServletConfig(getServletConfig());
		wac.setNamespace(getNamespace());
                //新增監聽ContextRefreshEvent的監聽器             SourceFilteringListener
                wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
              
               ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
		}

		postProcessWebApplicationContext(wac);
		applyInitializers(wac);
		wac.refresh();
	}
太難了 我自己都看懵逼了

SourceFilterListener可以根據輸入的引數進行選擇  所以實際監聽的是ContextRefreshListener所監聽的時間

ContextRefreshListener是FrameworkServlet的內部類

private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

		public void onApplicationEvent(ContextRefreshedEvent event) {
			FrameworkServlet.this.onApplicationEvent(event);
		}
	}

當監聽到事件後呼叫FrameworkServlet的onApplicationEvent方法  在它裡面會呼叫一次onRefresh方法  並將refreshEventReceviced設定為true  表示refresh過

public void onApplicationEvent(ContextRefreshedEvent event) {
		this.refreshEventReceived = true;
		onRefresh(event.getApplicationContext());
	}

最後在回到initWebApplicationContext方法  可以看到它會根據refreshEventRecived標誌來判斷是否要執行onRefresh

所以當使用第三種方法初始化時已經refresh過了 不需要呼叫onRefresh  同樣的第一種也一樣

不管哪一種只會onRefresh一次而且DispatcherServlet正是通過重寫這個方法來實現初始化的

將webApplicationContext設定到ServletContext中  主要原因是方便獲取

首先回顧一下配置Servlet的一些初始化引數

contextAttribute用作WebApplicationContext的屬性名稱

contextClass建立webApplication的型別

contextConfigLocation:SpringMVC配置檔案的位置

publishContext:是否將webApplicationContext設定到ServletContext的屬性中。

總結的我都要快吐血了。。。

接下來肯定留的懸念是onRefresh如何初始化的

那就要來看DispatcherServlet,它的入口方法是onRefresh方法

protected void onRefresh(ApplicationContext context) {
		initStrategies(context);
	}
被稱為init策略模式
protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}
在initStrategies中呼叫了關於初始化的9個方法

我們肯定會有疑問  為什麼不直接在onRefresh中直接呼叫呢,還要多一個方法包裝下,豈不是多餘麼

主要是分層的原因  根據方法就明確它的含義

onRefresh是用來重新整理容器的  initStartegies用來初始化一些策略元件的 

另外單獨寫出來還可以讓我們自己去覆蓋initStartegies使用新的模式初始化

initStrategies的具體內容是初始化9個元件

這部分太多了 後續可以分為單獨的一章再寫

DispatcherServlet的建立過程主要是對9大元件進行初始化  元件的具體作用後面再補充

最後是小結  在此片文章中分析了SpringMVC自身的建立過程

Servlet分3層  HttpServletBean  FrameworkServlet  DispatcherServlet

HttpServletBean直接繼承自HttpServlet 其作用是將Servlet裡的引數設定到相應的屬性

FrameworkServlet初始化了WebApplicationContext

DispatcherServlet初始化了自身的9個元件

但每一個的具體實現好複雜啊  也是Spring的特點結構簡單  實現複雜 頂層設計的比較優秀,所以很值得我們去學習

歡迎關注我的個人訂閱號,我會推送更好的文章給大家

訂閱號