ExtensionLoader
1、私有構(gòu)造
ExtensionLoader的構(gòu)造方法傳入一個Class,代表當前擴展點對應(yīng)的SPI接口。
每個ExtensionLoader實例管理自己Class的擴展點,包括加載、獲取等等。
type:當前擴展點對應(yīng)spi接口class;
objectFactory:擴展點工廠AdaptiveExtensionFactory,主要用于setter注入,后面再看。
2、單例
ExtensionLoader提供靜態(tài)方法,構(gòu)造ExtensionLoader實例。
單例往往要針對一個范圍(scope)來說,比如Spring中所說的單例,往往是在一個BeanFactory中,而一個應(yīng)用可以運行多個BeanFactory。又比如Class對象是單例,往往隱含的scope是同一ClassLoader。
ExtensionLoader在一個擴展點接口Class下只有一個實例,而每個擴展點實現(xiàn)實例在全局只有一個。
3、成員變量
ExtensionLoader的成員變量可以分為幾類
普通擴展點相關(guān):
active擴展點相關(guān):
adaptive擴展點相關(guān):
wrapper擴展點相關(guān):
4、加載擴展點Class
ExtensionLoader#getExtensionClasses:
當需要加載某個擴展點實現(xiàn)實例前,總會優(yōu)先加載該擴展點所有實現(xiàn)Class,并緩存到cachedClasses中。
ExtensionLoader#loadExtensionClasses:加載所有擴展點實現(xiàn)類
ExtensionLoader#cacheDefaultExtensionName:加載SPI注解中的value屬性,作為默認擴展點名稱,默認擴展點只能存在一個。
ExtensionLoader會掃描classpath三個路徑下的擴展點配置文件:
- META-INF/dubbo/internal:dubbo框架自己用的
- META-INF/dubbo/:用戶擴展用的
- META-INF/services/:官方也沒建議這樣使用
ExtensionLoader#loadDirectory:
1)類加載器:優(yōu)先線程類加載器,其次ExtensionLoader自己的類加載器;
2)掃描擴展點配置文件;
3)加載擴展類;
ExtensionLoader#loadResource:加載文件中每一行,key是擴展名,value是擴展實現(xiàn)類名。
ExtensionLoader#loadClass:最終將每個擴展實現(xiàn)類Class按照不同的方式,緩存到ExtensionLoader實例中。
普通擴展點
案例
對于一個擴展點MyExt:
@SPI
public interface MyExt {
String echo(URL url, String s);
}
MyExtImplA實現(xiàn)MyExt:
public class MyExtImplA implements MyExt {
@Override
public String echo(URL url, String s) {
return "ext1";
}
}
可以配置多個擴展點實現(xiàn)META-INF/dubbo/x.y.z.MyExt:
A=x.y.z.impl.MyExtImplA
B=x.y.z.impl.MyExtImplB
使用ExtensionLoader#getExtention獲取對應(yīng)擴展點:
@Test
void testExtension() {
ExtensionLoader
這種用法,對于用戶來說,和beanFactory極為類似,當然實現(xiàn)并不同。
MyExt a = beanFactory.getBean("A", MyExt.class);
原理
ExtensionLoader#getExtention固然有單例緩存(cachedInstances),這個直接跳過。
ExtensionLoader#createExtension:創(chuàng)建擴展點實現(xiàn)
0)getExtensionClasses:確保所有擴展點class被加載
1)通過無參構(gòu)造,實例化擴展點instance
2)injectExtention:對擴展點instance執(zhí)行setter注入,暫時忽略
3)包裝類相關(guān),暫時忽略
4)執(zhí)行instance的初始化方法
initExtension:初始化
ExtensionLoader和Spring的創(chuàng)建bean流程相比,確實很像,比如:
1)Spring可以通過各種方式選擇bean的一個構(gòu)造方法創(chuàng)建一個bean(AbstractAutowireCapableBeanFactory#createBeanInstance),而ExtensionLoader只能通過無參構(gòu)造創(chuàng)建擴展點;
2)Spring可以通過多種方式進行依賴注入(AbstractAutowireCapableBeanFactory#populateBean),比如Aware接口/setter/注解等,而ExtensionLoader只能支持setter注入;
3)Spring可以通過多種方式進行初始化(AbstractAutowireCapableBeanFactory#initializeBean),比如PostConstruct注解/InitializingBean/initMethod等,而ExtensionLoader只支持InitializingBean(LifeCycle)這種方式;
包裝擴展點
案例
上面在ExtensionLoader#createExtension的第三步,可能會走包裝擴展點邏輯。
假設(shè)有個擴展點MyExt2:
@SPI
public interface MyExt2 {
String echo(URL url, String s);
}
有普通擴展點實現(xiàn)MyExt2ImplA:
public class MyExt2ImplA implements MyExt2 {
@Override
public String echo(URL url, String s) {
return "A";
}
}
除此以外,還有兩個實現(xiàn)MyExt2的擴展點的MyExtWrapperA和MyExtWrapperB, 特點在于他有MyExt2的單參數(shù)構(gòu)造方法 。
public class MyExtWrapperA implements MyExt2 {
private final MyExt2 myExt2;
public MyExtWrapperA(MyExt2 myExt2) {
this.myExt2 = myExt2;
}
@Override
public String echo(URL url, String s) {
return "wrapA>>>" + myExt2.echo(url, s);
}
}
public class MyExtWrapperB implements MyExt2 {
private final MyExt2 myExt2;
public MyExtWrapperB(MyExt2 myExt2) {
this.myExt2 = myExt2;
}
@Override
public String echo(URL url, String s) {
return "wrapB>>>" + myExt2.echo(url, s);
}
}
然后編寫配置文件META-INF/x.y.z.myext2.MyExt2:
A=x.y.z.myext2.impl.MyExt2ImplA
wrapperA=x.y.z.myext2.impl.MyExtWrapperA
wrapperB=x.y.z.myext2.impl.MyExtWrapperB
測試驗證,echo方法輸出wrapB>>>wrapA>>>A。
@Test
void testWrapper() {
ExtensionLoader
但是包裝擴展點不能通過getExtension顯示獲取 ,比如:
// 包裝類無法通過name直接獲取
@Test
void testWrapper_IllegalStateException() {
ExtensionLoader
原理
包裝類之所以不暴露給用戶直接獲取,是因為包裝類提供類似aop的用途,對于用戶來說是透明的。
類加載階段
在類加載階段,isWrapperClass判斷一個擴展類是否是包裝類,如果是的話放入cachedWrapperClasses緩存。
對于包裝類,不會放入普通擴展點的緩存map,所以無法通過getExtension顯示獲取。
判斷是否是包裝類,取決于擴展點實現(xiàn)clazz是否有對應(yīng)擴展點type的單參構(gòu)造方法。
實例化階段
包裝類實例化,是通過ExtensionLoader.getExtension("A")獲取普通擴展點觸發(fā)的,而返回的會是一個包裝類。
即 如果一個擴展點存在包裝類,客戶端通過getExtension永遠無法獲取到原始擴展點實現(xiàn) 。
包裝類是硬編碼實現(xiàn)的:
1)本質(zhì)上包裝的順序是無序的,取決于擴展點配置文件的掃描順序。(SpringAOP可以設(shè)置順序)
2)包裝類即使只關(guān)注擴展點的一個方法,也必須要實現(xiàn)擴展點的所有方法,擴展點新增方法如果沒有默認實現(xiàn),需要修改所有包裝類。(SpringAOP如果用戶只關(guān)心其中一個方法,也可以實現(xiàn),因為是動態(tài)代理)
3)性能較好。(無反射)
自適應(yīng)擴展點
對于一個擴展點type,最多只有一個自適應(yīng)擴展點實現(xiàn)。
可以通過用戶硬編碼實現(xiàn),也可以通過dubbo自動生成,優(yōu)先取用戶硬編碼實現(xiàn)的自適應(yīng)擴展點。
硬編碼(Adaptive注解Class)
案例
假如有個水果擴展類,howMuch來統(tǒng)計交易上下文中該水果能賣多少錢。
@SPI
public interface Fruit {
int howMuch(String context);
}
有蘋果香蕉等實現(xiàn),負責計算自己能賣多少錢。
public class Apple implements Fruit {
@Override
public int howMuch(String context) {
return context.contains("apple") ? 1 : 0;
}
}
public class Banana implements Fruit {
@Override
public int howMuch(String context) {
return context.contains("banana") ? 2 : 0;
}
}
這里引入一個AdaptiveFruit,在類上加了Adaptive注解,目的是統(tǒng)計上下文中所有水果能賣多少錢。
getSupportedExtensionInstances這個方法能加載所有擴展點,并依靠Prioritized接口實現(xiàn)排序,這個原理忽略,和Spring的Ordered差不多。
@Adaptive
public class AdaptiveFruit implements Fruit {
private final Set
測試方法如下,用戶購買蘋果和香蕉,共花費3元。
核心api是ExtensionLoader#getAdaptiveExtension獲取自適應(yīng)擴展點實現(xiàn)。
@Test
void testAdaptiveFruit() {
ExtensionLoader
原理
在類加載階段,被Adaptive注解修飾的擴展點Class會被緩存到cachedAdaptiveClass。
注意,Adaptive注解類也不會作為普通擴展點暴露給用戶,即不能通過ExtensionLoader.getExtension通過擴展名直接獲取。
ExtensionLoader#getAdaptiveExtension獲取自適應(yīng)擴展點。
實例化階段,無參構(gòu)造反射創(chuàng)建Adaptive擴展點,并執(zhí)行setter注入。
dubbo優(yōu)先選取用戶實現(xiàn)的Adaptive擴展點實現(xiàn),否則會動態(tài)生成Adaptive擴展點。
動態(tài)生成(Adaptive注解Method)
案例
假設(shè)現(xiàn)在有個秒殺水果擴展點SecKillFruit。
相較于剛才的Fruit擴展點, 區(qū)別在于入?yún)⒏臑榱薝RL,且方法加了Adaptive注解 。
@SPI
public interface SecKillFruit {
@Adaptive
int howMuch(URL context);
}
蘋果秒殺0元,香蕉秒殺1元。
public class SecKillApple implements SecKillFruit {
@Override
public int howMuch(URL context) {
return 0;
}
}
public class SecKillBanana implements SecKillFruit {
@Override
public int howMuch(URL context) {
return 1;
}
}
擴展點配置文件META-INF/x.y.z.myext4.SecKillFruit:
apple=x.y.z.myext4.impl.SecKillApple
banana=x.y.z.myext4.impl.SecKillBanana
假設(shè)場景,每次只能秒殺一種水果,需要根據(jù)上下文不同,決定秒殺的是哪種水果,計算不同的價錢。
有下面的測試案例,關(guān)鍵點在于URL里增加了sec.kill.fruit=擴展點名,零編碼實現(xiàn)根據(jù)URL走不同策略。
sec.kill.fruit是SecKillFruit駝峰解析為小寫后用點分割得到。
@Test
void testAdaptiveFruit2() {
ExtensionLoader
也可以通過指定Adaptive注解的value,讓獲取擴展點名字的邏輯更加清晰。
比如取URL中的fruitType作為獲取擴展名的方式。
@SPI
public interface SecKillFruit {
@Adaptive("fruitType")
int howMuch(URL context);
}
原理
由于Dubbo內(nèi)部就是用URL做全局上下文來用,你可以理解為字符串無所不能。
所以為了減少重復(fù)代碼,很多策略都通過動態(tài)生成自適應(yīng)擴展來實現(xiàn)。
ExtensionLoader#createAdaptiveExtensionClass:如果沒有用戶Adaptive注解實現(xiàn)擴展點,走這里動態(tài)生成。
關(guān)鍵點在于AdaptiveClassCodeGenerator#generate如何生成java代碼。
擴展點接口必須有Adaptive注解方法,否則getAdaptiveExtension會異常。
關(guān)鍵在于generateMethodContent如何實現(xiàn)adaptive方法邏輯。
對于沒有Adaptive注解的方法,直接拋出異常。
對于Adaptive注解的方法,分為四步:
1)獲取URL:優(yōu)先從參數(shù)列表里直接找URL,降級從一個有URL的getter方法的Class里獲取URL,否則異常;
2)決定擴展名:優(yōu)先從Adaptive注解value屬性獲取,否則取擴展點類名去駝峰加點;
3)獲取擴展點:調(diào)用ExtensionLoader.getExtension;
4)委派給目標擴展實現(xiàn):調(diào)用目標擴展的目標方法,傳入原始參數(shù)列表;
比如針對SecKillFruit,最終生成的代碼如下。
對于Dubbo來說,雖然擴展點不同,但是都用URL上下文,就可以少寫重復(fù)代碼。
public class SecKillFruit$Adaptive implements x.y.z.myext4.SecKillFruit {
// Adaptive注解方法,通過ContextHolder.getUrl獲取URL
public int howMuch2(x.y.z.myext4.ContextHolder arg0) {
if (arg0 == null)
throw new IllegalArgumentException("...");
if (arg0.getUrl() == null)
throw new IllegalArgumentException("...");
org.apache.dubbo.common.URL url = arg0.getUrl();
String extName = url.getParameter("fruitType");
if (extName == null)
throw new IllegalStateException("...");
x.y.z.myext4.SecKillFruit extension = (x.y.z.myext4.SecKillFruit)
ExtensionLoader
.getExtensionLoader(x.y.z.myext4.SecKillFruit.class)
.getExtension(extName);
return extension.howMuch2(arg0);
}
// Adaptive注解方法,直接從參數(shù)列表中獲取URL
public int howMuch(org.apache.dubbo.common.URL arg0) {
if (arg0 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg0;
String extName = url.getParameter("fruitType");
if (extName == null)
throw new IllegalStateException("...");
x.y.z.myext4.SecKillFruit extension = (x.y.z.myext4.SecKillFruit)
ExtensionLoader
.getExtensionLoader(x.y.z.myext4.SecKillFruit.class)
.getExtension(extName);
return extension.howMuch(arg0);
}
// 沒有Adaptive注解的方法
public int howMuch() {
throw new UnsupportedOperationException("...");
}
}
Spring+jdk動態(tài)代理實現(xiàn)
上面原理分析不太好理解,這個事情也可以用Spring+jdk動態(tài)代理來實現(xiàn)。
其實這個需求和feign的FeignClient、mybatis的Mapper都比較像,寫完接口就相當于寫完實現(xiàn)。
針對同一個擴展點type設(shè)計一個 AdaptiveFactoryBean 。
public class AdaptiveFactoryBean implements FactoryBean
核心邏輯在InvocationHandler#invoke代理邏輯中,和AdaptiveClassCodeGenerator#generateMethodContent一樣。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (!cachedMethod2Adaptive.containsKey(method)) {
throw new UnsupportedOperationException();
}
// 1. 獲取URL
int urlIdx = cachedMethod2URLIndex.get(method);
URL url = (URL) args[urlIdx];
// 2. 從url里獲取擴展點名
Adaptive adaptive = cachedMethod2Adaptive.get(method);
String extName = null;
for (String key : adaptive.value()) {
extName = url.getParameter(key);
if (extName != null) {
break;
}
}
if (extName == null) {
extName = defaultExtName;
}
if (extName == null) {
throw new IllegalStateException();
}
// 3. 獲取擴展點
Object extension =
ExtensionLoader.getExtensionLoader(type).getExtension(extName);
// 4. 委派給擴展點
return method.invoke(extension, args);
}
為了注入所有包含Adaptive注解方法的擴展點AdaptiveFactoryBean,提供一個批量注冊BeanDefinition的 AdaptiveBeanPostProcessor ,實現(xiàn)比較粗糙,主要為了說明問題。
public class AdaptiveBeanPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware {
private final String packageToScan;
private Environment environment;
public AdaptiveBeanPostProcessor(String packageToScan) {
this.packageToScan = packageToScan;
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
ClassPathScanningCandidateComponentProvider scanner =
new ClassPathScanningCandidateComponentProvider(false, this.environment) {
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
// 有Adaptive注解方法
return beanDefinition.getMetadata()
.hasAnnotatedMethods("org.apache.dubbo.common.extension.Adaptive");
}
};
scanner.addIncludeFilter(new AnnotationTypeFilter(SPI.class));
Set
測試驗證:
@Configuration
public class AdaptiveFactoryBeanTest {
@Bean
public AdaptiveBeanPostProcessor adaptiveBeanPostProcessor() {
return new AdaptiveBeanPostProcessor("x.y.z.myext4");
}
@Test
void test() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(AdaptiveFactoryBeanTest.class);
applicationContext.refresh();
SecKillFruit secKillFruit = applicationContext.getBean("x.y.z.myext4.SecKillFruit$Adaptive_Spring", SecKillFruit.class);
URL url = new URL("myProtocol", "1.2.3.4", 1010, "path");
// 0元秒殺蘋果
url = url.addParameters("fruitType", "apple");
int money = secKillFruit.howMuch(url);
assertEquals(0, money);
// 1元秒殺香蕉
url = url.addParameters("fruitType", "banana");
money = secKillFruit.howMuch(url);
assertEquals(1, money);
// 無URL方法異常
assertThrows(UnsupportedOperationException.class, secKillFruit::howMuch);
}
}
是不是用spring+動態(tài)代理來說明,更加容易理解了。
依賴注入
無論是對于普通擴展點/包裝擴展點/自適應(yīng)擴展點,所有的擴展點實例都會經(jīng)過依賴注入。
案例
InjectExt是個擴展點,有實現(xiàn)InjectExtImplA,InjectExtImplA有一個Inner的setter方法。
public class InjectExtImplA implements InjectExt {
private Inner inner;
public void setInner(Inner inner) {
this.inner = inner;
}
@Override
public Inner getInner() {
return inner;
}
}
Inner是個擴展點,且能生成自適應(yīng)擴展實現(xiàn)。
@SPI
public interface Inner {
@Adaptive
String echo(URL url);
}
Inner有InnerA實現(xiàn)。
public class InnerA implements Inner {
@Override
public String echo(URL url) {
return "A";
}
}
測試方法,InjectExtImplA 被自動注入了Inner的自適應(yīng)實現(xiàn) 。
@Test
void testInject() {
ExtensionLoader
原理
ExtensionLoader#injectExtension依賴注入,循環(huán)每個setter方法,找到入?yún)lass和屬性名。
通過ExtensionFactory搜索依賴,整個注入過程的異常都被捕獲。
ExtensionFactory也是SPI接口。
這里走硬編碼實現(xiàn)的 AdaptiveExtensionFactory ,循環(huán)每個ExtensionFactory擴展點,通過type和name找擴展點實現(xiàn)。
ExtensionFactory擴展點有兩個實現(xiàn)。
原生的 SpiExtensionFactory , 沒有利用setter的屬性name ,直接獲取type對應(yīng)的自適應(yīng)擴展點。
這也是為什么案例中,被注入的擴展點用了Adaptive。
Spring相關(guān)的SpringExtensionFactory支持從多個ioc容器中,通過getBean(setter屬性名,擴展點)獲取bean。
激活擴展點
背景
ExtensionLoader#getExtension可以獲取單個擴展點實現(xiàn)。
ExtensionLoader#getSupportedExtensionInstances可以獲取所有擴展點實現(xiàn)。
現(xiàn)在 需要根據(jù)條件,獲取一類擴展點實現(xiàn),這就是所謂的激活擴展點 。
以Spring為例,如何利用Qualifier做到這點。
假設(shè)現(xiàn)在有個用戶接口,根據(jù)用戶類型和用戶等級有不同實現(xiàn)。
public interface User {
}
利用Qualifier注解,Category代表用戶類型,Level代表用戶等級。
@Qualifier
public @interface Category {
String value();
}
@Qualifier
public @interface Level {
int value();
}
針對User有四種實現(xiàn),包括vip1級用戶、vip2級用戶、普通用戶、普通2級用戶。
@Component
@Category("vip")
@Level(1)
public static class VipUser implements User {
}
@Component
@Category("vip")
@Level(2)
public static class GoldenVipUser implements User {
}
@Component
@Category("normal")
public static class UserImpl implements User {
}
@Component
@Category("normal")
@Level(2)
public static class UserImpl2 implements User {
}
通過Qualifier,可以按照需求注入不同類型等級用戶集合,做業(yè)務(wù)邏輯。
@Configuration
@ComponentScan
public class QualifierTest {
@Autowired
@Category("vip")
private List
案例
和上面Spring的案例一樣,替換成ExtensionLoader實現(xiàn),看起來語義差不多。
用戶等級作為分組,在URL參數(shù)上獲取用戶等級。
@Activate(group = {"vip"}, value = {"level:1"})
public class VipUser implements User {
}
@Activate(group = {"vip"}, value = {"level:2"}, order = 1000)
public class GoldenVipUser implements User {
}
@Activate(group = {"normal"}, order = 10)
public class UserImpl implements User {
}
@Activate(group = {"normal"}, value = {"level:2"}, order = 500)
public class UserImpl2 implements User {
}
測試方法如下,發(fā)現(xiàn)與Spring的Qualifier有相同也有不同。
比如通過group=vip和url不包含level去查詢:
1)UserImpl和UserImpl2查不到,因為group不滿足;
2)VipUser和GoldenVipUser查不到,因為url必須有l(wèi)evel,且分別為1和2;
又比如通過group=null和level=2去查詢:
1)UserImpl沒有設(shè)置Activate注解value,代表對url沒有約束,且查詢條件group=null,代表匹配所有g(shù)roup,所以可以查到;
2)VipUser對url有約束,必須level=1,所以查不到;
3)GoldenVipUser和UserImpl2,都滿足level=2,且查詢條件group=null,代表匹配所有g(shù)roup,所以都能查到;
@Test
void testActivate() {
ExtensionLoader
原理
類加載階段,激活擴展點在普通擴展點分支邏輯中。
所以 激活擴展點只是篩選普通擴展點的方式 ,屬于普通擴展點的子集。
ExtensionLoader#getActivateExtension獲取激活擴展點的入?yún)糠郑?/p>
1)查詢URL;2)查詢擴展點名稱集合;3)查詢分組
其中1和3用于Activate匹配,2用于直接從getExtension獲取擴展點加在Activate匹配的擴展點之后。
重點看isMatchGroup和isActive兩個方法。
isMatchGroup :如果查詢條件不包含group,則匹配,如果查詢條件包含group,注解中必須有g(shù)roup與其匹配。
isActive :匹配url
1)Activate沒有value約束,匹配
2)url匹配成功條件:如果注解value配置為k:v模式,要求url參數(shù)kv完全匹配;如果注解value配置為k模式,只需要url包含kv參數(shù)即可。其中k還支持后綴匹配。
比如@Activate(value = {"level"})只需要url中有l(wèi)evel=xxx即可,
而@Activate(value = {"level:2"})需要url中l(wèi)evel=2。
總結(jié)
本文分析了Dubbo2.7.6的SPI實現(xiàn)。
ExtensionLoader相較于java的spi能按需獲取擴展點,還有很多高級特性,與Spring的ioc和aop非常相似。
看似ExtensionLoader的功能都能通過Spring實現(xiàn),但是Dubbo不想依賴Spring,所以造了套輪子。
題外話:非常夸張的是,Dubbo一個RPC框架,竟然有27w行代碼,而同樣是RPC框架的sofa-rpc5.9.0只有14w行。
除了很多com.alibaba的老包兼容代碼,輪子是真的多,早期版本連json庫都是自己實現(xiàn)的,現(xiàn)在是換成fastjson了。
普通擴展點
ExtensionLoader#getExtension(name),普通擴展點通過擴展名獲取。
@SPI
public interface MyExt {
String echo(URL url, String s);
}
創(chuàng)建普通擴展點分為四個步驟
1)無參構(gòu)造
2)依賴注入
3)包裝
4)初始化
包裝擴展點
如果擴展點實現(xiàn)包含該擴展點的單參構(gòu)造方法,被認為是包裝擴展點。
public class WrapperExt implements Ext {
public WrapperExt(Ext ext) {
}
}
包裝擴展點無法通過擴展名顯示獲取,而是在用戶獲取普通擴展點時,自動包裝普通擴展點,返回給用戶,整個過程是透明的。
自適應(yīng)擴展點
ExtensionLoader#getAdaptiveExtension獲取自適應(yīng)擴展點。
每個擴展點最多只有一個自適應(yīng)擴展點。
自適應(yīng)擴展點分為兩類:硬編碼、動態(tài)生成。
硬編碼自適應(yīng)擴展點,在擴展點實現(xiàn)class上標注Adaptive注解,優(yōu)先級高于動態(tài)生成自適應(yīng)擴展點。
@Adaptive
public class AdaptiveFruit implements Fruit {
}
動態(tài)生成自適應(yīng)擴展點。
出現(xiàn)的背景是,dubbo中有許多依賴URL上下文選擇不同擴展點策略的場景,如果通過硬編碼實現(xiàn),會有很多重復(fù)代碼。
動態(tài)生成自適應(yīng)擴展點,針對@Adaptive注解方法且方法參數(shù)有URL的擴展點,采用javassist字節(jié)碼技術(shù),動態(tài)生成策略實現(xiàn)。
@SPI
public interface SecKillFruit {
@Adaptive("fruitType")
int howMuch(URL context);
}
激活擴展點
激活擴展點屬于普通擴展點的子集。
激活擴展點利用Activate注解,根據(jù)條件匹配一類擴展點實現(xiàn) 。
@Activate(group = {"vip"}, value = {"level:2"}, order = 1000)
public class GoldenVipUser implements User {
}
ExtensionLoader#getActivateExtension:通過group和URL查詢一類擴展點實現(xiàn)。
@Test
void testActivate() {
ExtensionLoader
依賴注入
無論是普通/包裝/自適應(yīng)擴展點,在暴露給用戶使用前,都會進行setter依賴注入。
依賴注入對象可來源于兩部分:
1)SpiExtensionFactory根據(jù)type獲取自適應(yīng)擴展點
2)SpringExtensionFactory根據(jù)setter屬性+type從ioc容器獲取擴展點
-
ECHO
+關(guān)注
關(guān)注
1文章
73瀏覽量
27188 -
RPC
+關(guān)注
關(guān)注
0文章
111瀏覽量
11542 -
URL
+關(guān)注
關(guān)注
0文章
139瀏覽量
15393 -
SPI接口
+關(guān)注
關(guān)注
0文章
259瀏覽量
34453
發(fā)布評論請先 登錄
相關(guān)推薦
評論