duboo源码分析前篇(Spring 容器里面自定义标签应用与实现源码分析)

最近一直在看dubbo的源码,总寻思要总结一下源码的一个感受,这篇文章本篇我们讲解spring自定义标签的使用及原理,这应该是dubbo与spring整合源码分析的前置基础知识,只有掌握了如何在spring容器里面自定义标签的使用,以及spring是如何解析自定义标签的底层实现,才能开始阅读dubbo的源码,文章分为三个小节来讲解。

  1. 为什么要自定义标签
  2. 自定义标签使用
  3. spring是如何解析自定义标签

1.为什么自定义标签

自定义标签是spring为了给开发人员扩展组件使用的,因为它提供了一个标准的公共可插拔的接口;目前我们都知道spring非常强大,不过实际上除了spring-core和spring-beans外,其他都是通过自定义标签扩展实现的,很多一些开源组件也是,如dubbo等。所以,对于想扩展spring组件的小伙伴来说,了解如何自定义标签和相应的原理是必经之路。

2.自定义标签使用

1设计配置属性和JavaBean

2.编写XSD文件

3.编写BeanDefinitionParser标签解析类

4.编写调用标签解析类的NamespaceHandler类

5.编写spring.handlers和spring.schemas提供给Spring容器读取

6.引入到spring的配置文件

基本自定义标签需要实现两个关键接口:NamespaceHandlerSupport,BeanDefinitionParser,下面结合一个小例子来说明自定义标签的全过程

(1) 设计配置属性和JavaBean

public class User implements Serializable {

    private String userName;

    private String email;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

(2) 编写XSD文件,用来校验我们自定义标签的相关信息

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
            targetNamespace="http://www.test.org/schema/user" xmlns:tns="http://www.test.org/schema/user"
            elementFormDefault="qualified">
    <element name="user">
        <complexType>
            <attribute name="id" type="string"/>
            <attribute name="userName" type="string"/>
            <attribute name="email" type="string"/>
        </complexType>
    </element>
</schema>

(3) 编写BeanDefinitionParser标签解析类
public class UserDefinitionParser extends AbstractSingleBeanDefinitionParser {

    @Override
    protected void doParse(Element element, BeanDefinitionBuilder builder) {
        String userName = element.getAttribute("userName");
        String email = element.getAttribute("email");
        if(StringUtils.hasText(userName)){
            builder.addPropertyValue("userName",userName);
        }
        if(StringUtils.hasText(email)){
            builder.addPropertyValue("email",email);
        }
    }

    @Override
    protected Class<?> getBeanClass(Element element) {
        return User.class;
    }
}
(4) 编写调用标签解析类的NamespaceHandler类
public class CustomNamespaceHandler extends NamespaceHandlerSupport {

    public void init() {
        registerBeanDefinitionParser("user",new UserDefinitionParser());
    }
}

代码中init方法中registerBeanDefinitionParser("user",new UserDefinitionParser())就是用来把节点名和解析类串联起来的,在配置中引用了User配置项时,就会触发UserDefinitionParser来解析配置

(5)编写spring.handlers和spring.schemas提供给Spring容器去读取

       spring.handlers:

       http\://www.test.org/schema/user=com.xp.handler.CustomNamespaceHandler

      spring.schemas:

      http\://www.test.org/schema/user.xsd=META-INF/user.xsd

(6)在Spring里面应用。如下代码

<?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:custom="http://www.test.org/schema/user"
       xsi:schemaLocation="
      http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
      http://www.test.org/schema/user http://www.test.org/schema/user.xsd"
       default-lazy-init="false">

    <custom:user id="user" userName="zhangshan" email="654119101@qq.com" />
</beans>

最后就是测试使用,先看看代码整个图

 

这里运行main方法就会得到user这个bean对象 com.xp.entity.User@180bc464,到这里整个Spring里面自定义标签的流程就已经完毕了,接下来,我们来看看Spring是在何时来触发对这些标签的解析呢,首先从

DefaultListableBeanFactory factory = new XmlBeanFactory(resource)这行代码来看源码,从这个XmlBeanFactory的构造方法进入
/**
 * Create a new XmlBeanFactory with the given resource,
 * which must be parsable using DOM.
 * @param resource XML resource to load bean definitions from
 * @throws BeansException in case of loading or parsing errors
 */
public XmlBeanFactory(Resource resource) throws BeansException {
   this(resource, null);
}

/**
 * Create a new XmlBeanFactory with the given input stream,
 * which must be parsable using DOM.
 * @param resource XML resource to load bean definitions from
 * @param parentBeanFactory parent bean factory
 * @throws BeansException in case of loading or parsing errors
 */
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
   super(parentBeanFactory);
   this.reader.loadBeanDefinitions(resource);
}在这里有一个this.reader.loadBeanDefinitions(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<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();
      }
   }
}

