第一節:概述
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
聲明:
- 這個只是個人研究使用,spring boot 啟動dubbo還有很多方法(@ImportResource)
- 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; } }
- 第一步是配置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 { // ... }
- 第二步是創建配置文件,在resource/META-INF/dubbo/com.alibaba.dubbo.rpc.Filter文件里面寫配置xxx=com.xxx.XxxFilter
- 第三步是創建對應的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參數的方式
- 需要在聲明一個自己的Filter的時候,同時必須創建一個ProviderConfig或者ConsumerConfig(以代替<dubbo:provider filter="xxx"/>這樣的配置)
- 需要手動往ExtensionLoad中加入我們自己的Filter(以代替通過讀取com.alibaba.dubbo.rpc.Filter配置加入到ExtensionLoad)
使用@Activate方式
- 使用@Activate的話,也要往ExtensionLoad中加入我們自己的Filter,但是不用創建ProviderConfig或者ConsumerConfig。
- 必須要在自己的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框架思想。