当前位置: 首页 - 数码 - SPI相关知识点

SPI相关知识点

2024-12-19 数码 0

SPI相关知识点

一、什么是SPI

SPI全称为Service Provider Interface,是一种服务发现机制,其本质是将界面实现类的全限定名配置在档案中,并由服务载入器读取配置档案。这样可以在执行时,动态为该界面替换实现类。

JDK提供了预设的SPI实现,但是Dubbo并未使用JDK提供的SPI,而是自己封装了一套。我们先来通过Dubbo官网给的两个例子简单了解下JDK和Dubbo的SPI是如何使用的。

1.1.JDK SPI示例

首先定义一个界面以及它的两个实现类

1 public interface Robot {

2 void sayHello();

3 }

4

5 public class OptimusPrime implements Robot {

6

7 @Override

8 public void sayHello() {

9 System.out.println(Hello, I am Optimus Prime.);

10 }

11 }

12

13 public class Bumblebee implements Robot {

14

15 @Override

16 public void sayHello() {

17 System.out.println(Hello, I am Bumblebee.);

18 }

19 }

接下来,在专案(以一个典型的maven专案为例)的“resources/META-INF/services”路径下建立一个档案,名称为Robot的全限定名“org.apache.spi.Robot”。档案内容如下:

org.apache.spi.OptimusPrime

org.apache.spi.Bumblebee

编写测试程式码,执行之后可以看到两个实现类被载入并呼叫了sayHello方法(呼叫结果演示略)。

public class JavaSPITest {

@Test

public void sayHello() throws Exception {

ServiceLoader serviceLoader = ServiceLoader.load(Robot.class);

System.out.println(Java SPI);

serviceLoader.forEach(Robot::sayHello);

}

}

1.2.Dubbo SPI示例

仍然使用JDK SPI示例中的界面和其实现类程式码,不过需要在界面Robot上添加注解@SPI

//使用SPI注解标注的界面

@SPI

public interface Robot {

void sayHello();

}

接下来,在专案(以一个典型的maven专案为例)的“resources/META-INF/dubbo”路径下建立一个档案,名称为Robot的全限定名“org.apache.spi.Robot”。档案内容如下:

optimusPrime=org.apache.spi.OptimusPrime

bumblebee=org.apache.spi.Bumblebee

通过测试类进行测试,则会执行相应实现类的sayHello方法:

//测试类

public class DubboSPITest {

@Test

public void sayHello() throws Exception {

//传入一个标注有@SPI的界面Class,通过getExtensionLoader获取该SPI界面的ExtensionLoader例项

ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class);

//传入要获取的实现类的name(META-INF/dubbo/org.apache.spi.Robot档案下的name),获取实现类例项

Robot optimusPrime = extensionLoader.getExtension(optimusPrime);

optimusPrime.sayHello();

Robot bumblebee = extensionLoader.getExtension(bumblebee);

bumblebee.sayHello();

}

}

通过与JDK SPI示例的比较,发现Dubbo SPI与JDK SPI最大的不同就是Dubbo SPI通过键值对的方式进行配置。这样最大的好处是可以按需载入指定的实现类(通过name指定)。下面就让我们以本例中getExtensionLoader与getExtension两个方法作为引子,分析Dubbo SPI的实现源代码。

回到顶部

二、getExtensionLoader

该方法根据SPI界面型别建立一个ExtensionLoader例项,即每种型别的SPI界面都有一个相应的ExtensionLoader。程式码如下所示:

/**************************************** 相关字段 ****************************************/

//ExtensionLoader对应的SPI界面型别

private final Class> type;

//ExtensionLoader对应的ExtensionFactory例项

private final ExtensionFactory objectFactory;

//ExtensionLoader全域性快取,key为SPI界面型别,value为相应的ExtensionLoader例项

private static final ConcurrentMap, ExtensionLoader>> EXTENSION_LOADERS = new ConcurrentHashMap();

/**************************************** 相关方法 ****************************************/

/**

* 静态方法,根据SPI界面型别获取相应的ExtensionLoader

*/

public static ExtensionLoader getExtensionLoader(Class type) {

//判断type是否为空、是否是界面型别、是否具有@SPI注解

if (type == null) {

throw new IllegalArgumentException(Extension type == null);

}

if (!type.isInterface()) {

throw new IllegalArgumentException(Extension type ( + type + ) is not an interface!);

}

if (!withExtensionAnnotation(type)) {

throw new IllegalArgumentException(Extension type ( + type + ) is not an extension...);

}

//从ExtensionLoader的快取中根据SPI界面型别获取对应的ExtensionLoader例项

ExtensionLoader loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);

//若快取没有该例项,则new一个,并且存放入快取,key为type

if (loader == null) {

EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));

loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);

}

return loader;

}

/**

* 私有构造器,对呼叫者而言,只能通过getExtensionLoader方法获取ExtensionLoader例项

*/

private ExtensionLoader(Class> type) {

//储存该ExtensionLoader的SPI界面型别资讯

this.type = type;

//若SPI界面型别为ExtensionFactory,则不设定字段ExtensionFactory,

//否则需要设定一个ExtensionFactory。具体的获取逻辑我们后面会讲到

objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());

}

回到顶部

三、getExtension

该方法用于获取ExtensionLoader对应的SPI界面的实现类例项,name用于指定需要获取的实现类型别。在后面,我们将SPI界面实现类统一称为扩充套件类。

/**************************************** 例项快取相关字段 ****************************************/

//全部SPI界面的扩充套件类例项快取,key为扩充套件类Class(每一个SPI界面的每一个实现类的Class都不同)

//value为对应的扩充套件类例项。注意该快取要与另外一个类似的快取cachedInstances区分开。

private static final ConcurrentMap, Object> EXTENSION_INSTANCES = new ConcurrentHashMap();

//每个ExtensionLoader对应的SPI界面的扩充套件类例项快取,

//key为扩充套件类的name,value为Holder物件,其持有/维护扩充套件类的例项。

private final ConcurrentMap> cachedInstances = new ConcurrentHashMap();

/**************************************** Class快取相关字段 ****************************************/

//每个ExtensionLoader对应的SPI界面的扩充套件类Class快取,key为扩充套件类的name,value为扩充套件类Class

private final Holder>> cachedClasses = new Holder();

