從零RPC(六) 諸多框架整和Spring的祕密

本章內容:

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章