本章内容:
1、Spring FactoryBean、InitializingBean的作用。
2、自定义xsd文件。用来自定义Spring xml文件的标签。
3、扩展BeanDefinitionParser建立自定义标签解析成为我们自定义bean的过程。
4、自定义bean的后置操作完成Netty服务开启(后面会介绍)、注册中心注册等动作。
一、Spring FactoryBean、InitializingBean的作用。
Spring的生命周期后面单独写Spring时介绍。现在只介绍 FactoryBean、InitializingBean这两个接口。
1、InitializingBean。 当我们把一个类实例化的过程交给IOC容器托管时,希望在该类实例化后做一些操作。就可以实现这个接口。
并在afterPropertiesSet()方法中写入逻辑。
2、FactoryBean。当我们自定义一个类,在这个类中得到另一个对象、类型。可以用这个接口。这样说有点抽象。比如一个具体场景:装饰者类(装饰者模式)。 我们希望在真正执行逻辑的类外面有个包装类,来做一些事情。就可以用这个接口,把装饰类(当前类本身)与被装饰的类(getObject()得到的对象)之间绑定。从而做一些逻辑。
在Rpc里利用这两个特性,后置动作中放入Netty服务的开启、注册中心的注册。装饰类来装饰对应的每个接口的实现类。
/**
*
* <p>Title: ProviderFactoryBean.java</p>
* <p>Description: </p>
* @author zhaojunjie
* @date 2020年3月28日
* @version 1.0
*/
public class ProviderFactoryBean implements InitializingBean,FactoryBean{
private Class<?> serviceInterface;
private Object serviceObject;
private String servicePort;
private long timeout;
private Object serviceProxyObject;
private String appKey;
private String groupName;
private int weight = 1;
private int workerThread = 10;
@Override
public Object getObject() throws Exception {
return serviceObject;
}
@Override
public Class getObjectType() {
return serviceInterface;
}
@Override
public void afterPropertiesSet() throws Exception {
//1、开启NettyServer
System.out.println(this.toString());
//2、注册到zookeeper注册中心
List<ProviderService> serviceMetaData = buildProviderServiceInfo();
RegisterCenter.singleton().registerProvider(serviceMetaData);
}
private List<ProviderService> buildProviderServiceInfo() {
List<ProviderService> providerList = Lists.newArrayList();
Method[] methods = serviceObject.getClass().getDeclaredMethods();
for (Method method : methods) {
ProviderService providerService = new ProviderService();
providerService.setServiceInterface(serviceInterface);
providerService.setServiceObject(serviceObject);
providerService.setServerIp(IPHelper.getCurrentIp());
providerService.setServicePort(Integer.parseInt(servicePort));
providerService.setTimeout(timeout);
providerService.setServiceMethod(method);
providerService.setWeight(weight);
providerService.setWorkerThread(workerThread);
providerService.setAppKey(appKey);
providerService.setGroupName(groupName);
providerList.add(providerService);
}
return providerList;
}
public Class<?> getServiceInterface() {
return serviceInterface;
}
public void setServiceInterface(Class<?> serviceInterface) {
this.serviceInterface = serviceInterface;
}
public Object getServiceObject() {
return serviceObject;
}
public void setServiceObject(Object serviceObject) {
this.serviceObject = serviceObject;
}
public String getServicePort() {
return servicePort;
}
public void setServicePort(String servicePort) {
this.servicePort = servicePort;
}
public long getTimeout() {
return timeout;
}
public void setTimeout(long timeout) {
this.timeout = timeout;
}
public Object getServiceProxyObject() {
return serviceProxyObject;
}
public void setServiceProxyObject(Object serviceProxyObject) {
this.serviceProxyObject = serviceProxyObject;
}
public String getAppKey() {
return appKey;
}
public void setAppKey(String appKey) {
this.appKey = appKey;
}
public String getGroupName() {
return groupName;
}
public void setGroupName(String groupName) {
this.groupName = groupName;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
public int getWorkerThread() {
return workerThread;
}
public void setWorkerThread(int workerThread) {
this.workerThread = workerThread;
}
@Override
public String toString() {
return "ProviderFactoryBean [serviceInterface=" + serviceInterface + ", serviceObject=" + serviceObject
+ ", servicePort=" + servicePort + ", timeout=" + timeout + ", serviceProxyObject=" + serviceProxyObject
+ ", appKey=" + appKey + ", groupName=" + groupName + ", weight=" + weight + ", workerThread="
+ workerThread + "]";
}
}
二、自定义xsd文件。用来自定义Spring xml文件的标签。
1、xsd文件的定义。文件放在resources/META-INF/ 目录下.比如 resources/META-INF/back-service.xsd
xmlns中定义地址:http://www.back.com/schema/back-service
定义Element的name 这个一定注意。解析时会根据这个name,找对应的解析类。再下面就是属性名称和类型的定义。相信一目了然,不再介绍。
<xsd:schema xmlns="http://www.back.com/schema/back-service"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
targetNamespace="http://www.back.com/schema/back-service"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/beans"/>
<xsd:element name="service">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:attribute name="interface" type="xsd:string" use="required"/>
<xsd:attribute name="timeout" type="xsd:int" use="required"/>
<xsd:attribute name="servicePort" type="xsd:int" use="required"/>
<xsd:attribute name="ref" type="xsd:string" use="required"/>
<xsd:attribute name="weight" type="xsd:int" use="optional"/>
<xsd:attribute name="workerThreads" type="xsd:int" use="optional"/>
<xsd:attribute name="appKey" type="xsd:string" use="required"/>
<xsd:attribute name="groupName" type="xsd:string" use="optional"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
</xsd:schema>
2、编写完成xsd后新建Spring的xml 我们来看下我们自定义标签的效果。xml上方要引入我们自定义的地址与xsd.同时把地址与本地xsd文件绑定。eclipse中的步骤是:window -> preferences -> XML -> XML CataLog ->Add Location中选定我们自定义的xsd路径。key那里把我们的xsd文件地址放入。例:http://www.back.com/schema/back-service.xsd
<?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:backService="http://www.back.com/schema/back-service"
xmlns:backReference="http://www.back.com/schema/back-reference"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.back.com/schema/back-service
http://www.back.com/schema/back-service.xsd
http://www.back.com/schema/back-reference
http://www.back.com/schema/back-reference.xsd" >
<bean id="serRemote" class="com.back.spring.test.RemoteServiceImpl" />
<backService:service id="aaa" interface="com.back.spring.test.RemoteService" timeout="10"
servicePort="9999" ref="serRemote" appKey="dodp" groupName="unps"/>
</beans>
三、扩展BeanDefinitionParser建立自定义标签解析成为我们自定义bean的过程。
1、 NamespaceHandlerSupport接口扩展,指定真正解析的类。根据xsd文件中的Element的name作为key,value就是我们第二个要扩展的真正解析的类
public class BackServiceNamespaceHandler extends NamespaceHandlerSupport{
@Override
public void init() {
registerBeanDefinitionParser("service", new ProviderFactoryBeanDefinitionParser());
}
}
2、AbstractSingleBeanDefinitionParser扩展。解析自定义标签的属性。然后将属性植入IOC创建的对象中。
public class ProviderFactoryBeanDefinitionParser extends AbstractSingleBeanDefinitionParser{
@Override
protected Class<?> getBeanClass(Element element) {
return ProviderFactoryBean.class;
}
@Override
protected void doParse(Element element, BeanDefinitionBuilder builder) {
try {
String timeout = element.getAttribute("timeout");
String serviceInterface = element.getAttribute("interface");
String servicePort = element.getAttribute("servicePort");
String ref = element.getAttribute("ref");
String weight = element.getAttribute("weight");
String workerThread = element.getAttribute("workerThread");
String appKey = element.getAttribute("appKey");
String groupName = element.getAttribute("groupName");
builder.addPropertyValue("timeout", Integer.parseInt(timeout));
builder.addPropertyValue("servicePort", Integer.parseInt(servicePort));
builder.addPropertyValue("serviceInterface", Class.forName(serviceInterface));
builder.addPropertyReference("serviceObject", ref);
builder.addPropertyValue("appKey", appKey);
if(NumberUtils.isNumber(weight)){
builder.addPropertyValue("weight", Integer.parseInt(weight));
}
if(NumberUtils.isNumber(workerThread)){
builder.addPropertyValue("workerThread", Integer.parseInt(workerThread));
}
if(StringUtils.isNotBlank(groupName)){
builder.addPropertyValue("groupName", groupName);
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
3、xsd文件解析必须创建两个文件。作用是schame指定本地文件、xsd指定对应的namespaceHandler类
1)resources/META-INF下新建文件spring.handlers 内容如下
http\://www.back.com/schema/back-service=com.back.spring.provider.BackServiceNamespaceHandler
2)resources/META-INF下新建文件spring.schemas 内容如下
http\://www.back.com/schema/back-service.xsd=META-INF/back-service.xsd
Ok,做到上面的几部其实就把我们需要的类整合进入Spring了,类似Dubbo等框架所说的整合Spring就是在做这些事,开发者只需要在Spring中使用自定义标签或者自定义类,就能完成自己想做的事了。
最后我们写个测试类来测试下上面的xml中的配置加载成为bean后是否帮我们做了向注册中心注册的动作(注册中心代码在上一篇博客中)。
public class TestSpringXsd {
public static void main(String[] args) throws InterruptedException {
ClassPathResource resource = new ClassPathResource("spring.xml");
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions(resource);
beanFactory.getBean("aaa");
TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
}
}
我们去观察下对应地址的zookeeper,发现如下:接口地址下发现已经将ip和端口以临时节点的形式注册到zookeeper