隨着軟件開發技術的持續發展,框架技術層出不窮。仍是那句話,任何框架技術都是對基礎技術的封裝。因此,真正要學好用好一個框架,研究其源碼都是最直接最有效的途徑。java
隨着Spring技術體系的強勢發展,SpringMVC框架在Java Web開發中愈來愈流行了。接下來我準備就SpringMVC框架源簡單寫幾篇探究文章,一是作個總結,再一個也可以跟你們作一個交流。web
關於SpringMVC源碼探究,我準備寫三篇文章,分別針對SpringMVC三個最主要功能來進行探究:初始化源碼探究、處理請求源碼探究、響應源碼探究。時間緣由,探究比較倉促,有不妥之處還望朋友們多多包涵。接下來是第一篇關於SpringMVC初始化源碼探究:spring
全部Java的MVC框架都是基於servlet的,SpringMVC也不例外。它提供核心控制器DispatcherServlet,而且在此Servlet實例化後作出一系列的初始化處理,從而保證後期的高效率運行。在研究源碼以前,咱們首先看一下DispatcherServlet的繼承結構:設計模式
這三個Servlet類是SpringMVC的核心控制器類,各負其職來完成SpringMVC的各項功能。服務器
其中,HttpServletBean主要作一些初始化的工做,將web.xml中配置的參數設置到核心控制器Servlet中,好比servlet標籤的初始化參數子標籤init-param標籤中配置的參數;mvc
FrameworkServlet將Servlet與Spring容器上下文關聯。其實也就是初始化FrameworkServlet的屬性webApplicationContext,這個屬性表明SpringMVC上下文,其實也就是spring技術中web.xml配置的ContextLoaderListener監聽器初始化的容器上下文,因此咱們也說SpringMVC是基於Spring的;app
DispatcherServlet完成SpringMVC對web請求各個功能的實現。好比請求映射處理、視圖處理、異常處理等。框架
任何web功能的實現都是從web.xml開始的,因此咱們先來看看web.xml中的配置:ide
<servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
這裏配置了SpringMVC核心處理器,而且傳遞了參數contextConfigLocation以指定SpringMVC核心配置文件的位置,在服務器啓動時進行加載並實例化。ui
HttpServletBean覆蓋了init()方法,全部初始化處理都是此Servlet實例化以後於init方法中完成的,下面咱們就來看看其中三個最主要信息的初始化:讀取初始化參數contextConfigLocation、構造SpringMVC容器、建立全部的Bean對象。
public final void init() throws ServletException { //...... PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);//從ServletConfig中獲取初始化參數(如contextConfigLocation=classpath:springmvc.xml等) if (!pvs.isEmpty()) { try { BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);//構造BeanWrapper對象,接下來就是由BeanWrapper完成初始化處理 ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); initBeanWrapper(bw); bw.setPropertyValues(pvs, true);//把初始化參數contextConfigLocation設置到核心控制器DispatcherServlet的contextConfigLocation屬性中. } catch (BeansException ex) { //... } } initServletBean();//這個方法在本類中是空實現,它用來是被子類(FrameworkServlet)覆蓋的,在子類中進行構造SpringMVC容器上下文對象,這也是模板設計模式的具體應用。 //...... }
下面先來看BeanWrapper是如何設置初始化參數的:從源碼咱們會看到,BeanWrapper是個接口,實現類是BeanWrapperImpl,BeanWrapperImpl是AbstractNestablePropertyAccessor的子類,AbstractNestablePropertyAccessor有個屬性wrappedObject,保存了核心控制器的引用,那麼BeanWrapperImpl類也具備對DispatcherServlet對象的引用。在BeanWrapperImpl中有一個方法setValue()用來保存初始化參數到DispatcherServlet的屬性中:
setValue(final Object object, Object valueToApply) //...... if (System.getSecurityManager() != null) { //...... } else { writeMethod.invoke(getWrappedInstance(), value);//這裏使用反射把獲取到的初始化參數contextConfigLocation核心控制器的屬性contextConfigLocation。 } //...... }
其實,SpringMVC自己能用到的初始化參數也就是contextConfigLocation了。
接下來咱們來看看FrameworkServlet類的initServletBean()方法如何構造SpringMVC容器上下文對象:FrameworkServlet是HttpServletBean的子類,它覆蓋了initServletBean()方法,主要用來在服務器啓動時構造SpringMVC容器上下文對象。
protected final void initServletBean() throws ServletException { //...... try { this.webApplicationContext = initWebApplicationContext();//建立出SpringMVC容器對象 initFrameworkServlet(); } //...... }
很顯然,調用initWebApplicationContext()方法建立出SpringMVC容器上下文對象以後,保存在了核心控制器的webApplicationContext屬性(此屬性在父類FrameworkServlet聲明)中,以備處理請求時使用。
進一步,咱們不妨來看看具體構造SpringMVC容器上下文的代碼:initWebApplicationContext()會調到方法createWebApplicationContext(ApplicationContext parent)來完成上下文對象的建立以及調到initStrategies(ApplicationContext context)衆多策略對象的初始化:
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) { Class<?> contextClass = getContextClass();//獲取SpringMVC容器的類名XmlWebApplicationContext.class //...... ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);//使用反射建立SpringMVC容器對象,其實就是這個類org.springframework.web.context.support.XmlWebApplicationContext的對象 wac.setEnvironment(getEnvironment()); wac.setParent(parent); wac.setConfigLocation(getContextConfigLocation());//把init()中設置的DispatcherServlet的contextConfigLocation屬性(保存着SpringMVC核心配置文件的位置)交給SpringMVC容器,因此之後運行過程當中SpringMVC可以使用springmvc.xml中全部的配置也就不足爲奇了。 configureAndRefreshWebApplicationContext(wac);//在這個方法中,掃描全部歸入spring管理的類,而且實例化保存到容器中(beanFactory屬性中),以備後用。 return wac; }
protected void initStrategies(ApplicationContext context) { initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); initHandlerMappings(context); initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context); }
最後,咱們來看一下createWebApplicationContext(ApplicationContext parent)方法時如何掃描全部歸入spring管理的類,而且實例化保存到容器中(beanFactory屬性中)的。其它方法的調用我很少說了,其中調到了ComponentScanBeanDefinitionParser類的parse(Element element, ParserContext parserContext)方法,以下:
public BeanDefinition parse(Element element, ParserContext parserContext) { String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE); basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage); String[] basePackages = StringUtils.tokenizeToStringArray(basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); // Actually scan for bean definitions and register them. ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element); Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);//掃描核心配置文件中<context:component-scan base-package=""/>指定的包及其子包,獲得全部spring相關注解的類信息 registerComponents(parserContext.getReaderContext(), beanDefinitions, element);//實例化上一步全部的Bean而且在SpringMVC容器的beanFactory屬性中進行註冊 return null; }
至此,SpringMVC在服務器啓動時所作的最主要的三個初始化處理已經完成,在後期在接收到HTTP請求時就能夠及時高效的進行處理。固然,初始化要作的遠不止這些,只要是配置文件和註解中涉及到的信息基本上都會在服務器啓動時作初始化處理,好比視圖解析器等等,限於篇幅緣由,再也不贅述。全部這些也均可以從SpringMVC源碼中找到具體答案,有興趣的朋友能夠繼續研究。