解析XML获取Bean

Posted 为人师表好少年

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了解析XML获取Bean相关的知识,希望对你有一定的参考价值。

一、基础代码

Spring加载bean实例的代码

public static void main(String[] args) throws IOException 
    // 1.获取资源
    Resource resource = new ClassPathResource("bean.xml");
    // 2.获取BeanFactory
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    // 3.获取资源的解析器
    XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
    // 4.装载资源
    reader.loadBeanDefinitions(resource);

    // 5.获取Bean实例
    Book book = factory.getBean(Book.class);
    System.out.println(book);

这段代码是 Spring 中编程式使用 IoC 容器,通过这四段简单的代码,我们可以初步判断 IoC 容器的使用过程。整个过程就分为三个步骤:

资源定位。我们一般用外部资源来描述 Bean 对象,所以在初始化 IoC 容器的第一步就是需要定位这个外部资源。

装载。装载就是 BeanDefinition 的载入。BeanDefinitionReader 读取、解析 Resource 资源,也就是将用户定义的 Bean 表示成 IoC 容器的内部数据结构:BeanDefinition

注册。向 IoC 容器注册在第二步解析好的 BeanDefinition,这个过程是通过BeanDefinitionRegistry接口来实现的。在 IoC 容器内部其实是将第二个过程解析得到的BeanDefinition注入到一个HashMap容器中,IoC 容器就是通过这个HashMap来维护这些BeanDefinition的。

​ 1)在这里需要注意的一点是这个过程并没有完成依赖注入(Bean 创建),Bean 创建是发生在应用第一次调用getBean()方法,向容器索要 Bean 时。
​ 2)当然我们可以通过设置预处理,即对某个 Bean 设置lazyinit = false 属性,那么这个 Bean 的依赖注入就会在容器初始化的时候完成。

简单点说,上面步骤的结果是:XML Resource -> XML Document -> Bean Definition

二、BeanDefinition

上面讲完了介绍到我们解析的Bean标签会封装成BeanDefinition,那么接下来看一下

1.基础介绍

org.springframework.beans.factory.config.BeanDefinition ,是一个接口,它描述了一个 Bean 实例的定义,包括属性值、构造方法值和继承自它的类的更多信息。代码如下:

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement 
 	
    // 其中方法就不粘贴了,太多了

2.BeanDefinition 的父关系

BeanDefinition 继承 AttributeAccessorBeanMetadataElement 接口。两个接口定义如下:

org.springframework.cor.AttributeAccessor 接口,定义了与其它对象的(元数据)进行连接和访问的约定,即对属性的修改,包括获取、设置、删除。代码如下:

public interface AttributeAccessor 

   void setAttribute(String name, @Nullable Object value);

   @Nullable
   Object getAttribute(String name);

   @Nullable
   Object removeAttribute(String name);

   boolean hasAttribute(String name);

   String[] attributeNames();

org.springframework.beans.BeanMetadataElement 接口,Bean 元对象持有的配置元素可以通过 getSource() 方法来获取。代码如下:

public interface BeanMetadataElement 

   @Nullable
   default Object getSource() 
      return null;
   

3.BeanDefinition 的子关系

我们常用的三个实现类有:

  • org.springframework.beans.factory.support.ChildBeanDefinition
  • org.springframework.beans.factory.support.RootBeanDefinition
  • org.springframework.beans.factory.support.GenericBeanDefinition
  1. ChildBeanDefinitionRootBeanDefinitionGenericBeanDefinition 三者都继承 AbstractBeanDefinition 抽象类,即 AbstractBeanDefinition 对三个子类的共同的类信息进行抽象。
  2. 如果配置文件中定义了父 和 子 ,则父 用 RootBeanDefinition 表示,子 用 ChildBeanDefinition 表示,而没有父 的就使用RootBeanDefinition 表示。
  3. GenericBeanDefinition 为一站式服务类。

三、调用链

1.loadBeanDefinitions() 加载

加载BeanDefinition

// XmlBeanDefinitionReader XML解析类
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException 
   // 将Resource资源封装成EncodedResource,封装的原因是为了进行编码,保证内容读取的正确性
   return loadBeanDefinitions(new EncodedResource(resource));

