关于Spring框架,说的最多的就是IOC和AOP了,即依赖注入和切面编程。这些也是使用spring过程中要用的最基本的东西了。但是,spring是如何进行运转的呢?这是一个问题呀,我们起码应该要明白spring的工作流程。
思考:
spring常用的方式有注解和xml配置两种方式,根据我们的猜测,这两种方式殊路同归。Spring首先要做的事,就是解析xml(找到注解的类),根据解析出来的文档进行各种类的信息组装,最后当需要某个类的实例时,根据反射、利用组装好的class信息,构造出我们想要的实例;
其实,主体思路确实如我们所想的那样,走的就是这个流程。但是,spring在其中添加了很多的检查工作,来保证安全性。接下来,我们简单分析一下;
过程分析(Spring4.x):
1.加载配置文件
String file="spring.xml"; Resource source=new ClassPathResource(file); BeanFactory factory=new XmlBeanFactory(source);
这是一段最简单的将xml文件加载到spring容器中,并从容器中获得注册Bean的代码了。那我们来看看XmlBeanFactory的代码:
1 public class XmlBeanFactory extends DefaultListableBeanFactory { 2 3 private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); 4 5 6 /** 7 * Create a new XmlBeanFactory with the given resource, 8 * which must be parsable using DOM. 9 * @param resource XML resource to load bean definitions from 10 * @throws BeansException in case of loading or parsing errors 11 */ 12 public XmlBeanFactory(Resource resource) throws BeansException { 13 this(resource, null); 14 } 15 16 /** 17 * Create a new XmlBeanFactory with the given input stream, 18 * which must be parsable using DOM. 19 * @param resource XML resource to load bean definitions from 20 * @param parentBeanFactory parent bean factory 21 * @throws BeansException in case of loading or parsing errors 22 */ 23 public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { 24 super(parentBeanFactory); 25 this.reader.loadBeanDefinitions(resource); 26 } 27 28 }
从源码看出,实际的加载xml文件即解析xml文件为Bean的工作是交给XmlBeanDefinitionReader来完成的,那我们再看看这个类的loadBeanDefinitions方法:
@Override public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource(resource)); } public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { Assert.notNull(encodedResource, "EncodedResource must not be null"); if (logger.isInfoEnabled()) { logger.info("Loading XML bean definitions from " + encodedResource.getResource()); } Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); if (currentResources == null) { currentResources = new HashSet<EncodedResource>(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } try { InputStream inputStream = encodedResource.getResource().getInputStream(); try { InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } finally { inputStream.close(); } } catch (IOException ex) { throw new BeanDefinitionStoreException( "IOException parsing XML document from " + encodedResource.getResource(), ex); } finally { currentResources.remove(encodedResource); if (currentResources.isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); } } }
可以看出,XmlBeanDefinitionReader类会先将Resource封装成EncodedResource类,并调用重载方法来完成解析工作。重载方法里关键部分是一下代码:
InputStream inputStream = encodedResource.getResource().getInputStream(); try { InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); }
上述代码就是从Resource对象中获得输入流,即输入的xml文件,并封装成InputSource类,交给doLoadBeanDefinitions方法来进行解析;
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { Document doc = doLoadDocument(inputSource, resource); return registerBeanDefinitions(doc, resource); } catch (BeanDefinitionStoreException ex) { throw ex; } catch (SAXParseException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex); } catch (SAXException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", ex); } catch (ParserConfigurationException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, ex); } catch (IOException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, ex); } catch (Throwable ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, ex); } }
这里面要关注的就是:
Document doc = doLoadDocument(inputSource, resource); return registerBeanDefinitions(doc, resource);
这两句代码,其中第一句就是讲xml文件解析成Ducnment对象,第二句是通过分析Document对象来注册类。这里我们来看第二个方法,第一个知道在这里xml文件被转为Document对象就行了;
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); int countBefore = getRegistry().getBeanDefinitionCount(); documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
看得出来,在这个方法中又创建了一个BeanDefinitionDocumentReader对象,利用他来解析Document对象内容。其中BeanDefinitionDocumentReader是个接口,他只有一个实现类DefaultBeanDefinitionDocumentReader,所以我们进入这个实现类的registerBeanDefinitions方法中去查看
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; logger.debug("Loading bean definitions"); Element root = doc.getDocumentElement(); doRegisterBeanDefinitions(root); }
一般spring里面do打头的方法,就是真正干活的方法了,让我们进doRegisterBeanDefinitions看看:
protected void doRegisterBeanDefinitions(Element root) { BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(getReaderContext(), root, parent); if (this.delegate.isDefaultNamespace(root)) { String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) { String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { if (logger.isInfoEnabled()) { logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + getReaderContext().getResource()); } return; } } } preProcessXml(root); parseBeanDefinitions(root, this.delegate); postProcessXml(root); this.delegate = parent; }
这里的重点方法是parseBeanDefinitions(root, this.delegate),在这里面有实际解析Document文件的步骤。另外preProcessXml(root)和postProcessXml(root)方法都是空实现,应该是留给用户自定义的。至于前面一段代码,是处理profile标签的内容;
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); } }
这个方法,循环解析Document中的每个节点(Bean),并判断是默认标签节点(bean,import,beans,alias)还是用户自定义标签节点(例如aop,tx),然后采用对应的解析方法parseDefaultElement(ele, delegate)和delegate.parseCustomElement(ele)。具体的解析标签,以后再说了。至此,我们大致明白了spring是怎么找到xml文件并将它解析为bean信息的宏观结构流程了。接下来,我们就该讨论一下,spring是怎么去获取这些bean信息创建出来的实例了。
ps.采用Applicationcontext的分析bean的步骤类似,接下来也接单看看;
首先我们打开ClassPathXmlApplicationContext的源码,该类变相实现了ApplicationContext接口;
public class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext { ... public ClassPathXmlApplicationContext(String configLocation) throws BeansException { this(new String[] {configLocation}, true, null); } public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); if (refresh) { refresh(); } } }
构造函数接受一个string类型的值,用来表示xml配置文件地址。重点是ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)这个方法,其中的setConfigLocations(configLocations)方法是将string解析,并设置为configLocations的值,否则spring会有默认值。实际的重点是refresh()方法,该方法里实现了beanfactory的初始化,即上面描述的加载解析bean信息的过程。另外,还有国际化等其他的操作。refresh方法属于AbstractApplicationContext这个抽象类:
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // 这里面就是初始化beanfactory ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } catch (BeansException ex) { // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } } }
上面的ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory()方法就是加载beanfactory的方法
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { refreshBeanFactory(); ConfigurableListableBeanFactory beanFactory = getBeanFactory(); if (logger.isDebugEnabled()) { logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory); } return beanFactory; }
其中refreshBeanFactory()方法是初始化beanfactory地方法,在该类中是抽象方法,由子类AbstractRefreshableApplicationContext实现
@Override 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; } } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); } }
我们直接看loadBeanDefinitions(beanFactory)方法,该方法也是抽象方法,由子类完成。
@Override protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { // Create a new XmlBeanDefinitionReader for the given BeanFactory. XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // Configure the bean definition reader with this context's // resource loading environment. beanDefinitionReader.setEnvironment(getEnvironment()); beanDefinitionReader.setResourceLoader(this); beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // Allow a subclass to provide custom initialization of the reader, // then proceed with actually loading the bean definitions. initBeanDefinitionReader(beanDefinitionReader); loadBeanDefinitions(beanDefinitionReader); }
看到这里,就和上面的步骤联系起来了,也是采用XmlBeanDefinitionReader来完成xml文件解析和bean的加载;