dubbo源碼分析4(spring配置文件解析機制)

  我們知道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連接的方式去向那個服務器發送數據並得到響應,然後解析數據;

  說起來是不是很簡單,但是其中我們要考慮的東西特別多,比如註冊中心用啥?註冊中心掛了怎麼辦?序列化方式?遠程調用服務時候負載均衡?超時時間?容錯方案?通訊協議?等等問題都需要考慮到,後續我們慢慢說

 

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