Spring Dubbo 開發筆記


第一節:概述

Spring-Dubbo 是我自己寫的一個基於spring-boot和dubbo,目的是使用Spring boot的風格來使用dubbo。(即可以了解Spring boot的啟動過程又可以學習一下dubbo的框架)

項目介紹:

github: https://github.com/Athlizo/spring-dubbo-parent

碼雲:  https://git.oschina.net/null_584_3382/spring-dubbo-parent

聲明:

  1.  這個只是個人研究使用,spring boot 啟動dubbo還有很多方法(@ImportResource)
  2. 2這個只是第一版,基本功能已實現(其實底層還是原來的,只是處理了在spring boot啟動的時候怎么去加載一些東西),一些擴展和優化還會繼續更新

你可以像配置springboot其他屬性一樣在application.yml中配置dubbo屬性

dubbo:
  application:
    name: lizo
  registry:
    address: multicast://224.5.6.7:1234
  protocol:
    name: dubbo
    port: 20887 

@EnableDubbo

使用這個注解來開啟dubbo服務

@SpringBootApplication
@EnableDubbo(basePackages = "com.alibaba.dubbo")
public class Provider {

    public static void main(String[] args) throws InterruptedException {
        ApplicationContext ctx = new SpringApplicationBuilder()
                .sources(Provider.class)
                .web(false) 
                .run(args);
        new CountDownLatch(1).await();
    }
}

 

這樣就可以使用基於注解的dubbo的rpc調用

定義接口 :

public interface AddService {
    int add(int a, int b);
}

 

定義服務實現類:

這里就做一個簡單加法

@Service
public class AddServiceImpl implements AddService {
    @Override
    public int add(int a, int b) {
        return a + b;
    }
}

配置消費bean

@Component
public class ConsumerAction {

    @Reference
    private AddService addService;

    public void add(int a,int b){
        System.out.println("ret = " + addService.add(a,b));
    }
}

快速定義Filter

可以像定義一般的spring bean 定義一個dubbo filter

@Bean
ProviderFilter consumerFilter(){
    return new ProviderFilter();
}

static class ProviderFilter extends AbstractDubboProviderFilterSupport {

    public Result invoke(Invoker<?> invoker, Invocation invocation) {
        System.out.println("ProviderFilter");
        return invoker.invoke(invocation);
    }
}        

 

 

當然如果你有跟定制化的需求,也可以使用dubbo原生的注解@Activate,只要繼承AbstractDubboFilterSupport

@Activate(group = Constants.PROVIDER)
static class CustomFilter extends AbstractDubboFilterSupport {
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        System.out.println("CustomFilter");
        return invoker.invoke(invocation);
    }

    public Filter getDefaultExtension() {
        return this;
    }
}

 

第二節:在Spring boot啟動中加載Dubbo服務

Dubbo啟動的時候,是可以使用自己的Spring來啟動dubbo服務,但是現在是需要把Dubbo啟動SpringApplicationContest的邏輯放入到Spring Boot的啟動邏輯中去。(主要是針對注解的方式)

常用的接口

按照調用的順序來介紹

ApplicationContextInitializer

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
    void initialize(C applicationContext);
}

Spring Boot啟動的時候,會掃描classpath下的META-INF.spring.factories, 其中有一個配置名為org.springframework.context.ApplicationContextInitializer,配置項為一些實現了ApplicationContextInitializer接口的類的全路徑名,這些類就是Spring Boot在啟動的時候首先會進行實例化。

ApplicationListener

同ApplicationContextInitializer加載方式一樣,META-INF.spring.factories中還有另外一個配置項org.springframework.context.ApplicationListener,定義在SpringBoot啟動的時候會初始化的ApplicationListener。嚴格來說ApplicationListener在整個SpringApplicationContext啟動的時候都會觸發調用邏輯(通過各種不同事件觸發)

BeanFactoryPostProcessor

public interface BeanFactoryPostProcessor {
    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}

從名字可以看出,這個接口的類主要是針對BeanFactory進行一些操作,和ApplicationContextInitializer實例化的方式不一樣,這個接口必須在BeanFactory中存在才會在啟動中生效,因此,ApplicationContextInitializer中常做的事情就是加入一些BeanFactoryPostProcessor 。

