0%

JDK、Dubbo、Spring三种SPI机制比较

SPI(Service Provider Interface)是一种服务发现机制,其本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。

SPI 的作用

使用 SPI 机制可以为程序提供扩展功能,例如 JDK 提供了 java.sql.Driver 接口,但却没有给出它的实现,因为数据库种类很多,不同的数据库厂商应该自己提供 Java 的驱动实现,这样有利用程序的扩展

SPI 和 API 的联系

API 和 SPI 都是相对的概念,通常:API 直接被应用开发人员使用,SPI 被框架扩展人员使用。

  • API (Application Programming Interface)

    • 实现方来制定接口并完成对接口的不同实现,调用方通常只选择实现方提供的实现。
  • SPI (Service Provider Interface)

    • 调用方来制定接口,不同的实现方对接口有不同的实现,调用方来选择需要的实现方

Java SPI

Java 内置 SPI 机制,核心类是 java.util.ServiceLoader。其作用就是,可以通过类名获取在”META-INF/services/“下的多个配置实现文件。

使用示例

制定一个机器人打招呼接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 机器人接口
*
* @author jaceding
* @date 2021/5/7
*/
public interface Robot {

/**
* 打招呼
*/
void sayHello();
}

大黄蜂实现:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 大黄蜂
*
* @author jaceding
* @date 2021/5/7
*/
public class Bumblebee implements Robot {
@Override
public void sayHello() {
System.out.println("hello, i am Bumblebee");
}
}

擎天柱实现:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 擎天柱
*
* @author jaceding
* @date 2021/5/7
*/
public class OptimusPrime implements Robot {
@Override
public void sayHello() {
System.out.println("hello, i am OptimusPrime");
}
}

在 META-INF/services 目录下新建文件 per.jaceding.demo.spi.jdk.Robot,内容如下:

1
2
per.jaceding.demo.spi.jdk.Bumblebee
per.jaceding.demo.spi.jdk.OptimusPrime

测试 Java SPI:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.util.ServiceLoader;

/**
* Java SPI 测试
*
* @author jaceding
* @date 2021/5/7
*/
public class JavaSPI {

public static void main(String[] args) {
ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);
serviceLoader.forEach(Robot::sayHello);
}
}

输出:

1
2
hello, i am Bumblebee
hello, i am OptimusPrime

ServiceLoader 源码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public final class ServiceLoader<S>
implements Iterable<S> {

private static final String PREFIX = "META-INF/services/";

// The class or interface representing the service being loaded
private final Class<S> service;

// The class loader used to locate, load, and instantiate providers
private final ClassLoader loader;

// The access control context taken when the ServiceLoader is created
private final AccessControlContext acc;

// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

// The current lazy-lookup iterator
private LazyIterator lookupIterator;

public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}

private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}

public static <S> ServiceLoader<S> load(Class<S> service) {
// 获取当前线程上下文类加载器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}

public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader){
// 将service接口类和线程上下文类加载器作为构造参数,新建一个ServiceLoader对象
return new ServiceLoader<>(service, loader);
}
}
  1. 调用 ServiceLoader.load(Robot.class)
  2. 将接口类 Robot 和 当前线程上下文类加载器作为参数继续调用 load 方法
  3. 将接口类 Robot 和 当前线程上下文类加载器作为构造参数新建一个 ServiceLoader 对象
  4. 初始化 service、loader、acc ,并调用 reload() 方法
  5. 清空缓存,将接口类 Robot 和 当前线程上下文类加载器作为构造参数新建一个LazyIterator对象并赋值给 lookupIterator

继续分析一下 LazyIterator 源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
private class LazyIterator
implements Iterator<S> {

Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;

private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}

private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}

private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}

public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}

public S next() {
if (acc == null) {
return nextService();
} else {
PrivilegedAction<S> action = new PrivilegedAction<S>() {
public S run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}

public void remove() {
throw new UnsupportedOperationException();
}
}

分析 hasNext() 方法:

调用 hasNext() 方法实际上会调用 hasNextService() 方法,该方法会获取配置文件中的配置的实现类的全限定类名,则将下一个实现类的全限定类名赋值给 nextName 并返回 true。

分析 next() 方法:

调用 next() 方法实际上会调用 nextService() 方法,该方法会通过 Class.forName 和 c.newInstance() 反射初始化 nextName 的值(该值在 hasNext() 时被赋值为某个全限定类名),然后再将初始化后的示例放入缓存 providers 中,然后返回该实例。

小结

通过上面的分析可以发现,使用SPI查找具体的实现的时候,需要遍历所有的实现,并实例化,然后我们在循环中才能找到我们需要实现。这应该也是它最大的缺点,需要把所有的实现都实例化了,即便我们不需要,也都给实例化了。

JDBC SPI 源码分析

以 mysql 的驱动为例,通常通过 JDBC 操作数据库需要经过如下步骤:

  1. 加载驱动
  2. 建立连接
  3. 运行SQL语句
  4. 获取结果
  5. 释放资源

其实第一步不需要手动加载驱动,因为会自动加载驱动。

JDBC 示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import java.sql.*;

/**
* JDBC demo
*
* @author jaceding
* @date 2020/5/7
*/
public class JdbcDemo {

private static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";
private static final String DB_URL = "jdbc:mysql://127.0.0.1:3309/weather?useUnicode=true&characterEncoding=UTF-8&useSSL=true&serverTimezone=Asia/Shanghai";

private static final String USER = "root";
private static final String PASS = "kC95aPV3DZOwzT1u";

public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try {
// 注册 JDBC 驱动
// Class.forName(JDBC_DRIVER);

// 打开链接
System.out.println("连接数据库...");
conn = DriverManager.getConnection(DB_URL, USER, PASS);

// 执行查询
System.out.println(" 实例化Statement对象...");
stmt = conn.createStatement();
String sql;
sql = "SELECT version() version";
ResultSet rs = stmt.executeQuery(sql);

// 展开结果集数据库
while (rs.next()) {
// 通过字段检索
String version = rs.getString("version");

// 输出数据
System.out.print("version: " + version);
System.out.print("\n");
}
// 完成后关闭
rs.close();
stmt.close();
conn.close();
} catch (SQLException se) {
// 处理 JDBC 错误
se.printStackTrace();
} catch (Exception e) {
// 处理 Class.forName 错误
e.printStackTrace();
} finally {
// 关闭资源
try {
if (stmt != null) {
stmt.close();
}
} catch (SQLException se2) {
}// 什么都不做
try {
if (conn != null) {
conn.close();
}
} catch (SQLException se) {
se.printStackTrace();
}
}
System.out.println("Goodbye!");
}
}

