借着找工作的契机,作为Java web开发中经常用到和考到的Spring,简单翻了翻源码,熟悉了一下它的加载过程。
先上一张类图:
这里借用在web项目中的加载过程来熟悉Spring ApplicationContext的加载过程:
1. 在一般的Web项目中,我们很多情况下是利用org.springframework.web.context.ContextLoaderListener这个类进行容器的初始化。该类会被Web容器(如Tomcat)自动实例化,并调用contextInitialized方法。
/** * Initialize the root web application context. */ @Override public void contextInitialized(ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); } |
2. initWebApplicationContext方式是从父类ContextLoader中继承来的。该方法的大致的逻辑是:判定web容器中是否注册了ROOT_APPLICATION_CONTEXT_STTRIBUTE(为WebApplicationContext.class.getName()+ “.ROOT”)的属性,如果有,则抛出异常,以此保证一个Web容器中只有一个Spring根容器;创建容器的时候,要判定需要实例化哪种类来实例化当前web容器的Spring根容器,如果我们设置了名称为“contextClass”的context-param,则取我们设置的类,该类应当实现ConfigurableWebApplicationContext接口或继承自实现了该接口的子类(如XmlWebApplicationContext、GroovyWebApplicationContext和AnnotationConfigWebApplicationContext),通常我们都不会设置,Spring会默认取与ContextLoader同目录下的ContextLoader.properties中记录的类名作为根容器的类型(默认是org.springframework.web.context.support.XmlWebApplicationContext);实例化容器;配置容器;设置容器为Web容器的属性。下面代码中去掉了log和异常等部分。
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) { throw new IllegalStateException( "Cannot initialize context because there is already a root application context present - " + "check whether you have multiple ContextLoader* definitions in your web.xml!"); } if (this.context == null) { this.context = createWebApplicationContext(servletContext); } if (this.context instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context; if (!cwac.isActive()) { if (cwac.getParent() == null) { ApplicationContext parent = loadParentContext(servletContext); cwac.setParent(parent); } configureAndRefreshWebApplicationContext(cwac, servletContext); } } servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); ClassLoader ccl = Thread.currentThread().getContextClassLoader(); if (ccl == ContextLoader.class.getClassLoader()) { currentContext = this.context; } else if (ccl != null) { currentContextPerThread.put(ccl, this.context); } return this.context; } |
3. configureAndRefreshWebApplicationContext方法负责对容器进行初始化,该方法的逻辑主要有一下几点:设置一个contextId(从contextId这个param获取,如果没有则默认是WebApplicationContext的类名 + “:” + servlet context的路径);设置配置位置(从contextConfigLocation 这个param获取,如果未配置,则默认是/WEB-INF/applicationContext.xml,在XmlWebApplicationContext中可以看出);自定义该congtext;调用该Context的refresh()方法。
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) { if (ObjectUtils.identityToString(wac).equals(wac.getId())) { String idParam = sc.getInitParameter(CONTEXT_ID_PARAM); if (idParam != null) { wac.setId(idParam); } else { wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath())); } }
wac.setServletContext(sc); String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM); if (configLocationParam != null) { wac.setConfigLocation(configLocationParam); } ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(sc, null); } customizeContext(sc, wac); wac.refresh(); } |
4. ConfigurableApplicationContext接口的实现类AbstractApplicationContext中的refresh方法,定义了一个模版,在该方法里,会完成加载资源、配置文件解析、Bean定义的注册、组件的初始化等工作。每一步工作都定义在响应的方法中,清晰明了。下面的方法省略了异常处理。
synchronized (this.startupShutdownMonitor) {. prepareRefresh(); //准备 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();//获得一个Bean工厂 prepareBeanFactory(beanFactory); //准备好工厂 postProcessBeanFactory(beanFactory);//处理工厂时执行 invokeBeanFactoryPostProcessors(beanFactory);//执行工厂处理 registerBeanPostProcessors(beanFactory);//注册bean产生拦截器 initMessageSource();//初始化消息源 initApplicationEventMulticaster();//初始化应用事件广播器 onRefresh();//初始化该容器子类其他特定的bean registerListeners();//检查监听器并注册 finishBeanFactoryInitialization(beanFactory);// 初始化非lazy的singleton的bean
finishRefresh();//发布结束fresh消息 } |
5. 这几个方法中比较重要的方法是obtainFreshBeanFactory方法和finishBeanFactoryInitialization方法,一个用来获得bean工厂,一个用来实例化bean并注入的。这里先分析obtainFreshBeanFactory方法。在继承链中,AbstractApplicationContext实现了该方法,并定义了refreshBeanFactory方法让后代实现。AbstractRefreshableApplicationContext又实现了refreshBeanFactory方法,如果有一个BeanFactory销毁它,然后再创建一个。
protected final void refreshBeanFactory() throws BeansException { if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); customizeBeanFactory(beanFactory); loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; } } } |
6. loadBeanDefinitions方法会由子类实现,在XmlWebApplicationContext中就实现了该方法。该方法的逻辑为:new一个XmlBeanDefinitionReader;设置该Reader的ResourceLoader(XmlWebApplicationContext设置的是它自己,因为它间接实现了ResourceLoader接口);、使用该Reader加载Bean定义。
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); beanDefinitionReader.setEnvironment(getEnvironment()); beanDefinitionReader.setResourceLoader(this); beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
initBeanDefinitionReader(beanDefinitionReader); loadBeanDefinitions(beanDefinitionReader); } |
7. loadeBeanDefinitions方法会使用reader从每个配置中读取bean定义。该方法大概逻辑为:从每个location解析出一些resouce(代表特定的资源,如一个文件)。在AbstractBeanDefinitionReader中实现的loadBeanDefinitions(String,Set<Resource> actualResources) 方法如下:
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException { ResourceLoader resourceLoader = getResourceLoader(); if (resourceLoader == null) { throw new BeanDefinitionStoreException( "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available"); }
if (resourceLoader instanceof ResourcePatternResolver) { try { Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); int loadCount = loadBeanDefinitions(resources); if (actualResources != null) { for (Resource resource : resources) { actualResources.add(resource); } } if (logger.isDebugEnabled()) { logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]"); } return loadCount; } catch (IOException ex) { throw new BeanDefinitionStoreException( "Could not resolve bean definition resource pattern [" + location + "]", ex); } } else { Resource resource = resourceLoader.getResource(location); int loadCount = loadBeanDefinitions(resource); if (actualResources != null) { actualResources.add(resource); } if (logger.isDebugEnabled()) { logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]"); } return loadCount; } } |
8. 在XmlBeanDefinitionReader中才是真正的加载Bean定义的实现。它从每个Resource中获得输入流,封装成InputSource;调用doLoadBeanDefinitions从该InputSource中获得一个Document,并使用registerBeanDefinitions方法根据该Document注册BeanDefinitions;registerBeanDefinitions方法中先是创建一个DefaultBeanDefinitionDocumentReader实例,再使用该实例来注册BeanDefinition;该实例会从Document的根元素开始注册BeanDefinition。值得注意的是在该类又会委派一个BeanDefinitionParserDelegate来解析Document元素。
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; if (delegate.isDefaultNamespace(ele)) { parseDefaultElement(ele, delegate); } else { delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } } |
9. BeanDefinitionParserDelegate会根据节点的命名空间使用不同的NamespaceHandler进行解析。在XmlBeanDefinitionReader创建DefaultBeanDefinitionDocumentReader时候会传入一个XmlReaderContext,该Context中保存了一个NamespaceHandlerResolver,该HandlerResolver维护一个Map,用来解析对于一个命名空间的具体Handler,其默认使用META-INF/spring.handlers 文件保存Handler的种类,Spring支持的Handler如下图:
BeanDefinitionDelegate使用获得的NamespaceHandler解析元素节点,获得BeanDefinition。
10. 不同的NamespaceHandler解析过程不一样。同时,一个NamespaceHandler中有多个Parser来解析不同种类的元素。以ContextNamspaceHandler为例,它支持的Parser如下图所示: