在Spring Bean注册解析(一)和Spring Bean注册解析(二)中咱们讲到,Spring在解析xml文件中的标签的时候会区分当前的标签是四种基本标签(import、alias、bean和beans)仍是自定义标签,若是是自定义标签,则会按照自定义标签的逻辑解析当前的标签。另外,即便是bean标签,其也可使用自定义的属性或者使用自定义的子标签。本文将对自定义标签和自定义属性的使用方式进行讲解,而且会从源码的角度对自定义标签和自定义属性的实现方式进行讲解。java
对于自定义标签,其主要包含两个部分:命名空间和转换逻辑的定义,而对于自定义标签的使用,咱们只须要按照自定义的命名空间规则,在Spring的xml文件中定义相关的bean便可。假设咱们有一个类Apple,而且咱们须要在xml文件使用自定义标签声明该Apple对象,以下是Apple的定义:node
public class Apple { private int price; private String origin; public int getPrice() { return price; } public void setPrice(int price) { this.price = price; } public String getOrigin() { return origin; } public void setOrigin(String origin) { this.origin = origin; } }
以下是咱们使用自定义标签在Spring的xml文件中为其声明对象的配置: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:myapple="http://www.lexueba.com/schema/apple" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.lexueba.com/schema/apple http://www.lexueba.com/schema/apple.xsd"> <myapple:apple id="apple" price="123" origin="Asia"/> </beans>
咱们这里使用了myapple:apple标签声明名为apple的bean,这里myapple就对应了上面的xmlns:myapple,其后指定了一个连接:http://www.lexueba.com/schema/apple,Spring在解析到该连接的时候,会到META-INF文件夹下找Spring.handlers和Spring.schemas文件(这里META-INF文件夹放在maven工程的resources目录下便可),而后读取这两个文件的内容,以下是其定义:缓存
Spring.handlers http\://www.lexueba.com/schema/apple=chapter4.eg3.MyNameSpaceHandler
Spring.schemas http\://www.lexueba.com/schema/apple.xsd=META-INF/custom-apple.xsd
能够看到,Spring.handlers指定了当前命名空间的处理逻辑类,而Spring.schemas则指定了一个xsd文件,该文件中则声明了myapple:apple各个属性的定义。咱们首先看下自定义标签各属性的定义:app
<?xml version="1.0" encoding="UTF-8"?> <xsd:schema xmlns="http://www.lexueba.com/schema/apple" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.lexueba.com/schema/apple" elementFormDefault="qualified"> <xsd:complexType name="apple"> <xsd:attribute name="id" type="xsd:string"> <xsd:annotation> <xsd:documentation> <![CDATA[ The unique identifier for a bean. ]]> </xsd:documentation> </xsd:annotation> </xsd:attribute> <xsd:attribute name="price" type="xsd:int"> <xsd:annotation> <xsd:documentation> <![CDATA[ The price for a bean. ]]> </xsd:documentation> </xsd:annotation> </xsd:attribute> <xsd:attribute name="origin" type="xsd:string"> <xsd:annotation> <xsd:documentation> <![CDATA[ The origin of the bean. ]]> </xsd:documentation> </xsd:annotation> </xsd:attribute> </xsd:complexType> <xsd:element name="apple" type="apple"> <xsd:annotation> <xsd:documentation><![CDATA[ The service config ]]></xsd:documentation> </xsd:annotation> </xsd:element> </xsd:schema>
能够看到,该xsd文件中声明了三个属性:id、price和origin。须要注意的是,这三个属性与咱们的Apple对象的属性price和origin没有直接的关系,这里只是一个xsd文件的声明,以表征Spring的applicationContext.xml文件中使用当前命名空间时可使用的标签属性。接下来咱们看一下Spring.handlers中定义的MyNameSpaceHandler声明:maven
public class MyNameSpaceHandler extends NamespaceHandlerSupport { @Override public void init() { registerBeanDefinitionParser("apple", new AppleBeanDefinitionParser()); } }
MyNameSpaceHandler只是注册了apple的标签的处理逻辑,真正的转换逻辑在AppleBeanDefinitionParser中。这里注册的apple必须与Spring的applicationContext.xml文件中myapple:apple标签后的apple保持一致,不然将找不到相应的处理逻辑。以下是AppleBeanDefinitionParser的处理逻辑:ide
public class AppleBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { @Override protected Class<?> getBeanClass(Element element) { return Apple.class; } @Override protected void doParse(Element element, BeanDefinitionBuilder builder) { String price = element.getAttribute("price"); String origin = element.getAttribute("origin"); if (StringUtils.hasText(price)) { builder.addPropertyValue("price", Integer.parseInt(price)); } if (StringUtils.hasText(origin)) { builder.addPropertyValue("origin", origin); } } }
能够看到,该处理逻辑中主要是获取当前标签中定义的price和origin属性的值,而后将其按照必定的处理逻辑注册到当前的BeanDefinition中。这里还实现了一个getBeanClass()方法,该方法用于代表当前自定义标签对应的BeanDefinition所表明的类的类型。以下是咱们的入口程序,用于检查当前的自定义标签是否正常工做的:ui
public class CustomSchemaApp { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); Apple apple = applicationContext.getBean(Apple.class); System.out.println(apple.getPrice() + ", " + apple.getOrigin()); } }
运行结果以下:this
123, Asia
咱们仍是从对整个applicationContext.xml文件开始读取的入口方法开始进行讲解,即DefaultBeanDefinitionDocumentReader.parseBeanDefinitions()方法,以下是该方法的源码:url
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { // 判断根节点使用的标签所对应的命名空间是否为Spring提供的默认命名空间, // 这里根节点为beans节点,该节点的命名空间经过其xmlns属性进行了定义 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)) { // 当前标签使用的是默认的命名空间,如bean标签, // 则按照默认命名空间的逻辑对其进行处理 parseDefaultElement(ele, delegate); } else { // 判断当前标签使用的命名空间是自定义的命名空间,如这里myapple:apple所 // 使用的就是自定义的命名空间,那么就按照定义命名空间逻辑进行处理 delegate.parseCustomElement(ele); } } } } else { // 若是根节点使用的命名空间不是默认的命名空间,则按照自定义的命名空间进行处理 delegate.parseCustomElement(root); } }
能够看到,该方法首先会判断当前文件指定的xmlns命名空间是否为默认命名空间,若是是,则按照默认命名空间进行处理,若是不是则直接按照自定义命名空间进行处理。这里须要注意的是,即便在默认的命名空间中,当前标签也可使用自定义的命名空间,咱们定义的myapple:apple就是这种类型,这里myapple就关联了xmlns:myapple后的myapple。以下是自定义命名空间的处理逻辑:
@Nullable public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) { // 获取当前标签对应的命名空间指定的url String namespaceUri = getNamespaceURI(ele); if (namespaceUri == null) { return null; } // 获取当前url所对应的NameSpaceHandler处理逻辑,也即咱们定义的MyNameSpaceHandler NamespaceHandler handler = this.readerContext .getNamespaceHandlerResolver() .resolve(namespaceUri); if (handler == null) { error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele); return null; } // 调用当前命名空间处理逻辑的parse()方法,以对当前标签进行转换 return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); }
这里getNamespaceURI()方法的做用是获取当前标签对应的命名空间url。在获取url以后,会调用NamespaceHandlerResolver.resolve(String)方法,该方法会经过当前命名空间的url获取META-INF/Spring.handlers文件内容,而且查找当前命名空间url对应的处理逻辑类。以下是该方法的声明:
@Nullable public NamespaceHandler resolve(String namespaceUri) { // 获取handlerMapping对象,其键为当前的命名空间url, // 值为当前命名空间的处理逻辑类对象,或者为处理逻辑类的包含全路径的类名 Map<String, Object> handlerMappings = getHandlerMappings(); // 查看是否存在当前url的处理类逻辑,没有则返回null Object handlerOrClassName = handlerMappings.get(namespaceUri); if (handlerOrClassName == null) { return null; } else if (handlerOrClassName instanceof NamespaceHandler) { // 若是存在当前url对应的处理类对象,则直接返回该处理对象 return (NamespaceHandler) handlerOrClassName; } else { // 若是当前url对应的处理逻辑仍是一个没初始化的全路径类名,则经过反射对其进行初始化 String className = (String) handlerOrClassName; try { Class<?> handlerClass = ClassUtils.forName(className, this.classLoader); // 判断该全路径类是否为NamespaceHandler接口的实现类 if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) { throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri + "] does not implement the [" + NamespaceHandler.class.getName() + "] interface"); } NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass); namespaceHandler.init(); // 调用处理逻辑的初始化方法 handlerMappings.put(namespaceUri, namespaceHandler); //缓存处理逻辑类对象 return namespaceHandler; } catch (ClassNotFoundException ex) { throw new FatalBeanException("Could not find NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]", ex); } catch (LinkageError err) { throw new FatalBeanException("Unresolvable class definition for" + "NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]", err); } } }
能够看到,在处理命名空间url的时候,首先会判断是否存在当前url的处理逻辑,不存在则直接返回。若是存在,则会判断其为一个NamespaceHandler对象,仍是一个全路径的类名,是NamespaceHandler对象则强制类型转换后返回,不然经过反射初始化该类,并调用其初始化方法,而后才返回。
咱们继续查看NamespaceHandler.parse()方法,以下是该方法的源码:
@Override @Nullable public BeanDefinition parse(Element element, ParserContext parserContext) { // 获取当前标签使用的parser处理类 BeanDefinitionParser parser = findParserForElement(element, parserContext); // 按照定义的parser处理类对当前标签进行处理,这里的处理类即咱们定义的AppleBeanDefinitionParser return (parser != null ? parser.parse(element, parserContext) : null); }
这里的parse()方法首先会查找当前标签订义的处理逻辑对象,找到后则调用其parse()方法对其进行处理。这里的parser也即咱们定义的AppleBeanDefinitionParser.parse()方法。这里须要注意的是,咱们在前面讲过,在MyNameSpaceHandler.init()方法中注册的处理类逻辑的键(即apple)必须与xml文件中myapple:apple后的apple一致,这就是这里findParserForElement()方法查找BeanDefinitionParser处理逻辑的依据。以下是findParserForElement()方法的源码:
@Nullable private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) { // 获取当前标签命名空间后的局部键名,即apple String localName = parserContext.getDelegate().getLocalName(element); // 经过使用的命名空间键获取对应的BeanDefinitionParser处理逻辑 BeanDefinitionParser parser = this.parsers.get(localName); if (parser == null) { parserContext.getReaderContext().fatal( "Cannot locate BeanDefinitionParser for element [" + localName + "]", element); } return parser; }
这里首先获取当前标签的命名空间后的键名,即myapple:apple后的apple,而后在parsers中获取该键对应的BeanDefinitionParser对象。其实在MyNameSpaceHandler.init()方法中进行的注册工做就是将其注册到了parsers对象中。
自定义属性的定义方式和自定义标签很是类似,其主要也是进行命名空间和转换逻辑的定义。假设咱们有一个Car对象,咱们须要使用自定义标签为其添加一个描述属性。以下是Car对象的定义:
public class Car { private long id; private String name; private String desc; public long getId() { return id; } public void setId(long id) { this.id = id; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
以下是在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:car="http://www.lexueba.com/schema/car-desc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="car" class="chapter4.eg2.Car" car:car-desc="This is test custom attribute"> <property name="id" value="1"/> <property name="name" value="baoma"/> </bean> </beans>
能够看到,car对象的定义使用的就是通常的bean定义,只不过其多了一个属性car:car-desc的使用。这里的car:car-desc对应的命名空间就是上面的http://www.lexueba.com/schema/car-desc。同自定义标签同样,自定义属性也须要在META-INF下的Spring.handlers和Spring.schemas文件中指定当前的处理逻辑和xsd定义,以下是这两个文件的定义:
Spring.handlers http\://www.lexueba.com/schema/car-desc=chapter4.eg2.MyCustomAttributeHandler
Spring.schemas http\://www.lexueba.com/schema/car.xsd=META-INF/custom-attribute.xsd
对应的xsd文件的定义以下:
<?xml version="1.0" encoding="UTF-8"?> <xsd:schema xmlns="http://www.lexueba.com/schema/car-desc" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.lexueba.com/schema/car-desc" elementFormDefault="qualified"> <xsd:attribute name="car-desc" type="xsd:string"/> </xsd:schema>
能够看到,该xsd文件中只定义了一个属性,即car-desc。以下是MyCustomAttributeHandler的声明:
public class MyCustomAttributeHandler extends NamespaceHandlerSupport { @Override public void init() { registerBeanDefinitionDecoratorForAttribute("car-desc", new CarDescInitializingBeanDefinitionDecorator()); } }
须要注意的是,和自定义标签不一样的是,自定义标签是将处理逻辑注册到parsers对象中,这里自定义属性是将处理逻辑注册到attributeDecorators中。以下CarDescInitializingBeanDefinitionDecorator的逻辑:
public class CarDescInitializingBeanDefinitionDecorator implements BeanDefinitionDecorator { @Override public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) { String desc = ((Attr) node).getValue(); definition.getBeanDefinition().getPropertyValues().addPropertyValue("desc", desc); return definition; } }
能够看到,对于car-desc的处理逻辑就是获取当前定义的属性的值,因为知道其是当前标签的一个属性,于是能够将其强转为一个Attr类型的对象,并获取其值,而后将其添加到指定的BeandDefinitionHolder中。这里须要注意的是,自定义标签继承的是AbstractSingleBeanDefinitionParser类,其实是实现的BeanDefinitionParser接口,而自定义属性实现的则是BeanDefinitionDecorator接口。
关于自定义属性的实现方式,须要注意的是,自定义属性只能在bean标签中使用,于是咱们能够直接进入对bean标签的处理逻辑中,即DefaultBeanDefinitionDocumentReader.processBeanDefinition()方法,以下是该方法的声明:
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { // 对bean标签的默认属性和子标签进行处理,将其封装为一个BeanDefinition对象, // 并放入BeanDefinitionHolder中 BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { // 进行自定义属性或自定义子标签的装饰 bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // 注册当前的BeanDefinition BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); }catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex); } // 调用注册了bean标签解析完成的事件处理逻辑 getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } }
这里咱们直接进入BeanDefinitionParserDelegate.decorateBeanDefinitionIfRequired()方法中:
public BeanDefinitionHolder decorateBeanDefinitionIfRequired( Element ele, BeanDefinitionHolder definitionHolder, @Nullable BeanDefinition containingBd) { BeanDefinitionHolder finalDefinition = definitionHolder; // 处理自定义属性 NamedNodeMap attributes = ele.getAttributes(); for (int i = 0; i < attributes.getLength(); i++) { Node node = attributes.item(i); finalDefinition = decorateIfRequired(node, finalDefinition, containingBd); } // 处理自定义子标签 NodeList children = ele.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Node node = children.item(i); if (node.getNodeType() == Node.ELEMENT_NODE) { finalDefinition = decorateIfRequired(node, finalDefinition, containingBd); } } return finalDefinition; }
能够看到,自定义属性和自定义子标签的解析都是经过decorateIfRequired()方法进行的,以下是该方法的定义:
public BeanDefinitionHolder decorateIfRequired( Node node, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) { // 获取当前自定义属性或子标签的命名空间url String namespaceUri = getNamespaceURI(node); // 判断其若是为spring默认的命名空间则不对其进行处理 if (namespaceUri != null && !isDefaultNamespace(namespaceUri)) { // 获取当前命名空间对应的NamespaceHandler对象 NamespaceHandler handler = this.readerContext .getNamespaceHandlerResolver() .resolve(namespaceUri); if (handler != null) { // 对当前的BeanDefinitionHolder进行装饰 BeanDefinitionHolder decorated = handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd)); if (decorated != null) { return decorated; } } else if (namespaceUri.startsWith("http://www.springframework.org/")) { error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", node); } else { // A custom namespace, not to be handled by Spring - maybe "xml:...". if (logger.isDebugEnabled()) { logger.debug("No Spring NamespaceHandler found for XML schema namespace [" + namespaceUri + "]"); } } } return originalDef; }
decorateIfRequired()方法首先会获取当前自定义属性或子标签对应的命名空间url,而后根据该url获取当前命名空间对应的NamespaceHandler处理逻辑,而且调用其decorate()方法进行装饰,以下是该方法的实现:
@Nullable public BeanDefinitionHolder decorate( Node node, BeanDefinitionHolder definition, ParserContext parserContext) { // 获取当前自定义属性或子标签注册的BeanDefinitionDecorator对象 BeanDefinitionDecorator decorator = findDecoratorForNode(node, parserContext); // 调用自定义的BeanDefinitionDecorator.decorate()方法进行装饰, // 这里就是咱们实现的CarDescInitializingBeanDefinitionDecorator类 return (decorator != null ? decorator.decorate(node, definition, parserContext) : null); }
和自定义标签不一样的是,自定义属性或自定义子标签查找当前Decorator的方法是须要对属性或子标签进行分别判断的,以下是findDecoratorForNode()的实现:
@Nullable private BeanDefinitionDecorator findDecoratorForNode(Node node, ParserContext parserContext) { BeanDefinitionDecorator decorator = null; // 获取当前标签或属性的局部键名 String localName = parserContext.getDelegate().getLocalName(node); // 判断当前节点是属性仍是子标签,根据状况不一样获取不一样的Decorator处理逻辑 if (node instanceof Element) { decorator = this.decorators.get(localName); } else if (node instanceof Attr) { decorator = this.attributeDecorators.get(localName); } else { parserContext.getReaderContext().fatal( "Cannot decorate based on Nodes of type [" + node.getClass().getName() + "]", node); } if (decorator == null) { parserContext.getReaderContext().fatal( "Cannot locate BeanDefinitionDecorator for " + (node instanceof Element ? "element" : "attribute") + " [" + localName + "]", node); } return decorator; }
对于BeanDefinitionDecorator处理逻辑的查找,能够看到,其会根据节点的类型进行判断,根据不一样的状况获取不一样的BeanDefinitionDecorator处理对象。
对于自定义子标签的使用,其与自定义标签的使用很是类似,不过须要注意的是,根据对自定义属性的源码解析,咱们知道自定义子标签并非自定义标签,自定义子标签只是起到对其父标签所定义的bean的一种装饰做用,于是自定义子标签的处理逻辑定义与自定义标签主要有两点不一样:①在NamespaceHandler.init()方法中注册自定义子标签的处理逻辑时须要使用registerBeanDefinitionDecorator(String, BeanDefinitionDecorator)方法;②自定义子标签的处理逻辑须要实现的是BeanDefinitionDecorator接口。其他部分的使用都和自定义标签一致。
本文主要对自定义标签,自定义属性和自定义子标签的使用方式和源码实现进行了讲解,有了对自定义标签的理解,咱们能够在Spring的xml文件中根据本身的须要实现本身的处理逻辑。另外须要说明的是,Spring源码中也大量使用了自定义标签,好比spring的AOP的定义,其标签为<aspectj-autoproxy />。从另外一个角度来看,咱们前面两篇文章对Spring的xml文件的解析进行了讲解,能够知道,Spring默认只会处理import、alias、bean和beans四种标签,对于其他的标签,如咱们所熟知的事务处理标签,这些都是使用自定义标签实现的。