Files
JavaYouth/docs/dubbo-sourcecode-v1/03.Dubbo源码系列V1-Dubbo第三节-可扩展机制SPI源码解析.md
2021-12-22 23:31:29 +08:00

1179 lines
42 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: 03.Dubbo源码系列V1-Dubbo第三节-可扩展机制SPI源码解析
tags:
- Dubbo
- rpc
categories:
- rpc
- Dubbo源码系列v1
keywords: Dubborpc
description: Dubbo里面SPI是基础大量用到了SPI
cover: 'https://gitee.com/youthlql/randombg/raw/master/logo/dubbo.png'
abbrlink: dbcfef47
date: 2021-09-12 15:21:58
---
## 第三节: Dubbo的可扩展机制SPI源码解析
### SPI的概念
https://www.cnblogs.com/happyframework/archive/2013/09/17/3325560.html
https://zhuanlan.zhihu.com/p/28909673
spi的概念看上面两就行了案例还是看我下面的举例讲的比较通俗。
### Java的SPI机制
#### 项目目录
```java
spi-demo
api-db-impl-mysql/
| api-db-impl-mysql.iml
| pom.xml
| src/
| | main/
| | | java/
| | | | com/
| | | | youthlql/
| | | | mysql/
| | | | MySQLSaveService.java
| | | resources/
| | | META-INF/
| | | services/
| | | com.youthlql.data.DataSaveService
| | test/
| | java/
| target/
| classes/
| | com/
| | | youthlql/
| | | mysql/
| | | MySQLSaveService.class
| | META-INF/
| | services/
| | com.youthlql.data.DataSaveService
| generated-sources/
| annotations/
api-db-impl-redis/
| api-db-impl-redis.iml
| pom.xml
| src/
| | main/
| | | java/
| | | | com/
| | | | youthlql/
| | | | redis/
| | | | RedisSaveService.java
| | | resources/
| | | META-INF/
| | | services/
| | | com.youthlql.data.DataSaveService
| | test/
| | java/
| target/
| classes/
| | com/
| | | youthlql/
| | | redis/
| | | RedisSaveService.class
| | META-INF/
| | services/
| | com.youthlql.data.DataSaveService
| generated-sources/
| annotations/
api-db-interface/
| api-db-interface.iml
| pom.xml
| src/
| | main/
| | | java/
| | | | com/
| | | | youthlql/
| | | | data/
| | | | DataSaveService.java
| | | resources/
| | test/
| | java/
| target/
| classes/
| | com/
| | youthlql/
| | data/
| | DataSaveService.class
| generated-sources/
| annotations/
app/
| app.iml
| pom.xml
| src/
| | main/
| | | java/
| | | | com/
| | | | youthlql/
| | | | redis/
| | | | MainTest.java
| | | resources/
| | test/
| | java/
| target/
| classes/
| | com/
| | youthlql/
| | redis/
| | MainTest.class
| generated-sources/
| annotations/
pom.xml
spi-demo.iml
```
#### MainTest
```java
import com.youthlql.data.DataSaveService;
import java.util.ServiceLoader;
/**
* 1、 ServiceLoaderload指定一个接口
* 他就会加载当前系统里面所有的这个接口的【指定实现】
* 2、SPIService Provider Interface
* 接口工程---提供接口
* ---- 实现工程1 实现接口 【META-INF/services 创建文件 接口名作为文件名 实现类全路径作为文件内容】
* ---- 实现工程2 实现接口
*
*
* 客户端----引用 工程1、或者 工程2
*
*
*
*/
public class MainTest {
public static void main(String[] args) {
//1、加载 可用的接口实现
ServiceLoader<DataSaveService> load = ServiceLoader.load(DataSaveService.class);
//拿到实现进行调用
for (DataSaveService service : load) {
service.saveData("你好....");
}
}
}
```
输出:
```java
MySQL保存了数据.......你好....
Redis保存了数据.......你好....
```
Java的SPI机制会默认加载**类路径**下`META-INF/services`的东西
#### DataSaveService
```
public interface DataSaveService {
void saveData(String data);
}
```
#### MySQLSaveService
```java
public class MySQLSaveService implements DataSaveService {
@Override
public void saveData(String data) {
System.out.println("MySQL保存了数据......." + data);
}
}
```
#### RedisSaveService
```java
public class RedisSaveService implements DataSaveService {
@Override
public void saveData(String data) {
System.out.println("Redis保存了数据......."+data);
}
}
```
#### SPI文件示例
api-db-impl-redis\src\main\resources\META-INF\services\com.youthlql.data.DataSaveService
```txt
com.youthlql.redis.RedisSaveService
```
api-db-impl-mysql\src\main\resources\META-INF\services\com.youthlql.data.DataSaveService
```txt
com.youthlql.mysql.MySQLSaveService
```
你没看错就是这么简单
#### Java的SPI机制的作用
我只需要规定接口就可以开放给任何人实现
> META-INF\services下的文件本文统称为**SPI文件**
### Dubbo为什么要实现自己的SPI机制
> SPI机制dubbo源码中大量用到读者务必理解清楚。
SPI文件里写了什么,java的`ServiceLoader`都会给你一次性加载完
```java
com.youthlql.RedCar
com.youthlql.BlackCar
com.youthlql,WhiteCar
....
```
`SpiTest.java`
```java
public static void main(String[] args) {
ServiceLoader<Car> cars = ServiceLoader.load(Car.class);
for (Car car : cars) {
System.out.println(car.getCarName(null));
}
}
```
假设现在有一个需求我们某一时刻只需要RedCar某一时刻又需要BlackCar就是说我们不想让它一次性加载完。
spi文件我们可以这样写
```java
red=com.youthlql.RedCar
black=com.youthlql.BlackCar
```
`SpiTest.java`
```java
public static void main(String[] args) {
ServiceLoader<Car> cars = ServiceLoader.load(Car.class,"red");
for (Car car : cars) {
System.out.println(car.getCarName(null));
}
}
```
因为这样的需求就引出了dubbo想要实现的SPI的功能。
前面介绍过Dubbo支持http协议支持dubbo协议等等实现这样的SPI之后。写代码时只需要在application.properties里直接指定你想要用的协议需要http协议时就只加载对应的protocol的http实现类
```properties
dubbo.protocols.p1.id=dubbo1
dubbo.protocols.p1.name=dubbo
dubbo.protocols.p1.port=20881
dubbo.protocols.p1.host=0.0.0.0
dubbo.protocols.p2.id=http
dubbo.protocols.p2.name=http
dubbo.protocols.p2.port=8083
dubbo.protocols.p2.host=0.0.0.0
```
### Demo
```java
ExtensionLoader<Protocol> extensionLoader = ExtensionLoader.getExtensionLoader(Protocol.class);
Protocol http = extensionLoader.getExtension("dubbo");
System.out.println(http);
```
上面这个Demo就是Dubbo常见的写法表示获取"dubbo"对应的Protocol扩展点。Protocol是一个接口。
#### getExtensionLoader源码
```java
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();
//中间代码略...
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() + "!");
}
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;
}
```
在ExtensionLoader类的内部有一个static的ConcurrentHashMap用来缓存**某个接口类型所对应的ExtensionLoader实例**
### ExtensionLoader源码
- Exte nsionLoader表示某个接口的扩展点加载器可以用来加载某个扩展点实例。
- 在ExtensionLoader中除开有上文的static的Map外还有两个非常重要的属性
1. **Class<?> type**表示当前ExtensionLoader实例是哪个接口的扩展点加载器
2. **ExtensionFactory objectFactory**扩展点工厂(对象工厂),可以获得某个对象
- **ExtensionLoader**和**ExtensionFactory**的区别在于:
1. ExtensionLoader最终所得到的对象是Dubbo SPI机制产生的
2. ExtensionFactory最终所得到的对象可能是Dubbo SPI机制所产生的也可能是从Spring容器中所获得的对象
- 在**ExtensionLoader**中有三个常用的方法:
1. **getExtension("dubbo")**表示获取名字为dubbo的扩展点实例
2. **getAdaptiveExtension()**表示获取一个自适应的扩展点实例
3. **getActivateExtension(URL url, String\[\] values, String group)**表示一个可以被url激活的扩展点实例后文详细解释
其中,什么是**自适应扩展点实例**?它其实就是当前这个接口的一个**代理对象。**
```java
ExtensionLoader<Protocol> extensionLoader = ExtensionLoader.getExtensionLoader(Protocol.class);
Protocol protocol = extensionLoader.getExtension("dubbo");
```
当我们调用上述代码我们会将得到一个DubboProtocol的实例对象但在getExtension()方法中Dubbo会对DubboProtocol对象进行**依赖注入也就是自动给属性赋值属性的类型为一个接口记为A接口**这个时候对于Dubbo来说它并不知道该给这个属性赋什么值换句话说Dubbo并不知道在进行依赖注入时该找一个什么的的扩展点对象给这个属性这时就会预先赋值一个A接口的自适应扩展点实例也就是A接口的一个代理对象。
后续在A接口的代理对象被真正用到时才会结合URL信息找到真正的A接口对应的扩展点实例进行调用。
#### getExtension()
在调用getExtension去获取一个扩展点实例后会对实例进行缓存下次再获取同样名字的扩展点实例时就会从缓存中拿了。
```java
public T getExtension(String name) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
// 获取默认扩展类
if ("true".equals(name)) {
return getDefaultExtension();
}
final Holder<Object> holder = getOrCreateHolder(name);
Object instance = holder.get();
// 如果有两个线程同时来获取同一个name的扩展点对象那只会有一个线程会进行创建
if (instance == null) {
synchronized (holder) { // 一个name对应一把锁
instance = holder.get();
if (instance == null) {
// 创建扩展点实例对象
instance = createExtension(name); // 创建扩展点对象
holder.set(instance);
}
}
}
return (T) instance;
}
private Holder<Object> getOrCreateHolder(String name) {
// Map<String, Object>
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<>());
holder = cachedInstances.get(name);
}
return holder;
}
```
什么是默认的扩展类就是像下面这样在接口上指定了spi注解
```java
@SPI("red")
public interface Car {
String getCarName);
}
```
#### createExtension()
```java
private T createExtension(String name) {
//根据name获取扩展类 {name: Class} key-Value 接口的所有实现类
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);
}
//对生成出来的实例进行依赖注入(给实例的属性进行赋值)
injectExtension(instance);
// AOP:对依赖注入后的实例进行AOPWrapper,把当前接口类的所有的Wrapper全部一层一层包裹在实例对象上
// 每包裹个Wrapper后也会对Wrapper对象进行依赖注入
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
//返回最终的Wrapper对象,debug一下你就会看到返回的不是redCar而是carWrapper
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
```
> wrapper就相当于在你想要的对象外面再封一层可以做一些事情详见下面讲的AOP
#### getExtensionClasses()
```java
/**
* 加载当前ExtensionLoader对象中指定的接口的所有扩展
* @return
*/
private Map<String, Class<?>> getExtensionClasses() {
// cachedClasses是一个Holder对象持有的就是一个Map<String, Class<?>>
// 为什么要多此一举也是为了解决并发Holder对象用来作为锁
Map<String, Class<?>> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
classes = loadExtensionClasses(); // 加载、解析文件 Map
cachedClasses.set(classes);
}
}
}
return classes;
}
/**
* synchronized in getExtensionClasses
* */
private Map<String, Class<?>> loadExtensionClasses() {
// cache接口默认的扩展类也就是上面说的@SPI("red")注解标记的接口指定的名
cacheDefaultExtensionName();
Map<String, Class<?>> 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;
}
private static final String SERVICES_DIRECTORY = "META-INF/services/";
private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
```
getExtensionClasses()是用来加载当前接口所有的扩展点实现类的返回一个Map。之后可以从这个Map中按照指定的name获取对应的扩展点实现类。
Dubbo在加载一个接口的扩展点时思路是这样的
1. 根据接口的全限定名去META-INF/dubbo/internal/目录下寻找对应的文件调用loadResource方法进行加载
2. 根据接口的全限定名去META-INF/dubbo/目录下寻找对应的文件调用loadResource方法进行加载
3. 根据接口的全限定名去META-INF/services/目录下寻找对应的文件调用loadResource方法进行加载
这里其实会设计到老版本兼容的逻辑,不解释了。
#### loadDirectory()
```java
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
String fileName = dir + type;
try {
// 根据文件中的内容得到urls 每个url表示一个扩展 http=org.apache.dubbo.rpc.protocol.http.HttpProtocol
Enumeration<java.net.URL> urls;
ClassLoader classLoader = findClassLoader();
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
if (urls != null) {
while (urls.hasMoreElements()) {
java.net.URL resourceURL = urls.nextElement();
// 遍历url进行加载,把扩展类添加到extensionClasses中
loadResource(extensionClasses, classLoader, resourceURL);
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", description file: " + fileName + ").", t);
}
}
```
1. BootstrapClassLoader用于加载JAVA核心类库也就是环境变量的%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar等。在JVM启动时加入-Xbootclasspath参数可以把对应路径也加载到Bootstrap的路径列表中来
2. ExtentionClassLoader扩展类加载器加载环境变量%JRE_HOME%\lib\ext目录下的class文件
3. AppclassLoader加载**classpath**中的class类。
#### loadResource()
loadResource方法就是完成对文件内容的解析按行进行解析会解析出**"="**两边的内容,"="左边的内容就是扩展点的name右边的内容就是扩展点实现类并且会利用ExtensionLoader类的类加载器来加载扩展点实现类。然后调用loadClass方法对name和扩展点实例进行详细的解析并且最终把他们放到Map中去。
```java
private void loadResource(Map<String, Class<?>> 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;
int i = line.indexOf('=');
if (i > 0) {
name = line.substring(0, i).trim();
line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
// 加载类并添加到extensionClasses中
loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
exceptions.put(line, e);
}
}
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", class file: " + resourceURL + ") in " + resourceURL, t);
}
}
```
#### loadClass()
```java
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) 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.");
}
/*
1.当前扩展点实现类上是否存在@Adaptive注解如果存在则把该类认为是当前接口的默认自适应类
接口代理类并把该类存到cachedAdaptiveClass属性上。
2.当前扩展点实现是否是一个当前接口的一个Wrapper类如何判断的就是看当前类中是否存在一
个构造方法,该构造方法只有一个参数,参数类型为接口类型,如果存在这一的构造方法,那么这个
类就是该接口的Wrapper类如果是则把该类添加到cachedWrapperClasses中去.
cachedWrapperClasses是一个set。
*/
if (clazz.isAnnotationPresent(Adaptive.class)) {
cacheAdaptiveClass(clazz);
} else if (isWrapperClass(clazz)) {
cacheWrapperClass(clazz);
} else {
// 需要有无参的构造方法,没有会报错
clazz.getConstructor();
/*
1. 本来应该这样写的 red=com.youthlql.RedCar
2.如果你前面的red这个name没写像这样只写了个全类名com.youthlql.RedCar
3.默认会去com.youthlql.RedCar这个类上找有没有@Extension注解起名了【官方已经标记成废弃了】
*/
if (StringUtils.isEmpty(name)) {
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
//name可以配多个
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
//判断一下当前扩展点实现类上是否存在@Activate注解如果存在则把该类添加到cachedActivates中
cacheActivateClass(clazz, names[0]);
//遍历多个name把每个name和对应的实现类存到extensionClasses中去extensionClasses就是上文所提到的map
for (String n : names) {
// clazz: name
cacheName(clazz, n);
// name: clazz
saveInExtensionClass(extensionClasses, clazz, n);
}
}
}
}
```
至此加载类就走完了。回到createExtension(String name)方法中的逻辑当前这个接口的所有扩展点实现类都扫描完了之后就可以根据用户所指定的名字找到对应的实现类了然后进行实例化然后进行IOC(依赖注入)和AOP。
### Dubbo中的IOC
#### 什么是dubbo的IOC
```java
package cn.imlql.ioc;
import cn.imlql.spi.Car;
public class BlackPerson implements Person {
private Car car;
public void setCar(Car car) {
this.car = car;
}
@Override
public Car getCar() {
return car;
}
}
```
```java
package cn.imlql.ioc;
import cn.imlql.spi.Car;
import com.alibaba.dubbo.common.extension.SPI;
@SPI
public interface Person {
Car getCar();
}
```
SPI文件resources\META-INF\dubbo\cn.imlql.ioc.Person
```java
black=cn.imlql.ioc.BlackPerson
```
1. BlackPerson类中有个car属性,dubbo会把car属性通过set方法注入到BlackPerson中
2. 但是具体注入哪一个dubbo并不知道person.getCar()这一步dubbo虽然不知道具体注入哪一个实现类对象但是dubbo生成了一个代理对象给注入鉣了
3. 真正需要确定是哪个car的实现类时候是调用car的方法的时候通过URL这个东西来确定注入哪个car的实现类
```java
package cn.imlql.ioc;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
/**
* <p>
* </p>
*
* @author https://github.com/youthlql
* @since 2021/9/4 - 20:08
*/
public class DubboIOCTest {
public static void main(String[] args) {
ExtensionLoader<Person> extensionLoader = ExtensionLoader.getExtensionLoader(Person.class);
Person person = extensionLoader.getExtension("black"); // BlackPerson
URL url = new URL("x", "localhost", 8080);
url = url.addParameter("car", "black");
System.out.println(person.getCar().getCarName(url)); // 代理逻辑
}
}
```
#### injectExtension()
上面提到过这个方法
```java
private T injectExtension(T instance) {
if (objectFactory == null) {
return instance;
}
try {
//根据当前实例的类找到这个类中的setter方法进行依赖注入
for (Method method : instance.getClass().getMethods()) {
if (!isSetter(method)) {
continue;
}
// 利用set方法注入
/**
* Check {@link DisableInject} to see if we need auto injection for this property
*/
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
//先分析出setter方法的参数类型pt
Class<?> pt = method.getParameterTypes()[0]; // Person接口
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
//再截取出setter方法所对应的属性名property得到setxxx中的xxx
String property = getSetterProperty(method); // person
//1.得到一个对象这里就会从Spring容器或通过DubboSpi机制得到一个对象比较特殊的是
//2.如果是通过DubboSpi机制得到的对象是pt这个类型的一个自适应对象(代理对象)。
//3.也就是说这里既可以从spring容器里拿到一个对象也可以从dubbo里拿到一个代理对象
Object object = objectFactory.getExtension(pt, property); // User.class, user
if (object != null) {
//再反射调用setter方法进行注入
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这个类是如何拿到Spring容器里的对象
```java
//我们可以看到objectFactory是在这里赋值的
private ExtensionLoader(Class<?> type) {
this.type = type;
// objectFactory表示当前ExtensionLoader内部的一个对象工厂可以用来获取对象 AdaptiveExtensionFactory
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
```
#### getAdaptiveExtension()
关键就是后面的`getAdaptiveExtension()`方法,点进去调用链是这样的
```java
getAdaptiveExtension() ---> createAdaptiveExtension() ---> getAdaptiveExtensionClass()
```
```java
private Class<?> getAdaptiveExtensionClass() {
// 获取当前接口的所有扩展类
getExtensionClasses();
// 缓存了@Adaptive注解标记的类
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
// 如果某个接口没有手动指定一个Adaptive类(@Adaptive注解)那么就自动生成一个Adaptive类
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
private Class<?> createAdaptiveExtensionClass() {
// cachedDefaultName表示接口默认的扩展类
String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
ClassLoader classLoader = findClassLoader();
org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader);
}
```
1. getAdaptiveExtension()的作用就是获取标注了@Adaptive注解的factory工厂ExtensionFactory的实现类中只有AdaptiveExtensionFactory标注了此注解
2. 也就是说objectFactory就是AdaptiveExtensionFactory类型并且调用了AdaptiveExtensionFactory的getExtension()方法
#### AdaptiveExtensionFactory类
```java
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
private final List<ExtensionFactory> factories;
public AdaptiveExtensionFactory() {
// 支持哪些ExtensionFactory (Spi, Spring)从这里就把SpringExtensionFactory搞进来了
ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
for (String name : loader.getSupportedExtensions()) { // spi, spring
list.add(loader.getExtension(name));
}
factories = Collections.unmodifiableList(list);
}
@Override
public <T> T getExtension(Class<T> type, String name) {
// 遍历两个ExtensionFactory从ExtensionFactory中得到实例只要从某个ExtensionFactory中获取到对象实例就可以了
for (ExtensionFactory factory : factories) {
T extension = factory.getExtension(type, name); // 顺序是这样的SpringExtensionFactory,, SpiExtensionFactory
if (extension != null) {
return extension;
}
}
return null;
}
}
```
#### SpringExtensionFactory类
```java
// 从Spring容器中获取bean
// 先根据name拿再根据类型拿
@Override
@SuppressWarnings("unchecked")
public <T> T getExtension(Class<T> type, String name) {
//SPI should be get from SpiExtensionFactory
// 如果接口上存在SPI注解就不从spring中获取对象实例了
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
return null;
}
// 从ApplicationContext中获取bean, byname
for (ApplicationContext context : CONTEXTS) {
if (context.containsBean(name)) {
Object bean = context.getBean(name);
if (type.isInstance(bean)) {
return (T) bean;
}
}
}
```
#### SpiExtensionFactory类
```java
public class SpiExtensionFactory implements ExtensionFactory {
//type就是属性的类型Car
@Override
public <T> T getExtension(Class<T> type, String name) {
// 接口上存在SPI注解
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
//根据属性类型得到一个扩展点加载器
ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
if (!loader.getSupportedExtensions().isEmpty()) {
//这里就又回到了上面写过的getAdaptiveExtension方法
return loader.getAdaptiveExtension();
}
}
return null;
}
}
```
dubbo生成的代理对象代码可以看下面的`自适应扩展点补充`这里应该就知道为什么要用URL了
```java
public class DubboIOCTest {
public static void main(String[] args) {
ExtensionLoader<Person> extensionLoader = ExtensionLoader.getExtensionLoader(Person.class);
Person person = extensionLoader.getExtension("black"); // BlackPerson
URL url = new URL("x", "localhost", 8080);
url = url.addParameter("car", "black");
System.out.println(person.getCar().getCarName(url)); // 代理逻辑
}
}
```
需要注意的是代理对象被代理的方法也要加@Adaptive注解同时加URL参数就像上面调用了car的getCarName方法那么下面就长这样。如果不加的话会抛一个UnsupportedOperationException。具体的可以自己debug到生成代理对象那一步然后把代理对象值复制到txt文件里自己看下
```java
@SPI
public interface Car {
@Adaptive
String getCarName(URL url);
//参数如果是自己定义的类只要这个类里也有URL这个属性就可以
@Adaptive
String hello(MyClass myclass);
}
```
### Dubbo中的AOP
dubbo中也实现了一套非常简单的AOP就是利用Wrapper如果一个接口的扩展点中包含了多个Wrapper类那么在实例化完某个扩展点后就会利用这些Wrapper类对这个实例进行包裹比如现在有一个DubboProtocol的实例同时对于Protocol这个接口还有很多的Wrapper比如ProtocolFilterWrapper、ProtocolListenerWrapper那么当对DubboProtocol的实例完成了IOC之后就会先调用new ProtocolFilterWrapper(DubboProtocol实例)生成一个新的Protocol的实例再对此实例进行IOC完了之后会再调用new ProtocolListenerWrapper(ProtocolFilterWrapper实例)生成一个新的Protocol的实例然后进行IOC从而完成DubboProtocol实例的AOP。
```java
private T createExtension(String name) {
//根据name获取扩展类 {name: Class} key-Value 接口的所有实现类
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);
}
//对生成出来的实例进行依赖注入(给实例的属性进行赋值)
injectExtension(instance);
// AOP:对依赖注入后的实例进行AOPWrapper,把当前接口类的所有的Wrapper全部一层一层包裹在实例对象上
// 每包裹个Wrapper后也会对Wrapper对象进行依赖注入
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
//返回最终的Wrapper对象,debug一下你就会看到返回的不是redCar而是carWrapper
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
```
### 自适应扩展点补充
1. 上面提到的自适应扩展点对象也就是某个接口的代理对象是通过Dubbo内部生成代理类然后生成代理对象的。
2. 额外的在Dubbo中还设计另外一种机制来生成自适应扩展点这种机制就是可以通过@Adaptive注解来指定某个类为某个接口的代理类如果指定了Dubbo在生成自适应扩展点对象时实际上生成的就是@Adaptive注解所注解的类的实例对象。
#### createAdaptiveExtensionClass方法
createAdaptiveExtensionClass方法就是Dubbo中默认生成Adaptive类实例的逻辑。说白了这个实例就是当前这个接口的一个代理对象。比如下面的代码
```java
ExtensionLoader<Protocol> extensionLoader = ExtensionLoader.getExtensionLoader(Protocol.class);
Protocol protocol = extensionLoader.getAdaptiveExtension();
```
这个代码就是Protocol接口的一个代理对象那么代理逻辑就是在**new** AdaptiveClassCodeGenerator(**type**, **cachedDefaultName**).generate()方法中。
1. type就是接口
2. cacheDefaultName就是该接口默认的扩展点实现的名字
再看个例子Protocol接口的Adaptive类的代理
```java
package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
public void destroy() {
throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
}
public int getDefaultPort() {
throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
}
public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
if (arg0 == null)
throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null)
throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
org.apache.dubbo.common.URL url = arg0.getUrl();
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null)
throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0);
}
public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
if (arg1 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg1;
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
}
```
可以看到Protocol接口中有四个方法但是只有export和refer两个方法进行代理。为什么因为Protocol接口中在export方法和refer方法上加了@Adaptive注解。但是不是只要在方法上加了@Adaptive注解就可以进行代理还有其他条件比如
1. 该方法如果是无参的,那么则会报错
2. 该方法有参数可以有多个并且其中某个参数类型是URL那么则可以进行代理
3. 该方法有参数可以有多个但是没有URL类型的参数那么则不能进行代理
4. 该方法有参数可以有多个没有URL类型的参数但是如果这些参数类型对应的类中存在getUrl方法返回值类型为URL那么也可以进行代理
所以可以发现某个接口的Adaptive对象在调用某个方法时是通过该方法中的URL参数或者getURL方法通过调用ExtensionLoader.getExtensionLoader(com.luban.Car.class).getExtension(extName);得到一个扩展点实例,然后调用该实例对应的方法。
### Activate扩展点
上文说到每个扩展点都有一个name通过这个name可以获得该name对应的扩展点实例但是有的场景下希望一次性获得多个扩展点实例
#### demo
```java
ExtensionLoader<Filter> extensionLoader = ExtensionLoader.getExtensionLoader(Filter.class);
URL url = new URL("http://", "localhost", 8080);
url = url.addParameter("cache", "test");
List<Filter> activateExtensions = extensionLoader.getActivateExtension(url,
new String[]{"validation"},
CommonConstants.CONSUMER);
for (Filter activateExtension : activateExtensions) {
System.out.println(activateExtension);
}
```
会找到5个Filter
```java
org.apache.dubbo.rpc.filter.ConsumerContextFilter@4566e5bd
org.apache.dubbo.rpc.protocol.dubbo.filter.FutureFilter@1ed4004b
org.apache.dubbo.monitor.support.MonitorFilter@ff5b51f
org.apache.dubbo.cache.filter.CacheFilter@25bbe1b6
org.apache.dubbo.validation.filter.ValidationFilter@5702b3b1
```
前三个是通过CommonConstants.CONSUMER找到的
CacheFilter是通过url中的参数找到的
ValidationFilter是通过指定的name找到的
在一个扩展点类上,可以添加@Activate注解,这个注解的属性有:
1. String\[\] group()表示这个扩展点是属于哪组的这里组通常分为PROVIDER和CONSUMER表示该扩展点能在服务提供者端或者消费端使用
2. String\[\] value()表示的是URL中的某个参数key当利用getActivateExtension方法来寻找扩展点时如果传入的url中包含的参数的所有key中包括了当前扩展点中的value值那么则表示当前url可以使用该扩展点。