## 1.1 Spring自定义标签过程
Spring支持自定义标签,主要过程为:
1.设置标签的属性,并编写xsd文件
2.编写标签对应的JavaBean
3.编写NamespaceHandler和BeanDefinitionParser完成解析工作
4.编写spring.handlers和spring.schemas串联起所有部件
5.从Spring容器获取实例并测试
涉及的文件:
* xsd文件
* JavaBean.java 标签对应的Bean
* NamespaceHandler
* BeanDefinitionParser
* spring.handlers
* spring.schemas
**xsd**
xsd是xml schema definition的缩写,用来代替dtd文件,定义xml文件的规则。我们扩展Spring自定义标签时,使用xsd用于描述标签的规则。具体教程可见 [xsd教程](http://www.w3school.com.cn/schema/schema_intro.asp)。
在 XML 中,元素名称是由开发者定义的,当两个不同的文档使用相同的元素名时,就会发生命名冲突。spring的xml文件中会设置引用到的命名空间,xsd内部也会设置标签的命名空间,
**JavaBean**
这个类与标签相对应,根据标签的属性及其内容设置类中对应的属性。比如,dubbo的`<dubbo:service>`标签对应的是ServiceBean,内部保存了服务器的配置信息。
**NamespaceHandler**
Spring读取xml文件时,会根据标签的命名空间找到其对应的NamespaceHandler,我们在NamespaceHandler内会注册标签对应的解析器BeanDefinitionParser。
**BeanDefinitionParser**
BeanDefinitionParser是标签对应的解析器,Spring读取到对应标签时会使用该类进行解析;
**spring.handlers**
内部保存命名空间与NamespaceHandler类的对应关系;必须放在classpath下的META-INF文件夹中。
**spring.schemas**
内部保存命名空间对应的xsd文件位置;必须放在classpath下的META-INF文件夹中。
![自定义标签解析流程](http://www.uxiaowo.com/dubbo/EleParse.png)
## 1.2 Spring自定义标签实例
**目标**
我们要实现一个Proxy类,其实现了Info接口
```
public interface Info {
public String getInfo();
}
```
Proxy类内部保存了 private Info delegate;调用getInfo时实际上会调用delegate的getInfo。
```
private Info delegate;
public String getInfo() {
preDelgate();
String result = delegate.getInfo();
return postDelgate(result);
}
```
我们要定义一个proxy标签,实现这个功能,我们将命名空间设置为`http://study.shisj.com/schema/proxy`,前缀设置为`dubboStudy`,proxy标签有三个属性:
* name 代理的名称
* class 代理的接口
* ref,引用一个Info实例
**proxy.xsd**
有了标签的属性就可以定义xsd文件了,targetNamespace是标签proxy的命名空间
```
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema
xmlns="http://study.shisj.com/schema/proxy"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
targetNamespace="http://study.shisj.com/schema/proxy"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/beans" />
<xsd:element name="proxy">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="class" type="xsd:string" />
<xsd:attribute name="ref" type="xsd:string" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
</xsd:schema>
```
**Proxy.java**
```
public class Proxy implements Info{
private String id;
private String name;
private Info delegate;
private String ref;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getInfo() {
preDelgate();
String result = delegate.getInfo();
return postDelgate(result);
}
public Info getDelegate() {
return delegate;
}
public void setDelegate(Info delegate) {
this.delegate = delegate;
}
private void preDelgate() {
}
private String postDelgate(String result) {
return result;
}
public String getRef() {
return ref;
}
public void setRef(String ref) {
this.ref = ref;
}
}
```
**NamespaceHandler**
定义命名空间的处理器,内部设置了proxy标签的解析器
```
public class MyNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("proxy", new ProxyBeanDefinitionParser());
}
}
```
**ProxyBeanDefinitionParser**
Spring解析到proxy标签时会调用这个类进行解析生成实例
```
public class ProxyBeanDefinitionParser implements BeanDefinitionParser {
public BeanDefinition parse(Element element, ParserContext parserContext) {
RootBeanDefinition beanDefinition = new RootBeanDefinition();
String name = element.getAttribute("name");
beanDefinition.setBeanClass(Proxy.class);
beanDefinition.setLazyInit(false);
String id = element.getAttribute("id");
String ref = element.getAttribute("ref");
if (StringUtils.hasText(id)) {
beanDefinition.getPropertyValues().addPropertyValue("id", id);
}
if (StringUtils.hasText(name)) {
beanDefinition.getPropertyValues().addPropertyValue("name", name);
}
if (StringUtils.hasText(ref)) {
beanDefinition.getPropertyValues().addPropertyValue("delegate", new RuntimeBeanReference(ref));
}
parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
return beanDefinition;
}
}
```
**spring.handlers**
告诉Spring如何根据命名空间后如何查找`命名空间处理器`
```
http\://study.shisj.com/schema/proxy=com.shisj.study.dubbo.spring.MyNamespaceHandler
```
**spring.schemas**
通知Spring如何根据命名空间后如何查找对应的xsd文件
```
http\://study.shisj.com/schema/proxy.xsd=META-INF/proxy.xsd
```
**测试程序**
下面是Spring的配置文件,内部指定了前缀dubboStudy的命名空间为`http://study.shisj.com/schema/prox`,在`xsi:schemaLocation`中指定schema文件的位置
```
<?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:dubboStudy="http://study.shisj.com/schema/proxy"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://study.shisj.com/schema/proxy
http://study.shisj.com/schema/proxy.xsd">
<dubboStudy:proxy id="proxy" name="myhome" class="com.shisj.study.dubbo.spring.Info" ref="beijing"/>
<!--AddressInfo会输出地址信息-->
<bean id="beijing" class="com.shisj.study.dubbo.spring.AddressInfo">
<property name="country" value="China"/>
<property name="province" value="Beijing"/>
<property name="city" value="Beijing"/>
</bean>
</beans>
```
Java测试
```
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring-proxy.xml");
Proxy proxy = (Proxy) ctx.getBean("proxy");
System.out.println(proxy.getInfo()); // 调用AddressInfo的getInfo()
// [Addr] country:China,province:Beijing,city:Beijing
```
## dubbo标签相关文件
dubbo标签的前缀为dubbo,命名空间为http://code.alibabatech.com/schema/dubbo
```
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation=“
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd"
```
* XSD文件:dubbo.xsd
* com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
### DubboNamespaceHandler
根据dubbo的命名空间会使用这个类查找标签对应的解析器
```
registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
registerBeanDefinitionParser("annotation", new DubboBeanDefinitionParser(AnnotationBean.class, true));
```
### DubboBeanDefinitionParser
DubboBeanDefinitionParser用来处理所有标签的解析,主要流程是:
1. 创建RootBeanDefinition,内部保存了Bean的信息,便于后续初始化生成实例,主要有两个属性:BeanClass,指定的Class类型,解析标签时对应的是保存标签数据的类;LazyInit:是否延迟初始化,一般都会设置为false,即不在Spring启动过程中初始化实例。
2. 获取id,若require为true,必须要有id,从id > name > interface 获取,若有重复,后面自带加count
3. 向Spring的ParserContext中注册id和BeanDefinition的对应关系
4. 根据BeanClass通过反射查找set方法,从标签属性中获取对应的值并设置。
5. 其他还有针对不同标签设置的逻辑。