BeanPostProcessor

public interface BeanPostProcessor {
         // 先於afterPropertiesSet() 和init-method
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

如果BeanFactoryPostProcessor針對的是BeanFactory,那么BeanPostProcessor針對就是BeanFactory中的所有bean了。分別提供了Bean初始化之前和bean初始化之后的相關處理。和BeanFactoryPostProcessor一樣,也需要在啟動中注冊到BeanFactory中才會生效,一般通過BeanFactoryPostProcessor 加入。

小結

從上面的分析可以看出

  • 如果需要在SpringApplication初始化的時候需要做事情,使用ApplicationContextInitializer
  • 如果在Spring啟動的各個階段有一些定制化的處理,使用ApplicationListener
  • 如果需要對BeanFactory做一些處理,例如添加一些Bean,使用BeanFactoryPostProcessor
  • 如果需要對BeanFactory中的bean做一些處理,使用BeanPostProcessor ,(個人理解是該接口主要針對bean批量處理,否則針對特定的bean就使用InitializeBean或者Init-method完成初始化邏輯)

SpringBoot接入Dubbo

注:這里的接入主要是對使用注解的方式。

Dubbo是中處理@Service和@Reference

關鍵類:com.alibaba.dubbo.config.spring.AnnotationBean 代碼邏輯比較簡單,這個類實現了BeanFactoryPostProcessor和BeanPostProcessor這2個接口,分別作用:

  • 在BeanFactory處理結束以后,掃描Service注解的類,加入到BeanFactory中
  • 在Bean初始化之前,對@Reference注解的set方法和屬性創建遠程調用代理類並注入
  • 在Bean初始化之后,對@Service注解的類暴露遠程調用服務(生成Exporter)

因此,SpringBoot接入dubbo的關鍵在於:在完成BeanFactoryPostProcessor調用之前,把AnnotationBean加入到BeanFactory中就可以了

接入方式

這里不直接使用AnnotationBean,而是另外定義一個類,新類的名字為AnnotationBeanProcessor(為了貼代碼方便),作用是一樣的,只是修改里面的部分處理邏輯。

方法1. 利用ApplicationContextInitializer

代碼如下

public class DubboContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    public void initialize(ConfigurableApplicationContext applicationContext) {
        AnnotationBeanProcessor annotationBeanProcessor= new AnnotationBeanProcessor(${構造參數});
        annotationBeanProcessor.setApplicationContext(applicationContext);
        applicationContext.addBeanFactoryPostProcessor(annotationBeanProcessor);
    }
}

特點——簡單暴力

方法2 利用BeanFactoryPostProcessor

在ApplicationContextInitializer中加入其它的一個BeanFactoryPostProcessor,然后在這個BeanFactoryPostProcessor加入AnnotationBeanProcessor

public class DubboContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    public void initialize(ConfigurableApplicationContext applicationContext) {
        DubboBeanDefinitionRegistryPostProcessor dubboBeanDefinitionRegistryPostProcessor = new DubboBeanDefinitionRegistryPostProcessor();
        applicationContext.addBeanFactoryPostProcessor(dubboBeanDefinitionRegistryPostProcessor);
    }

    public class DubboBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
        public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(AnnotationBeanProcessor.class);
            beanDefinition.getConstructorArgumentValues()
                    .addGenericArgumentValue(${構造參數});
            beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            registry.registerBeanDefinition("annotationBeanProcessor", beanDefinition);
        }   
    }
} 

特點: 這樣做雖然感覺有點繞,但是好處就是可以在其它的一些關鍵的BeanDefinitionRegistryPostProcessor 后再執行,這樣就可以使用xxxAware接口,Spring會自動幫我們注入。可以利用Spring提供的一些便利功能。 雖然利用ApplicationListener也可以做到,但是不推薦

方法3 利用ImportBeanDefinitionRegistrar

@Import注解中加入ImportBeanDefinitionRegistrar的實現類,實現對bean definition 層面的開發。