//SPI界面的扩充套件类中具有@Adaptive注解的扩充套件类Class快取

private volatile Class> cachedAdaptiveClass = null;

//SPI界面的扩充套件类中被判定为具有包装功能的扩充套件类Class快取

private Set> cachedWrapperClasses;

//SPI界面的扩充套件类中具有@Activate注解的快取,key是扩充套件类names[0],value为@Activate的Class

private final Map cachedActivates = new ConcurrentHashMap();

/**************************************** name快取相关字段 ****************************************/

//SPI界面的扩充套件类的name快取,key为扩充套件类的Class,value为扩充套件类的names[0]

private final ConcurrentMap, String> cachedNames = new ConcurrentHashMap();

//SPI界面的预设扩充套件类的name

private String cachedDefaultName;

/**************************************** 其他相关字段 ****************************************/

//ExtensionLoader对应的SPI界面Class

private final Class> type;

/**************************************** 相关方法 ****************************************/

/**

* 获取ExtensionLoader对应的SPI界面的扩充套件类例项

*/

public T getExtension(String name) {

//检查扩充套件类的name

if (StringUtils.isEmpty(name)) {

throw new IllegalArgumentException(Extension name == null);

}

//如果传入的name值为true,则获取预设的SPI界面扩充套件类例项

if (true.equals(name)) {

return getDefaultExtension();

}

//getOrCreateHolder方法比较简单,它从快取“cachedInstances”中获取该name对应的Holder例项,

//Holder是一个“持有类”,其可能持有扩充套件类例项

Holder holder = getOrCreateHolder(name);

Object instance = holder.get();

//如果未获取到例项,在监视器锁中进行第二次获取与建立

if (instance == null) {

synchronized (holder) {

instance = holder.get();

if (instance == null) {

//建立name对应的扩充套件类例项,并快取到cachedInstances中

instance = createExtension(name);

holder.set(instance);

}

}

}

return (T) instance;

}

/**

* 获取预设的扩充套件类例项

*/

public T getDefaultExtension() {

//呼叫getExtensionClasses方法获取SPI界面配置的扩充套件类资讯,返回结果为一个Map,

//其中key为扩充套件类name,value为该name对应的扩充套件类Class。

getExtensionClasses();

//如果SPI界面预设扩充套件类name为空或者为true,则预设的扩充套件类例项为null

if (StringUtils.isBlank(cachedDefaultName) true.equals(cachedDefaultName)) {

return null;

}

//否则呼叫getExtension根据预设的扩充套件类name去获取例项

return getExtension(cachedDefaultName);

}

/**

* 建立一个扩充套件类的例项

*/

private T createExtension(String name) {

//呼叫getExtensionClasses方法,从返回结果中获取name对应的扩充套件类Class,如果Class为null,则丢掷异常

Class> clazz = getExtensionClasses().get(name);

if (clazz == null) {

throw findException(name);

}

try {

//从快取“EXTENSION_INSTANCES”中根据扩充套件类Class获取例项

T instance = (T) EXTENSION_INSTANCES.get(clazz);

if (instance == null) {

//快取未命中则通过反射建立一个例项,并存入快取

EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());

instance = (T) EXTENSION_INSTANCES.get(clazz);

}

//向这个扩充套件类例项注入其所需要的属性(以setXXX为准),该方法比较复杂,我们后面会进行分析

injectExtension(instance);

//获取SPI界面扩充套件类中具有包装功能的扩充套件类Class快取,然后对instance进行层层包装(装饰器模式),

//对每次包装出来的新例项进行属性注入,全部包装完成后让instance指向最终的包装结果例项

Set> wrapperClasses = cachedWrapperClasses;

if (CollectionUtils.isNotEmpty(wrapperClasses)) {

for (Class> wrapperClass : wrapperClasses) {

instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));

}

}

return instance;

} catch (Throwable t) {

throw new IllegalStateException(Extension instance ... couldn\t be instantiated: );

}

}

/**

* 获取SPI界面配置的扩充套件类资讯

*/

private Map> getExtensionClasses() {

//从快取“cachedClasses”中获取已载入的扩充套件类

Map> classes = cachedClasses.get();

if (classes == null) {

synchronized (cachedClasses) {

classes = cachedClasses.get();

if (classes == null) {

//快取为空,呼叫loadExtensionClasses方法载入SPI界面配置的扩充套件类资讯,并快取结果

classes = loadExtensionClasses();

cachedClasses.set(classes);

}

}

}

return classes;

}

/**

* 载入SPI界面配置的扩充套件类资讯

*/

private Map> loadExtensionClasses() {

//获取SPI界面预设扩充套件类的name并进行快取

cacheDefaultExtensionName();

//读取并解析SPI界面的配置档案,去几个固定的目录下读取

//(1):META-INF/services/SPI界面全限定类名(或SPI界面全限定类名替换org.apache为com.alibaba)

//(2):META-INF/dubbo/SPI界面全限定类名(或SPI界面全限定类名替换org.apache为com.alibaba)

//(3):META-INF/dubbo/internal/SPI界面全限定类名(或SPI界面全限定类名替换org.apache为com.alibaba)

Map> extensionClasses = new HashMap();

loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());

loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace(org.apache,

com.alibaba));

loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());

loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace(org.apache,

com.alibaba));

loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());

loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace(org.apache,

com.alibaba));

return extensionClasses;

}

/**

* 获取SPI界面预设扩充套件类的name并进行快取

*/

private void cacheDefaultExtensionName() {

//获取ExtensionLoader对应的SPI界面上的SPI注解

final SPI defaultAnnotation = type.getAnnotation(SPI.class);

if (defaultAnnotation != null) {

//获取SPI注解的value值并进行校验

String value = defaultAnnotation.value();

if ((value = value.trim()).length() > 0) {

String[] names = NAME_SEPARATOR.split(value);

if (names.length > 1) {

throw new IllegalStateException(More than 1 default extension name ...);

}

if (names.length == 1) {

//将其作为SPI界面预设扩充套件类的name并进行快取

cachedDefaultName = names[0];

}

}

}

}

/**

* 载入SPI界面的配置档案

*/

private void loadDirectory(Map> extensionClasses, String dir, String type) {

String fileName = dir + type;

try {

Enumeration urls;

//获取并使用类载入器去载入档案(同名档案进行内容合并)

ClassLoader classLoader = findClassLoader();

if (classLoader != null) {

urls = classLoader.getResources(fileName);

} else {

urls = ClassLoader.getSystemResources(fileName);

}

if (urls != null) {

//遍历获取到的URL,并呼叫loadResource去载入资源

while (urls.hasMoreElements()) {

java.net.URL resourceURL = urls.nextElement();

loadResource(extensionClasses, classLoader, resourceURL);

}

}

} catch (Throwable t) {

logger.error(Exception occurred when loading extension class (interface: ...);

}

}

/**

* 在loadDirectory的基础上,对每个档案中的内容进行载入与解析

*/

private void loadResource(Map> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {

try {

try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {

String line;

while ((line = reader.readLine()) != null) {

//按行读取,每一行先去掉#号后面的内容(#号后面的内容为注释)

final int ci = line.indexOf(\#\);

if (ci >= 0) {

line = line.substring(0, ci);

}

line = line.trim();

if (line.length() > 0) {

try {

String name = null;

//按“=”号撷取name和扩充套件类的全限定类名,可以看出name是可以没有的

int i = line.indexOf(\=\);

if (i > 0) {

name = line.substring(0, i).trim();

line = line.substring(i + 1).trim();

}

//如果line,即扩充套件类全限定类名不为空,通过Class.forName获取其Class,

//然后呼叫loadClass继续载入该Class

if (line.length() > 0) {

loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);

}

} catch (Throwable t) {

IllegalStateException e = new IllegalStateException(Failed to load ...);

exceptions.put(line, e);

}

}

}

}

} catch (Throwable t) {

logger.error(Exception occurred when loading extension class (interface: ...);

}

}

/**

* 在loadResource的基础上,对档案中的每行获取到的Class进行分析,并进行快取

*/

private void loadClass(Map> extensionClasses, java.net.URL resourceURL, Class> clazz, String name) throws NoSuchMethodException {

//判断配置的扩充套件类是否为指定SPI界面的实现类

if (!type.isAssignableFrom(clazz)) {

throw new IllegalStateException(Error occurred when loading ... is not subtype of interface.);

}

if (clazz.isAnnotationPresent(Adaptive.class)) {

//若扩充套件类有@Adaptive注解,将这个Class存入快取“cachedAdaptiveClass”

//注意:一个SPI界面配置档案中,只能配置一个有@Adaptive注解的扩充套件类

cacheAdaptiveClass(clazz);

} else if (isWrapperClass(clazz)) {

//若扩充套件类具有clazz.getConstructor(type)这样的构造器,则认为其是一个具有包装功能的扩充套件类,

//并将其存入快取“cachedWrapperClasses”,一个SPI界面可以配置多个用于包装的扩充套件类

cacheWrapperClass(clazz);

} else {

//进入到这个分支,表示该Class只是一个普通的SPI界面扩充套件类。

//判断该扩充套件类是否具有无参构造器

clazz.getConstructor();

//如果该扩充套件类在SPI界面配置档案中未定义name,则判断扩充套件类是否具有@Extension注解,

//如果有,则以@Extension的value值作为name;否则以扩充套件类的SimpleName作为name,

//并且,如果扩充套件类的SimpleName以SPI界面的SimpleName作为字尾结尾,则name需要去掉该字尾

if (StringUtils.isEmpty(name)) {

name = findAnnotationName(clazz);

if (name.length() == 0) {

throw new IllegalStateException(No such extension name for the class ...);

}

}

//对name按照,进行分割

String[] names = NAME_SEPARATOR.split(name);

if (ArrayUtils.isNotEmpty(names)) {

//names不为空

//若扩充套件类具有@Activate注解,则使用names阵列的第一个元素作为key,@Activate的Class为value

//将对映关系存入快取“cachedActivates”

cacheActivateClass(clazz, names[0]);

for (String n : names) {

//将该扩充套件类的name和Class存入快取“cachedNames”,name保持为names[0]

cacheName(clazz, n);

//将该扩充套件类的name和Class存入方法引数extensionClasses

saveInExtensionClass(extensionClasses, clazz, name);

}

}

}

}

通过以上的源代码分析,我们了解到getExtension方法获取一个SPI界面的扩充套件类例项的流程分为解析配置档案、载入并快取扩充套件类、建立并加工扩充套件类例项几个步骤。

在得到一个最终可用的扩充套件类例项前,该例项会进行属性注入与层层包装,这些行为在官网上被成为扩充套件点特性,这里我们把它称之为“扩充套件类特性”。官方给出了4个特性,分别为“扩充套件点自动包装”、“扩充套件点自动装配”、“扩充套件点自适应”以及“扩充套件点自动启用”。在下面的其余章节中,我们将重点来研究这几个扩充套件点特性。

回到顶部

四、扩充套件点自适应

在分析与getExtension方法密切相关的扩充套件点自动装配与扩充套件点自动包装之前,我们先来了解一下扩充套件点自适应这个特性。扩充套件点自适应是一个非常重要的特性,因为dubbo某些SPI界面的扩充套件并不想在框架启动阶段被载入,而是希望在拓展方法被呼叫时,根据执行时引数进行载入,这种动态决定呼叫哪个扩充套件类例项的方式使得dubbo非常的灵活。此外,先了解这个特性,也有助于我们更好的分析自动装配与自动包装的源代码。

4.1.什么是自适应扩充套件类

自适应扩充套件类同样是某个SPI界面的扩充套件类,该扩充套件类具备这样的一些特征:它没有实际的业务逻辑,而是能够根据传入的引数,动态的获取对应的扩充套件类例项。可以看一下官网给的例子:

/**

* 一个模拟的SPI界面

*/

@SPI

public interface WheelMaker {

Wheel makeWheel(URL url);

}

/**

* SPI界面的自适应扩充套件类

*/

public class AdaptiveWheelMaker implements WheelMaker {

//自适应扩充套件类该方法的逻辑为通过URL中的引数,动态的获取真正需要执行的SPI界面扩充套件类

public Wheel makeWheel(URL url) {

if (url == null) {

throw new IllegalArgumentException(url == null);

}

// 1.从URL中获取WheelMaker名称

String wheelMakerName = url.getParameter(Wheel.maker);

if (wheelMakerName == null) {

throw new IllegalArgumentException(wheelMakerName == null);

}

// 2.通过SPI载入具体的WheelMaker

WheelMaker wheelMaker = ExtensionLoader.getExtensionLoader(WheelMaker.class).getExtension(wheelMakerName);

// 3.呼叫目标方法

return wheelMaker.makeWheel(URL url);

}

}

/**

* 一个模拟的SPI界面

*/

@SPI

public interface CarMaker {

Car makeCar(URL url);

}

/**

* 汽车制造者SPI界面的扩充套件类

*/

public class RaceCarMaker implements CarMaker {

WheelMaker wheelMaker;

//在injectExtension方法中会通过setter注入AdaptiveWheelMaker

//目前我们只知道自动装配行为的结果,原因会在“扩充套件点自动装配”小节分析

public setWheelMaker(WheelMaker wheelMaker) {

this.wheelMaker = wheelMaker;

}

//实现的方法

public Car makeCar(URL url) {

//实际呼叫AdaptiveWheelMaker的makeWheel方法,获得当前执行环境引数url下需要使用的Wheel扩充套件类

Wheel wheel = wheelMaker.makeWheel(url);

return new RaceCar(wheel, ...);

}

}

假设执行时传入这样一个url引数“dubbo://192.168.0.101:20880/XxxService?wheel.maker=MichelinWheelMaker”,那么RaceCar最终的wheel为扩充套件类“MichelinWheelMaker”制造出来的轮胎。

4.2.获取自适应扩充套件类例项

让我们回到ExtensionLoader类,在该类中提供了getAdaptiveExtension方法用于获取一个SPI界面的自适应扩充套件类的例项,这个方法的源代码分析如下:

/**************************************** 相关字段 ****************************************/

//快取的自适应扩充套件类例项

private final Holder cachedAdaptiveInstance = new Holder();

//SPI界面的扩充套件类中具有@Adaptive注解的扩充套件类Class快取

private volatile Class> cachedAdaptiveClass = null;

//建立自适应扩充套件类例项的异常资讯

private volatile Throwable createAdaptiveInstanceError;

/**************************************** 相关方法 ****************************************/

/**

* 获取自适应扩充套件类的例项

*/

public T getAdaptiveExtension() {

//从ExtensionLoader的快取字段中获取资料,cachedAdaptiveInstance为一个Holder物件

Object instance = cachedAdaptiveInstance.get();

if (instance == null) {

//需要判断一下建立自适应扩充套件物件的Throwable快取是否存在,如果存在,直接丢掷

if (createAdaptiveInstanceError == null) {

//并发控制

synchronized (cachedAdaptiveInstance) {

instance = cachedAdaptiveInstance.get();

if (instance == null) {

try {

//建立自适应扩充套件类例项

instance = createAdaptiveExtension();

//设定快取

cachedAdaptiveInstance.set(instance);

} catch (Throwable t) {

createAdaptiveInstanceError = t;

throw new IllegalStateException(......);

}

}

}

} else {

throw new IllegalStateException(......);

}

}

return (T) instance;

}

/**

* 建立自适应扩充套件类例项

*/

private T createAdaptiveExtension() {

try {

//(1)呼叫getAdaptiveExtensionClass方法获取自适应扩充套件类的Class;

//(2)通过newInstance例项化一个自适应扩充套件类的物件;

//(3)呼叫injectExtension方法向自适应拓展类的例项中注入依赖,参考“扩充套件点自动装配”小节;

return injectExtension((T) getAdaptiveExtensionClass().newInstance());

} catch (Exception e) {

throw new IllegalStateException(......);

}

}

/**

* 获取自适应扩充套件类的Class

*/

private Class> getAdaptiveExtensionClass() {

//获取该ExtensionLoader对应SPI界面配置的所有扩充套件类(参考“getExtensionClsses”小节)

getExtensionClasses();

//检查具有@Adaptive注解的扩充套件类快取,若快取不为空,则直接返回快取

if (cachedAdaptiveClass != null) {

return cachedAdaptiveClass;

}

//如果SPI界面配置的所有扩充套件类都没有被@Adaptive标注,则建立自适应扩充套件类

return cachedAdaptiveClass = createAdaptiveExtensionClass();

}

/**

* 建立自适应扩充套件类

*/

private Class> createAdaptiveExtensionClass() {

//通过AdaptiveClassCodeGenerator的generate方法建立自适应扩充套件程式码

//该方检测SPI界面中是否有被@Adapative注解的方法,对于要生成自适应扩充套件类的SPI界面

//必须至少包含一个被@Adaptive注解的方法,否则会丢掷异常

String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();

ClassLoader classLoader = findClassLoader();

//获取编译器实现类,一样是通过AdaptiveExtension进行获取,获取之后进行编译

org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();

return compiler.compile(code, classLoader);

}

通过getAdaptiveExtension方法的流程可以发现,要想获得一个SPI界面的自适应扩充套件类例项,有2种方式:

在SPI界面的配置档案中配置具有@Adaptive注解的扩充套件类,在执行解析SPI界面配置档案方法getExtensionClasses时,它会呼叫loadClass方法,该方法判断扩充套件类是否具有@Adaptive注解,如果有,则将该类Class快取到ExtensionLoader的字段“cachedAdaptiveClass”中,然后直接例项化该Class的例项并进行自动装配;如果未配置@Adaptive修饰的扩充套件类,则Dubbo会使用字节码技术建立一个自适应扩充套件类,前提是SPI界面上至少有一个被@Adaptive注解的方法;在上述两种建立自适应扩充套件类例项的方法中,都与@Adaptive这个注解息息相关,该注解的程式码如下:

@Documented

@Retention(RetentionPolicy.RUNTIME)

@Target({ElementType.TYPE, ElementType.METHOD})

public @interface Adaptive {

String[] value() default {};

}

可以看到,@Adaptive注解既可以使用在类上,又可以使用在方法上。其包含一个字串阵列的属性,在通过字节码技术建立自适应扩充套件类时,该属性参与到生成逻辑中,具体的建立逻辑我们马上通过下一节来了解。

4.3.关于建立自适应扩充套件类

Dubbo通过字节码技术建立一个自适应扩充套件类,第一步使用AdaptiveClassCodeGenerator类建立一个自适应扩充套件类的程式码字串,第二步通过ExtensionLoader获取字节码编译器Compiler并编译载入第一步中的程式码字串,拿到自适应扩充套件类的Class。下面我们对每一个步骤进行详细的源代码分析。

4.3.1.程式码拼接

回顾一下ExtensionLoader.createAdaptiveExtensionClass方法中呼叫逻辑

String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();

首先传入SPI界面的Class与预设的扩充套件类名称(即@SPI注解的value值)建立一个AdaptiveClassCodeGenerator例项,之后呼叫generate方法生成程式码,AdaptiveClassCodeGenerator相对应的源代码如下:

/**************************************** 相关字段 ****************************************/

//SPI界面型别

private final Class> type;

//SPI界面预设的扩充套件类名称,即@SPI注解的value属性值

private String defaultExtName;

/**************************************** 相关方法 ****************************************/

/**

* 可以看到,构造器并未做太多的事情,只是简单的将传入的引数为成员变数赋值

*/

public AdaptiveClassCodeGenerator(Class> type, String defaultExtName) {

this.type = type;

this.defaultExtName = defaultExtName;

}

/**

* 建立自适应扩充套件类的程式码串

*/

public String generate() {

//遍历SPI界面是否具有@Adaptive注解修饰的方法,如果没有则丢掷异常

if (!hasAdaptiveMethod()) {

throw new IllegalStateException(......);

}

//按照一个JAVA类的程式码组成顺序,拼接程式码字串

StringBuilder code = new StringBuilder();

//拼接包资讯字串: package + SPI界面所在的包

code.append(generatePackageInfo());

//拼接import字串: import + ExtensionLoader的全限定名

code.append(generateImports());

//拼接类开头字串: public class + SPI界面简单名 + $Adaptive implements + SPI界面全限定名 + {

//注意:使用全限定名的原因为import程式码串中只汇入ExtensionLoader的类,下同

code.append(generateClassDeclaration());

//拼接每一个方法字串,逻辑比较复杂,在generateMethod方法源代码中详细说明

Method[] methods = type.getMethods();

for (Method method : methods) {

code.append(generateMethod(method));

}

//拼接类结束字串: }

code.append(});

return code.toString();

}

/**

* 检测该SPI界面是否具有@Adaptive修饰的方法

*/

private boolean hasAdaptiveMethod() {

return Arrays.stream(type.getMethods()).anyMatch(m -> m.isAnnotationPresent(Adaptive.class));

}

/**

* 生成自适应扩充套件类的方法程式码串

*/

private String generateMethod(Method method) {

//获取方法返回值型别全限定名

String methodReturnType = method.getReturnType().getCanonicalName();

//获取方法名

String methodName = method.getName();

//获取方法内容,有无@Adaptive修饰的方法其方法内容不同,详细见下generateMethodContent源代码

String methodContent = generateMethodContent(method);

//获取方法引数列表,格式为引数型别全限定名 arg0, 引数型别全限定名 arg1, ...

String methodArgs = generateMethodArguments(method);

//获取方法异常丢掷,格式为throws 异常1全限定名, 异常2全限定名, ...

String methodThrows = generateMethodThrows(method);

//获取一个方法程式码串

return String.format(CODE_METHOD_DECLARATION, methodReturnType, methodName, methodArgs, methodThrows, methodContent);

}

/**

* 生成自适应扩充套件类的方法体程式码串

*/

private String generateMethodContent(Method method) {

Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);

StringBuilder code = new StringBuilder(512);

//判断当前要生成的方法是否有@Adaptive注解

if (adaptiveAnnotation == null) {

//对于没有@Adaptive注解的方法,生成throw new UnsupportedOperationException(...)程式码

return generateUnsupported(method);

} else {

//检查方法引数列表中是否有型别为com.apache.dubbo.common.URL的引数

//简单提一下,com.apache.dubbo.common.URL是dubbo框架中各元件的资料总线

int urlTypeIndex = getUrlTypeIndex(method);

if (urlTypeIndex != -1) {

//如果方法引数列表中有URL型别引数,则为该引数生成判断是否为null以及赋值的程式码:

//(generateUrlNullCheck方法比较简单,不进行详细的源代码分析,这里只给出逻辑)

//if (arg%d == null) throw new IllegalArgumentException(url == null);

//com.apache.dubbo.common.URL url = arg%d;

//d的值为urlTypeIndex

code.append(generateUrlNullCheck(urlTypeIndex));

} else {

//如果方法引数列表中没有URL型别引数,则需要遍历引数列表中每一个引数的型别资讯,

//判断哪一个引数具有public URL getXXX()签名形式的方法,如果有则停止遍历且生成如下程式码:

//(generateUrlAssignmentIndirectly方法比较简单,不进行详细的源代码分析,这里只给出逻辑)

//if (arg%d == null) 备注:此处的d为具备public URL getXXX()方法的引数下标,从0开始

// throw new IllegalArgumentException(引数全限定名 + argument == null);

//if (arg%d.getter方法名() == null)

// throw new IllegalArgumentException(引数全限定名 + argument getUrl() == null);

//com.apache.dubbo.common.URL url = arg%d.getter方法名();

code.append(generateUrlAssignmentIndirectly(method));

}

//获取该方法的@Adaptive注解的属性值value(为一个String阵列),这里列出处理逻辑

//如果属性值value为非空阵列,直接获取阵列内容即可;

//如果value为空阵列,则需将SPI界面的类名按照驼峰命名法进行检测,对每个驼峰进行分割并插入.号,

//然后转为小写,比如LoadBalance经过处理后,得到load.balance

String[] value = getMethodAdaptiveValue(adaptiveAnnotation);

//判断当前方法的引数列表中是否有型别为org.apache.dubbo.rpc.Invocation的引数

boolean hasInvocation = hasInvocationArgument(method);

//为当前方法引数列表中第一个型别为org.apache.dubbo.rpc.Invocation的引数生成判null语句以及呼叫语句

//if (arg%d == null) throw new IllegalArgumentException(invocation == null);

//String methodName = arg%d.getMethodName();

//%d是org.apache.dubbo.rpc.Invocation型别的引数在引数列表中的下标

code.append(generateInvocationArgumentNullCheck(method));

//使用前面获取到的字串阵列value以及Invocation引数存在标识拼接获取扩充套件类extName的程式码串,

//这是自适应扩充套件类核心的程式码,因为自适应扩充套件类的目的就是要根据当前执行引数,

//判断应该获取SPI界面的哪一个扩充套件类,而ExtensionLoader.getExtension方法的引数就是这个extName

//该方法逻辑比较复杂,判断分支较多,详细的分析在下面generateExtNameAssignment中

code.append(generateExtNameAssignment(value, hasInvocation));

//对上一步获取的区域性变数extName拼接其判null程式码

//if(extName == null) throw new IllegalStateException(Failed to get extension name from...);

code.append(generateExtNameNullCheck(value));

//生成获取extName对应扩充套件类例项的程式码串,如下:

//%s extension = (% //其中%s为成员变数type.getName,即SPI界面的Class的全限定名

code.append(generateExtensionAssignment());

//生成目标方法呼叫逻辑,被@Adaptive注解的方法,第一个任务是根据执行时引数获取对应的扩充套件类例项,

//第二个任务就是呼叫这个例项的同名方法。生成的方法呼叫程式码串格式为:

//(1)如果该方法无返回值extension.方法名(arg0, arg2, ..., argN);

//(2)如果该方法有返回值return extension.方法名(arg0, arg1, ..., argN);

//其中extension为上一步生成的扩充套件类例项变数名,arg0、arg1就为当前@Adaptive注解方法的引数名

code.append(generateReturnAndInvocation(method));

}

return code.toString();

}

/**

* 副档名extName获取程式码串

*/

private String generateExtNameAssignment(String[] value, boolean hasInvocation) {

String getNameCode = null;

//反向遍历value,目的是生成从URL中获取拓展名的程式码,生成的程式赋值给getNameCode变数

for (int i = value.length - 1; i >= 0; --i) {

//当遍历的元素是最后一个元素时

if (i == value.length - 1) {

//若预设副档名不空(即@SPI注解的value值不空)

if (null != defaultExtName) {

//由于protocol是URL的成员变数,可通过getProtocol方法获取,其他的则是从

//URL的成员变数parameters(一个Map)中获取。

//因为获取方式不同,所以这里要判断value[i]是否为protocol

if (!protocol.equals(value[i])) {

//需要判断一下hasInvocation标识(即当前方法是否有Invocation引数)

if (hasInvocation) {

//生成的程式码功能等价于下面的程式码:

//url.getMethodParameter(methodName, value[i], defaultExtName)

//注意,methodName是generateInvocationArgumentNullCheck生成的区域性变数,下同

getNameCode = String.format(url.getMethodParameter(methodName, %s, %s), value[i], defaultExtName);

} else {

//生成的程式码功能等价于下面的程式码:

//url.getParameter(value[i], defaultExtName)

getNameCode = String.format(url.getParameter(%s, %s), value[i], defaultExtName);

}

} else {

//生成的程式码功能等价于下面程式码:

//( url.getProtocol() == null ? defaultExtName : url.getProtocol() )

getNameCode = String.format(( url.getProtocol() == null ? %s : url.getProtocol() ), defaultExtName);

}

//若预设副档名为空

} else {

if (!protocol.equals(value[i])) {

if (hasInvocation) {

//生成的程式码格式同上,即

//url.getMethodParameter(methodName, value[i], defaultExtName)

getNameCode = String.format(url.getMethodParameter(methodName, %s, %s), value[i], defaultExtName);

} else {

//生成的程式码功能等价于:url.getParameter(value[i])

getNameCode = String.format(url.getParameter(%s), value[i]);

}

} else {

//生成的程式码功能等价于:url.getProtocol()

getNameCode = url.getProtocol();

}

}

//当遍历的元素不为最后一个时

} else {

if (!protocol.equals(value[i])) {

if (hasInvocation) {

//生成的程式码同上,即:

//url.getMethodParameter(methodName, value[i], defaultExtName)

getNameCode = String.format(url.getMethodParameter(methodName, %s, %s), value[i], defaultExtName);

} else {

//在上一次获取的getNameCode程式码结果基础上,再次获取getNameCode,即一层层的获取值

//生成的程式码功能等价于下面的程式码:

//url.getParameter(value[i], getNameCode)

//以Transporter界面的connect方法为例,最终生成的程式码如下:

//url.getParameter(client, url.getParameter(transporter, netty))

getNameCode = String.format(url.getParameter(%s, %s), value[i], getNameCode);

}

} else {

//在上一次获取的getNameCode程式码结果基础上,再次获取getNameCode,即一层层的获取值

//生成的程式码功能等价于下面的程式码:

//url.getProtocol() == null ? getNameCode : url.getProtocol()

//以Protocol界面的connect方法为例,最终生成的程式码如下:

//url.getProtocol() == null ? dubbo : url.getProtocol()

getNameCode = String.format(url.getProtocol() == null ? (%s) : url.getProtocol(), getNameCode);

}

}

}

//返回生成extName的程式码:String extName = getNameCode;

return String.format(CODE_EXT_NAME_ASSIGNMENT, getNameCode);

}

总结一下生成自适应扩充套件类程式码字串的逻辑,首先Dubbo只会对具有@Adaptive注解的方法才生成详细的程式码,而没有@Adaptive注解的程式码直接生成一段丢掷异常的程式码;其次生成的程式码逻辑会根据当前方法的引数以及@Adaptive、@SPI注解的value值等因素,获取到扩充套件类的name,从而通过ExtensionLoader拿到相应的扩充套件类例项,并呼叫该例项的同名方法,可以说是做了一个“动态分发”。

4.3.2.程式码编译

完成自适应扩充套件类的程式码串生成后,获取一个字节码编译器对程式码进行编译和载入:

ClassLoader classLoader = findClassLoader();

//获取编译器实现类,一样是通过AdaptiveExtension进行获取,获取之后进行编译

//在这里具体获取Compiler的细节略去,最终获取到JavassistCompiler类的例项进行编译

org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();

return compiler.compile(code, classLoader);

对于这段程式码,我们可以运用本小节学到知识,来分析一下Compiler界面的自适应扩充套件类是什么。

首先看一下Compiler这个SPI界面的配置档案:

adaptive=org.apache.dubbo.common.compiler.support.AdaptiveCompiler

jdk=org.apache.dubbo.common.compiler.support.JdkCompiler

javassist=org.apache.dubbo.common.compiler.support.JavassistCompiler

一共配置了3个扩充套件类,根据name的名称,可以肯定AdaptiveCompiler是具有@Adaptive注解的扩充套件类

@Adaptive

public class AdaptiveCompiler implements Compiler {

private static volatile String DEFAULT_COMPILER;

public static void setDefaultCompiler(String compiler) {

DEFAULT_COMPILER = compiler;

}

@Override

public Class> compile(String code, ClassLoader classLoader) {

Compiler compiler;

ExtensionLoader loader = ExtensionLoader.getExtensionLoader(Compiler.class);

String name = DEFAULT_COMPILER; // copy reference

if (name != null && name.length() > 0) {

compiler = loader.getExtension(name);

} else {

compiler = loader.getDefaultExtension();

}

return compiler.compile(code, classLoader);

}

}

根据4.2小节中的程式码逻辑,在具有@Adaptive注解修饰的扩充套件类的前提下,自适应扩充套件类例项一定获取到这个被修饰的类例项,因此“ExtensionLoader.getExtensionLoader(....Compiler.class).getAdaptiveExtension();”这段程式码获取到了一个AdaptiveCompiler的例项。AdaptiveCompiler的compile方法很简单,由于成员变数“DEFAULT_COMPILER”始终为null,其会呼叫ExtensionLoader的getDefaultExtension方法获取预设的扩充套件类例项,即@SPI界面注解value值作为name的例项,看看Compiler界面程式码:

@SPI(javassist)

public interface Compiler {

Class> compile(String code, ClassLoader classLoader);

}

其预设的扩充套件类的name为“javassist”,即类“org.apache.dubbo.common.compiler.support.JavassistCompiler”,这个类的具体compile方法不多赘述,就是校验程式码字串格式的正确性,并进行载入。

回到顶部

五、扩充套件点自动装配

了解了扩充套件点自适应后,让我们回到扩充套件点自动装配这个特性上来。

在getExtension方法的源代码分析中,当扩充套件类例项通过反射被初始化后,便呼叫injectExtension方法为其注入属性,官方将获取扩充套件类例项过程中的这种行为称为“扩充套件点自动装配”。下面我们来看一下自动装配的源代码,看看Dubbo是如何进行自动装配的:

/**

* 自动装配扩充套件类例项

*/

private T injectExtension(T instance) {

try {

//如果objectFactory不为空,则进行自动装配

if (objectFactory != null) {

//遍历扩充套件类例项的所有方法

for (Method method : instance.getClass().getMethods()) {

//检测方法是否以set开头、只有一个引数、访问修饰符为public

if (isSetter(method)) {

//对于有@DisableInject的注解的setter方法,不需要注入

if (method.getAnnotation(DisableInject.class) != null) {

continue;

}

//获取setter方法的引数型别

Class> pt = method.getParameterTypes()[0];

//判断引数型别是否是原始型别(基本型别+String+基本型别的包装类+Date)

if (ReflectUtils.isPrimitives(pt)) {

continue;

}

try {

//获取要注入属性名,比如setName方法中对应的属性名为name

String property = getSetterProperty(method);

//从objectFactory中根据属性名与属性的Class型别获取依赖物件,

//获取到的是一个SPI界面的自适应扩充套件类的物件或者Spring环境下的一个bean,

//objectFactory.getExtension方法的细节我们在ExtensionFactory中将详细讨论

Object object = objectFactory.getExtension(pt, property);

if (object != null) {

//呼叫setter方法进行注入

method.invoke(instance, object);

}

} catch (Exception e) {

logger.error(Failed to inject via method ...);

}

}

}

}

} catch (Exception e) {

logger.error(e.getMessage(), e);

}

return instance;

}

对扩充套件类进行自动装配时,装配目标是名为“setXXX”的方法映射出来的属性,并且认为该属性的型别为setXXX方法的引数型别,属性名为“XXX”部分第一个字母小写之后加上其余字母。举个例子,有方法“setMar(Car foo)”,则要注入的属性型别为Car,属性名为mar。获取到装配目标的型别资讯和名称资讯后,呼叫objectFactory的getExtension方法获取属性的例项。

回忆一下,objectFactory这个物件,是getExtensionLoader方法中呼叫ExtensionLoader的私有构造器建立的,程式码如下:

private ExtensionLoader(Class> type) {

this.type = type;

objectFactory = (type == ExtensionFactory.class ? null :

ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());

}

这几行简单的程式码包含了很多资讯:首先,ExtensionFactory自身也是一个SPI界面,其同样能够通过ExtensionLoader进行初始化。其次,当获取一个SPI界面的ExtensionLoader时,如果该SPI界面为ExtensionFactory,则不会设定objectFactory字段值,否则会通过getAdaptiveExtension方法拿到一个ExtensionFactory的例项并将字段objectFactory的值设定为它。

在讲解扩充套件点自适应的程式码编译小节中,我们分析了获取Compiler自适应扩充套件类例项的流程。同样的,在这里我们也可以照葫芦画瓢的来分析获取ExtensionFactory界面自适应扩充套件例项的流程。具体过程就不赘述了,最终获取到的自适应扩充套件例项是一个AdaptiveExtensionFactory例项物件。下面我们会花几个小节介绍一个ExtensionFactory相关的家族成员。

5.1.ExtensionFactory

ExtensionFactory是一个SPI界面,其源代码比较简单,包含一个方法用于根据Class以及name获取一个例项物件,方法名getExtension暗含了要获取的是扩充套件类物件。我们可以大胆猜测,这个方法应该是根据SPI界面Class和其配置档案中扩充套件类的name获取扩充套件类的例项。

@SPI

public interface ExtensionFactory {

T getExtension(Class type, String name);

}

ExtensionFactory这个界面的继承树如下图所示:

它有3个实现类,分别是AdaptiveExtensionFactory、SpiExtensionFactory、SpringExtensionFactory,相关的META-INFO配置档案内容如下:

#位于dubbo-common模组下的META-INFO/dubbo/internal/com.apache.dubbo.common.ExtensionFactory档案

adaptive=org.apache.dubbo.common.extension.factory.AdaptiveExtensionFactory

spi=org.apache.dubbo.common.extension.factory.SpiExtensionFactory

#位于dubbo-spring模组下的META-INF/dubbo/internal/com.apache.dubbo.common.ExtensionFactory档案

spring=org.apache.dubbo.config.spring.extension.SpringExtensionFactory

5.2.AdaptiveExtensionFactory

其实从配置档案中AdaptiveExtensionFactory的name可知,其应该为一个具有@Adaptive注解的扩充套件类作为自适应扩充套件类。

@Adaptive

public class AdaptiveExtensionFactory implements ExtensionFactory {

//维护了一组ExtensionFctory界面扩充套件类的例项

private final List factories;

//构造方法,获取所有ExtensionFactory配置的扩充套件类例项

public AdaptiveExtensionFactory() {

//载入ExtensionFactory对应的ExtensionLoader

ExtensionLoader loader =

ExtensionLoader.getExtensionLoader(ExtensionFactory.class);

List list = new ArrayList();、

//getSupportedExtensions方法用于获取ExtensionLoader的cachedClasses快取的keySet

//即SPI界面配置档案中name的Set集合

for (String name : loader.getSupportedExtensions()) {

//根据name名字,呼叫getExtension方法获取对应扩充套件类的例项并快取

list.add(loader.getExtension(name));

}

factories = Collections.unmodifiableList(list);

}

@Override

public T getExtension(Class type, String name) {

for (ExtensionFactory factory : factories) {

//呼叫每一个ExtensionFactory实现类的getExtension方法,并返回第一个不为null的物件

T extension = factory.getExtension(type, name);

if (extension != null) {

return extension;

}

}

return null;

}

}

5.3.SpiExtensionFactory

该Factory只支援具有@SPI注解的界面。

public class SpiExtensionFactory implements ExtensionFactory {

@Override

public T getExtension(Class type, String name) {

if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {

ExtensionLoader loader = ExtensionLoader.getExtensionLoader(type);

//getSupportedExtensions方法用于获取ExtensionLoader的cachedClasses快取的keySet

//即SPI界面配置档案中name的Set集合

if (!loader.getSupportedExtensions().isEmpty()) {

return loader.getAdaptiveExtension();

}

}

return null;

}

}

5.4.SpringExtensionFactory

顾名思义,这个Factory应用在Spring环境下,是dubbo与Spring结合使用的ExtensionFactory。与SpiExtensionFactory的行为相反,它不支援@SPI注解的界面。

public class SpringExtensionFactory implements ExtensionFactory {

private static final Logger logger = LoggerFactory.getLogger(SpringExtensionFactory.class);

private static final Set CONTEXTS = new ConcurrentHashSet();

private static final ApplicationListener SHUTDOWN_HOOK_LISTENER = new ShutdownHookListener();

public static void addApplicationContext(ApplicationContext context) {

CONTEXTS.add(context);

if (context instanceof ConfigurableApplicationContext) {

((ConfigurableApplicationContext) context).registerShutdownHook();

DubboShutdownHook.getDubboShutdownHook().unregister();

}

//先SpringContext注册dubbo关闭钩子

BeanFactoryUtils.addApplicationListener(context, SHUTDOWN_HOOK_LISTENER);

}

//省略了一些CONTEXTS的CRUD方法...

@Override

@SuppressWarnings(unchecked)

public T getExtension(Class type, String name) {

//不支援被@SPI注解标注的界面型别

if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {

return null;

}

//首先根据name从Spring上下文获取bean,并验证该bean与type是否一致

for (ApplicationContext context : CONTEXTS) {

if (context.containsBean(name)) {

Object bean = context.getBean(name);

if (type.isInstance(bean)) {

return (T) bean;

}

}

}

logger.warn(No spring extension (bean) named ...);

//如果type的型别为Object,直接返回null

if (Object.class == type) {

return null;

}

//尝试根据type从Spring上下文获取bean,如果type对应的bean并非唯一,直接报错

for (ApplicationContext context : CONTEXTS) {

try {

return context.getBean(type);

} catch (NoUniqueBeanDefinitionException multiBeanExe) {

logger.warn(Find more than 1 spring extensions (beans) of type ...);

} catch (NoSuchBeanDefinitionException noBeanExe) {

if (logger.isDebugEnabled()) {

logger.debug(Error when get spring extension(bean) for type: ...);

}

}

}

logger.warn(No spring extension (bean) named: ...);

//未找到,返回null

return null;

}

private static class ShutdownHookListener implements ApplicationListener {

@Override

public void onApplicationEvent(ApplicationEvent event) {

if (event instanceof ContextClosedEvent) {

DubboShutdownHook shutdownHook = DubboShutdownHook.getDubboShutdownHook();

shutdownHook.doDestroy();

}

}

}

}

SpringExtensionFactory维护了Spring上下文集合“Set”。在getExtension方法中,通过Spring上下文去获取例项。并且,SpringExtensionFactory在设定SpringContext时,会向获取到的Context注册一个Spring容器关闭事件的钩子,用于关闭dubbo。

回到顶部

六、扩充套件点自动包装

扩充套件点例项自动装配之后,便开始进行自动包装,包装的过程为遍历快取“cachedWrapperClasses”并将装配好的扩充套件类例项作为包装扩充套件类的构造器引数,建立一个新的包装类例项,然后对这个新的例项进行自动装配。

如何判定一个扩充套件类是否是包装类呢?在getExtension方法的源代码分析中已经有提及,就是某个SPI界面的扩充套件类具有一个特定特征的建构函式,这个建构函式是单引数,并且引数型别是该扩充套件类实现的SPI界面型别。举个官网给出的例子:

package com.alibaba.xxx;

import org.apache.dubbo.rpc.Protocol;

public class XxxProtocolWrapper implements Protocol {

Protocol impl;

//单引数建构函式,且引数型别为其实现的SPI界面型别Protocol

public XxxProtocolWrapper(Protocol protocol) { impl = protocol; }

// 界面方法做一个操作后,再呼叫extension的方法

public void refer() {

//... 一些操作

impl.refer();

// ... 一些操作

}

}

自动包装的机制能够近似的实现AOP的功能,通过Wrapper类可以把所有扩充套件点公共逻辑移至Wrapper中,新加的Wrapper在所有的扩充套件点上添加了逻辑。

标签: 手机数码app软件推荐中关村手机对比手机评测app现在什么电子产品最火数码之家手机客户端

上一篇:系统维护与软件维护两个关键任务的并行进程

下一篇:春水绕村流三十六陂共鸣探秘传统水利工程守护生态和谐从田间到家园三十六陂春水的故事与意义

相关推荐
推荐资讯
热门文章