这里先从最简单的一个Spring例子开始。
下面是Spring的context的配置
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:context="http://www.springframework.org/schema/context" 4 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 5 xsi:schemaLocation="http://www.springframework.org/schema/context 6 http://www.springframework.org/schema/context/spring-context.xsd 7 http://www.springframework.org/schema/beans 8 http://www.springframework.org/schema/beans/spring-beans.xsd"> 9 10 <!-- 自动扫描web包 ,将带有注解的类 纳入spring容器管理 --> 11 <context:component-scan base-package="com.tuhooo.practice"></context:component-scan> 12 </beans>
一个简单的Service类,MyBean.java
1 @Service 2 public class MyBean { 3 public void printStr() { 4 System.out.println("你好吗......"); 5 } 6 }
main方法中获取bean,并调用MyBean.java中的方法。
1 public class App { 2 public static void main( String[] args ) { 3 XmlBeanFactory applicationContext = new XmlBeanFactory(new ClassPathResource("spring-config.xml")); 4 MyBean myBean = (MyBean) applicationContext.getBean("myBean"); 5 myBean.printStr(); 6 } 7 }
当我在使用XmlBeanFactory这个类的时候,已经被标记为@deprecated,也就是这个类已经被抛弃了,但是并不妨碍它作为我们滴入口类进行分析呀。
XmlBeanFactory.java
1 @Deprecated 2 @SuppressWarnings({"serial", "all"}) 3 public class XmlBeanFactory extends DefaultListableBeanFactory { 4 5 /** 6 * 加载Bean定义的Reader 7 */ 8 private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); 9 10 /** 11 * 通过给定的可通过DOM解析的资源来创建一个XmlBeanFactory 12 * @param resource 用来加载bean定义的XML资源 13 * @throws BeansException 加载或者解析时候的错误 14 */ 15 public XmlBeanFactory(Resource resource) throws BeansException { 16 this(resource, null); 17 } 18 19 /** 20 * 通过给定的可通过DOM解析的输入流来创建一个XmlBeanFactory 21 * @param resource 用来加载bean定义的XML资源 22 * @param parentBeanFactory 父代的bean factory 23 * @throws BeansException 加载或者解析时候的错误 24 */ 25 public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { 26 super(parentBeanFactory); 27 this.reader.loadBeanDefinitions(resource); /*这个是我们要进入, 并分析的方法*/ 28 } 29 }
接下来看一下XmlBeanDefinitionReader中的loadBeanDefinitions(resource)的逻辑是怎样的。
XmlBeanDefinitionReader.loadBeanDefinitions(resource)
XmlBeanDefinitionReader的类图如下:
下面是XmlBeanDefinitionReader.java这个类截取的一部分:
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { /** * Load bean definitions from the specified XML file. * @param resource the resource descriptor for the XML file * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of loading or parsing errors */ @Override public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource(resource)); } /** * Load bean definitions from the specified XML file. * @param encodedResource the resource descriptor for the XML file, * allowing to specify an encoding to use for parsing the file * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of loading or parsing errors */ 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<>(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.loadBeanDefinitions(EncodedResource encodedResource)方法内部看一下。
这里EncodedResource验重的逻辑是:
public class EncodedResource implements InputStreamSource { @Override public boolean equals(Object other) { if (this == other) { return true; } if (!(other instanceof EncodedResource)) { return false; } EncodedResource otherResource = (EncodedResource) other; return (this.resource.equals(otherResource.resource) && ObjectUtils.nullSafeEquals(this.charset, otherResource.charset) && ObjectUtils.nullSafeEquals(this.encoding, otherResource.encoding)); } }
这个loadBeanDefinitions方法还是很清晰的,你说像我这种制杖都看懂了,就想说一句,还有谁看不懂~~~
但仍有几个地方需要后续再跟进看一下:
1. 这里有个局部线程变量来存放正在加载的资源,this.resourceCurrentlyBeingLoaded.get(),可以我们看到线程在哪儿?
2. 你说搞encodedResource我理解,但是为什么在doLoadBeanDefinitions方法中既有inputSource,又有encodedResource,不会显得很多余么?
XmlBeanDefinitionReader.doLoadBeanDefinitions(InputSource, Resource)
1 public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { 2 3 /** 4 * Actually load bean definitions from the specified XML file. 5 * @param inputSource the SAX InputSource to read from 6 * @param resource the resource descriptor for the XML file 7 * @return the number of bean definitions found 8 * @throws BeanDefinitionStoreException in case of loading or parsing errors 9 * @see #doLoadDocument 10 * @see #registerBeanDefinitions 11 */ 12 protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) 13 throws BeanDefinitionStoreException { 14 try { 15 Document doc = doLoadDocument(inputSource, resource); 16 return registerBeanDefinitions(doc, resource); 17 } 18 catch (BeanDefinitionStoreException ex) { 19 throw ex; 20 } 21 catch (SAXParseException ex) { 22 throw new XmlBeanDefinitionStoreException(resource.getDescription(), 23 "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex); 24 } 25 catch (SAXException ex) { 26 throw new XmlBeanDefinitionStoreException(resource.getDescription(), 27 "XML document from " + resource + " is invalid", ex); 28 } 29 catch (ParserConfigurationException ex) { 30 throw new BeanDefinitionStoreException(resource.getDescription(), 31 "Parser configuration exception parsing XML from " + resource, ex); 32 } 33 catch (IOException ex) { 34 throw new BeanDefinitionStoreException(resource.getDescription(), 35 "IOException parsing XML document from " + resource, ex); 36 } 37 catch (Throwable ex) { 38 throw new BeanDefinitionStoreException(resource.getDescription(), 39 "Unexpected exception parsing XML document from " + resource, ex); 40 } 41 } 42 }
最终把硬盘上的配置文件以Document的形式读到了内存中。
XmlBeanDefinitionReader.registerBeanDefinitions(Document, Resource)
1 public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { 2 3 public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { 4 BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); 5 int countBefore = getRegistry().getBeanDefinitionCount(); 6 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); 7 return getRegistry().getBeanDefinitionCount() - countBefore; 8 } 9 10 /** 11 * Create the {@link BeanDefinitionDocumentReader} to use for actually 12 * reading bean definitions from an XML document. 13 * <p>The default implementation instantiates the specified "documentReaderClass". 14 * @see #setDocumentReaderClass 15 */ 16 protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() { 17 return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass)); 18 } 19 20 /** 21 * Create the {@link XmlReaderContext} to pass over to the document reader. 22 */ 23 public XmlReaderContext createReaderContext(Resource resource) { 24 return new XmlReaderContext(resource, this.problemReporter, this.eventListener, 25 this.sourceExtractor, this, getNamespaceHandlerResolver()); 26 } 27 } 28
首先不看别的,直接看这段代码就行:
1. 构造一个BeanDefinitionDocumentReader,然后用它来注册BeanDefinition,它还需要一个读取时候的上下文;
2. 返回的是新加入Registry的BeanDefinition的个数
3. createBeanDefinitionDocumentReader的作用还没看太明白
DefaultBeanDefinitionDocumentReader.registerBeanDefinitions
再去看一下doRegisterBeanDefinitions方法
真正处理的方法又流转到了parseBeanDefinitions中,但是我没明白delegate是干啥啊?
Resource的类图
public class ClassPathResource extends AbstractFileResolvingResource { /** * Create a new {@code ClassPathResource} for {@code ClassLoader} usage. * A leading slash will be removed, as the ClassLoader resource access * methods will not accept it. * <p>The thread context class loader will be used for * loading the resource. * @param path the absolute path within the class path * @see java.lang.ClassLoader#getResourceAsStream(String) * @see org.springframework.util.ClassUtils#getDefaultClassLoader() */ public ClassPathResource(String path) { this(path, (ClassLoader) null); } /** * Create a new {@code ClassPathResource} for {@code ClassLoader} usage. * A leading slash will be removed, as the ClassLoader resource access * methods will not accept it. * @param path the absolute path within the classpath * @param classLoader the class loader to load the resource with, * or {@code null} for the thread context class loader * @see ClassLoader#getResourceAsStream(String) */ public ClassPathResource(String path, @Nullable ClassLoader classLoader) { Assert.notNull(path, "Path must not be null"); String pathToUse = StringUtils.cleanPath(path); if (pathToUse.startsWith("/")) { pathToUse = pathToUse.substring(1); } this.path = pathToUse; this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader()); } }
在代码new ClassPathResource("beanFactoryTest.xml");中其实没有啥动作,就是对ClassPathResource的几个属性赋值了,尤其是类加载器。也就是说这个时候其实并没有读取文件内容。