项目中有一个HelloService的接口,接口中有一个sayHello方法,但是实现并没有在本项目中,而是在其他项目,在项目启动时先注入一个代理对象。接下来看如何扩展spring的schema。很多的操作和解释都写在了代码的注释里面,所以在外部就不做过多的解释了。
项目文件的目录结构如下
├─java
│ └─wtf
│ ├─main
│ │ Test.java (测试类)
│ │
│ ├─namespace
│ │ ├─handler
│ │ │ MyConsumer.java (代理consumer节点的Bean)
│ │ │ MyNamespaceHandler.java (myrpc.xsd schema的处理类)
│ │ │ MyRpcBeanDefinitionParse.java (处理xml节点的统一处理类)
│ │ │
│ │ └─proxy
│ │ RpcServiceBeanProxy.java (动态代理对象)
│ │
│ └─service
│ HelloService.java (代理的接口)
│
└─resources
│ applicationContext.xml (spring的入口文件)
│ myconsumer.xml (使用了mrpx.xsd schema 的xmlbean文件)
│
└─META-INF
myrpc.xsd
spring.handlers (spring 要求的扩展点名称格式,不允许修改)
spring.schemas (spring 要求的扩展点格式,不允许修改)
一、定义xsd文件
myrpc.xsd
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.myrpccompany.com/schema/myrpc"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
targetNamespace="http://www.myrpccompany.com/schema/myrpc"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:element name="consumer">
<xsd:complexType>
<xsd:attribute name="id" type="xsd:ID"/>
<xsd:attribute name="interface" type="xsd:string" use="required"/>
</xsd:complexType>
</xsd:element>
</xsd:schema>
定义一个xml的schema,空间为http://www.myrpccompany.com/schema/myrpc
定义一个名称为consumer的节点,consumer节点中有两个属性,一个是id,一个是interface,id属性讲作为实例化Bean的id,interface属性是指的要实现哪个接口。
二、根据xsd文件,编写xml文件
consumer.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:myrpc="http://www.myrpccompany.com/schema/myrpc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.myrpccompany.com/schema/myrpc http://www.myrpccompany.com/schema/myrpc/myrpc.xsd">
<myrpc:consumer id="helloService" interface="wtf.service.HelloService"/>
</beans>
引用第一步中定义的schema,然后命名bean的id为helloService,实现的接口是wtf.service.HelloService这个接口
xml文件都比较熟悉,没什么好讲的。
三、接入spring进行扩展
在spring中,如果想扩展其schema,需要在class根目录新建一个META-INF文件夹,文件夹中需要有两个文件
- spring.handlers 需要处理定义的xsd schema的类,需要继承NamespaceHandlerSupport类,这样当元素读取到定义的这种schema时,讲会交给在此文件中指定的类进行处理。
- spring.schemas 有哪些自定的schema
spring.handlers
http\://www.myrpccompany.com/schema/myrpc=wtf.namespace.handler.MyNamespaceHandler
spring.schemas
http\://www.myrpccompany.com/schema/myrpc/myrpc.xsd=META-INF/myrpc.xsd
四、处理Schema中接收到的元素
处理myrpc.xml 中myrpcscheam的类
package wtf.namespace.handler;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
/**
* Created by wangtengfei1 on 2017/8/2.
*/
public class MyNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
/**
*spring解析到consumer这个节点时,将会自动交给MyRpcBeanDefinitionParse这个解析器进行处理,
* MyRpcBeanDefinitionParse需要继承BeanDefinitionParser,并且实现它的parse方法
* MyRpcBeanDefinitionParse构造函数接收了一个MyConsumer的class
* 这么定义是因为在myrpc.xsd中不一定只有一个consumer节点,也有有些其他的节点,
* 我们想把myrpc.xsd中定义的所有节点,均交给MyRpcBeanDefinitionParse进行处理,
* 只是对各个节点的具体实例化,需要传入相应的ClassBean进行接收,
* MyConsumer就是为了consumer节点在实例化之前存储的一些信息
*/
registerBeanDefinitionParser("consumer", new MyRpcBeanDefinitionParse(MyConsumer.class));
}
}
具体的处理方法
package wtf.namespace.handler;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;
/**
* Created by wangtengfei1 on 2017/8/3.
*/
public class MyRpcBeanDefinitionParse implements BeanDefinitionParser {
private final Class<?> beanClass;
MyRpcBeanDefinitionParse(Class<?> beanClass){
this.beanClass = beanClass;
}
public BeanDefinition parse(Element element, ParserContext parserContext) {
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition();
rootBeanDefinition.setBeanClass(beanClass);
String id = element.getAttribute("id");
parserContext.getRegistry().registerBeanDefinition(id,rootBeanDefinition);
//处理Consumer中的属性和xml中定义的attribute不一致的问题
if(beanClass.equals(MyConsumer.class)){
String interfaceId = element.getAttribute("interface");
rootBeanDefinition.getPropertyValues().addPropertyValue("interfaceId",interfaceId);
}
return rootBeanDefinition;
}
}
MyConsumer类的信息 ,相关信息在代码中都有注释,就不再做过多解释
package wtf.namespace.handler;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import wtf.namespace.proxy.RpcServiceBeanProxy;
/**
* Created by wangtengfei1 on 2017/8/3.
*/
public class MyConsumer implements InitializingBean,FactoryBean,
ApplicationContextAware,BeanNameAware,DisposableBean {
protected String id; //用来接收xml 文件的id属性 作为bean的id
protected String interfaceId; //用来接收xml中定义的interface属性,用来作为代理的类型
protected Class objectType; //代理类的类型,也就是xml中定义的接口类型
//在使用@Autowire或者@Resource等,需要注入时真正调用的方法
public Object getObject() throws Exception {
RpcServiceBeanProxy proxy = new RpcServiceBeanProxy(objectType);
return proxy.getProxyOject();
}
//获取代理对象的类型,spring会自动调用
public Class<?> getObjectType() {
try {
Class<?> aClass = Class.forName(interfaceId);
//判断interface节点中定义的是不是一个接口,如果不是接口就返回null
if(aClass.isInterface()){
objectType = aClass;
return aClass;
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
public boolean isSingleton() {
return false;
}
public void afterPropertiesSet() throws Exception {
}
public void setBeanName(String s) {
}
public void destroy() throws Exception {
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getInterfaceId() {
return interfaceId;
}
public void setInterfaceId(String interfaceId) {
this.interfaceId = interfaceId;
}
}
对于本类中使用的RpcServiceBeanProxy 类,是我在另外一篇博客中使用的 javassist 实现接口动态代理 用来返回一个代理对象。并实现sayHello方法,输出一句 “hi from dynamic proxy class”.
五、测试
package wtf.main;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.web.context.support.XmlWebApplicationContext;
import wtf.service.HelloService;
/**
* Created by wangtengfei1 on 2017/8/2.
*/
public class Test {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
HelloService helloService = (HelloService) applicationContext.getBean("helloService");
helloService.sayHello();
}
}
其中 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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<import resource="myconsumer.xml"/>
</beans>
运行test测试,输出的结果为
以上示例简单的演示了如何扩展spring的schema,然后借助于spring,让它帮忙加载我们需要的东西。以便对spring进行扩展。主要的设想是集成spring实现一个简单的rpc框架。