## 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文件夾中。

## 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. 其他還有針對不同標簽設置的邏輯。