public class AnnotationBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    private String BEAN_NAME = "annotationBeanProcessor";

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        List<String> basePackages = getPackagesToScan(importingClassMetadata);
        if (!registry.containsBeanDefinition(BEAN_NAME)) {
            addPostProcessor(registry, basePackages);
        }
    }

    // register annotationBeanProcessor.class
    private void addPostProcessor(BeanDefinitionRegistry registry, List<String> basePackages) {
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(AnnotationBeanProcessor.class);
        beanDefinition.getConstructorArgumentValues()
                .addGenericArgumentValue(basePackages);
        beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        registry.registerBeanDefinition(BEAN_NAME, beanDefinition);
    }

    //獲取掃描的包路徑
    private List<String> getPackagesToScan(AnnotationMetadata metadata) {
        //EnableDubbo 是一個注解,用於開啟掃描dubbo的bean,並且可以自己定義掃描basePackages
        AnnotationAttributes attributes = AnnotationAttributes.fromMap(
                metadata.getAnnotationAttributes(EnableDubbo.class.getName()));
        String[] basePackages = attributes.getStringArray("basePackages");
        return Arrays.asList(basePackages);
    }
}

其中還使用到了EnableDubbo.class ,其實這個是一個注解,里面定義了basePackages的屬性。 特點:1 通過注解使Dubbo是否生效,還可以自己配置basePackages的掃描包路徑,而不用寫死在代碼里。2. 很Spring Boot Style

@Reference和@Service

  • @Reference注解的set方法和屬性,Dubbo會創建ReferenceBean(代理類)然后注入進進。
  • @Service就是在bean初始化,會生成一個ServiceBean,然后exporter(監聽遠程調用請求)。

而這2個流程的理想處理方式就是在BeanPostProcessor 中,因為上面這2個處理邏輯不是針對某個特殊的bean,而是針對所有的bean,只要有@Reference或者@Service,並且滿足basepackage限制就行。

dubbo現有的邏輯是分別在所有bean初始化之前進行@Reference相關流程,而在所有bean初始化之后調用@Service處理流程。(這2個流程我是修改在ApplicationContext初始化成功以后再進行,並且這樣做還會帶來一定的好處)

第三節 Spring 加載dubbo擴展

dubbo框架中,提供了多種擴展,比如Dubbo的過濾器擴展,路由擴展等等。並且dubbo已經提供了擴展的一些默認實現。本篇文章主要介紹:1)dubbo擴展原理,2)通過簡單的改造,使dubbo讓擴展的使用更方便。

dubbo擴展

怎么創建dubbo擴展

以攔截器作為例子說明,引用dubbo官方文檔中的例子。

<!-- 在xml配置文件中設置 -->
<dubbo:reference filter="xxx,yyy" /> <!-- 消費方調用過程攔截 -->
<dubbo:consumer filter="xxx,yyy"/> <!-- 消費方調用過程缺省攔截器,將攔截所有reference -->
<dubbo:service filter="xxx,yyy" /> <!-- 提供方調用過程攔截 -->
<dubbo:provider filter="xxx,yyy"/> <!-- 提供方調用過程缺省攔截器,將攔截所有service -->
dubbo擴展配置文件
src
 |-main
    |-java
        |-com
            |-xxx
                |-XxxFilter.java (實現Filter接口)
    |-resources
        |-META-INF
            |-dubbo
                |-com.alibaba.dubbo.rpc.Filter (純文本文件,內容為:xxx=com.xxx.XxxFilter)
//擴展類
package com.xxx;
 