上述代码中没有手动加载驱动也能正常运行,因为 com.mysql.cj.jdbc.Driver 会自动加载。

查看 DriverManager 源代码,其中有一个静态代码块:

1
2
3
4
5
6
7
8
/**
* Load the initial JDBC drivers by checking the System property
* jdbc.properties and then use the {@code ServiceLoader} mechanism
*/
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}

loadInitialDrivers() 方法会加载 JDBC 驱动,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
private static void loadInitialDrivers() {
String drivers;
try {
// 这里主要是获取通过 System.setProperty("jdbc.drivers", "com.mysql.cj.jdbc.Driver"); 这种方式注册的驱动
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
// If the driver is packaged as a Service Provider, load it.
// Get all the drivers through the classloader
// exposed as a java.sql.Driver.class service.
// ServiceLoader.load() replaces the sun.misc.Providers()

AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
// 通过 Java SPI 获取 Driver 实现类
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();

/* Load these drivers, so that they can be instantiated.
* It may be the case that the driver class may not be there
* i.e. there may be a packaged driver with the service class
* as implementation of java.sql.Driver but the actual class
* may be missing. In that case a java.util.ServiceConfigurationError
* will be thrown at runtime by the VM trying to locate
* and load the service.
*
* Adding a try catch block to catch those runtime errors
* if driver not available in classpath but it's
* packaged as service and that service is there in classpath.
*/
try{
// 这里依次遍历通过 SPI 注册的驱动,next 方法中会通过反射初始化实例,然后会触发驱动中的静态代码块完成自动注册
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});

println("DriverManager.initialize: jdbc.drivers = " + drivers);

if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
// 如果是通过 System.setProperty("jdbc.drivers", "com.mysql.cj.jdbc.Driver") 这种方式设置的驱动则通过Class.forName注册驱动
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}

从 loadInitialDrivers() 中分别通过:

  1. System.getProperty(“jdbc.drivers”) 获取驱动
  2. Java SPI 获取驱动

这里重点分析 Java SPI 方式获取,mysql-connector-java-8.0.20.jar 中 META-INF\services 包含 java.sql.Driver 文件,如下:

所以通过 Java SPI 可以获取到 com.mysql.cj.jdbc.Driver,然后依次遍历通过 SPI 注册的驱动,next 方法中会通过反射初始化实例(ServiceLoader源码中有分析),然后会触发驱动中的静态代码块完成自动注册。

com.mysql.cj.jdbc.Driver 源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
//
// Register ourselves with the DriverManager
//
static {
try {
// 注册驱动
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}

/**
* Construct a new driver and register it with DriverManager
*
* @throws SQLException
* if a database error occurs.
*/
public Driver() throws SQLException {
// Required for Class.forName().newInstance()
}
}

java.sql.DriverManager.registerDriver(new Driver()) 源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// List of registered JDBC drivers
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();

public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {

registerDriver(driver, null);
}

public static synchronized void registerDriver(java.sql.Driver driver,
DriverAction da)
throws SQLException {

/* Register the driver if it has not already been added to our list */
if(driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}

println("registerDriver: " + driver);

}

注册驱动实际上就是用一个并发 List 存储数据库驱动。

那么 DriverManager.getConnection(DB_URL, USER, PASS) 如何选择驱动呢?其源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
public static Connection getConnection(String url,
String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();

if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}

return (getConnection(url, info, Reflection.getCallerClass()));
}

// Worker method called by the public getConnection() methods.
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
/*
* When callerCl is null, we should check the application's
* (which is invoking this class indirectly)
* classloader, so that the JDBC driver class outside rt.jar
* can be loaded from here.
*/
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
// synchronize loading of the correct classloader.
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}

if(url == null) {
throw new SQLException("The url cannot be null", "08001");
}

println("DriverManager.getConnection(\"" + url + "\")");

// Walk through the loaded registeredDrivers attempting to make a connection.
// Remember the first exception that gets raised so we can reraise it.
SQLException reason = null;

// 重点在这里
for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
// 连接正常且不为空则返回,说明哪个能够连接成功则使用哪个驱动
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}

} else {
println(" skipping: " + aDriver.getClass().getName());
}

}

// if we got here nobody could connect.
if (reason != null) {
println("getConnection failed: " + reason);
throw reason;
}

println("getConnection: no suitable driver found for "+ url);
throw new SQLException("No suitable driver found for "+ url, "08001");
}

从上述的代码中可以发现,DriverManager 依次遍历已注册的数据库驱动,哪个连接成功则使用哪个

Dubbo SPI

