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的特點結構簡單 實現複雜 頂層設計的比較優秀,所以很值得我們去學習
歡迎關注我的個人訂閱號,我會推送更好的文章給大家