// XmlBeanDefinitionReader XML解析类
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException 
   Assert.notNull(encodedResource, "EncodedResource must not be null");
   if (logger.isTraceEnabled()) 
      logger.trace("Loading XML bean definitions from " + encodedResource);
   

   // 获取已经加载过的资源
   Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();

   // 将当前资源加入记录中,如果已存在,则抛出异常
   // 主要为了避免一个EncodedResource在加载时,还没加载完成,又加载自身,从而导致死循环
   if (!currentResources.add(encodedResource)) 
      throw new BeanDefinitionStoreException(
            "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
   

   // 获取输入流
   try (InputStream inputStream = encodedResource.getResource().getInputStream()) 
      InputSource inputSource = new InputSource(inputStream);
      if (encodedResource.getEncoding() != null) 
         // 设置编码
         inputSource.setEncoding(encodedResource.getEncoding());
      
      // **核心逻辑部分,执行加载 BeanDefinition
      return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
   
   catch (IOException ex) 
      throw new BeanDefinitionStoreException(
            "IOException parsing XML document from " + encodedResource.getResource(), ex);
   
   finally 
      // 缓存中剔除该资源,代表加载完了该文件
      currentResources.remove(encodedResource);
      if (currentResources.isEmpty()) 
         this.resourcesCurrentlyBeingLoaded.remove();
      
   

-> doLoadBeanDefinitions()

// XmlBeanDefinitionReader XML解析类
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
      throws BeanDefinitionStoreException 

   try 
      // 获取XML Document实例
      Document doc = doLoadDocument(inputSource, resource);
      // 根据 Document 实例,注册 Bean 信息
      int count = registerBeanDefinitions(doc, resource);
      if (logger.isDebugEnabled()) 
         logger.debug("Loaded " + count + " bean definitions from " + resource);
      
      return count;
   
   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);
   

2.doLoadDocument()

XML文件解析成Docuemnt

private DocumentLoader documentLoader = new DefaultDocumentLoader();

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception 
   return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
         getValidationModeForResource(resource), isNamespaceAware());

// DefaultDocumentLoader 默认的文档加载器
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
      ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception 

   DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
   if (logger.isTraceEnabled()) 
      logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
   
   DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
   return builder.parse(inputSource);

3.registerBeanDefinitions() 开始注册

注册BeanDefinition

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException 
   // <1>创建对象BeanDefinitionDocumentReader
   BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
   // <2>获取已注册的BeanDefinition数量
   int countBefore = getRegistry().getBeanDefinitionCount();
   // <3>创建XmlReaderContext
   // <4>注册新的Bean
   documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
   // <5>两个相减,返回注册成功的数量
   return getRegistry().getBeanDefinitionCount() - countBefore;

-> registerBeanDefinitions()

@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) 
   this.readerContext = readerContext;
   doRegisterBeanDefinitions(doc.getDocumentElement());

-> doRegisterBeanDefinitions()

该方法特殊处理profile属性,然后进行解析

protected void doRegisterBeanDefinitions(Element root) 
   // 记录老的 BeanDefinitionParserDelegate 对象
   BeanDefinitionParserDelegate parent = this.delegate;
   // 创建 BeanDefinitionParserDelegate 对象,并进行设置到 delegate
   // *** 它负责解析Document的各种对象
   this.delegate = createDelegate(getReaderContext(), root, parent);

   // 检查 <beans /> 根标签的命名空间是否为空,或者是 http://www.springframework.org/schema/beans
   if (this.delegate.isDefaultNamespace(root)) 
      // 处理profile属性
      String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
      if (StringUtils.hasText(profileSpec)) 
         // 使用分隔符切分,可能有多个 profile 。
         String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
               profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
         // 如果所有 profile 都无效,则不进行注册
         // We cannot use Profiles.of(...) since profile expressions are not supported
         // in XML config. See SPR-12458 for details.
         if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) 
            if (logger.isDebugEnabled()) 
               logger.debug("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;



protected void preProcessXml(Element root) 


protected void postProcessXml(Element root) 

4.parseBeanDefinitions() 分类解析

下面代码中会区分2类,

  1. 默认命名空间,例如:<bean id="book" class="com.h3c.ss.Book"></bean>
  2. 自定义注解方式,例如:<tx:annotation-driven>
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) 
   // <1>如果根节点使用默认命名空间,执行默认解析
   if (delegate.isDefaultNamespace(root)) 
      // 遍历子节点,也就是XML文件中的内容
      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 
      //<2> 根节点非默认命名空间,执行自定义解析
      delegate.parseCustomElement(root);
   

-> parseDefaultElement() 解析默认命名空间

public static final String BEAN_ELEMENT = "bean";
public static final String IMPORT_ELEMENT = "import";
public static final String ALIAS_ATTRIBUTE = "alias";
public static final String NESTED_BEANS_ELEMENT = "beans";


private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) 
   // 解析import标签
   if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) 
      importBeanDefinitionResource(ele);
   
   // 解析alias
   else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) 
      processAliasRegistration(ele);
   
   // 解析bean
   else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) 
      processBeanDefinition(ele, delegate);
   
   // 解析嵌套的bean
   else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) 
      // recurse
      doRegisterBeanDefinitions(ele);
   

-> parseCustomElement() 自定义解析

5.importBeanDefinitionResource() 解析import标签

6.processBeanDefinition() 解析bean标签

