我們知道dubbo一般也不會單獨使用的吧,都會和spring一起使用,知道爲什麼嗎?
因爲dubbo是基於spring的擴展機制進行擴展的,所以首先我們要知道spring提供了一種什麼擴展機制?
先看下圖,基於spring的配置文件都會有如下所示這段東西,這是幹啥的呢?
1.spring配置文件的文件頭
首先我爲了偷懶,要去找一個spring配置文件的網圖,下圖所示,這是一個很常見的spring配置文件,但是前面那一堆xmlns是什麼東西啊,我擦(╯—﹏—)╯(┷━━━┷
<?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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="find*" propagation="NOT_SUPPORTED" /> </tx:attributes> </tx:advice> <aop:config> <aop:aspect id="***" ref="***"/> <aop:pointcut id="***" expression="****" /> </aop:config> </beans>
而且我們看看上述的標籤,可以分爲三塊,第一部分是文件頭(也就是xmlns那一堆), 第二部分是<bean id="xx" class="xx.xx.xxx"></bean>這種bean的標籤,第三部分是<tx:advice id="txAdvice" transaction-manager="txManager"></tx:advice>這類標籤名字還帶有冒號,冒號還跟着一個奇怪的東西的๑乛◡乛๑
1.1 xmlns部分
xmlns全程是xml namespace,翻譯一下就是xml命名空間,你非要問這個有什麼用?其實啥用沒有,你把他看成一個鍵值對,key-->uuid(例如下圖tx--->http://www.springframework.org/schema/tx), 鍵表示標籤的前綴,值就是一個唯一標識,下圖步驟1所示
但是這個唯一標識的話,還需要在xsi:schemaLocation中對應起來,如步驟2所示;
而且每一個唯一標識還會對應一個xsd文件,步驟3所示
每一個xsd文件中就是描述了當前命名空間中指定的標籤中,規範了各個屬性,步驟4所示
說出來你可能不信,現在根據上圖步驟4的url找到xsd文件(默認先從jar包中找,沒有的話,纔會去網絡上下載)
注意,這個文件其實在spring-tx.jar中META-INF/spring.schemas中可以找到本地該文件的地址,如果沒有,纔會聯網去spring官方地址那裏去下載
我們輕輕一點開這個xsd文件康康,隨意看看就能看到<tx:advice>標籤中各個屬性值,以及規定的子標籤了(有興趣瞭解xsd文件語法的可以自己學習一下,可以自定義spring配置文件的標籤, 反正我不怎麼會這個,嘿嘿( ̄▽ ̄)ノ,我明明不會,但是我就是不學)
注意:spring配置文件中命名空間xmlns:tx="http://www.springframework.org/schema/tx" 要和xsd文件頭中的xmlns 、targetNamespace保持一致的呀
1.2 bean標籤
上面說了一大堆沒啥用的東西,我們可以在spring配置文件中看到<bean>這種標籤, 這種標籤名是沒有帶冒號的?這是爲啥?
1.3 帶有標籤前綴的標籤
我擦,好像已經在1.1中說過了,那麼我們就過\(@ ̄∇ ̄@)/,其實我很想寫的更多,但是這裏空白太小了,寫不下,嘿嘿~
2.自定義spring標籤栗子
說了這麼多,我們自己來搗鼓一個自定義標籤出來,目錄結構如下:
2.1.接口和實現類:
public interface MenuService { void sayHello(); } public class MenuServiceImpl implements MenuService{ @Override public void sayHello() { System.out.println("hello world"); } }
2.2. 編寫app.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://com.protagonist.com/schema" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://com.protagonist.com/schema http://com.protagonist.com/schema/rpc.xsd"> <myrpc:reference id="menuService" interface="com.protagonist.springload.MenuService" /> </beans>
2.3 編寫spring.handlers文件
描述上面那個命名空間xmlns:myrpc 對應的命名空間處理器,以及標籤解析起
http\://com.protagonist.com/schema=com.protagonist.config.RPCNamespaceHandler
下面的代碼可能略多,其實核心的就是在解析到reference標籤的時候,獲取到interface屬性的接口全路徑,然後使用jdk動態代理對這個接口生成一個動態代理類
package com.protagonist.config; import org.springframework.beans.factory.xml.NamespaceHandlerSupport; public class RPCNamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { /** * 在app.xml文件中解析當前命名空間所在的標籤的時候,有reference屬性,就會RPCBeanDefinitionParser解析起去解析該標籤 * */ registerBeanDefinitionParser("reference", new RPCBeanDefinitionParser()); } }
這裏的getBeanClass方法中,注意返回的對象是ReferenceBean.class
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
public class RPCBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
/**
* app.xml配置文件中, 解析標籤對應的javaBean對象
* @param element
* @return
*/
protected Class getBeanClass(Element element) {
return ReferenceBean.class;
}
/**
* 解析標籤,取出interface屬性的值
* @param element
* @param bean
*/
protected void doParse(Element element, BeanDefinitionBuilder bean) {
String interfaceClass = element.getAttribute("interface");
if (StringUtils.hasText(interfaceClass)) {
bean.addPropertyValue("interfaceClass", interfaceClass);
}
}
}
ReferenceBean類實現了FactoryBean接口,只要spring容器初始化創建bean實例的話就會調用getObject方法
import org.springframework.beans.factory.FactoryBean; public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean { @Override public Object getObject() throws Exception { return get(); } @Override public Class<?> getObjectType() { return getInterfaceClass(); } @Override public boolean isSingleton() { return true; } }
ReferenceConfig類其實就是根據接口的全名稱,生成一個動態代理類
import com.protagonist.springload.ProxyFactory;
public class ReferenceConfig<T> {
private Class<?> interfaceClass;
/**
* 接口代理類引用
*/
private transient volatile T ref;
public synchronized T get() {
if (ref == null) {
init();
}
return ref;
}
/**
* 將xml文件中的接口的全路徑,使用jdk動態代理生成一個代理對象
*/
private void init() {
ref = new ProxyFactory(interfaceClass).getProxyObject();
}
public Class<?> getInterfaceClass() {
return interfaceClass;
}
public void setInterfaceClass(Class<?> interfaceClass) {
this.interfaceClass = interfaceClass;
}
}
ProxyFactory類,其實就是封裝了一下jdk動態代理,在下面的invoke方法中,我們可以使用Socket去連接遠程服務器,進行交互,獲取響應數據
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class ProxyFactory implements InvocationHandler { private Class interfaceClass; public ProxyFactory(Class interfaceClass) { this.interfaceClass = interfaceClass; } /** * 返回代理對象,此處用泛型爲了調用時不用強轉,用Object需要強轉 */ public <T> T getProxyObject(){ return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(),//類加載器 new Class[]{interfaceClass},//爲哪些接口做代理 this);//(把這些方法攔截到哪處理) } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(method); System.out.println("將要發送的數據進行編碼"); System.out.println("開始發送網絡請求"); System.out.println("獲取響應數據"); System.out.println("開始解碼,獲取明文數據"); return null; } }
2.4.編寫spring.schemas文件
用於描述上面app.xml文件中綠色部分對應的xsd文件的實際位置
http\://com.protagonist.com/schema/rpc.xsd=META-INF/rpc.xsd
2.5 編寫xsd文件
用於描述當前命名空間的標籤內都有啥屬性,注意下面紅色部分要和app.xml文件中保持一致
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <xsd:schema xmlns="http://com.protagonist.com/schema" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:tool="http://www.springframework.org/schema/tool" targetNamespace="http://com.protagonist.com/schema"> <xsd:import namespace="http://www.w3.org/XML/1998/namespace"/> <xsd:import namespace="http://www.springframework.org/schema/beans"/> <xsd:import namespace="http://www.springframework.org/schema/tool"/> <xsd:complexType name="referenceType"> <xsd:complexContent> <xsd:extension base="beans:identifiedType"> <xsd:attribute name="interface" type="xsd:token" use="required"> <xsd:annotation> <xsd:documentation><![CDATA[ The service interface class name. ]]></xsd:documentation> <xsd:appinfo> <tool:annotation> <tool:expected-type type="java.lang.Class"/> </tool:annotation> </xsd:appinfo> </xsd:annotation> </xsd:attribute> </xsd:extension> </xsd:complexContent> </xsd:complexType> <xsd:element name="reference" type="referenceType"> <xsd:annotation> <xsd:documentation><![CDATA[ Reference service config ]]></xsd:documentation> </xsd:annotation> </xsd:element> </xsd:schema>
2.6 單元測試類以及結果
import com.protagonist.springload.MenuService; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import javax.annotation.Resource; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:app.xml") public class DemoTest { @Resource private MenuService menuService; @Test public void testSpring(){ menuService.sayHello(); } }
3.解析spring配置文件的原理
就使用上面舉了一個很簡單的栗子,我們調試一下,看看spring的xml配置文件中我們自定義的那個標籤,是怎麼解析的呢?
這個時候對spring源碼瞭解過的小夥伴肯定就會跳出來說,不就是會把配置文件中的一個個bean解析成BeanDefinition對象麼?道友我五百年前就知道了( ̄o ̄) . z Z
這就很不給面子呀,我擦,對於這種小夥伴,我都會把你毒打一頓,讓你體會社會的險惡๑乛◡乛๑
言歸正傳,我們就大概看看是怎麼從一個xml文件編程BeanDefinition的吧,我們只看大概流程,畢竟這不是講spring源碼的....
3.1 spring容器初始化的過程,會將app.xml文件封裝爲Resource對象
3.2 讀取Resource中的內容, 轉爲Document對象
3.3. 根據Document中的頭標籤內容,解析出來指定的命名空間uri
3.4 將配置文件META-INF/spring.handler中內容轉爲Properties對象,這個類就是繼承了HashTable,就是一個Map
3.5. 前面幾步就獲取到了命名空間處理器的全類名,這裏就是使用反射進行實例化,執行初始化方法
3.6 到了我們自定義命名空間處理器重寫的方法中,然後就是實例化RPCBeanDefinitionParser,後續初始化的流程會執行RPCBeanDefinitionParser的doParse方法,由於篇幅有限,有興趣的可以自己調試
4.總結
這篇寫的還是蠻多的,其實就是簡單的使用了一下spring的自定義標籤的功能,我們自己也簡單的實現了一個沒什麼用的超級簡易版的dubbo遠程調用的mock(雖然說還沒有真正的去調用,哈哈哈),dubbo實現的大概思路就是這個樣子;
就是根據dubbo的配置文件,找到我們要引用的藉口的全路徑,然後使用動態代理生成對象,去註冊中心中找到該接口和方法的所在的服務器的ip和端口,然後通過建立tcp連接的方式去向那個服務器發送數據並得到響應,然後解析數據;
說起來是不是很簡單,但是其中我們要考慮的東西特別多,比如註冊中心用啥?註冊中心掛了怎麼辦?序列化方式?遠程調用服務時候負載均衡?超時時間?容錯方案?通訊協議?等等問題都需要考慮到,後續我們慢慢說