Dubbo 并未使用 Java SPI,而是重新实现了一套功能更强的 SPI 机制。Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader可以加载指定的实现类,Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下。

使用示例

制定一个机器人打招呼接口(Dubbo SPI 需要在 Robot 接口上标注 @SPI 注解):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import org.apache.dubbo.common.extension.SPI;

/**
* 机器人接口
*
* @author jaceding
* @date 2021/5/7
*/
@SPI
public interface Robot {

/**
* 打招呼
*/
void sayHello();
}

大黄蜂实现:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 大黄蜂
*
* @author jaceding
* @date 2021/5/7
*/
public class Bumblebee implements Robot {
@Override
public void sayHello() {
System.out.println("hello, i am Bumblebee");
}
}

擎天柱实现:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 擎天柱
*
* @author jaceding
* @date 2021/5/7
*/
public class OptimusPrime implements Robot {
@Override
public void sayHello() {
System.out.println("hello, i am OptimusPrime");
}
}

在 META-INF/dubbo 目录下新建文件 per.jaceding.demo.spi.dubbo.Robot,内容如下:

1
2
optimusPrime = per.jaceding.demo.spi.dubbo.OptimusPrime
bumblebee = per.jaceding.demo.spi.dubbo.Bumblebee

测试 Dubbo SPI,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.apache.dubbo.common.extension.ExtensionLoader;

/**
* @author jaceding
* @date 2021/5/7
*/
public class DubboSPI {

public static void main(String[] args) {
ExtensionLoader<Robot> extensionLoader =
ExtensionLoader.getExtensionLoader(Robot.class);
Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
optimusPrime.sayHello();
Robot bumblebee = extensionLoader.getExtension("bumblebee");
bumblebee.sayHello();
}
}

输出:

1
2
hello, i am OptimusPrime
hello, i am Bumblebee

ExtensionLoader 源码分析

使用示例中先通过 ExtensionLoader.getExtensionLoader(Robot.class) 获取一个 ExtensionLoader 实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(64);
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
// ... 省略一些校验代码
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;
}
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

这里主要是构造了一个 ExtensionLoader 实例,并放入缓存中

继续分析 extensionLoader.getExtension(“optimusPrime”) 方法,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public T getExtension(String name) {
return getExtension(name, true);
}

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 中
holder.set(instance);
}
}
}
return (T) instance;
}
// 创建一个 Holder对象
private Holder<Object> getOrCreateHolder(String name) {
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<>());
holder = cachedInstances.get(name);
}
return holder;
}

这里的主要逻辑是,首先检查缓存,缓存未命中则创建扩展对象,继续分析 createExtension(name, wrap),源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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.getDeclaredConstructor().newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
// 这里往后则是 Dubbo IOC 和 AOP 的实现
// 向实例中注入依赖
injectExtension(instance);
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));
}
}
}
}

initExtension(instance);
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}

这里先通过 getExtensionClasses 获取接口的所有的拓展类,然后通过反射创建需要的实例并放入缓存中,注入相关依赖后返回。Dubbo IOC 和 AOP 这里暂时不分析,加下来继续分析下 getExtensionClasses() ,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
private Map<String, Class<?>> getExtensionClasses() {
Map<String, Class<?>> classes = cachedClasses.get();
// 双重检查
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
// 加载扩展类
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}

/**
* synchronized in getExtensionClasses
*/
private Map<String, Class<?>> loadExtensionClasses() {
cacheDefaultExtensionName();

Map<String, Class<?>> extensionClasses = new HashMap<>();
// 加载指定文件夹下的配置文件
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;
}

private void cacheDefaultExtensionName() {
// 获取 SPI 注解,这里的 type 变量是在调用 getExtensionLoader 方法时传入的
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if (defaultAnnotation == null) {
return;
}

String value = defaultAnnotation.value();
if ((value = value.trim()).length() > 0) {
// 对 SPI 注解内容进行切分
String[] names = NAME_SEPARATOR.split(value);
// 检测 SPI 注解内容是否合法,不合法则抛出异常
if (names.length > 1) {
throw new IllegalStateException("More than 1 default extension name on extension " + type.getName()
+ ": " + Arrays.toString(names));
}
// 设置默认名称,参考 getDefaultExtension 方法
if (names.length == 1) {
cachedDefaultName = names[0];
}
}
}

这里也是先检查缓存,若缓存未命中,则通过 synchronized 加锁。加锁后再次检查缓存,并判空。此时如果 classes 仍为 null,则通过 loadExtensionClasses 加载拓展类。loadExtensionClasses() 方法中先调用 cacheDefaultExtensionName() 方法对 SPI 注解进行解析,然后调用 loadDirectory 方法加载指定文件夹配置文件。继续分析 loadDirectory() 方法,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) {
// fileName = 文件夹路径 + type 全限定名
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);
}
}

loadDirectory 方法先通过 classLoader 获取所有资源链接,然后再通过 loadResource 方法加载资源。

继续分析 loadResource() 方法,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
java.net.URL resourceURL, boolean overridden, String... excludedPackages) {
try {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
String line;
String clazz = null;
// 按行读取配置
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();
clazz = line.substring(i + 1).trim();
} else {
clazz = line;
}
if (StringUtils.isNotEmpty(clazz) && !isExcluded(clazz, excludedPackages)) {
// 加载类,并通过 loadClass 方法对类进行缓存
loadClass(extensionClasses, resourceURL, Class.forName(clazz, true, classLoader), name, overridden);
}
} 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);
}
}

loadResource 方法用于读取和解析配置文件,并通过反射加载类,最后调用 loadClass 方法进行其他操作。

loadClass 方法用于主要用于操作缓存,该方法的逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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 注解
if (clazz.isAnnotationPresent(Adaptive.class)) {
cacheAdaptiveClass(clazz, overridden);
} else if (isWrapperClass(clazz)) { // 检测 clazz 是否是 Wrapper 类型
// 存储 clazz 到 cachedWrapperClasses 缓存中
cacheWrapperClass(clazz);
} else {
// 程序进入此分支,表明 clazz 是一个普通的拓展类
// 检测 clazz 是否有默认的构造方法,如果没有,则抛出异常
clazz.getConstructor();
if (StringUtils.isEmpty(name)) {
// 如果 name 为空,则尝试从 Extension 注解中获取 name,或使用小写的类名作为 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)) {
cacheActivateClass(clazz, names[0]);
for (String n : names) {
// 存储 Class 到名称的映射关系
cacheName(clazz, n);
saveInExtensionClass(extensionClasses, clazz, n, overridden);
}
}
}
}
private void cacheName(Class<?> clazz, String name) {
if (!cachedNames.containsKey(clazz)) {
cachedNames.put(clazz, name);
}
}
private void saveInExtensionClass(Map<String, Class<?>> extensionClasses, Class<?> clazz, String name, boolean overridden) {
Class<?> c = extensionClasses.get(name);
if (c == null || overridden) {
// 存储名称到 Class 的映射关系
extensionClasses.put(name, clazz);
} else if (c != clazz) {
String duplicateMsg = "Duplicate extension " + type.getName() + " name " + name + " on " + c.getName() + " and " + clazz.getName();
logger.error(duplicateMsg);
throw new IllegalStateException(duplicateMsg);
}
}

loadClass 方法操作了不同的缓存,比如 cachedAdaptiveClass、cachedWrapperClasses 和 cachedNames 等等。

Dubbo IOC

Dubbo IOC 是通过 setter 方法注入依赖。Dubbo 首先会通过反射获取到实例的所有方法,然后再遍历方法列表,检测方法名是否具有 setter 方法特征。若有,则通过 ObjectFactory 获取依赖对象,最后通过反射调用 setter 方法将依赖设置到目标对象中。整个过程对应的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
private T injectExtension(T instance) {
if (objectFactory == null) {
return instance;
}

try {
// 遍历目标类的所有方法
for (Method method : instance.getClass().getMethods()) {
// 检测方法是否以 set 开头,且方法仅有一个参数,且方法访问级别为 public
if (!isSetter(method)) {
continue;
}
// 检查方法是否包含 DisableInject 注解:判断是否需要自动注入这个属性
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
// 获取 setter 方法参数类型
Class<?> pt = method.getParameterTypes()[0];
if (ReflectUtils.isPrimitives(pt)) {
continue;
}

try {
// 获取属性名,比如 setName 方法对应属性名 name
String property = getSetterProperty(method);
// 从 ObjectFactory 中获取依赖对象
Object object = objectFactory.getExtension(pt, property);
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;
}
private boolean isSetter(Method method) {
return method.getName().startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers());
}
private String getSetterProperty(Method method) {
return method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
}

在上面代码中,objectFactory 变量的类型为 AdaptiveExtensionFactory,AdaptiveExtensionFactory 内部维护了一个 ExtensionFactory 列表,用于存储其他类型的 ExtensionFactory。Dubbo 目前提供了两种 ExtensionFactory,分别是 SpiExtensionFactory 和 SpringExtensionFactory。前者用于创建自适应的拓展,后者是用于从 Spring 的 IOC 容器中获取所需的拓展。

Dubbo IOC 目前仅支持 setter 方式注入,总的来说,逻辑比较简单易懂。

小结

通过上面的分析可以发现,Dubbo SPI 解决了 Java SPI 会初始化所有实现的缺点,只会按需初始化。

SPI 自适应拓展

在 Dubbo 中,很多拓展都是通过 SPI 机制进行加载的,比如 Protocol、Cluster、LoadBalance 等。有时,有些拓展并不想在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载。这听起来有些矛盾。拓展未被加载,那么拓展方法就无法被调用(静态方法除外)。拓展方法未被调用,拓展就无法被加载。对于这个矛盾的问题,Dubbo 通过自适应拓展机制很好的解决了。自适应拓展机制的实现逻辑比较复杂,首先 Dubbo 会为拓展接口生成具有代理功能的代码。然后通过 javassist 或 jdk 编译这段代码,得到 Class 类,最后再通过反射创建代理类。

通过一个示例进行演示,例如:我们有一个车轮制造厂接口 WheelMaker:

1
2
3
public interface WheelMaker {
Wheel makeWheel(URL url);
}

WheelMaker 接口的自适应实现类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class AdaptiveWheelMaker implements WheelMaker {
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);
}
}

AdaptiveWheelMaker 是一个代理类,与传统的代理逻辑不同,AdaptiveWheelMaker 所代理的对象是在 makeWheel 方法中通过 SPI 加载得到的。makeWheel 方法主要做了三件事情:

  1. 从 URL 中获取 WheelMaker 名称
  2. 通过 SPI 加载具体的 WheelMaker 实现类
  3. 调用目标方法

接下来,我们来看看汽车制造厂 CarMaker 接口与其实现类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface CarMaker {
Car makeCar(URL url);
}

public class RaceCarMaker implements CarMaker {
WheelMaker wheelMaker;

// 通过 setter 注入 AdaptiveWheelMaker
public setWheelMaker(WheelMaker wheelMaker) {
this.wheelMaker = wheelMaker;
}

public Car makeCar(URL url) {
Wheel wheel = wheelMaker.makeWheel(url);
return new RaceCar(wheel, ...);
}
}

