最近想寫一些系列文章來深入探討理解下Spring MVC的運(yùn)行方式及源碼實(shí)現(xiàn)凸丸。好了脱盲,廢話不多說了漂洋,直接進(jìn)入正題。
大家都知道一個(gè)傳統(tǒng)的Web Application都是從web.xml開始的指攒,這個(gè)文件也是Application容器加載項(xiàng)目時(shí)第一個(gè)讀取的。對(duì)于Spring MVC來說僻焚,我們需要在web.xml中配置一個(gè)DispatcherServlet作為前端控制器并為其指定一些初始參數(shù)允悦。從類名就可以看出,DispatcherServlet本質(zhì)上還是一個(gè)Servlet虑啤,只不過多了一些和Spring MVC框架有關(guān)的一些功能隙弛。
今天打算在這里講的并不是DispatcherServlet架馋,而是它父類的父類HttpServletBean,下圖是一個(gè)描述了DispatcherServlet繼承結(jié)構(gòu)的圖全闷,圖中只展示了Spring提供的三個(gè)類绩蜻,其實(shí)在往上就是servlet-api中的HttpServlet, GenericServlet等。
好了室埋,開始上源碼办绝,先從類聲明開始。
public abstract class HttpServletBean extends HttpServlet
implements EnvironmentCapable, EnvironmentAware {
可以看到HttpServletBean繼承了HttpServlet姚淆,所以它只是在功能上對(duì)HttpServlet進(jìn)行了一些擴(kuò)展孕蝉。并且它實(shí)現(xiàn)了EnvironmentCapable和EnvironmentAware接口。
- EnviromentCapable接口里定義腌逢,所有實(shí)現(xiàn)它的類必須能返回一個(gè)Environment的實(shí)例降淮。
- EnvironmentAware接口里定義,所有實(shí)現(xiàn)它的類必須提供一個(gè)setEnvironment的方法以便于set一個(gè)現(xiàn)有的Environment實(shí)例搏讶。
那看到這里佳鳖,相信大家不禁要問,這個(gè)Enviroment又是一個(gè)什么東西媒惕,它起到了什么樣的作用系吩?在這里我不準(zhǔn)備展開說,簡單來說Environmenti 里封裝了3樣?xùn)|西妒蔚,Spring MVC的profile配置信息穿挨,System Properties的一個(gè)Map以及System Environment的一個(gè)Map。
接下去看一下這個(gè)類中的成員變量聲明肴盏,不多就3個(gè)科盛。
protected final Log logger = LogFactory.getLog(getClass());
private final Set<String> requiredProperties = new HashSet<String>();
private ConfigurableEnvironment environment;
- Log不需要多說了吧,用來記錄日志的菜皂。
- 一個(gè)名為requiredProperties的HashSet可以用來指定那些你認(rèn)為必須存在的servlet參數(shù)贞绵,可以通過下面這個(gè)方法添加如果有的話。
protected final void addRequiredProperty(String property) {
this.requiredProperties.add(property);
}
- 一個(gè)類型為ConfigurableEnvironment的變量去存放Environment的實(shí)例恍飘。
@Override
public void setEnvironment(Environment environment) {
this.environment = (ConfigurableEnvironment) environment;
}
然后我們一起來看一下比較關(guān)鍵的一個(gè)方法init,這個(gè)方法覆蓋了它祖先類GenericServlet中定義的空實(shí)現(xiàn)榨崩,并且這個(gè)方法會(huì)在容器初始化每個(gè)Servlet的時(shí)候被調(diào)用一次。
- 123行實(shí)例化了一個(gè)ServletConfigPropertyValues的對(duì)象常侣,PropertyValues類其實(shí)是一個(gè)容器類蜡饵,它內(nèi)部會(huì)維護(hù)一個(gè)PropertyValue (注意此處沒有s) 的數(shù)組,而PropertyValue是一個(gè)專門存放屬性/參數(shù)的結(jié)構(gòu)胳施,它可以存儲(chǔ)一個(gè)String類型的屬性名以及一個(gè)Object類型的屬性對(duì)象溯祸。
public class PropertyValue extends BeanMetadataAttributeAccessor implements Serializable {
private final String name;
private final Object value;
....
那么ServletConfigPropertyValues其實(shí)就是一個(gè)存放和Servlet相關(guān)的參數(shù)的容器類,比如那些定義在web.xml中的initParam。
124行使用了一個(gè)PropertyAccessorFactory類里的方法焦辅,并且返回了一個(gè)BeanWrapper, 簡單來說BeanWrapper就是一個(gè)對(duì)象的包裹者博杖,它里面會(huì)存放被包裹對(duì)象的引用并且提供一系列利用了反射機(jī)制的方法去設(shè)置被包裹者的屬性,大家可以看到在這行代碼里傳入了一個(gè)this引用筷登,這里的this指的就是當(dāng)前這個(gè)類的實(shí)例剃根,而實(shí)際中這個(gè)其實(shí)是一個(gè)DispatcherServlet的實(shí)例,因?yàn)槲覀冊(cè)趙eb.xml里使用的是DispatcherServlet而不是直接去使用HttpServletBean前方。
125行new了一個(gè)ServletContextResourceLoader的實(shí)例狈醉,也說一下ResourceLoader吧,這個(gè)接口定義了一些獲取Resource的方法惠险,你可以用它來獲取各種各樣的資源苗傅,比如配置文件,class文件等班巩。那ServletContextResourceLoader的實(shí)現(xiàn)其實(shí)是提供了獲取那些和當(dāng)前Servlet相關(guān)的一些Resource渣慕,它會(huì)通過ServletContext中的一些方法去獲取資源,比如servletContext.getResourceAsStream(), servletContext.getResource()
127行調(diào)用了一個(gè)initBeanWrapper的方法抱慌,這個(gè)方法是一個(gè)空實(shí)現(xiàn)逊桦,可以在子類中擴(kuò)展如果有需要的話锅移。
protected void initBeanWrapper(BeanWrapper bw) throws BeansException {
}
- 128行調(diào)用了BeanWrapper上的setPropertyValues方法猾浦,并且把前面提到的ServletConfigPropertyValues作為參數(shù)傳了進(jìn)去漆际。這句代碼的作用就是把ServletConfigPropertyValues中的那些Servlet初始化參數(shù)設(shè)置到BeanWrapper所包裹的實(shí)例中的相應(yīng)成員變量里淮菠。(有點(diǎn)拗口。团南。)谈宛。
舉個(gè)例子,前面已經(jīng)提到BeanWrapper里被包裹對(duì)象其實(shí)是一個(gè)DispatcherServlet户秤,我們平時(shí)在web.xml中定義這個(gè)Servlet的時(shí)候一般都會(huì)有一些initParam, 比如
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*: *-dispatcher-servlet.xml
</param-value></init-param>
那這個(gè)參數(shù)的名字和值就會(huì)作為一個(gè)PropertyVaule存放在ServletConfigPropertyValues中,在128行被調(diào)用的時(shí)候這個(gè)參數(shù)的值就會(huì)被set到被包裹者實(shí)例的相應(yīng)的屬性中, 在這里就是DispatcherServlet的contextConfigLocation中逮矛。
public void setContextConfigLocation(String contextConfigLocation) {
this.contextConfigLocation = contextConfigLocation;
}
- 136行調(diào)用了一個(gè)initServletBean的方法鸡号,這個(gè)方法是一個(gè)抽象方法,真正的實(shí)現(xiàn)是在子類FrameworkServlet里须鼎。
結(jié)語:
從源碼可以看出鲸伴,HttpServletBean是在HttpServlet的基礎(chǔ)上提供了把Servlet配置中相關(guān)的一些屬性,參數(shù)設(shè)置到成員變量上這樣的一個(gè)功能晋控,這么做的好處就是你可以很方便的通過getter/setter方法去獲取參數(shù)值汞窗,而不是通過一個(gè)通用的Map去獲取。
在下一篇將會(huì)深入分析下HttpServletBean的一個(gè)子類FrameworkServlet赡译。