这段代码大概的流程就是将传入的Resource文件,也就是appliction-context文件转为文件流,然后去解析里面的内容,我们重点看这个doLoadBeanDefinitions(inputSource, encodedResource.getResource())方法

/**
 * Actually load bean definitions from the specified XML file.
 * @param inputSource the SAX InputSource to read from
 * @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
 * @see #doLoadDocument
 * @see #registerBeanDefinitions
 */
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);
   }
}

这里的代码大家应该比较熟悉了,就是完成对xml文件的解析,将里面的内容解析成Document对象,然后调用registerBeanDefinitions(doc, resource)开始注册BeanDefinition

/**
 * Register the bean definitions contained in the given DOM document.
 * Called by {@code loadBeanDefinitions}.
 * <p>Creates a new instance of the parser class and invokes
 * {@code registerBeanDefinitions} on it.
 * @param doc the DOM document
 * @param resource the resource descriptor (for context information)
 * @return the number of bean definitions found
 * @throws BeanDefinitionStoreException in case of parsing errors
 * @see #loadBeanDefinitions
 * @see #setDocumentReaderClass
 * @see BeanDefinitionDocumentReader#registerBeanDefinitions
 */
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;
}

先是获取注册的BeanDefinitiond的个数,然后调用documentReader.registerBeanDefinitions(doc, createReaderContext(resource))这个方法,继续进入这个方法

/**
 * This implementation parses bean definitions according to the "spring-beans" XSD
 * (or DTD, historically).
 * <p>Opens a DOM Document; then initializes the default settings
 * specified at the {@code <beans/>} level; then parses the contained bean definitions.
 */
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
   this.readerContext = readerContext;
   logger.debug("Loading bean definitions");
   Element root = doc.getDocumentElement();
   doRegisterBeanDefinitions(root);
}

这里有一个doRegisterBeanDefinitions(root)方法,点进去

/**
 * Register each bean definition within the given root {@code <beans/>} element.
 */
protected void doRegisterBeanDefinitions(Element root) {
   // Any nested <beans> elements will cause recursion in this method. In
   // order to propagate and preserve <beans> default-* attributes correctly,
   // keep track of the current (parent) delegate, which may be null. Create
   // the new (child) delegate with a reference to the parent for fallback purposes,
   // then ultimately reset this.delegate back to its original (parent) reference.
   // this behavior emulates a stack of delegates without actually necessitating one.
   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)) {
            return;
         }
      }
   }

   preProcessXml(root);
   parseBeanDefinitions(root, this.delegate);
   postProcessXml(root);

   this.delegate = parent;
}

看这个方法上面的注释,也大概知道这个方法是从传过来解析完成Element对象获取信息注册BeanDefinition,里面的这个parseBeanDefinitions(root,this.delegate)方法就是核心的方法,继续跟踪进去

/**
 * Parse the elements at the root level in the document:
 * "import", "alias", "bean".
 * @param root the DOM root element of the 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);
   }
}

在这个方法里面可以看到。如果是Spring默认的nameSpace的话,就会进入spring默认的解析逻辑,而我们是自己自定义的标签,就会走到else分支,点进这个delegate.parseCustomElement(root)方法里面

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
   String namespaceUri = getNamespaceURI(ele);
   NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
   if (handler == null) {
      error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
      return null;
   }
   return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

在NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);这行代码会通过传入的 namespaceUri这个参数也就是我们在配置文件配的http://www.test.org/schema/user去找对应的handler,也就是我们的

CustomNamespaceHandler这个类,然后调用hanlder.parse(ele, new ParserContext(this.readerContext, this, containingBd))方法,最终会调用到我们自己写UserDefinitionParser这个类parse方法,自此完成了对我们自定义标签的解析,最后随着spirng容器启动后,就会实例化这些Bean对象,这一篇内容暂且就讲到这里,后续会继续讲Dubbo的核心源码,服务导出,服务引入,以及Dubbo的SPI机制。

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值