import com.alibaba.dubbo.rpc.Filter;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.dubbo.rpc.Result;
import com.alibaba.dubbo.rpc.RpcException;
 
 
public class XxxFilter implements Filter {
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        // before filter ...
        Result result = invoker.invoke(invocation);
        // after filter ...
        return result;
    }
}
  1. 第一步是配置Dubbo Filter,有兩種方法,第一種是在配置文件里面加入相關擴展配置,例如<dubbo:provider filter="xxx"/>。第二種方法是針對集合類擴展,比如:Filter, InvokerListener, ExportListener, TelnetHandler, StatusChecker等,可以同時加載多個實現,使用@Activate的方式自動激活來簡化配置,如:    
    import com.alibaba.dubbo.common.extension.Activate;
    import com.alibaba.dubbo.rpc.Filter;
     
    @Activate(group = "provider", value = "xxx") // 只對提供方激活,group可選"provider"或"consumer"
    public class XxxFilter implements Filter {
        // ...
    } 
  2. 第二步是創建配置文件,在resource/META-INF/dubbo/com.alibaba.dubbo.rpc.Filter文件里面寫配置xxx=com.xxx.XxxFilter
  3. 第三步是創建對應的Class,在配置對應的包下面新建一個XxxFilter,實現Dubbo的Filter接口。

通過以上3步就可以自定義一個Filter

Dubbo是怎么加載擴展

 ExtensionLoader<T>

1) 創建:

這個類是用戶管理所有的dubbo擴展,是一個泛型,根據不同的擴展類(例如Filter,Protocol等),保存該類擴展的所有實現類。

例如,要想獲取每個具體的ExtensionLoader,使用getExtensionLoader(Class) 來獲取,如果沒有就創建,如果之前創建過就返回之前創建的,邏輯比較簡單。

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    ... //省略參數校驗
    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    if (loader == null) {
        //EXTENSION_LOADERS用於保存ExtensionLoader所有泛型實現子類
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
    return loader;
}
2)獲取:

獲取有效的擴展類,是通過getActivateExtension方法。代碼如下:

public List<T> getActivateExtension(URL url, String[] values, String group) {
    List<T> exts = new ArrayList<T>();
    List<String> names = values == null ? new ArrayList<String>(0) : Arrays.asList(values);
    if (! names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {
        //加載擴展擴展的類
        getExtensionClasses();
        //加載@Activate注解的配置的擴展
        for (Map.Entry<String, Activate> entry : cachedActivates.entrySet()) {
            String name = entry.getKey();
            Activate activate = entry.getValue();
            if (isMatchGroup(group, activate.group())) {
                T ext = getExtension(name);
                if (! names.contains(name)
                        && ! names.contains(Constants.REMOVE_VALUE_PREFIX + name) 
                        && isActive(activate, url)) {
                    exts.add(ext);
                }
            }
        }
        Collections.sort(exts, ActivateComparator.COMPARATOR);
    }
    List<T> usrs = new ArrayList<T>();
    //加載 通過values傳遞過來的指定擴展
    for (int i = 0; i < names.size(); i ++) {
       String name = names.get(i);
        if (! name.startsWith(Constants.REMOVE_VALUE_PREFIX)
              && ! names.contains(Constants.REMOVE_VALUE_PREFIX + name)) {
           if (Constants.DEFAULT_KEY.equals(name)) {
              if (usrs.size() > 0) {
              exts.addAll(0, usrs);
              usrs.clear();
              }
           } else {
           T ext = getExtension(name);
           usrs.add(ext);
           }
        }
    }
    if (usrs.size() > 0) {
       exts.addAll(usrs);
    }
    return exts;
}

傳入的參數說明:

  • url-我們知道dubbo的RPC調用相關信息都是通過URL形式保存的,因此url參數即是當前的某個服務調用
  • values-用來指定加載某些特殊的擴展,例如通過<dubbo:provider filter="xxx"/>來配置的過濾器,則在url中會有相關信息(service.filter),然后加載指定過濾器
  • group-用來處理@Activate注解的方式配置擴展

其中有一個比較重要的方法,getExtensionClasses(),原理就是初始化擴展名字及其對應的Class,在往里面看

private Map<String, Class<?>> getExtensionClasses() {
       Map<String, Class<?>> classes = cachedClasses.get();
       if (classes == null) {
           synchronized (cachedClasses) {
               classes = cachedClasses.get();
               if (classes == null) {
                   classes = loadExtensionClasses();
                   cachedClasses.set(classes);
               }
           }
       }
       return classes;
}

其中loadExtensionClasses方法如下:這里看到了,為什么要配置在自定義過濾器的時候我們需要配置META-INFO.dubbo.com.alibaba.dubbo.rpc.Filter這個文件,原來就是在這里通過文件加載到ExtensionLoad中去的,並且路徑寫死在這里。

private Map<String, Class<?>> loadExtensionClasses() {
    ...
    Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
    // META-INF/dubbo/internal/
    loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
    // META-INF/dubbo/
    loadFile(extensionClasses, DUBBO_DIRECTORY);
    // META-INF/services/
    loadFile(extensionClasses, SERVICES_DIRECTORY);
    return extensionClasses;
}
3) 使用

