SPI(Service Provider Interface) 是一种服务发现机制 ,其本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。
SPI 的作用 使用 SPI 机制可以为程序提供扩展功能,例如 JDK 提供了 java.sql.Driver
接口,但却没有给出它的实现,因为数据库种类很多,不同的数据库厂商应该自己提供 Java 的驱动实现,这样有利用程序的扩展 。
SPI 和 API 的联系 API 和 SPI 都是相对的概念,通常:API 直接被应用开发人员使用,SPI 被框架扩展人员使用。
Java SPI Java 内置 SPI 机制,核心类是 java.util.ServiceLoader
。其作用就是,可以通过类名获取在”META-INF/services/“下的多个配置实现文件。
使用示例 制定一个机器人打招呼接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 public interface Robot { void sayHello () ; }
大黄蜂实现:
1 2 3 4 5 6 7 8 9 10 11 12 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 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;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 Bumblebeehello, 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/" ; private final Class<S> service; private final ClassLoader loader; private final AccessControlContext acc; private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); 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) { return new ServiceLoader<>(service, loader); } }
调用 ServiceLoader.load(Robot.class)
将接口类 Robot 和 当前线程上下文类加载器作为参数继续调用 load 方法
将接口类 Robot 和 当前线程上下文类加载器作为构造参数新建一个 ServiceLoader 对象
初始化 service、loader、acc ,并调用 reload() 方法
清空缓存,将接口类 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(); } 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 操作数据库需要经过如下步骤:
加载驱动
建立连接
运行SQL语句
获取结果
释放资源
其实第一步不需要手动加载驱动 ,因为会自动加载驱动。
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.*;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 { 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) { se.printStackTrace(); } catch (Exception e) { 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 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 { drivers = AccessController.doPrivileged(new PrivilegedAction<String>() { public String run () { return System.getProperty("jdbc.drivers" ); } }); } catch (Exception ex) { drivers = null ; } AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run () { ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class ) ; Iterator<Driver> driversIterator = loadedDrivers.iterator(); try { while (driversIterator.hasNext()) { driversIterator.next(); } } catch (Throwable t) { } 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 { println("DriverManager.Initialize: loading " + aDriver); Class.forName(aDriver, true , ClassLoader.getSystemClassLoader()); } catch (Exception ex) { println("DriverManager.Initialize: load failed: " + ex); } } }
从 loadInitialDrivers() 中分别通过:
System.getProperty(“jdbc.drivers”) 获取驱动
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 { static { try { java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!" ); } } public Driver () throws SQLException { } }
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 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 { if (driver != null ) { registeredDrivers.addIfAbsent(new DriverInfo(driver, da)); } else { 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())); } private static Connection getConnection ( String url, java.util.Properties info, Class<?> caller) throws SQLException { ClassLoader callerCL = caller != null ? caller.getClassLoader() : null ; synchronized (DriverManager.class ) { if (callerCL == null ) { callerCL = Thread.currentThread().getContextClassLoader(); } } if (url == null ) { throw new SQLException("The url cannot be null" , "08001" ); } println("DriverManager.getConnection(\"" + url + "\")" ); SQLException reason = null ; for (DriverInfo aDriver : registeredDrivers) { if (isDriverAllowed(aDriver.driver, callerCL)) { try { println(" trying " + aDriver.driver.getClass().getName()); Connection con = aDriver.driver.connect(url, info); if (con != null ) { println("getConnection returning " + aDriver.driver.getClass().getName()); return (con); } } catch (SQLException ex) { if (reason == null ) { reason = ex; } } } else { println(" skipping: " + aDriver.getClass().getName()); } } 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;@SPI public interface Robot { void sayHello () ; }
大黄蜂实现:
1 2 3 4 5 6 7 8 9 10 11 12 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 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;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 OptimusPrimehello, 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(); } 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; } 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); } 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; } 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 () { final SPI defaultAnnotation = type.getAnnotation(SPI.class ) ; if (defaultAnnotation == null ) { return ; } String value = defaultAnnotation.value(); if ((value = value.trim()).length() > 0 ) { String[] names = NAME_SEPARATOR.split(value); if (names.length > 1 ) { throw new IllegalStateException("More than 1 default extension name on extension " + type.getName() + ": " + Arrays.toString(names)); } 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) { String fileName = dir + type; try { Enumeration<java.net.URL> urls = null ; ClassLoader classLoader = findClassLoader(); 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(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." ); } if (clazz.isAnnotationPresent(Adaptive.class )) { cacheAdaptiveClass(clazz, overridden); } else if (isWrapperClass(clazz)) { cacheWrapperClass(clazz); } else { clazz.getConstructor(); 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); } } String[] names = NAME_SEPARATOR.split(name); if (ArrayUtils.isNotEmpty(names)) { cacheActivateClass(clazz, names[0 ]); for (String n : names) { 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) { 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()) { if (!isSetter(method)) { continue ; } if (method.getAnnotation(DisableInject.class ) ! = null ) { continue ; } Class<?> pt = method.getParameterTypes()[0 ]; if (ReflectUtils.isPrimitives(pt)) { continue ; } try { String property = getSetterProperty(method); Object object = objectFactory.getExtension(pt, property); 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; } 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" ); } String wheelMakerName = url.getParameter("Wheel.maker" ); if (wheelMakerName == null ) { throw new IllegalArgumentException("wheelMakerName == null" ); } WheelMaker wheelMaker = ExtensionLoader .getExtensionLoader(WheelMaker.class ).getExtension (wheelMakerName ) ; return wheelMaker.makeWheel(url); } }
AdaptiveWheelMaker 是一个代理类,与传统的代理逻辑不同,AdaptiveWheelMaker 所代理的对象是在 makeWheel 方法中通过 SPI 加载得到的。makeWheel 方法主要做了三件事情:
从 URL 中获取 WheelMaker 名称
通过 SPI 加载具体的 WheelMaker 实现类
调用目标方法
接下来,我们来看看汽车制造厂 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; 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 参数传入:
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 方法的代码比较少,但却包含了三个逻辑,分别如下:
调用 getAdaptiveExtensionClass 方法获取自适应拓展 Class 对象
通过反射进行实例化
调用 injectExtension 方法向拓展实例中注入依赖
前两个逻辑比较好理解,第三个逻辑用于向自适应拓展对象中注入依赖。这个逻辑看似多余,但有存在的必要,这里简单说明一下。前面说过,Dubbo 中有两种类型的自适应拓展,一种是手工编码的,一种是自动生成的。手工编码的自适应拓展中可能存在着一些依赖,而自动生成的 Adaptive 拓展则不会依赖其他类。这里调用 injectExtension 方法的目的是为手工编码的自适应拓展注入依赖,这一点需要注意。关于 injectExtension 方法,前文已经分析过了,,这里不再分析。
继续分析 getAdaptiveExtensionClass 方法的逻辑:
1 2 3 4 5 6 7 8 9 10 private Class<?> getAdaptiveExtensionClass() { getExtensionClasses(); if (cachedAdaptiveClass != null ) { return cachedAdaptiveClass; } return cachedAdaptiveClass = createAdaptiveExtensionClass(); }
getAdaptiveExtensionClass 方法同样包含了三个逻辑,如下:
调用 getExtensionClasses 获取所有的拓展类
检查缓存,若缓存不为空,则返回缓存
若缓存为空,则调用 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 () ; 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 () { 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 { int urlTypeIndex = getUrlTypeIndex(method); if (urlTypeIndex != -1 ) { code.append(generateUrlNullCheck(urlTypeIndex)); } else { code.append(generateUrlAssignmentIndirectly(method)); } String[] value = getMethodAdaptiveValue(adaptiveAnnotation); boolean hasInvocation = hasInvocationArgument(method); code.append(generateInvocationArgumentNullCheck(method)); code.append(generateExtNameAssignment(value, hasInvocation)); code.append(generateExtNameNullCheck(value)); code.append(generateExtensionAssignment()); 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<>(); for (int i = 0 ; i < pts.length; ++i) { for (Method m : pts[i].getMethods()) { String name = m.getName(); 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); } } } if (getterReturnUrl.size() <= 0 ) { 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(); 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(); return Arrays.stream(pts).anyMatch(p -> CLASSNAME_INVOCATION.equals(p.getName())); } 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) { String getNameCode = null ; for (int i = value.length - 1 ; i >= 0 ; --i) { if (i == value.length - 1 ) { if (null != defaultExtName) { if (!"protocol" .equals(value[i])) { if (hasInvocation) { getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")" , value[i], defaultExtName); } else { getNameCode = String.format("url.getParameter(\"%s\", \"%s\")" , value[i], defaultExtName); } } else { 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 { getNameCode = String.format("url.getParameter(\"%s\")" , value[i]); } } else { getNameCode = "url.getProtocol()" ; } } } else { if (!"protocol" .equals(value[i])) { if (hasInvocation) { getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")" , value[i], defaultExtName); } else { getNameCode = String.format("url.getParameter(\"%s\", %s)" , value[i], getNameCode); } } else { 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 ({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY}) Server bind (URL url, ChannelHandler handler) throws RemotingException ; @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) { 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(", " )); 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 public interface Robot { void sayHello () ; }
大黄蜂实现:
1 2 3 4 5 6 7 8 9 10 11 12 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 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;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 完美支持语法提示。