解析工作分为三步:

  1. 解析默认标签。
  2. 解析默认标签后下得自定义标签。
  3. 注册解析后的 BeanDefinition 。
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) 
   // <1> 如果解析成功,则返回 BeanDefinitionHolder 对象。而 BeanDefinitionHolder 为 name 和 alias 的 BeanDefinition 对象
   // 如果解析失败,则返回 null 。
   BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
   if (bdHolder != null) 
      // <2> 解析默认标签后下得自定义标签
      bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
      try 
         // <3> 进行 BeanDefinition 的注册
         // 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));
   

-> 第1步:解析beanName

  1. 解析 idname 属性,确定 aliases 集合
  2. 检测 beanName 是否唯一
  3. 对属性进行解析并封装成 AbstractBeanDefinition 实例 beanDefinition
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) 
   return parseBeanDefinitionElement(ele, null);


@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) 
   // 获取Id 和 name
   String id = ele.getAttribute(ID_ATTRIBUTE);
   String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

   // 解析name
   List<String> aliases = new ArrayList<>();
   if (StringUtils.hasLength(nameAttr)) 
      // name属性可以存在多个,可以以不同的分隔符间隔,这里就是用于拆分的工具类
      String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS

2、FactoryBean:是一个特殊的bean,具有工厂生成对象的能力,只能生成特定的对象。bean必须使用 FactoryBean接口,此接口提供方法 getObject() 用于获得特定bean。

<bean   id="" class="FB"> 先创建FB实例,使用调用getObject()方法,并返回方法的返回值

FB fb = new FB();

return fb.getObject();

BeanFactory 和 FactoryBean 对比?

BeanFactory:工厂,用于生成任意bean。

FactoryBean:特殊bean,用于生成另一个特定的bean。例如:ProxyFactoryBean ,此工厂bean用于生产代理。

<bean  id=""   class="....ProxyFactoryBean"> 获得代理对象实例,AOP使用。

二、作用域
作用域:用于确定Spring创建Bean的实例个数。

 技术分享图片

 

取值:

singleton 单例,是默认值。

prototype 多例,每执行一次getBean将获得一个实例。例如:struts整合spring,配置action多例。

配置信息:

<bean   id=""   class=""    scope="">

例如:

<bean id="userServiceId"   class="com.zju.scope.UserServiceImpl"    scope="prototype"></bean>

例1:singleton 单例

UserService.java

package com.zk.myspring;

public interface UserService {
	public void addUser();
}

UserServiceImpl.java

package com.zk.myspring;

public class UserServiceImpl implements UserService{

	@Override
	public void addUser() {
		// TODO Auto-generated method stub
		System.out.println("UserService");
	}

}

ApplicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

<!-- 默认scope为singleton -->
<bean id="userserviceId"  class="com.zk.myspring.UserServiceImpl" scope="singleton"></bean>
</beans>

TestDemo.java

package com.zk.myspring;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class TestDemo {
	
	@Test
	public void demo1(){
		String xmlpath="ApplicationContext.xml";
		ApplicationContext ac=new ClassPathXmlApplicationContext(xmlpath);
		UserService us=ac.getBean("userserviceId", UserServiceImpl.class);
		UserService us2=ac.getBean("userserviceId", UserServiceImpl.class);
		//us.addUser();
		//us2.addUser();
		System.out.println(us);
		System.out.println(us2);
		System.out.println(us==us2);
	}
}

运行效果图:

技术分享图片

 

例2:prototype 多例

UserService.java

package com.zk.myspring;

public interface UserService {
	public void addUser();
}

UserServiceImpl.java

package com.zk.myspring;

public class UserServiceImpl implements UserService{

	@Override
	public void addUser() {
		// TODO Auto-generated method stub
		System.out.println("UserService");
	}

}

TestDemo.java

package com.zk.myspring;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class TestDemo {
	
	@Test
	public void demo1(){
		String xmlpath="ApplicationContext.xml";
		ApplicationContext ac=new ClassPathXmlApplicationContext(xmlpath);
		UserService us=ac.getBean("userserviceId", UserServiceImpl.class);
		UserService us2=ac.getBean("userserviceId", UserServiceImpl.class);
		//us.addUser();
		//us2.addUser();
		System.out.println(us);
		System.out.println(us2);
		System.out.println(us==us2);
	}
}

  

 

ApplicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<bean id="userserviceId"  class="com.zk.myspring.UserServiceImpl" scope="prototype"></bean>
</beans>

运行效果图:

技术分享图片

 



以上是关于解析XML获取Bean的主要内容,如果未能解决你的问题,请参考以下文章

java解析xml文件,会把节点属性中的换行转换成空格,怎样才能避免此类转换,即保留换行

在java中解析xml有哪几种方法

java解析xml获取对应值

java解析xml的几种方式哪种最好?

java解析xml的几种方式哪种最好?

java如何读取xml节点元素值?