本章內容:
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