BeanDefinition 解析和注册
?????? 上一篇SpringIOC之BeanFactory大概分析了SpringIOC之最简单的容器接口的设计和实现的框架,可以看出Spring的接口职责的明确划分。有了对上层接口设计的认识,这一篇就直接从容器XmlBeanFactory着手,解读一下从Bean配置的读取、解析、并注册为BeanDefinition的过程,最后介绍常用BeanFactoryPostProcessor对BeanDefinition解析之后做进一步修改,从而实现一些特殊的需求。
BeanFactory container = new XmlBeanFactory(new ClassPathResource("配置文件路径"));//Person person = (Person)container.getBean("person");//you can use person object now
? ? ?? 短短两行代码非常简单,其实代表了两大步骤:容器启动加载BeanDefinition资源、实例化Bean对象。本文着重介绍启动加载资源,不对Bean的实例化和装配过程进行分析。初始化BeanFactory时走的是下面类图蓝色的路径:

?
??? 从上图XmlBeanFactory出发沿蓝色路径,一次初始化浅色类对象,跟踪代码如下:
?
//在XmlBeanFactory Class中,初始化XmlBeanDefinitionReader ,//并将自己做为BeanDefinitionRegistry传递//给XmlBeanDefinitionReader private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);//XmlBeanDefinitionReader Class的构造方法public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {super(registry);}//进入XmlBeanDefinitionReader父类AbstractBeanDefinitionReader构造//方法,这里会走PathMatchingResourcePatternResolver路径protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {Assert.notNull(registry, "BeanDefinitionRegistry must not be null");this.registry = registry;// Determine ResourceLoader to use.//一般ApplicationContext走这条路径,因为ApplicationContext实现了//ResourceLoader的功能,因此它比XmlBeanFactory更方便if (this.registry instanceof ResourceLoader) {this.resourceLoader = (ResourceLoader) this.registry;}else {this.resourceLoader = new PathMatchingResourcePatternResolver();}}//初始化PathMatchingResourcePatternResolver,构造方法//用DefaultResourceLoader对象做为//PathMatchingResourcePatternResolver依赖的ResourceLoaderpublic PathMatchingResourcePatternResolver() {this.resourceLoader = new DefaultResourceLoader();}//DefaultResourceLoader构造方法,主要是初始化ClassLoaderpublic DefaultResourceLoader() {this.classLoader = ClassUtils.getDefaultClassLoader();}?
??? 到此,XmlBeanFactory初始化完毕,接着在XmlBeanFactory中开始加载BeanDefinition资源:
this.reader.loadBeanDefinitions(resource);
?
??? XmlBeanDefinition.loadBeanDefinitions方法:
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 currentResources = (Set) this.resourcesCurrentlyBeingLoaded.get();if (currentResources == null) {currentResources = new HashSet(4);this.resourcesCurrentlyBeingLoaded.set(currentResources);}if (!currentResources.add(encodedResource)) {throw new BeanDefinitionStoreException("Detected recursive 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.set(null);}}}?
????? 直接看try-catch部分,会根据client指定的Resource获取InputStream,然后再包装成org.xml.sax.InputSource对象,接着把加载任务交给doLoadBeanDefinitions方法,其他是资源清理相关代码。这里不同Resource获取InputStream可以看作是一个策略模式的实现,具体看ApplicationContext会更明显,最常见的如ClassPathRersource,FileSystemResource,ByteArrayResource,UrlResource获取InputStream方式如下:
//ClassPathResourcepublic InputStream getInputStream() throws IOException {InputStream is = null;if (this.clazz != null) {is = this.clazz.getResourceAsStream(this.path);}else {is = this.classLoader.getResourceAsStream(this.path);}if (is == null) {throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");}return is;}//FileSystemResourcepublic FileInputStream(File file) throws FileNotFoundException {String name = (file != null ? file.getPath() : null);SecurityManager security = System.getSecurityManager();if (security != null) { security.checkRead(name);} if (name == null) { throw new NullPointerException(); }fd = new FileDescriptor();open(name); }//UrlResourcepublic InputStream getInputStream() throws IOException {URLConnection con = this.url.openConnection();con.setUseCaches(false);return con.getInputStream();}//ByteArrayResourcepublic InputStream getInputStream() throws IOException {return new ByteArrayInputStream(this.byteArray);}?????? 重点看doLoadBeanDefinitions(InputSource, Resource),去除异常处理代码之后,总共就3行:
int validationMode = getValidationModeForResource(resource);Document doc = this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());return registerBeanDefinitions(doc, resource);
?????? 第一行获取验证模式,如xsd,schema等XML的验证,可以重写该方法来做自己特定的配置验证。第二行是将输入流加载为xml的Document对象,中间会依赖DefaultDocumentLoader,最后会用javax.xml.parsers包来parse xml文档。到这里还没解析XML配置中各个Bean,继续看源码registerBeanDefinitions(Document,Resource)方法:
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();int countBefore = getRegistry().getBeanDefinitionCount();documentReader.registerBeanDefinitions(doc, createReaderContext(resource));return getRegistry().getBeanDefinitionCount() - countBefore;
?????? 下面的主角是BeanDefinitionDocumentReader,当然此处使用DefaultBeanDefinitionDocumentReader实现类,看他的方法registerBeanDefinitions—ocument,XmlReaderContext):
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {this.readerContext = readerContext;logger.debug("Loading bean definitions");Element root = doc.getDocumentElement();BeanDefinitionParserDelegate delegate = createHelper(readerContext, root);preProcessXml(root);parseBeanDefinitions(root, delegate);postProcessXml(root);}??????? 留下两个钩子preProcessXml和postProcessXml给客户端增强,主要是中间的parseBeanDefinitions方法:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {if (delegate.isDefaultNamespace(root.getNamespaceURI())) {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;String namespaceUri = ele.getNamespaceURI();if (delegate.isDefaultNamespace(namespaceUri)) {parseDefaultElement(ele, delegate);}else {delegate.parseCustomElement(ele);}}}}else {delegate.parseCustomElement(root);}}//处理具体节点private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {if (DomUtils.nodeNameEquals(ele, IMPORT_ELEMENT)) {//处理xml import文档importBeanDefinitionResource(ele);}else if (DomUtils.nodeNameEquals(ele, ALIAS_ELEMENT)) {processAliasRegistration(ele);}else if (DomUtils.nodeNameEquals(ele, BEAN_ELEMENT)) {//具体处理一个BeanDefinition processBeanDefinition(ele, delegate);}}?????????? 具体processBeanDefinition:
/** * Process the given bean element, parsing the bean definition * and registering it with the registry. */protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);if (bdHolder != null) {bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);try {// Register the final decorated instance.BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());}catch (BeanDefinitionStoreException ex) {getReaderContext().error("Failed to register bean definition with name '" +bdHolder.getBeanName() + "'", ele, ex);}// Send registration event.getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));}}?????? 这里会利用工具类BeanDefinitionReaderUtils来将bean对象注册到BeanDefinitionRegistry中,具体代码嘛就跟上一篇例子使用DefaultListableBeanFactory编码实现很像了。最终Bean配置会被解析成BeanDefinition注册到DefaultListableBeanFactory.beanDefinitionMap中。之后客户端如果要获取Bean对象,XmlBeanFactory会根据注册的BeanDefinition信息进行实例化。
????? 貌似Bean配置的读取和解析就已进结束,其实不尽然,Web开发配置时经常配置PropertyPlaceholderConfigurer将一些数据库用户名密码等配置放到单独的property文件中,做为一种BeanFactoryPostProcessor,它实际上是在BeanDefinition解析之后,对BeanDefinition信息进行了修改。对于ApplicationContext来说这一步是自动的,无需客户端手动调用。而对于XmlBeanFactory来说就需要自己手动调用了:
XmlBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("testXml.xml"));// 声明要使用的PropertyPlaceholderConfigurerPropertyPlaceholderConfigurer propertyPostProcessor = new PropertyPlaceholderConfigurer();propertyPostProcessor.setLocation(new ClassPathResource("dbValue.properties"));// 执行后处理操作propertyPostProcessor.postProcessBeanFactory(beanFactory);//之后再获取bean,BeanDefinition原始的配置信息已经被dbValue.properties对应的值修改 ......??
?