1. 介紹
在dubbo系列前面的章節中,我們可以創建指定的擴展點對象,那如果擴展點中包含另一個擴展點屬性,屬性是如何創建的,如果我們期望在擴展點目標方法前後增加切面,dubbo又是如何處理的,本章將介紹這些內容。
2.Dubbo IOC示例
業務場景說明:bird有一個Animal屬性,我們在運行時會通過Url總線設置這個屬性的類型爲cat,然後通過bird調用cat的speak方法。
修改擴展點接口,新增加一個speak(Url url)方法,並且標註自適應註解
@SPI
public interface Animal {
public void speak();
@Adaptive("friend")
public void speak(URL url);
}
對應實現類:
public class Cat implements Animal{
@Override
public void speak() {
System.out.println("I'm a cat");
}
@Override
public void speak(URL url) {
System.out.println("I'm cat");
}
}
public class Bird implements Animal{
private Animal friend;
public void setFriend(Animal friend) {
this.friend = friend;
}
@Override
public void speak() {
System.out.println("I'm a bird");
}
@Override
public void speak(URL url) {
friend.speak(url);
}
}
測試類:
public static void main(String[] args) {
ExtensionLoader<Animal> extensionLoader = ExtensionLoader.getExtensionLoader(Animal.class);
Animal animal = extensionLoader.getExtension("bird");
Map<String,String> map = new HashMap<>();
map.put("friend","cat");
URL url = new URL("","",1,map);
animal.speak(url);
}
執行結果:I’m cat
3.Dubbo IOC原理
熟悉spring的同學應該清楚,在spring的ioc中,例如A中有B屬性,那麼要注入B,會通過xml或註解指定B的引用,然後在A實例化時,會通過指定的引用實例化B。但是在上面的例子中,在我們創建bird擴展點時,並沒有指定friend屬性的類型,而是在運行speak方法時,通過url參數指定了friend的類型,這個動作似乎和上一篇文章介紹的自適應擴展點很相似,沒錯,dubbo的ioc就是通過自適應擴展點來實現的,在創建bird時,他的friend是一個自適應擴展點,然後在運行擴展方法時,才知道具體類型。
4. Dubbo IOC源碼分析
Dubbo IOC 是通過 setter 方法注入依賴。Dubbo 首先會通過反射獲取到實例的所有方法,然後再遍歷方法列表,檢測方法名是否具有 setter 方法特徵。若有,則通過 ObjectFactory 獲取依賴對象,最後通過反射調用 setter 方法將依賴設置到目標對象中。整個過程對應的代碼如下:
private T injectExtension(T instance) {
try {
if (objectFactory != null) {
// 遍歷目標類的所有方法
for (Method method : instance.getClass().getMethods()) {
// 檢測方法是否以 set 開頭,且方法僅有一個參數,且方法訪問級別爲 public
if (method.getName().startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers())) {
// 獲取 setter 方法參數類型
Class<?> pt = method.getParameterTypes()[0];
try {
// 獲取屬性名,比如 setName 方法對應屬性名 name
String property = method.getName().length() > 3 ?
method.getName().substring(3, 4).toLowerCase() +
method.getName().substring(4) : "";
// 從 ObjectFactory 中獲取依賴對象
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
// 通過反射調用 setter 方法設置依賴
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("fail to inject via method...");
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
在上面代碼中,objectFactory 變量的類型爲 AdaptiveExtensionFactory,AdaptiveExtensionFactory 內部維護了一個 ExtensionFactory 列表,用於存儲其他類型的 ExtensionFactory。
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
private final List<ExtensionFactory> factories;
public AdaptiveExtensionFactory() {
...省略
}
@Override
public <T> T getExtension(Class<T> type, String name) {
for (ExtensionFactory factory : factories) {
T extension = factory.getExtension(type, name);
if (extension != null) {
return extension;
}
}
return null;
}
}
在上面的getExtension方法中,factories表示當前支持的所有factory擴展點,具體包括SpiExtensionFactoryhe SpringExtensionFactory,當前的場景時從SpiExtensionFactoryhe獲取擴展點,代碼如下:
public class SpiExtensionFactory implements ExtensionFactory {
@Override
public <T> T getExtension(Class<T> type, String name) {
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
if (!loader.getSupportedExtensions().isEmpty()) {
return loader.getAdaptiveExtension();
}
}
return null;
}
}
又看到了熟悉的loader.getAdaptiveExtension(),至此真相大白。
2.Dubbo AOP示例
如果我們期望在speak前後增加切面動作,那麼代碼改動如下:
public class AnimalLogWrapper implements Animal {
private Animal animal;
public AnimalLogWrapper(Animal animal){
this.animal = animal;
}
@Override
public void speak() {
System.out.println("before speak");
animal.speak();
System.out.println("after speak");
}
@Override
public void speak(URL url) {
}
}
新建一個wrapper類,實現Animal接口,並且包含一個Animal屬性,且擁有帶參構造方法。
bird=com.hy.study.spi.Bird
cat=com.hy.study.spi.Cat
dog=com.hy.study.spi.Dog
com.hy.study.spi.AnimalLogWrapper
把當前的wrapper類寫入配置文件
public static void main(String[] args) {
ExtensionLoader<Animal> extensionLoader = ExtensionLoader.getExtensionLoader(Animal.class);
Animal animal = extensionLoader.getExtension("bird");
animal.speak();
}
運行結果:
before speak
I’m a bird
after speak
3.Dubbo AOP源碼分析
private T createExtension(String name) {
...省略
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
...省略
}
cachedWrapperClasses是從配置文件中獲取的wrapper對象,可以包含多個wrapper對象,然後把當前bird擴展點的實現注入到wrapper的屬性中,從而實現了切面。