💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
## 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. 其他还有针对不同标签设置的逻辑。