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