還是以Filter為例子,在哪里調用Filter呢?首先要明白,dubbo的遠程調用都是通過抽象接口Invoker為核心。ProtocolFilterWrapper的類中的buildInvokerChain方法用來創建Invoder的調用鏈。核心代碼如下:

private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
    Invoker<T> last = invoker;
    List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
    if (filters.size() > 0) {
        for (int i = filters.size() - 1; i >= 0; i --) {
            final Filter filter = filters.get(i);
            final Invoker<T> next = last;
            last = new Invoker<T>() {
                ...
                public Result invoke(Invocation invocation) throws RpcException {
                    return filter.invoke(next, invocation);
                }
               ...
            };
        }
    }
    return last;
}

這里就使用到了ExtensionLoader的getActivateExtension方法獲取當前有效的Filter。

Spring Boot Style

在第二節中介紹了怎么使用Spring boot來加載dubbo的相關bean,那么就會想,對於dubbo的Filter,有沒有更優雅的聲明方式?例如Spring MVC中的聲明一個Filter就是直接聲明一個普通的Bean一樣。

還是以Filter為例子(注意,下面分析Filter是針對全局的Filter,即Provider或者Consumer層面Filter)

首先,擴展是從ExtensionLoad<Filter>中獲取,那么我們的目的就是在ExtensionLoad<Filter>中加入自己的Filter。上面說過,ExtensionLoad加載擴展有2種方式,一種是通過參數中Values來獲取,另外一種是通過@Activate注解。簡單分析一下:

使用Values參數的方式

  1. 需要在聲明一個自己的Filter的時候,同時必須創建一個ProviderConfig或者ConsumerConfig(以代替<dubbo:provider filter="xxx"/>這樣的配置)
  2. 需要手動往ExtensionLoad中加入我們自己的Filter(以代替通過讀取com.alibaba.dubbo.rpc.Filter配置加入到ExtensionLoad)

使用@Activate方式

  1. 使用@Activate的話,也要往ExtensionLoad中加入我們自己的Filter,但是不用創建ProviderConfig或者ConsumerConfig。
  2. 必須要在自己的Filter上有@Activate注解。

這兩種方式都能達成我們的需求,但是從開發難度來說使用@Activate注解相對簡單,而且第二點可以通過其他方式(proxy代理)來解決。因此下下面介紹基於@Activate注解來快速創建一個Dubbo的Filter。

3.1 實現方式

    自定義一個BeanPostProcessor,對每一個Bean做以下處理:

3.2 完成以后的效果

這樣就可以快速創建一個Dubbo Filter

@Bean
ProviderFilter providerFilter(){
    return new ProviderFilter();
}

static class ProviderFilter extends AbstractDubboProviderFilterSupport {
    public Result invoke(Invoker<?> invoker, Invocation invocation) {
        System.out.println("ProviderFilter");
        return invoker.invoke(invocation);
    }
}

如果有跟定制化的需求,可以使用@Activate注解。

@Bean
CustomFilter customFilter(){
    return new CustomFilter();
}

@Activate(group = Constants.PROVIDER)
static class CustomFilter extends AbstractDubboFilterSupport {
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        System.out.println("CustomFilter");
        return invoker.invoke(invocation);
    }

    public Filter getDefaultExtension() {
        return this;
    }
}

 

 總結

  如果在不改動dubbo內部代碼的情況下,只能在上層邏輯進行修改,因此能做的事還是比較有限。不過通過這個項目,既了解到了Spring boot啟動的過程,也知道理解了dubbo框架思想。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM