1 jdk spi
spi全名Service Provider Interface,是一种服务发现机制,即为接口寻找服务实现的机制。
jdk spi 约定提供方只需制定接口规范,提供给外部实现,通过读取jar包META-INF/services/目录以接口名命令的文件来加载该接口的实现,文件里就是该接口的具体实现。
下面是一个简单的例子:
提供一个接口JdkSpiService
public interface JdkSpiService {
public void hello();
}
提供两个接口实现类JdkSpiServiceImpl1,JdkSpiServiceImpl2
public class JdkSpiServiceImpl1 implements JdkSpiService {
@Override
public void hello() {
System.out.println("hello jdk SPI 1");
}
}
public class JdkSpiServiceImpl2 implements JdkSpiService {
@Override
public void hello() {
System.out.println("hello jdk SPI 2");
}
}
在resource目录下创建com.example.demo.jdkspi.service.JdkSpiService文件内容如下
com.example.demo.jdkspi.service.impl.JdkSpiServiceImpl1
com.example.demo.jdkspi.service.impl.JdkSpiServiceImpl2
编写测试类JdkSpiDemo
public class JdkSpiDemo {
public static void main(String[] args) {
ServiceLoader<JdkSpiService> load = ServiceLoader.load(JdkSpiService.class);
for (JdkSpiService jdkSpiService : load) {
jdkSpiService.hello();
}
}
}
运行结果如下:
读取了并加载了配置文件里配置的两个实现。
jdk spi的缺点:
不能按需加载,一旦加载就加载并实例化全部的实现,要通过遍历来寻找需要的实现
并发下获取到的实例是不安全的。
2 dubbo spi
基于jdk spi本身的特点,已经dubbo自身的需要,dubbo自己实现了一套spi。
2.1 基本应用
2.1.1 普通应用
创建一个maven工程,添加如下依赖
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>dubbo-a-my-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>dubbo-a-my-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-common</artifactId>
<version>2.7.9</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-config-spring</artifactId>
<version>2.7.9</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
创建一个接口SpiService如下,接口上添加@SPI注解
@SPI
public interface SpiService {
public void hello(String name);
}
创建一个实现DefaultSpiServiceImpl ,如下
public class DefaultSpiServiceImpl implements SpiService {
@Override
public void hello(String name) {
System.out.println("default hello dubbo spi "+name);
}
}
在resources目录下创建META-INF/dubbo目录,创建com.example.dubboamydemo.spi.service.SpiService文件,等号左边为实现类的key,右边是实现类的全限定名,如下:
defaultSpiServiceImpl=com.example.dubboamydemo.spi.service.impl.DefaultSpiServiceImpl
创建测试类,通过ExtensionLoader.getExtensionLoader拿到ExtensionLoader,通过调用getExtension方法传入配置文件中的key获取到实现类的实例。
public class DubboSpiDemo {
public static void main(String[] args) {
ExtensionLoader<SpiService> extensionLoader = ExtensionLoader.getExtensionLoader(SpiService.class);
SpiService defaultSpiServiceImpl = extensionLoader.getExtension("defaultSpiServiceImpl");
defaultSpiServiceImpl.hello("default");
}
}
运行main方法,结果如下:
可以看到执行了我们的默认实现。
dubbo的spi还可以配置默认的实现类,比如现在修改SpiService接口,在@SPI注解上添加参数
@SPI("defaultSpiServiceImpl")
public interface SpiService {
public void hello(String name);
}
修改测试类
public class DubboSpiDemo {
public static void main(String[] args) {
ExtensionLoader<SpiService> extensionLoader = ExtensionLoader.getExtensionLoader(SpiService.class);
SpiService defaultExtension = extensionLoader.getDefaultExtension();
defaultExtension.hello("default123");
}
}
运行测类,结果如下
可以看到,通过在@SPI注解添加名称,将名称对应的实现类作为默认实现,在调用extensionLoader.getDefaultExtension()那到实现类实例。
2.1.2 自适应 @Adaptive
dubbo的SPI可以通过@Adaptive注解实现自适应的扩展点。@Adaptive可以配置在类上,也可配置在方法上,配置在类上是指配置在实现类上,配置在方法上是指配置在接口的方法上。
2.1.2.1 类上的@Adaptive
现在我们再增加一个接口实现AdaptiveSpiServiceImpl ,并再类上配置@Adaptive注解,如下:
@Adaptive
public class AdaptiveSpiServiceImpl implements SpiService {
@Override
public void hello(String name) {
System.out.println("adaptive hello dubbo spi "+ name);
}
}
修改配置文件com.example.dubboamydemo.spi.service.SpiService,将AdaptiveSpiServiceImpl配置进去
defaultSpiServiceImpl=com.example.dubboamydemo.spi.service.impl.DefaultSpiServiceImpl
adaptiveSpiServiceImpl=com.example.dubboamydemo.spi.service.impl.AdaptiveSpiServiceImpl
修改测试类
public class DubboSpiDemo {
public static void main(String[] args) {
ExtensionLoader<SpiService> extensionLoader = ExtensionLoader.getExtensionLoader(SpiService.class);
SpiService adaptiveExtension = extensionLoader.getAdaptiveExtension();
adaptiveExtension.hello("adaptive");
}
}
运行测试类结果如下:
2.1.2.2 方法上的@Adaptive
这里需要注意一点,配置在方法上时对方法是有特殊要求的,方法必须要有入参,且第一个参数必须是org.apache.dubbo.common.URL类型。
现在修改SpiService接口,添加一个方法如下,在方法上添加@Adaptive注解,注解里面可以传入一个字符串,dubbo会将字符串作为key,取得URL中的参数,作为实现类的名字,如果不传默认为@SPI注解传入的值作为实现类的名称,如果这两个注解都没有传入值,默认是接口名以大写字母为分割通过点连接,并将大写字母变小写作为key从URL中获取实现类名称,如SpiService这个接口的名称就是:spi.service。
@SPI("defaultSpiServiceImpl")
public interface SpiService {
public void hello(String name);
@Adaptive("nameKey")
public void hello(URL url, String name);
}
修改测试类
public static void main(String[] args) {
ExtensionLoader<SpiService> extensionLoader = ExtensionLoader.getExtensionLoader(SpiService.class);
SpiService adaptiveExtension = extensionLoader.getAdaptiveExtension();
URL url = URL.valueOf("test://12.13.14.11/mm?nameKey=adaptiveSpiServiceImpl");
adaptiveExtension.hello(url,"adaptive");
}
注意需要将上面添加在AdaptiveSpiServiceImpl类上的@Adaptive注解注释
//@Adaptive
public class AdaptiveSpiServiceImpl implements SpiService {
@Override
public void hello(String name) {
System.out.println("adaptive hello dubbo spi "+ name);
}
@Override
public void hello(URL url, String naame) {
System.out.println("adaptiveSpiServiceImpl++"+name);
}
}
DefaultSpiServiceImpl 类也实现hello(URL url, String name) 方法:
public class DefaultSpiServiceImpl implements SpiService {
@Override
public void hello(String name) {
System.out.println("default hello dubbo spi "+name);
}
@Override
public void hello(URL url, String name) {
System.out.println("defaultSpiServiceImpl+++"+name);
}
}
现在运行测试类结果如下:
可以看到现在获取到的是AdaptiveSpiServiceImpl的实例,
现在修改测试类中的URL,将nameKey的值改为defaultSpiServiceImpl
public class DubboSpiDemo {
public static void main(String[] args) {
ExtensionLoader<SpiService> extensionLoader = ExtensionLoader.getExtensionLoader(SpiService.class);
SpiService adaptiveExtension = extensionLoader.getAdaptiveExtension();
URL url = URL.valueOf("test://12.13.14.11/mm?nameKey=defaultSpiServiceImpl");
adaptiveExtension.hello(url,"adaptive");
}
}
运行结果如下:
获取到的是DefaultSpiServiceImpl实例。
2.1.3 通过URL可激活的扩展点
指通过传入的URL的参数判断哪些实现是激活的,即需要加载实例化哪些实现。 通过ExtensionLoader#getActivateExtension方法获取激活的实例集合
2.1.3.1 指定激活的实现类名称
修改测试类
public class DubboSpiDemo {
public static void main(String[] args) {
ExtensionLoader<SpiService> extensionLoader = ExtensionLoader.getExtensionLoader(SpiService.class);
URL url = URL.valueOf("test://12.13.14.11/mm?nameKey=defaultSpiServiceImpl,adaptiveSpiServiceImpl");
List<SpiService> nameKey = extensionLoader.getActivateExtension(url, "nameKey");
nameKey.forEach(spiService -> {
spiService.hello("daf");
});
}
}
URL中参数nameKey指定哪些扩展点需要被激活,getActivateExtension方法传入url和url中指定激活扩展点名称的key。
运行main方法
可以看到defaultSpiServiceImpl,adaptiveSpiServiceImpl被激活。
2.1.3.2 @Activate 指定激活的扩展点
新增一个实现类ActivateSpiServiceImpl ,添加@Activate注解,该注解有两个重要属性value和group,这两个属性其实代表了两个条件,只有两个属性代表的条件同时为true,@Activate注解修饰的实现类就会被激活。
value:是一个字符串数组,格式为{“key1:value1”,“key2:value2”},key、value表示URL中参数的一个键值对,即传入的URL中要包含value数组中的一个键值对,那么该条件就为true,如果不配该属性,那么这个条件默认为true。这里的value值并不表示实现类在配置文件中的key,可以是任意值,只要URL中也有此键值对,那么该条件就为true
group:是一个字符串数组,只要数组中包含,调用ExtensionLoader#getActivateExtension方法传入的group值,那么该条件就为true,如果不配该属性,那么这个条件默认为true。
@Activate(value = {
"key1:value1"})
public class ActivateSpiServiceImpl implements SpiService {
@Override
public void hello(String name) {
System.out.println("activate hello dubbo spi "+ name);
}
@Activate
@Override
public void hello(URL url, String name) {
System.out.println("ActivateSpiServiceImpl +++ "+name);
}
}
修改测试类,url中增加键值对key1=value1
public class DubboSpiDemo {
public static void main(String[] args) {
ExtensionLoader<SpiService> extensionLoader = ExtensionLoader.getExtensionLoader(SpiService.class);
URL url = URL.valueOf("test://12.13.14.11/mm?nameKey=defaultSpiServiceImpl,adaptiveSpiServiceImpl&key1=value1");
List<SpiService> nameKey = extensionLoader.getActivateExtension(url, "nameKey");
nameKey.forEach(spiService -> {
spiService.hello("daf");
});
}
}
运行main方法
可以看到加上nameKey指定的激活类,一共加载了3个实现类。
ExtensionLoader.getActivateExtension方法还可以传入group,过滤激活类。
修改ActivateSpiServiceImpl ,@Activate注解中加入group属性,值为group2。
@Activate(value = {
"key1:value1"},group = "group2")
public class ActivateSpiServiceImpl implements SpiService {
@Override
public void hello(String name) {
System.out.println("activate hello dubbo spi "+ name);
}
@Activate
@Override
public void hello(URL url, String name) {
System.out.println("ActivateSpiServiceImpl +++ "+name);
}
}
修改测试类,ExtensionLoader#getActivateExtension方法中传入group参数,为group1
public class DubboSpiDemo {
public static void main(String[] args) {
ExtensionLoader<SpiService> extensionLoader = ExtensionLoader.getExtensionLoader(SpiService.class);
URL url = URL.valueOf("test://12.13.14.11/mm?nameKey=defaultSpiServiceImpl,adaptiveSpiServiceImpl&key1=value1");
List<SpiService> nameKey = extensionLoader.getActivateExtension(url, "nameKey", "group1");
nameKey.forEach(spiService -> {
spiService.hello("daf");
});
}
}
运行main方法
由于@Activate注解传入的group不包含ExtensionLoader#getActivateExtension方法传入的group值,所以@Activate修饰的类没有被激活。
以上就是dubbo的spi几种常见的简单用法。下面开始分析其源码实现。
2.2 dubbo SPI 源码详解
2.2.1 ExtensionLoader
由上面的几个例子可以看到使用dubbo的SPI,首先就需要获取的ExtensionLoader实例,获取实例是通过调用其静态方法ExtensionLoader.getExtensionLoader完成的,现在进入getExtensionLoader方法。
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
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, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
}
/**
*
* EXTENSION_LOADERS 缓存ExtensionLoader,一个类型一个 ExtensionLoader
* 从缓存EXTENSION_LOADERS 获取该类型的ExtensionLoader,不存在则创建一个
* new EXTENSION_LOADERS 里主要是创建了ExtensionFactory,这是用来做依赖注入的,优先从dubbo spi里面取,
* 取不到再从spring里面取。
*/
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
由上面代码可以看到getExtensionLoader方法并没有做太多的事情,先做了对传入的class进行交易,然后看EXTENSION_LOADERS缓存中是否存在传入类型的一个ExtensionLoader,不存在就new一个,并且放入缓存;ExtensionLoader的构造方法中给两个遍历赋了值,一个是type,一个是objectFactory,其中objectFactory与dubbo的依赖注入有关,这个放到后面说。
2.2.2 ExtensionLoader#getExtensionClasses
首先我们研究getExtensionClasses这个方法,这个方法的作用就是读取配置文件获取实现类型,根据不同的条件缓存将Class<?>放在不同的缓存中。
private Map<String, Class<?>> getExtensionClasses() {
/**
* cachedClasses缓存了除了@Adaptive修饰的类和Wrapper类型的类剩下的接口配置文件中的所有类
* 以key-value形式放到map中。
*/
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方法加载,进入loadExtensionClasses方法
private Map<String, Class<?>> loadExtensionClasses() {
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
/**
* strategies通过jdk的SPI配置的加载配置的策略。
*/
for (LoadingStrategy strategy : strategies) {
loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
}
return extensionClasses;
}
这里使用策略模式,加载加载目录,strategies是ExtensionLoader实例化时调用loadLoadingStrategies()方法赋值的,而loadLoadingStrategies方法即通过java的spi获取到LoadingStrategy 接口配置的三个实现
org.apache.dubbo.common.extension.DubboInternalLoadingStrategy
org.apache.dubbo.common.extension.DubboLoadingStrategy
org.apache.dubbo.common.extension.ServicesLoadingStrategy
三个实现类中分别配置了一个加载路径:
META-INF/dubbo/internal/:通常时dubbo内部使用的
META-INF/dubbo/
META-INF/services/:可以和jdk的spi兼容
现在回到loadExtensionClasses,可以看到循环里调用两次loadDirectory方法,这是为了兼容以前阿里巴巴的类。进入loadDirectory方法
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) {
String fileName = dir + type;
try {
Enumeration<java.net.URL> urls = null;
ClassLoader classLoader = findClassLoader();
// try to load from ExtensionLoader's ClassLoader first
/**
* 加载配置文件
*/
if (extensionLoaderClassLoaderFirst) {
ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
urls = extensionLoaderClassLoader.getResources(fileName);
}
}
/**
* 加载配置文件
*/
if (urls == null || !urls.hasMoreElements()) {
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
}
/**
* 如果找到配置文件,解析配置文件
*/
if (urls != null) {
while (urls.hasMoreElements()) {
java.net.URL resourceURL = urls.nextElement();
loadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages);
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", description file: " + fileName + ").", t);
}
}
该方法根据策略中配置的路径加载配置文件,然后调用loadResource方法解析配置文件,进入loadResource方法,该方法逐行读取,解析出配置的实现类的key和全类限定名,根据配置的全类限定名加载类。然后调用loadClass,进入loadClass。
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
boolean overridden) throws NoSuchMethodException {
/**
* 如果配置的实现类没有实现接口则抛异常
*/
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error occurred when loading extension class (interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + " is not subtype of interface.");
}
/**
* 如果实现类上有Adaptive注解,就赋值给cachedAdaptiveClass缓存,方法结束
*/
if (clazz.isAnnotationPresent(Adaptive.class)) {
cacheAdaptiveClass(clazz, overridden);
/**
* 如果实现类时Wrapper类,就缓存到cachedWrapperClasses中,方法结束
* Wrapper类就是该实现类有一个传入本身类型参数的构造函数。
*/
} else if (isWrapperClass(clazz)) {
cacheWrapperClass(clazz);
} else {
clazz.getConstructor();
if (StringUtils.isEmpty(name)) {
/**
* 如果配置文件中没有配置key,看实现类中是否有被Extension注解修饰,并配置了key
* 如果没有则生成一个key,生成规则:
* 假如实现类名称是以接口名结尾,则截取接口名前面的字段,并转为小写作为key
* 如果实现类名称不是以接口名结尾,直接转为小写作为key
*/
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
/**
* 如果该类被Activate注解修饰,则将Activate注解缓存到cachedActivates
*/
cacheActivateClass(clazz, names[0]);
for (String n : names) {
cacheName(clazz, n);
/**
* 将配置文件中的名称和class,以键值对的形式存入extensionClasses
*/
saveInExtensionClass(extensionClasses, clazz, n, overridden);
}
}
}
}
根据实现类的不同配置,将加载的类型存入不同的缓存,若配置文件中没有配置key,会生成一个key,生成规则看代码注释。
以上就是ExtensionLoader#getExtensionClasses的全部逻辑。
2.2.3 ExtensionLoader#getExtension获取普通的扩展对象
getExtension(String name),重载了一个两个参数的方法如下:
public T getExtension(String name, boolean wrap) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
return getDefaultExtension();
}
/**
* 这里为包装成Holder是为了下面可以加锁,锁住这个对象,防止并发问题。
*/
final Holder<Object> holder = getOrCreateHolder(name);
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
instance = createExtension(name, wrap);
holder.set(instance);
}
}
}
return (T) instance;
}
该方法就是首先从缓存中获取实例,如果缓存中没有对象则调用createExtension方法;加载配置文件配置的实现类,并依次将其实例化、依赖注入、aop逻辑、初始化,后返回实例。
private T createExtension(String name, boolean wrap) {
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
/**
* 根据类型从缓存中获取实例,不存在就通过反射实例化。
*/
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
/**
* 依赖注入,判断是否有setter方法,即以set开头,只有一个参数的方法,
* 根据传入的参数作为类型。
* 截取方法名除set后的部分,并将首字母小写,作为依赖属性的名称。
* 从dudbbo的spi里或srping容器里取得依赖对象。
*/
injectExtension(instance);
/**
* 通过Wrapper实现aop逻辑
*/
if (wrap) {
List<Class<?>> wrapperClassesList = new ArrayList<>();
if (cachedWrapperClasses != null) {
wrapperClassesList.addAll(cachedWrapperClasses);
wrapperClassesList.sort(WrapperComparator.COMPARATOR);
Collections.reverse(wrapperClassesList);
}
if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
for (Class<?> wrapperClass : wrapperClassesList) {
Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
if (wrapper == null
|| (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
}
}
/**
* 初始化逻辑,实现了Lifecycle接口 会调用其initialize方法,完成一些初始逻辑
*/
initExtension(instance);
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
2.2.3.1 injectExtension 依赖注入
dubbo的依赖注入逻辑:
1.获取类型的所有方法,断是否有setter方法,即以set开头,只有一个参数的方法,有则继续。
2.如果set方法被DisableInject注解修饰则不注入
3.获取依赖注入扩展类的名字,截取方法名除set后的部分,并将首字母小写,作为依赖属性的名称。
4.使用set方法传入参数的类型和根据set方法名获取到的名称作为参数获取一个扩展点实例。
5.如果获取到的依赖实例不为空,执行setter方法。
private T injectExtension(T instance) {
if (objectFactory == null) {
return instance;
}
try {
/**
* 获取类型的所有方法,断是否有setter方法,即以set开头,只有一个参数的方法。
*/
for (Method method : instance.getClass().getMethods()) {
if (!isSetter(method)) {
continue;
}
/**
* Check {@link DisableInject} to see if we need auto injection for this property
*/
/**
* 如果set方法被DisableInject注解修饰则不注入
*/
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
Class<?> pt = method.getParameterTypes()[0];
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
/**
* 获取依赖注入扩展类的名字,截取方法名除set后的部分,并将首字母小写,作为依赖属性的名称
*/
String property = getSetterProperty(method);
/**
* 使用set方法传入参数的类型和根据set方法名获取到的名称作为参数获取一个扩展点实例。
* 如果是注入dubbo spi的依赖那么名称没有用。是根据接口的@Adaptive注解传入的key或根据接口名默认生成的key
* 从URL中获取的参数值作为的扩展点名称。
*/
Object object = objectFactory.getExtension(pt, property);
/**
* 如果获取到的依赖实例不为空,执行setter方法
*/
if (object != null) {
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("Failed to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
由上面代码可以看出依赖对象是通过objectFactory.getExtension(pt, property);从一个对象工厂中获取的objectFactory的类型是ExtensionFactory,objectFactory是在ExtensionLoader构造方法中初始化的。
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
可以看到ObjectFactory通过dubbo SPI 获取的自适应扩展类。查找ExtensionFactory 所有的实现,发现由一个AdaptiveExtensionFactory的实现,发下其被@Adaptive注解,所有获取到的就是这个类。
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
private final List<ExtensionFactory> factories;
public AdaptiveExtensionFactory() {
/**
* 通过dubbo SPI获取到所有普通的扩展点实例。
*/
ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
for (String name : loader.getSupportedExtensions()) {
list.add(loader.getExtension(name));
}
factories = Collections.unmodifiableList(list);
}
@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;
}
}
该类在构造方法中通过dubbo SPI获取到所有普通的扩展点实例,这里的配置文件org.apache.dubbo.common.extension.ExtensionFactory有两个,一个在dubbo-common的jar里,另一个在dubbo-config-spring的jar包里。
dubbo-common:
adaptive=org.apache.dubbo.common.extension.factory.AdaptiveExtensionFactory
spi=org.apache.dubbo.common.extension.factory.SpiExtensionFactory
dubbo-config-spring
spring=org.apache.dubbo.config.spring.extension.SpringExtensionFactory
所以最后AdaptiveExtensionFactory类的factories中有两个扩展点实例SpiExtensionFactory、SpringExtensionFactory,并且SpiExtensionFactory的顺序排在前面。
下面我们来看看ExtensionFactory接口的抽象方法getExtension,该方法就是循环factories,调用其getExtension方法,如果能获取到实例对象就直接返回,如果都没有就返回null;
先看SpiExtensionFactory的方法
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);
/**
* 依赖注入的类行必须存在一个普通的扩展点类型
* 返回的对象是自适应扩展点对象
* 由上面两个条件可以看出如果要使用dubbo的SPI依赖注入:
* 该接口必须存在自适应类扩展类和普通扩展类。
* 所以需要在接口的方法上配置@Adaptive注解,并且接口必须有一个第一个参数是URL的方法。
*/
if (!loader.getSupportedExtensions().isEmpty()) {
return loader.getAdaptiveExtension();
}
}
return null;
}
}
通过dubbo 的SPI 做依赖注入,需要在接口的方法上配置@Adaptive注解,并且接口必须有一个第一个参数是URL的方法,还需要由普通的扩展类。
如果SpiExtensionFactory获取到的依赖实例我null,再从SpringExtensionFactory获取,该类就是从spring容器中获取对象。
2.2.3.1.1 dubbo SPI依赖注入用法示例
SpiService如下:
@SPI("defaultSpiServiceImpl")
public interface SpiService {
public void hello(String name);
@Adaptive("nameKey")
public void hello(URL url, String name);
}
com.example.dubboamydemo.spi.service.SpiService配置文件中配置了2.1章节所创建的示例类
defaultSpiServiceImpl=com.example.dubboamydemo.spi.service.impl.DefaultSpiServiceImpl
adaptiveSpiServiceImpl=com.example.dubboamydemo.spi.service.impl.AdaptiveSpiServiceImpl
activateSpiServiceImpl=com.example.dubboamydemo.spi.service.impl.ActivateSpiServiceImpl
DefaultSpiServiceImpl 的实现如下:调用一个参数的hello方法,通过URL中传入的nameKey的值获取到ActivateSpiServiceImpl
public class DefaultSpiServiceImpl implements SpiService {
SpiService spiService;
@Override
public void hello(String name) {
URL url = URL.valueOf("test://12.13.14.11/mm?nameKey=activateSpiServiceImpl");
spiService.hello(url,name);
}
@Override
public void hello(URL url, String name) {
System.out.println("defaultSpiServiceImpl+++"+name);
}
public void setActivateSpiServiceImpl(SpiService spiService) {
this.spiService = spiService;
}
}
测试类如下:
public class DubboSpiDemo {
public static void main(String[] args) {
ExtensionLoader<SpiService> extensionLoader = ExtensionLoader.getExtensionLoader(SpiService.class);
SpiService defaultSpiServiceImpl = extensionLoader.getExtension("defaultSpiServiceImpl");
defaultSpiServiceImpl.hello("default");
}
}
运行测试类:
可见最后调用的是ActivateSpiServiceImpl,依赖注入成功。
2.2.3.2 基于Wrapper AOP实现
dubbo的aop时通过Wrapper包装类实现的,Wrapper包装类是指实现了接口类型,并且提供了一个传入接口类型参数的构造方法的类。
下面是ExtensionLoader#createExtension方法中AOP的逻辑:
1.在cachedWrapperClasses缓存中获取所有的Wrapper类,cachedWrapperClasses缓存是getExtensionClasses方法加载类时,如果时Wrapper类就放入缓存。
2.判断Wrapper类上是否有Wrapper注解,没有Wrapper注解,则对所有的扩展类都用此Wrapper类包装,即所有的扩展类都会用到这个aop,有Wrapper注解,Wrapper注解有两个属性matches和mismatches,都是数组类型,如果获取的扩展类型的名称包含在matches中并且不包含在mismatches中,那么该名称对应的扩展类就会被这个Wrapper包装。
/**
* 通过Wrapper实现aop逻辑
*/
if (wrap) {
/**
* 在cachedWrapperClasses缓存中获取所有的Wrapper类。
* Wrapper包装类是指实现了接口类型,并且提供了一个传入接口类型参数的构造方法的类。
* cachedWrapperClasses缓存是getExtensionClasses方法加载类时,如果时Wrapper类就放入缓存
*/
List<Class<?>> wrapperClassesList = new ArrayList<>();
if (cachedWrapperClasses != null) {
wrapperClassesList.addAll(cachedWrapperClasses);
wrapperClassesList.sort(WrapperComparator.COMPARATOR);
Collections.reverse(wrapperClassesList);
}
/**
* 判断Wrapper类上是否有Wrapper注解
* 1.没有Wrapper注解,则对所有的扩展类都用此Wrapper类包装,即所有的扩展类都会用到这个aop
* 2.有Wrapper注解,如果Wrapper注解有两个属性matches和mismatches,都时数组类型
* 如果获取的扩展类型的名称包含在matches中并且不包含在mismatches中,那么该名称对应的扩展类就会被这个Wrapper包装。
*/
if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
for (Class<?> wrapperClass : wrapperClassesList) {
Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
if (wrapper == null
|| (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
}
}
2.2.3.2.1 dubbo AOP 用法示例
添加一个Wrapper类
public class SpiServiceWrapper implements SpiService {
SpiService spiService;
public SpiServiceWrapper(SpiService spiService) {
this.spiService = spiService;
}
@Override
public void hello(String name) {
System.out.println("aop 调用前执行点逻辑");
spiService.hello("aop");
System.out.println("aop 调用后执行点逻辑");
}
@Override
public void hello(URL url, String name) {
System.out.println("url aop 调用前执行点逻辑");
spiService.hello(url,name);
System.out.println("url aop 调用后执行点逻辑");
}
}
在com.example.dubboamydemo.spi.service.SpiService配置文件中加入Wrapper类的配置
defaultSpiServiceImpl=com.example.dubboamydemo.spi.service.impl.DefaultSpiServiceImpl
adaptiveSpiServiceImpl=com.example.dubboamydemo.spi.service.impl.AdaptiveSpiServiceImpl
activateSpiServiceImpl=com.example.dubboamydemo.spi.service.impl.ActivateSpiServiceImpl
wrapper=com.example.dubboamydemo.spi.service.impl.SpiServiceWrapper
测试类如下
public class DubboSpiDemo {
public static void main(String[] args) {
ExtensionLoader<SpiService> extensionLoader = ExtensionLoader.getExtensionLoader(SpiService.class);
SpiService defaultSpiServiceImpl = extensionLoader.getExtension("defaultSpiServiceImpl");
defaultSpiServiceImpl.hello("default");
}
}
运行main方法结果如下:
由于使用了上面依赖注入的代码所以结果入上,不过可以看出我们aop的逻辑已经执行了。
2.2.3.3 initExtension 初始化
如果扩展类实现了Lifecycle接口,那么会执行其initialize方法,执行初始化逻辑。
private void initExtension(T instance) {
if (instance instanceof Lifecycle) {
Lifecycle lifecycle = (Lifecycle) instance;
lifecycle.initialize();
}
}
2.2.4 ExtensionLoader#getAdaptiveExtension获取自适应对象
进入getAdaptiveExtension方法:
public T getAdaptiveExtension() {
/**
* 从cachedAdaptiveInstance缓存中获取自适应扩展类
* cachedAdaptiveInstance是getExtensionClasses方法,加载配置文件时
* 如果配置的类上面有@Adaptive注解就会放入这个缓存。
*/
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if (createAdaptiveInstanceError != null) {
throw new IllegalStateException("Failed to create adaptive instance: " +
createAdaptiveInstanceError.toString(),
createAdaptiveInstanceError);
}
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
/**
* 如果缓存中存在了自适应类,就直接返回
*/
if (instance == null) {
try {
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
}
}
}
}
return (T) instance;
}
该方法首先从cachedAdaptiveInstance缓存中获取自适应实例,如果存在就直接返回,不存在调用createAdaptiveExtension方法创建自适应实例。
private T createAdaptiveExtension() {
try {
/**
* 1.getAdaptiveExtensionClass方法获取自适应扩展类型。从缓存获取,缓存中没有
* 就自己生成一个类型。
* 2.获取到自适应扩展类型后newInstance实例化。
* 3.调用injectExtension方法,完成依赖注入。
*/
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
该方法就是获取到自适应扩展类型,反射实例化,然后依赖注入,最后返回实例。
下面进入getAdaptiveExtensionClass看是如何获取到自适应扩展类型:
private Class<?> getAdaptiveExtensionClass() {
getExtensionClasses();
/**
* 首先从cachedAdaptiveClass缓存中获取自适应扩展类型,
* cachedAdaptiveClass缓存是getExtensionClasses方法加载配置文件中的类时,如果类上面
* 有被@Adaptive修饰,就会放到这个缓存。
*/
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
/**
* 如果cachedAdaptiveClass缓存的扩展类类型为空,那么会生成一个自适应扩展类型。
*/
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
getAdaptiveExtensionClass方法首先从cachedAdaptiveClass缓存中获取自适应扩展类型,cachedAdaptiveClass缓存是getExtensionClasses方法加载配置文件中的类时,如果类上面有被@Adaptive修饰,就会放到这个缓存。
如果缓存里有值则直接返回。
如果为null,调用createAdaptiveExtensionClass方法通过javassist字节码技术生成一个自适应扩展类,这里就是@Adaptive修饰在接口方法上的逻辑。
生成的自适应类如下,具体的生成逻辑这里不做概述。
package com.example.dubboamydemo.spi.service;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class SpiService$Adaptive implements com.example.dubboamydemo.spi.service.SpiService {
public void hello(java.lang.String arg0) {
throw new UnsupportedOperationException("The method public abstract void com.example.dubboamydemo.spi.service.SpiService.hello(java.lang.String) of interface com.example.dubboamydemo.spi.service.SpiService is not adaptive method!");
}
public void hello(org.apache.dubbo.common.URL arg0, java.lang.String arg1) {
if (arg0 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg0;
String extName = url.getParameter("nameKey", "defaultSpiServiceImpl");
if(extName == null) throw new IllegalStateException("Failed to get extension (com.example.dubboamydemo.spi.service.SpiService) name from url (" + url.toString() + ") use keys([nameKey])");
com.example.dubboamydemo.spi.service.SpiService extension = (com.example.dubboamydemo.spi.service.SpiService)ExtensionLoader.getExtensionLoader(com.example.dubboamydemo.spi.service.SpiService.class).getExtension(extName);
extension.hello(arg0, arg1);
}
}
其中第一个参数不是URL参数的方法都被抛出异常。有第一个参数是URL的方法,首先会根据url获取需要的扩展点类名称url.getParameter(“nameKey”, “defaultSpiServiceImpl”),其中nameKey的获取逻辑如下:
1.首先取Adaptive注解上面是否配置了名称,如果配置了直接返回
2.如果Adaptive注解上面没有配置名称,会根据接口名生成一个,生成规则:通过大写字母将接口名分割,以点号连接,再将所有的大写字母转为小写
private String[] getMethodAdaptiveValue(Adaptive adaptiveAnnotation) {
/**
* 首先取Adaptive注解上面是否配置了名称,如果配置了直接返回
*/
String[] value = adaptiveAnnotation.value();
// value is not set, use the value generated from class name as the key
if (value.length == 0) {
/**
* 如果Adaptive注解上面没有配置名称,会根据接口名生成一个,生成规则:
* 通过大写字母将接口名分割,以点号连接,再将所有的大写字母转为小写
*/
String splitName = StringUtils.camelToSplitName(type.getSimpleName(), ".");
value = new String[]{
splitName};
}
return value;
}
2.2.4 ExtensionLoader#getActivateExtension 获取所有激活的扩展点
getActivateExtension 首先通过传入的key从URL中获取配置的激活的扩展点名称,可配置多个以逗号隔开。
public List<T> getActivateExtension(URL url, String key, String group) {
String value = url.getParameter(key);
return getActivateExtension(url, StringUtils.isEmpty(value) ? null : COMMA_SPLIT_PATTERN.split(value), group);
}
进入重载方法,改方法完成了对激活类的加载:
1.判断url中配置的扩展点名字是否包含-default,不包含则进入第2点,包含则跳过第2点
2.循环cachedActivates缓存中所有被@Activate注解修饰的扩展点,判断group配置的值中是否包含方法入参传入的group,如果包含则为true,判断value中配置的key:value是包含一个URL中的参数,如果有则为true,如果上面两个条件都为true,那么被@Activate修饰的扩展点将会被激活。
3.通过方法入参key从URL获取到的名称所对应的扩展点类会被激活。
public List<T> getActivateExtension(URL url, String[] values, String group) {
List<T> activateExtensions = new ArrayList<>();
List<String> names = values == null ? new ArrayList<>(0) : asList(values);
/**
* 判断url中配置的扩展点名字是否包含-default
*/
if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
/**
* 加载扩展配置文件,加载所有配置的扩展类
*/
getExtensionClasses();
/**
* 循环cachedActivates缓存中所有被@Activate注解修饰的扩展点
* 获取@Activate中配置的group和value
* 判断group配置的值中是否包含方法入参传入的group,如果包含则为true
* 判断value中配置的key:value是包含一个URL中的参数,如果有则为true
* 如果上面两个条件都为true,那么被@Activate修饰的扩展点将会被激活。
*/
for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
String name = entry.getKey();
Object activate = entry.getValue();
String[] activateGroup, activateValue;
if (activate instanceof Activate) {
activateGroup = ((Activate) activate).group();
activateValue = ((Activate) activate).value();
} else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
} else {
continue;
}
/**
* 判断group配置的值中是否包含方法入参传入的group,如果包含则为true
*/
if (isMatchGroup(group, activateGroup)
&& !names.contains(name)
&& !names.contains(REMOVE_VALUE_PREFIX + name)
/**
* 判断value中配置的key:value是包含一个URL中的参数,如果有则为true
*/
&& isActive(activateValue, url)) {
activateExtensions.add(getExtension(name));
}
}
activateExtensions.sort(ActivateComparator.COMPARATOR);
}
List<T> loadedExtensions = new ArrayList<>();
/**
* 通过方法入参key从URL获取到的名称所对应的扩展点类会被激活
*/
for (int i = 0; i < names.size(); i++) {
String name = names.get(i);
if (!name.startsWith(REMOVE_VALUE_PREFIX)
&& !names.contains(REMOVE_VALUE_PREFIX + name)) {
if (DEFAULT_KEY.equals(name)) {
if (!loadedExtensions.isEmpty()) {
activateExtensions.addAll(0, loadedExtensions);
loadedExtensions.clear();
}
} else {
loadedExtensions.add(getExtension(name));
}
}
}
if (!loadedExtensions.isEmpty()) {
activateExtensions.addAll(loadedExtensions);
}
return activateExtensions;
}