前言
学习spring源码已经有一年多了,了解过的朋友肯定都知道spring源码是一块非常难啃的骨头,所以每当找到一丝丝成就感就想拿出来与大家一起分享,这样也能让自己始终保持着对spring源码学习的兴趣。
虽然现在使用xml已经不是主流的方式了,但是一些公共的开源组件都基于自身的功能定制了自定义标签,比如dubbo。
自定义XML关键的几个配置
- spring.handler
定义解析xml元素的处理类。 - spring.schemas
指定xsd文件的位置。 - xxx.xsd
类似于语法规范,约束自定义标签的属性类型等。
具体实现
spring自身中其实也有很多自定义的标签,比如context、aop等,所以我们自己实现的方式很简单,照抄就可以了。
下面我们就按照context标签的实现方式来自己搞一个。
1、建一个META-INF目录,并创建spring.handler和spring.schemas两个文件。
2、完成spring.handler文件中的内容
ContextNamespaceHandler点进去,继承了NamespaceHandlerSupport ,并重写init方法。
public class ContextNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
}
}
照着实现即可,就定义一个标签user,创建了两个新的类,WylBeanDefinitionParser和User(user是指定标签生成的类)。
public class MyCustomNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("user", new WylBeanDefinitionParser());
}
}
public class WylBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
@Override
protected Class<?> getBeanClass(Element element) {
return User.class;
}
@Override
protected void doParse(Element element, BeanDefinitionBuilder builder) {
String name = element.getAttribute("name");
if (StringUtils.hasLength(name)) {
builder.addPropertyValue("name", name);
}
String age = element.getAttribute("age");
if (StringUtils.hasLength(age)) {
builder.addPropertyValue("age", age);
}
}
}
public class User {
private String name;
private String age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
3、处理spring.schemas文件
这个文件很简单定义一下你的xsd文件的位置即可
4、处理xsd文件
这个就按照语法规范来实现就可以了
定义了标签名user,与前面MyCustomNamespaceHandler中的保持一致,并且定义了三个属性,id是标识。
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.springframework.org/schema/wyl"
elementFormDefault="qualified">
<xsd:element name="user">
<xsd:complexType>
<xsd:attribute name="id" type="xsd:string">
</xsd:attribute>
<xsd:attribute name="name" type="xsd:string">
</xsd:attribute>
<xsd:attribute name="age" type="xsd:string">
</xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:schema>
5、使用
上面4步完成后,自定义标签就已经完成了,接下来只要在你的application.xml文件中添加自己的命名空间和schema位置就可以使用了
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:wyl="http://www.springframework.org/schema/wyl"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/wyl http://www.springframework.org/schema/wyl.xsd ">
<wyl:user id="myselfTag" name="wangwu" age="18"></wyl:user>
</beans>
6、测试
public class TestSpring {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean1.xml");
User user = (User) applicationContext.getBean("wyl");
System.out.println("myselfTag: " + user.getName());
}
}
源码分析
既然是学习源码,只照抄方法肯定是不够的,其实这部分逻辑还是比较清晰的,并且从头到尾我们自己重写的方法也就3个,一起来简单分析下吧。
1、MyCustomNamespaceHandler中init方法
public class MyCustomNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("user", new WylBeanDefinitionParser());
}
}
这个init方法的调用链路比较长,入口肯定是从refresh方法中的obtainFreshBeanFactory()方法开始,最终会执行到如下的方法中,然后先从spring.handles文件中获取com.wyl.learn.config.MyCustomNamespaceHandler值(文件中等号右边定义的内容),再利用反射拿到Class对象,调用init方法即可。
@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
String namespaceUri = getNamespaceURI(ele);
if (namespaceUri == null) {
return null;
}
//init方法调用的入口
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
//init方法返回后,接着就处理parse方法调用的入口
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
public NamespaceHandler resolve(String namespaceUri) {
//获取所有的spring.handles文件中的内容,等号左边是key,等号右边的value
Map<String, Object> handlerMappings = getHandlerMappings();
//根据key找到对象的value,也就是全限定类名,使用反射就可以得到具体的对象
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
return null;
}
else if (handlerOrClassName instanceof NamespaceHandler) {
return (NamespaceHandler) handlerOrClassName;
}
else {
String className = (String) handlerOrClassName;
try {
//反射获得Class对象
Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
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);
//调用自己重写的init方法
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);
}
}
}
2、 WylBeanDefinitionParser中doParse方法
public class WylBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
@Override
protected Class<?> getBeanClass(Element element) {
return User.class;
}
@Override
protected void doParse(Element element, BeanDefinitionBuilder builder) {
String name = element.getAttribute("name");
if (StringUtils.hasLength(name)) {
builder.addPropertyValue("name", name);
}
String age = element.getAttribute("age");
if (StringUtils.hasLength(age)) {
builder.addPropertyValue("age", age);
}
}
}
这个方法主要就是通过addPropertyValue把属性添加到beanDefinition对象的propertyValueList集合属性中。
public BeanDefinitionBuilder addPropertyValue(String name, @Nullable Object value) {
this.beanDefinition.getPropertyValues().add(name, value);
return this;
}
add方法
public MutablePropertyValues add(String propertyName, @Nullable Object propertyValue) {
addPropertyValue(new PropertyValue(propertyName, propertyValue));
return this;
}
addPropertyValue方法
public MutablePropertyValues addPropertyValue(PropertyValue pv) {
for (int i = 0; i < this.propertyValueList.size(); i++) {
PropertyValue currentPv = this.propertyValueList.get(i);
if (currentPv.getName().equals(pv.getName())) {
pv = mergeIfRequired(pv, currentPv);
setPropertyValueAt(pv, i);
return this;
}
}
//最终添加到propertyValueList集合中
this.propertyValueList.add(pv);
return this;
}
propertyValueList是MutablePropertyValues类的属性,而MutablePropertyValues又是beanDefinition中的一个属性。
3、 最后还有一个getBeanClass方法
此方法返回的是你自定义标签的BeanClass对象,也就是User,容器在标签解析时就可以通过这个方法得到beanDefinition的class类型,最终通过doParse给属性赋完值以后就可以添加到容器中了,添加到容器中的对象之后就可以通过getBean方法获取了,属性可以从propertyValueList中获取。