RaceCarMaker 持有一个 WheelMaker 类型的成员变量,在程序启动时,我们可以将 AdaptiveWheelMaker 通过 setter 方法注入到 RaceCarMaker 中。在运行时,假设有这样一个 url 参数传入:

1
dubbo://192.168.0.101:20880/XxxService?wheel.maker=MichelinWheelMaker

RaceCarMaker 的 makeCar 方法将上面的 url 作为参数传给 AdaptiveWheelMaker 的 makeWheel 方法,makeWheel 方法从 url 中提取 wheel.maker 参数,得到 MichelinWheelMaker。之后再通过 SPI 加载配置名为 MichelinWheelMaker 的实现类,得到具体的 WheelMaker 实例。

上面的示例展示了自适应拓展类的核心实现 —- 在拓展接口的方法被调用时,通过 SPI 加载具体的拓展实现类,并调用拓展对象的同名方法。接下来,我们深入到源码中,探索自适应拓展类生成的过程。

源码分析

在对自适应拓展生成过程进行深入分析之前,我们先来看一下与自适应拓展息息相关的一个注解,即 Adaptive 注解。该注解的定义如下:

1
2
3
4
5
6
7
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {

String[] value() default {};
}

从上面的代码中可知,Adaptive 可注解在类或方法上。当 Adaptive 注解在类上时,Dubbo 不会为该类生成代理类。注解在方法(接口方法)上时,Dubbo 则会为该方法生成代理逻辑。Adaptive 注解在类上的情况很少,在 Dubbo 中,仅有两个类被 Adaptive 注解了,分别是 AdaptiveCompiler 和 AdaptiveExtensionFactory。此种情况,表示拓展的加载逻辑由人工编码完成。更多时候,Adaptive 是注解在接口方法上的,表示拓展的加载逻辑需由框架自动生成。Adaptive 注解的地方不同,相应的处理逻辑也是不同的。注解在类上时,处理逻辑比较简单,本文就不分析了。注解在接口方法上时,处理逻辑较为复杂,本章将会重点分析此块逻辑。

获取自适应扩展

getAdaptiveExtension 方法是获取自适应拓展的入口方法,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public T getAdaptiveExtension() {
// 从缓存中获取自适应拓展
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;
}

getAdaptiveExtension() 方法首先会检查缓存,缓存未命中,则调用 createAdaptiveExtension() 方法创建自适应拓展。

继续分析 createAdaptiveExtension() 方法:

