spring的應用初始化流程一直沒有搞明白,剛剛又碰到了相關的問題。決定得好好看看這個流程。我們在開發spring的項目當中基本上都會在web.xml通過:
<context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/conf/application-*.xml </param-value> </context-param>
來初始化各個spring的配置文件,但是我們只是知道這段代碼的功能, 並不是很清楚我們配置了這段代碼之后為什么就能去初始化配置文件。當然我們還會加上:
<listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
這一個listener,我首先就會想contextConfigLocation這個一定能在ContextLoaderListener這個類當中找到,打開了源碼,這個listener是實現了ServletContextListener這個接口的,這個接口只有兩個方法:
public interface ServletContextListener extends EventListener { public abstract void contextInitialized(ServletContextEvent servletcontextevent); public abstract void contextDestroyed(ServletContextEvent servletcontextevent); }
而且它是繼承了EventListener這個接口的,打開這個接口的代碼讓我大吃一驚,里面沒有方法啥都沒有:
package java.util; public interface EventListener { }
而且還是java.util包下的,並不是spring之中的東西。
這樣找了之后沒有找到,往回退到ContextLoaderListener這個類的方法上,contextInitialized方法是用來初始化上下文的:
public void contextInitialized(ServletContextEvent event) { contextLoader = createContextLoader(); contextLoader.initWebApplicationContext(event.getServletContext()); }
方法中有個createContextLoader方法:
protected ContextLoader createContextLoader() { return new ContextLoader(); }
這個方法返回了一個ContextLoader實例,進入到ContextLoader類中,按ctrl+f來尋找contextConfigLocation,這時沒有出現電腦的咚的聲音,找到了它:
protected WebApplicationContext createWebApplicationContext(ServletContext servletContext, ApplicationContext parent) throws BeansException { Class contextClass = determineContextClass(servletContext); if(!(org.springframework.web.context.ConfigurableWebApplicationContext.class).isAssignableFrom(contextClass)) { throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + (org.springframework.web.context.ConfigurableWebApplicationContext.class).getName() + "]"); } else { ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass); wac.setParent(parent); wac.setServletContext(servletContext); wac.setConfigLocation(servletContext.getInitParameter("<span style="color:#ff0000;">contextConfigLocation</span>")); customizeContext(servletContext, wac); wac.refresh(); return wac; } }
通過代碼,ConfigurableWebApplicationContext設置了從servletContext獲取到的參數的值,再進入ConfigurableWebApplicationContext的代碼中,它只是一個接口,進入StaticWebApplicationContext的setConfigLocation方法:
public void setConfigLocation(String configLocation) { if(configLocation != null) throw new UnsupportedOperationException("StaticWebApplicationContext does not support config locations"); else return; }
這個方法中很奇怪,當參數不為空就拋出異常,查看spring的文檔:The StaticWebApplicationContext
class does not support this method.說是此類不支持這個方法,這下子又卡住了。又要退回去,看這句:
ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
pring使用BeanUtils來初始化contextClass這個類實例,contextClass是通過以下代碼得到的:
protected Class determineContextClass(ServletContext servletContext) throws ApplicationContextException { String contextClassName = servletContext.getInitParameter("contextClass"); if(contextClassName != null) try { return ClassUtils.forName(contextClassName); } catch(ClassNotFoundException ex) { throw new ApplicationContextException("Failed to load custom context class [" + contextClassName + "]", ex); } contextClassName = defaultStrategies.getProperty((org.springframework.web.context.WebApplicationContext.class).getName()); try { return ClassUtils.forName(contextClassName, (org.springframework.web.context.ContextLoader.class).getClassLoader()); } catch(ClassNotFoundException ex) { throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", ex); } }
這里使用了反射,再來看BeanUtils的instantiateClass方法:
return instantiateClass(clazz.getDeclaredConstructor((Class[])null), null);
通過反射得到contextClass的構造方法。下面是instantiateClass方法的重載,主要是下面兩句代碼:
ReflectionUtils.makeAccessible(ctor); return ctor.newInstance(args);
ctor是通過反射得到的contextClass的構造方法,args是構造方法當中的參數。這里為null,說明new了contextClass的無參構造方法。
這時又要退回到determineContextClass 這個方法中,我們主要看:
contextClassName = defaultStrategies.getProperty((org.springframework.web.context.WebApplicationContext.class).getName());
這句代碼,我們可以猜它是通過Properties的getProperty方法得到WebApplicationContext 的實例,這時我們又到了WebApplicationContext 這個接口當中,這個接口繼承了ApplicationContext這個接口,我們都知道我們進行spring開發都會通過Application ctx=new FileSystemXmlApplicationContext("beans.xml");或ApplicationContext ctx=new ClassPathXmlApplicationContext("beans.xml");或ServletContext servletContext = request.getSession().getServletContext();ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);這三種方法獲得一個ApplicationContext,然后就可以對配置文件當中的bean進行操作了。所以這里我們基本上已經搞清楚初始化spring配置文件的流程了。
總結:通過查看這幾個類的源代碼,java的反射使用范圍之廣再次體現出來。如看了之后覺得有錯誤或者不同意見,歡迎提出來,我也是第一次才研究這個問題。