1
2
3
4
5
6
7
8
private T createAdaptiveExtension() {
try {
// 获取自适应拓展类,并通过反射实例化,然后注入相关依赖
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}

createAdaptiveExtension 方法的代码比较少,但却包含了三个逻辑,分别如下:

  1. 调用 getAdaptiveExtensionClass 方法获取自适应拓展 Class 对象
  2. 通过反射进行实例化
  3. 调用 injectExtension 方法向拓展实例中注入依赖

前两个逻辑比较好理解,第三个逻辑用于向自适应拓展对象中注入依赖。这个逻辑看似多余,但有存在的必要,这里简单说明一下。前面说过,Dubbo 中有两种类型的自适应拓展,一种是手工编码的,一种是自动生成的。手工编码的自适应拓展中可能存在着一些依赖,而自动生成的 Adaptive 拓展则不会依赖其他类。这里调用 injectExtension 方法的目的是为手工编码的自适应拓展注入依赖,这一点需要注意。关于 injectExtension 方法,前文已经分析过了,,这里不再分析。

继续分析 getAdaptiveExtensionClass 方法的逻辑:

1
2
3
4
5
6
7
8
9
10
private Class<?> getAdaptiveExtensionClass() {
// 通过 SPI 获取所有的拓展类
getExtensionClasses();
// 检查缓存,若缓存不为空,则直接返回缓存
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
// 创建自适应拓展类
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

getAdaptiveExtensionClass 方法同样包含了三个逻辑,如下:

  1. 调用 getExtensionClasses 获取所有的拓展类
  2. 检查缓存,若缓存不为空,则返回缓存
  3. 若缓存为空,则调用 createAdaptiveExtensionClass 创建自适应拓展类

getExtensionClasses 这个方法用于获取某个接口的所有实现类。比如该方法可以获取 Protocol 接口的 DubboProtocol、HttpProtocol、InjvmProtocol 等实现类。在获取实现类的过程中,如果某个实现类被 Adaptive 注解修饰了,那么该类就会被赋值给 cachedAdaptiveClass 变量。此时,上面步骤中的第二步条件成立(缓存不为空),直接返回 cachedAdaptiveClass 即可。如果所有的实现类均未被 Adaptive 注解修饰,那么创建自适应拓展类。相关代码如下:

1
2
3
4
5
6
7
8
9
private Class<?> createAdaptiveExtensionClass() {
// 构建自适应拓展代码
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();
// 编译代码,生成 Class
return compiler.compile(code, classLoader);
}

createAdaptiveExtensionClass 方法用于生成自适应拓展类,该方法首先会生成自适应拓展类的源码,然后通过 Compiler 实例(Dubbo 默认使用 javassist 作为编译器)编译源码,得到代理类 Class 实例。接下来,我们把重点放在代理类代码生成的逻辑上。

自适应拓展类代码生成

构建自适应扩展代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public String generate() {
// no need to generate adaptive class since there's no adaptive method found.
// 检测是否包含 Adaptive 注解
if (!hasAdaptiveMethod()) {
throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
}

StringBuilder code = new StringBuilder();
code.append(generatePackageInfo());
code.append(generateImports());
code.append(generateClassDeclaration());

Method[] methods = type.getMethods();
for (Method method : methods) {
code.append(generateMethod(method));
}
code.append("}");

if (logger.isDebugEnabled()) {
logger.debug(code.toString());
}
return code.toString();
}

由于这部分代码包含的内容较多,所以对其进行拆分分析。

Adaptive 注解检测

hasAdaptiveMethod 检测是否包含 Adaptive 注解,源码如下:

1
2
3
private boolean hasAdaptiveMethod() {
return Arrays.stream(type.getMethods()).anyMatch(m -> m.isAnnotationPresent(Adaptive.class));
}

在生成代理类源码之前,会通过反射检测接口方法是否包含 Adaptive 注解。对于要生成自适应拓展的接口,Dubbo 要求该接口至少有一个方法被 Adaptive 注解修饰。若不满足此条件,就会抛出运行时异常。

生成类和方法

通过 Adaptive 注解检测后,即可开始生成代码。代码生成的顺序与 Java 文件内容顺序一致,首先会生成 package 语句,然后生成 import 语句,紧接着生成类名代码、最后生成方法。整个逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
StringBuilder code = new StringBuilder();
code.append(generatePackageInfo());
code.append(generateImports());
code.append(generateClassDeclaration());

Method[] methods = type.getMethods();
for (Method method : methods) {
code.append(generateMethod(method));
}
code.append("}");

private String generatePackageInfo() {
return String.format(CODE_PACKAGE, type.getPackage().getName());
}
private String generateImports() {
return String.format(CODE_IMPORTS, ExtensionLoader.class.getName());
}
private String generateClassDeclaration() {
return String.format(CODE_CLASS_DECLARATION, type.getSimpleName(), type.getCanonicalName());
}
private String generateMethod(Method method) {
String methodReturnType = method.getReturnType().getCanonicalName();
String methodName = method.getName();
String methodContent = generateMethodContent(method);
String methodArgs = generateMethodArguments(method);
String methodThrows = generateMethodThrows(method);
return String.format(CODE_METHOD_DECLARATION, methodReturnType, methodName, methodArgs, methodThrows, methodContent);
}

生成类的逻辑比较简单,以 Dubbo 的 Protocol 接口为例,生成的类代码如下:

1
2
3
4
5
package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
// 省略方法代码
}

生成方法的逻辑比较复杂,一个方法可以被 Adaptive 注解修饰,也可以不被修饰。这里将未被 Adaptive 注解修饰的方法称为“无 Adaptive 注解方法”。无 Adaptive 注解方法生成的逻辑只有 generateMethodContent() 这部分不同,无 Adaptive 注解方法生成的逻辑也比较简单,接下来主要分析被 Adaptive 注解修饰的方法 generateMethodContent() 方法的生成逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
private String generateMethodContent(Method method) {
Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
StringBuilder code = new StringBuilder(512);
if (adaptiveAnnotation == null) {
return generateUnsupported(method);
} else {
// 遍历参数列表,确定 URL 参数位置
int urlTypeIndex = getUrlTypeIndex(method);
// urlTypeIndex != -1,表示参数列表中存在 URL 参数
if (urlTypeIndex != -1) {
// 为 URL 类型参数生成判空代码和赋值代码
code.append(generateUrlNullCheck(urlTypeIndex));
} else {
// 没有URL类型参数则调用 generateUrlAssignmentIndirectly 生成代码
code.append(generateUrlAssignmentIndirectly(method));
}
// 获取 Adaptive 注解值
String[] value = getMethodAdaptiveValue(adaptiveAnnotation);
// 检测方法列表中是否存在 Invocation 类型的参数
boolean hasInvocation = hasInvocationArgument(method);

code.append(generateInvocationArgumentNullCheck(method));

code.append(generateExtNameAssignment(value, hasInvocation));
// check extName == null?
code.append(generateExtNameNullCheck(value));

code.append(generateExtensionAssignment());

// return statement
code.append(generateReturnAndInvocation(method));
}

return code.toString();
}
private int getUrlTypeIndex(Method method) {
int urlTypeIndex = -1;
Class<?>[] pts = method.getParameterTypes();
for (int i = 0; i < pts.length; ++i) {
if (pts[i].equals(URL.class)) {
urlTypeIndex = i;
break;
}
}
return urlTypeIndex;
}
private String generateUrlAssignmentIndirectly(Method method) {
Class<?>[] pts = method.getParameterTypes();

Map<String, Integer> getterReturnUrl = new HashMap<>();
// 遍历方法的参数类型列表
// find URL getter method
for (int i = 0; i < pts.length; ++i) {
// 获取某一类型参数的全部方法,遍历方法列表,寻找可返回 URL 的 getter 方法
for (Method m : pts[i].getMethods()) {
String name = m.getName();
// 1. 方法名以 get 开头,或方法名大于3个字符
// 2. 方法的访问权限为 public
// 3. 非静态方法
// 4. 方法参数数量为0
// 5. 方法返回值类型为 URL
if ((name.startsWith("get") || name.length() > 3)
&& Modifier.isPublic(m.getModifiers())
&& !Modifier.isStatic(m.getModifiers())
&& m.getParameterTypes().length == 0
&& m.getReturnType() == URL.class) {
getterReturnUrl.put(name, i);
}
}
}
// 如果所有参数中均不包含可返回 URL 的 getter 方法,则抛出异常
if (getterReturnUrl.size() <= 0) {
// getter method not found, throw
throw new IllegalStateException("Failed to create adaptive class for interface " + type.getName()
+ ": not found url parameter or url attribute in parameters of method " + method.getName());
}

// 生成相关代码
Integer index = getterReturnUrl.get("getUrl");
if (index != null) {
return generateGetUrlNullCheck(index, pts[index], "getUrl");
} else {
Map.Entry<String, Integer> entry = getterReturnUrl.entrySet().iterator().next();
return generateGetUrlNullCheck(entry.getValue(), pts[entry.getValue()], entry.getKey());
}
}

这段代码逻辑会先从 URL 中提取目标拓展的名称,因此需要先从方法的参数列表或其他参数中获取URL数据。

例如,我我们要为 Protocol 接口的 refer 和 export 方法生成代理逻辑,方法定义如下:

1
2
Invoker refer(Class<T> arg0, URL arg1) throws RpcException;
Exporter export(Invoker<T> arg0) throws RpcException;

对于 refer 方法,通过遍历 refer 的参数列表即可获取 URL 数据,这个还比较简单。

对于 export 方法,因为export 参数列表中没有 URL 参数,需要从 Invoker 参数中获取 URL 数据。获取方式是调用 Invoker 中可返回 URL 的 getter 方法,比如 getUrl。如果 Invoker 中无相关 getter 方法,此时则会抛出异常。

获取到URL之后为之生成判空和赋值代码。以 Protocol 的 refer 和 export 方法为例,上面的代码为它们生成如下内容(代码已格式化):

1
2
3
4
5
6
7
8
9
10
11
refer:
if (arg1 == null)
throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg1;

export:
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();
获取 Adaptive 注解值

Adaptive 注解值 value 类型为 String[],可填写多个值,默认情况下为空数组。若 value 为非空数组,直接获取数组内容即可。若 value 为空数组,则需进行额外处理。处理过程是将类名转换为字符数组,然后遍历字符数组,并将字符放入 StringBuilder 中。若字符为大写字母,则向 StringBuilder 中添加点号,随后将字符变为小写存入 StringBuilder 中。比如 LoadBalance 经过处理后,得到 load.balance。

1
2
3
4
5
6
7
8
9
private String[] getMethodAdaptiveValue(Adaptive adaptiveAnnotation) {
String[] value = adaptiveAnnotation.value();
// value is not set, use the value generated from class name as the key
if (value.length == 0) {
String splitName = StringUtils.camelToSplitName(type.getSimpleName(), ".");
value = new String[]{splitName};
}
return value;
}
检测 Invocation 参数

此段逻辑是检测方法列表中是否存在 Invocation 类型的参数,若存在,则为其生成判空代码和其他一些代码。相应的逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
boolean hasInvocation = hasInvocationArgument(method);

code.append(generateInvocationArgumentNullCheck(method));

private boolean hasInvocationArgument(Method method) {
Class<?>[] pts = method.getParameterTypes();
// 判断是否存在参数等于org.apache.dubbo.rpc.Invocation
return Arrays.stream(pts).anyMatch(p -> CLASSNAME_INVOCATION.equals(p.getName()));
}
// 为 Invocation 类型参数生成判空代码和 getMethodName 方法调用代码
private String generateInvocationArgumentNullCheck(Method method) {
Class<?>[] pts = method.getParameterTypes();
return IntStream.range(0, pts.length).filter(i -> CLASSNAME_INVOCATION.equals(pts[i].getName()))
.mapToObj(i -> String.format(CODE_INVOCATION_ARGUMENT_NULL_CHECK, i, i))
.findFirst().orElse("");
}
生成拓展名获取逻辑

根据 SPI 和 Adaptive 注解值生成“获取拓展名逻辑”,同时生成逻辑也受 Invocation 类型参数影响,综合因素导致本段逻辑相对复杂。本段逻辑可能会生成但不限于下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
code.append(generateExtNameAssignment(value, hasInvocation));

private String generateExtNameAssignment(String[] value, boolean hasInvocation) {
// TODO: refactor it
String getNameCode = null;
// 遍历 value,这里的 value 是 Adaptive 的注解值,2.2.3.3 节分析过 value 变量的获取过程。
// 此处循环目的是生成从 URL 中获取拓展名的代码,生成的代码会赋值给 getNameCode 变量。注意这
// 个循环的遍历顺序是由后向前遍历的。
for (int i = value.length - 1; i >= 0; --i) {
// 当 i 为最后一个元素的坐标时
if (i == value.length - 1) {
// 默认拓展名非空
if (null != defaultExtName) {
// protocol 是 url 的一部分,可通过 getProtocol 方法获取,其他的则是从
// URL 参数中获取。因为获取方式不同,所以这里要判断 value[i] 是否为 protocol
if (!"protocol".equals(value[i])) {
// hasInvocation 用于标识方法参数列表中是否有 Invocation 类型参数
if (hasInvocation) {
// 生成的代码功能等价于下面的代码:
// url.getMethodParameter(methodName, value[i], defaultExtName)
// 以 LoadBalance 接口的 select 方法为例,最终生成的代码如下:
// url.getMethodParameter(methodName, "loadbalance", "random")
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) {
// 生成代码格式同上
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 中获取协议的代码,比如 "dubbo"
getNameCode = "url.getProtocol()";
}
}
} else {
if (!"protocol".equals(value[i])) {
if (hasInvocation) {
// 生成代码格式同上
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
} else {
// 生成的代码功能等价于下面的代码:
// 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 {
// 生成的代码功能等价于下面的代码:
// url.getProtocol() == null ? getNameCode : url.getProtocol()
// 以 Protocol 接口的 connect 方法为例,最终生成的代码如下:
// url.getProtocol() == null ? "dubbo" : url.getProtocol()
getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode);
}
}
}

return String.format(CODE_EXT_NAME_ASSIGNMENT, getNameCode);
}

上面代码比较复杂,不是很好理解,以 Transporter 接口的自适应拓展类代码生成过程举例说明。

首先看一下 Transporter 接口的定义,如下:

1
2
3
4
5
6
7
8
9
10
@SPI("netty")
public interface Transporter {
// @Adaptive({server, transporter})
@Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
Server bind(URL url, ChannelHandler handler) throws RemotingException;

// @Adaptive({client, transporter})
@Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
Client connect(URL url, ChannelHandler handler) throws RemotingException;
}

下面对 connect 方法代理逻辑生成的过程进行分析,此时生成代理逻辑所用到的变量如下:

1
2
3
4
String defaultExtName = "netty";
boolean hasInvocation = false;
String getNameCode = null;
String[] value = ["client", "transporter"];

下面对 value 数组进行遍历,此时 i = 1, value[i] = “transporter”,生成的代码如下:

1
getNameCode = url.getParameter("transporter", "netty");

接下来,for 循环继续执行,此时 i = 0, value[i] = “client”,生成的代码如下:

1
getNameCode = url.getParameter("client", url.getParameter("transporter", "netty"));

for 循环结束运行,现在为 extName 变量生成赋值和判空代码,如下:

1
2
3
4
5
6
String extName = url.getParameter("client", url.getParameter("transporter", "netty"));
if (extName == null) {
throw new IllegalStateException(
"Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString()
+ ") use keys([client, transporter])");
}
生成拓展加载与目标方法调用逻辑

本段代码逻辑用于根据拓展名加载拓展实例,并调用拓展实例的目标方法。相关逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private String generateReturnAndInvocation(Method method) {
// 生成拓展获取代码,格式如下:
// type全限定名 extension = (type全限定名)ExtensionLoader全限定名
// .getExtensionLoader(type全限定名.class).getExtension(extName);
// Tips: 格式化字符串中的 %<s 表示使用前一个转换符所描述的参数,即 type 全限定名
String returnStatement = method.getReturnType().equals(void.class) ? "" : "return ";

String args = IntStream.range(0, method.getParameters().length)
.mapToObj(i -> String.format(CODE_EXTENSION_METHOD_INVOKE_ARGUMENT, i))
.collect(Collectors.joining(", "));
// 生成目标方法调用逻辑,格式为:
// extension.方法名(arg0, arg2, ..., argN);
return returnStatement + String.format("extension.%s(%s);\n", method.getName(), args);
}

以 Protocol 接口举例说明,上面代码生成的内容如下:

1
2
3
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader
.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
生成完整的方法

主要用于生成方法定义的代码,比较简单,就不分析代码了。

以 Protocol 的 refer 方法为例,生成的内容如下:

1
2
3
public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) {
// 省略方法体
}

Spring SPI

Spring 的 SPI 配置文件是一个固定的文件 META-INF/spring.factories,功能上和 JDK 的类似,每个接口可以有多个扩展实现。

使用示例

制定一个机器人打招呼接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 机器人接口
*
* @author jaceding
* @date 2021/5/7
*/
public interface Robot {

/**
* 打招呼
*/
void sayHello();
}

大黄蜂实现:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 大黄蜂
*
* @author jaceding
* @date 2021/5/7
*/
public class Bumblebee implements Robot {
@Override
public void sayHello() {
System.out.println("hello, i am Bumblebee");
}
}

擎天柱实现:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 擎天柱
*
* @author jaceding
* @date 2021/5/7
*/
public class OptimusPrime implements Robot {
@Override
public void sayHello() {
System.out.println("hello, i am OptimusPrime");
}
}

在 META-INF 目录下新建 spring.factories 文件,添加如下内容:

1
2
per.jaceding.demo.spi.spring.Robot=per.jaceding.demo.spi.spring.Bumblebee,\
per.jaceding.demo.spi.spring.OptimusPrime

测试 Spring SPI:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.springframework.core.io.support.SpringFactoriesLoader;

import java.util.List;

/**
* @author jaceding
* @date 2021/5/7
*/
public class SpringSPI {

public static void main(String[] args) {
List<Robot> list1 = SpringFactoriesLoader.loadFactories(Robot.class, SpringSPI.class.getClassLoader());
list1.forEach(Robot::sayHello);
List<String> list2 = SpringFactoriesLoader.loadFactoryNames(Robot.class, SpringSPI.class.getClassLoader());
list2.forEach(System.out::println);
}
}

输出:

1
2
3
4
hello, i am Bumblebee
hello, i am OptimusPrime
per.jaceding.demo.spi.spring.Bumblebee
per.jaceding.demo.spi.spring.OptimusPrime

小结

Spring SPI 中,将所有的配置放到一个固定的文件中,省去了配置一大堆文件的麻烦。

Spring的SPI 虽然属于spring-framework(core),但是目前主要用在spring boot中。

总结

三种 SPI 机制对比之下,JDK 内置的机制是最弱的,但是由于是 JDK 内置,所以还是有一定应用场景,毕竟不用额外的依赖。

Dubbo 的功能最丰富,但机制有点复杂了,而且只能配合 Dubbo 使用,不能完全算是一个独立的模块。

Spring 的功能和JDK的相差无几,最大的区别是所有扩展点写在一个 spring.factories 文件中,也算是一个改进,并且 IDEA 完美支持语法提示。

坚持原创技术分享,您的支持将鼓励我继续创作!