從這篇文章開始會(huì)從頭開始以 apache shenyu為路徑,一一學(xué)習(xí)shenyu中用到的技術(shù)及設(shè)計(jì)第步,當(dāng)前文章學(xué)習(xí)shenyu中的spi(apache-shenyu 2.4.3版本)
apache shenyu前身soul網(wǎng)關(guān),是一款java中spring5新引入的project-reactor的webflux武学,reactor-netty等為基礎(chǔ)實(shí)現(xiàn)的高性能網(wǎng)關(guān)啥酱,現(xiàn)已進(jìn)入apache孵化器仔拟,作者yu199195 (xiaoyu) (github.com)
作者也是國內(nèi)知名開源社區(qū)dromara的創(chuàng)始人合是,并且作有多個(gè)開源產(chǎn)品了罪,apache-shenyu是其中之一apache/incubator-shenyu: ShenYu is High-Performance Java API Gateway. (github.com)
SPI是java多態(tài)和插件化非常重要的一環(huán)。
簡(jiǎn)單的例子聪全,java ee中jdbc的數(shù)據(jù)庫驅(qū)動(dòng)泊藕,我們可以任意切換連接的數(shù)據(jù)庫,例如mysql难礼,oracle等等娃圆,但是對(duì)于你的java應(yīng)用來說并不知道具體使用哪個(gè)數(shù)據(jù)庫汽久,而jdbc將對(duì)于數(shù)據(jù)庫的操作抽象出一套標(biāo)準(zhǔn)的接口,jdbc對(duì)于數(shù)據(jù)庫的操作基于接口來進(jìn)行編碼踊餐,而不需要知道具體的實(shí)現(xiàn),但是不同數(shù)據(jù)庫的語法臀稚,實(shí)現(xiàn)邏輯都不相同吝岭,但是他們會(huì)根據(jù)jdbc提供的標(biāo)準(zhǔn)接口實(shí)現(xiàn)驅(qū)動(dòng)程序,那么在java應(yīng)用側(cè)只需要在使用哪個(gè)數(shù)據(jù)庫時(shí)吧寺,指定好driver-class-name即可窜管。
jdbc抽象的主要是Driver接口和Connection接口
以上為不同數(shù)據(jù)庫廠商提供的Driver實(shí)現(xiàn),jdbc不需要知道如何實(shí)現(xiàn)稚机,只需要使用Driver接口編碼幕帆,然后由配置指定其子類,這就是一種SPI思想
public interface Driver {
// 不同的driver實(shí)現(xiàn)拿到不同的連接
Connection connect(String url, java.util.Properties info)
throws SQLException;
boolean acceptsURL(String url) throws SQLException;
DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info)
throws SQLException;
int getMajorVersion();
int getMinorVersion();
boolean jdbcCompliant();
//------------------------- JDBC 4.1 -----------------------------------
public Logger getParentLogger() throws SQLFeatureNotSupportedException;
}
還有一堆方法赖条,感興趣的直接看java.sql.Connection接口失乾,那么jdbc將連接的各種動(dòng)作,和數(shù)據(jù)庫交互的邏輯抽象為對(duì)應(yīng)方法纬乍,那么jdbc直接使用這些方法就可以了碱茁,實(shí)現(xiàn)則由數(shù)據(jù)庫那邊不同的實(shí)現(xiàn)通過SPI注入。
當(dāng)然如果通過指定子類名稱仿贬,可以通過反射直接創(chuàng)建其實(shí)例注入纽竣。
下面介紹如果不知道子類名稱的情況下的spi實(shí)現(xiàn)。
java原生SPI茧泪,使用ServiceLoader蜓氨,METAINF/services實(shí)現(xiàn)
具體點(diǎn)大家可以繼續(xù)搜索學(xué)習(xí),這里只講解使用方法
ServiceLoader + META-INF/services 的使用方法
我們可以通過ServiceLoader#load方法來加載子類队伟。然后通過services文件夾中命名接口/抽象類內(nèi)部寫入要load的實(shí)現(xiàn)類名稱穴吹,其實(shí)也可以通過classLoader將當(dāng)前classpath所有類變量判斷是否是其子類判斷出要找的類
- 如果有多個(gè)實(shí)現(xiàn)類,我們就要指定具體某個(gè)實(shí)現(xiàn)類缰泡,那就要么在代碼邏輯寫死或者又引入新的配置邏輯
- 性能很差
所有java提供了SPI機(jī)制刀荒,快速并且解耦的實(shí)現(xiàn)了選擇性子類發(fā)現(xiàn)機(jī)制
舉例com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy是hystrix中關(guān)于線程執(zhí)行相關(guān)的策略抽象。是一個(gè)抽象類(接口也可以)
我們可以使用自己的實(shí)現(xiàn)棘钞,通過SPI機(jī)制
實(shí)現(xiàn)當(dāng)然也可以多個(gè),但是SPI機(jī)制大部分用來實(shí)現(xiàn)多種實(shí)現(xiàn)宜猜,通過配置或者參數(shù)來選擇一個(gè)實(shí)現(xiàn)使用泼返,多個(gè)實(shí)現(xiàn)可以自由替換,上面的例子就是 jdbc抽象的Driver和Connection接口通過 driver-class-name來選擇姨拥,
當(dāng)然也可以是動(dòng)態(tài)切換绅喉,例如下面要看的基于dubbo的spi方式
hystrix源碼
private static <T> T findService(
Class<T> spi,
ClassLoader classLoader) throws ServiceConfigurationError {
// 這里加載出來是一個(gè)可迭代的集合渠鸽,所以是可以放入多個(gè)實(shí)現(xiàn)
ServiceLoader<T> sl = ServiceLoader.load(spi,
classLoader);
for (T s : sl) {
// hystrix的這個(gè)抽象是直接返回第一個(gè)實(shí)現(xiàn)
if (s != null)
return s;
}
return null;
}
private <T> T getPluginImplementation(Class<T> pluginClass) {
// 這里是通過配置文件的 全類名,通過反射實(shí)例化柴罐,這也是一種常見的抽象機(jī)制
T p = getPluginImplementationViaProperties(pluginClass, dynamicProperties);
if (p != null) return p;
// 利用java的SPI指定子類,然后就會(huì)使用其實(shí)現(xiàn)處理業(yè)務(wù)
return findService(pluginClass, classLoader);
}
shenyu的SPI徽缚,是參照了dubbo的spi實(shí)現(xiàn),但是本文只閱讀apache-shenyu的設(shè)計(jì)以及解決的問題
由一個(gè) @SPI注解開始
/**
* SPI Extend the processing.
* All spi system reference the apache implementation of
* <a >Apache Dubbo Common Extension</a>.
*
* @see ExtensionFactory
* @see ExtensionLoader
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface SPI {
/**
* Value string.
*
* @return the string
*/
String value() default "";
}
舉例一個(gè)場(chǎng)景革屠,apache-shenyu支持多種rule(即后端業(yè)務(wù)服務(wù)的url或者規(guī)則)注冊(cè)方式凿试,也就是業(yè)務(wù)服務(wù)有哪些接口可以通過各種方式上報(bào)給shenyu-admin服務(wù),然后shenyu-admin會(huì)把數(shù)據(jù)同步給shenyu-bootstrap服務(wù)(真正做網(wǎng)關(guān)的服務(wù))分別提供了似芝,nacos那婉,http,consul党瓮,etcd详炬,zookeeper多種rule上報(bào)實(shí)現(xiàn),只需要通過配置選擇即可寞奸。那么在shenyu的邏輯代碼中只需要對(duì)抽象出來的ShenyuClientServerRegisterRepository接口統(tǒng)一處理呛谜,不需要知道不同上報(bào)方式的差別,這就是java的抽象方式枪萄,通過接口或者抽象類(盡量使用接口)的方式抽象后不需要if呻率,switch來判斷了,這種只要選擇一個(gè)實(shí)現(xiàn)的邏輯使用SPI機(jī)制精簡(jiǎn)了代碼呻引,解耦了模塊間的依賴礼仗,一下圖中多種上報(bào)實(shí)現(xiàn)只需要在實(shí)現(xiàn)類中關(guān)注
apache-shenyu中很多這種SPI抽象邏輯,例如還有RateLimiterAlgorithm接口將限流邏輯抽象逻悠,
下面看代碼
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface SPI {
// 指定在抽象方元践,也就放到接口上相當(dāng)于jdk中的META-INF/services文件的名稱
/**
* Value string.
* 這里可以不使用 META-INF/shenyu路徑中的文件指定其實(shí)現(xiàn),直接通過注解童谒,多一種切換方式单旁,當(dāng)然這里的spi加載也可以多實(shí)現(xiàn),下面代碼會(huì)提到
* @return the string
*/
String value() default "";
}
@SPI
public interface ShenyuClientServerRegisterRepository {
// 省略代碼
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Join {
// 放到實(shí)現(xiàn)類上饥伊,相當(dāng)于jdk中的META-INF/services文件中的配置
}
ExtensionFactory spi工廠象浑,那么spi的實(shí)現(xiàn)也可以使用多個(gè)實(shí)現(xiàn),但是apache-shenyu目前只有一個(gè)spi工廠,這里想看spi工廠多實(shí)現(xiàn)可以找dubbo源碼看一看
@SPI("spi")
public interface ExtensionFactory {
/**
* Gets Extension.
*
* @param <T> the type parameter
* @param key the key
* @param clazz the clazz
* @return the extension
*/
<T> T getExtension(String key, Class<T> clazz);
}
@Join
public class SpiExtensionFactory implements ExtensionFactory {
@Override
public <T> T getExtension(final String key, final Class<T> clazz) {
return Optional.ofNullable(clazz)
.filter(Class::isInterface)
.filter(cls -> cls.isAnnotationPresent(SPI.class))
.map(ExtensionLoader::getExtensionLoader)
.map(ExtensionLoader::getDefaultJoin)
.orElse(null);
}
}
下面看看核心代碼ExtensionLoader
@SuppressWarnings("all")
public final class ExtensionLoader<T> {
private static final Logger LOG = LoggerFactory.getLogger(ExtensionLoader.class);
// 學(xué)習(xí)jdk的spi機(jī)制指定實(shí)現(xiàn)方式
private static final String SHENYU_DIRECTORY = "META-INF/shenyu/";
// key為抽象的類的class對(duì)象琅豆,value為當(dāng)前類對(duì)象的實(shí)例愉豺,使用泛型機(jī)制,在實(shí)例化時(shí)類型已經(jīng)確定茫因,保證類型安全蚪拦,這里針對(duì)不同的抽象,使用各自的Loader類
private static final Map<Class<?>, ExtensionLoader<?>> LOADERS = new ConcurrentHashMap<>();
// 抽象出來的接口的class對(duì)象
private final Class<T> clazz;
private final ClassLoader classLoader;
// 緩存的 實(shí)現(xiàn)類class,實(shí)現(xiàn)類的key(dubbo的spi可以設(shè)置kv映射) -> 實(shí)現(xiàn)類class對(duì)象驰贷,一個(gè)holder對(duì)象緩存當(dāng)前抽象接口的所有子類class
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
// 緩存的實(shí)現(xiàn)類 實(shí)例盛嘿,實(shí)現(xiàn)類key -> 實(shí)現(xiàn)類的實(shí)例對(duì)象,一個(gè)holder對(duì)象緩存一個(gè) 實(shí)例
private final Map<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
// key為實(shí)現(xiàn)類class對(duì)象括袒,value為其實(shí)例
private final Map<Class<?>, Object> joinInstances = new ConcurrentHashMap<>();
private String cachedDefaultName;
/**
* Instantiates a new Extension loader.
*
* @param clazz the clazz.
*/
private ExtensionLoader(final Class<T> clazz, final ClassLoader cl) {
//一個(gè)在內(nèi)部實(shí)例化其 傳入的抽象接口class對(duì)象的ExtensionLoader
this.clazz = clazz;
this.classLoader = cl;
if (!Objects.equals(clazz, ExtensionFactory.class)) {
ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getExtensionClasses();
}
}
// 暴露出去的唯二static方法之一次兆,區(qū)別需要傳入classLoader,基本都是用另外一個(gè)锹锰,其他方法通過實(shí)例調(diào)用
public static <T> ExtensionLoader<T> getExtensionLoader(final Class<T> clazz, final ClassLoader cl) {
Objects.requireNonNull(clazz, "extension clazz is null");
// 可以看到這里的SPI限制必須使用接口类垦,接口可以多繼承,相對(duì)抽象類還是好用一些的城须。
if (!clazz.isInterface()) {
throw new IllegalArgumentException("extension clazz (" + clazz + ") is not interface!");
}
// 必須是@SPI注解的接口
if (!clazz.isAnnotationPresent(SPI.class)) {
throw new IllegalArgumentException("extension clazz (" + clazz + ") without @" + SPI.class + " Annotation");
}
// 獲取對(duì)應(yīng) 抽象接口的ExtensionLoader
ExtensionLoader<T> extensionLoader = (ExtensionLoader<T>) LOADERS.get(clazz);
if (Objects.nonNull(extensionLoader)) {
return extensionLoader;
}
// 如果沒有調(diào)用過實(shí)例化一個(gè)ExtensionLoader,看來是懶加載
LOADERS.putIfAbsent(clazz, new ExtensionLoader<>(clazz, cl));
return (ExtensionLoader<T>) LOADERS.get(clazz);
}
// 暴露出去的唯二static方法米苹,大部分使用這個(gè)糕伐,其他方法通過實(shí)例調(diào)用
public static <T> ExtensionLoader<T> getExtensionLoader(final Class<T> clazz) {
return getExtensionLoader(clazz, ExtensionLoader.class.getClassLoader());
}
// 通過實(shí)現(xiàn)類key獲取其實(shí)例
public T getDefaultJoin() {
getExtensionClasses();
if (StringUtils.isBlank(cachedDefaultName)) {
return null;
}
return getJoin(cachedDefaultName);
}
// 通過實(shí)現(xiàn)類key獲取其實(shí)例
public T getJoin(final String name) {
if (StringUtils.isBlank(name)) {
throw new NullPointerException("get join name is null");
}
Holder<Object> objectHolder = cachedInstances.get(name);
if (Objects.isNull(objectHolder)) {
// 第一次獲取其實(shí)例,放入一個(gè)holder容器
cachedInstances.putIfAbsent(name, new Holder<>());
objectHolder = cachedInstances.get(name);
}
Object value = objectHolder.getValue();
// 通過雙重校驗(yàn) 保證單例
if (Objects.isNull(value)) {
synchronized (cachedInstances) {
value = objectHolder.getValue();
if (Objects.isNull(value)) {
// 創(chuàng)建要獲取的實(shí)例
value = createExtension(name);
objectHolder.setValue(value);
}
}
}
return (T) value;
}
// 獲取所有實(shí)現(xiàn)蘸嘶,為什么沒有參數(shù)良瞧,因?yàn)閷?duì)于當(dāng)前類的實(shí)例只會(huì)對(duì)應(yīng)一個(gè)
// 抽象的接口,和其所有實(shí)現(xiàn)類的實(shí)例緩存训唱,如果前面獲取到了抽象接口的ExtensionLoader則直接可獲取所有實(shí)現(xiàn)的實(shí)例
public List<T> getJoins() {
// 獲取所有實(shí)現(xiàn)類的緩存褥蚯,如果第一次獲取,會(huì)將所有class對(duì)象加載并緩存
Map<String, Class<?>> extensionClasses = this.getExtensionClasses();
if (extensionClasses.isEmpty()) {
return Collections.emptyList();
}
// 如果剛加載的所有class子類實(shí)現(xiàn)的對(duì)象與其實(shí)現(xiàn)類的實(shí)例數(shù)量况增,說明所有實(shí)現(xiàn)class對(duì)象已經(jīng)都實(shí)例化了直接返回
if (Objects.equals(extensionClasses.size(), cachedInstances.size())) {
return (List<T>) this.cachedInstances.values().stream().map(e -> {
return e.getValue();
}).collect(Collectors.toList());
}
List<T> joins = new ArrayList<>();
extensionClasses.forEach((name, v) -> {
// 如果哪些class沒有實(shí)例化赞庶,進(jìn)行實(shí)例化
T join = this.getJoin(name);
joins.add(join);
});
return joins;
}
@SuppressWarnings("unchecked")
private T createExtension(final String name) {
// 獲取子類實(shí)現(xiàn)的class對(duì)象
Class<?> aClass = getExtensionClasses().get(name);
if (Objects.isNull(aClass)) {
throw new IllegalArgumentException("name is error");
}
Object o = joinInstances.get(aClass);
if (Objects.isNull(o)) {
try {
// 這里只通過concurrentMap + putIfAbsent保證線程安全,實(shí)例化出來多個(gè)無所謂澳骤,只會(huì)成功放入第一個(gè)歧强,也是單例的。
joinInstances.putIfAbsent(aClass, aClass.newInstance());
o = joinInstances.get(aClass);
} catch (InstantiationException | IllegalAccessException e) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: "
+ aClass + ") could not be instantiated: " + e.getMessage(), e);
}
}
return (T) o;
}
// class對(duì)象必須保證只加載一次为肮,也就是一個(gè)抽象接口的所有子類class只會(huì)加載一次
public Map<String, Class<?>> getExtensionClasses() {
Map<String, Class<?>> classes = cachedClasses.getValue();
// class對(duì)象必須保證只加載一次摊册,通過雙重校驗(yàn)
if (Objects.isNull(classes)) {
synchronized (cachedClasses) {
classes = cachedClasses.getValue();
if (Objects.isNull(classes)) {
classes = loadExtensionClass();
cachedClasses.setValue(classes);
}
}
}
return classes;
}
// 加載子類 class
private Map<String, Class<?>> loadExtensionClass() {
SPI annotation = clazz.getAnnotation(SPI.class);
if (Objects.nonNull(annotation)) {
String value = annotation.value();
if (StringUtils.isNotBlank(value)) {
cachedDefaultName = value;
}
}
Map<String, Class<?>> classes = new HashMap<>(16);
loadDirectory(classes);
return classes;
}
// 加載子類 class
private void loadDirectory(final Map<String, Class<?>> classes) {
String fileName = SHENYU_DIRECTORY + clazz.getName();
try {
Enumeration<URL> urls = Objects.nonNull(this.classLoader) ? classLoader.getResources(fileName)
: ClassLoader.getSystemResources(fileName);
if (Objects.nonNull(urls)) {
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
loadResources(classes, url);
}
}
} catch (IOException t) {
LOG.error("load extension class error {}", fileName, t);
}
}
private void loadResources(final Map<String, Class<?>> classes, final URL url) throws IOException {
try (InputStream inputStream = url.openStream()) {
Properties properties = new Properties();
properties.load(inputStream);
properties.forEach((k, v) -> {
// dubbo的spi形式不同于jdk,是http=org.apache.shenyu.admin.controller.ShenyuClientHttpRegistryController的形式颊艳,左邊為key茅特,右邊為類名,可以維護(hù)一個(gè)map形式的子類實(shí)現(xiàn)集合
String name = (String) k;
String classPath = (String) v;
if (StringUtils.isNotBlank(name) && StringUtils.isNotBlank(classPath)) {
try {
loadClass(classes, name, classPath);
} catch (ClassNotFoundException e) {
throw new IllegalStateException("load extension resources error", e);
}
}
});
} catch (IOException e) {
throw new IllegalStateException("load extension resources error", e);
}
}
private void loadClass(final Map<String, Class<?>> classes,
final String name, final String classPath) throws ClassNotFoundException {
//獲取子類實(shí)現(xiàn)的class對(duì)象
Class<?> subClass = Objects.nonNull(this.classLoader) ? Class.forName(classPath, true, this.classLoader) : Class.forName(classPath);
// 校驗(yàn)必須為其子類
if (!clazz.isAssignableFrom(subClass)) {
throw new IllegalStateException("load extension resources error," + subClass + " subtype is not of " + clazz);
}
// 校驗(yàn)必須標(biāo)注 @Join注解
if (!subClass.isAnnotationPresent(Join.class)) {
throw new IllegalStateException("load extension resources error," + subClass + " without @" + Join.class + " annotation");
}
Class<?> oldClass = classes.get(name);
if (Objects.isNull(oldClass)) {
//放入
classes.put(name, subClass);
} else if (!Objects.equals(oldClass, subClass)) {
// 如果產(chǎn)生了重復(fù)放入棋枕,校驗(yàn)是否相同白修,不同報(bào)錯(cuò)
throw new IllegalStateException("load extension resources error,Duplicate class " + clazz.getName() + " name " + name + " on " + oldClass.getName() + " or " + subClass.getName());
}
}
/**
* The type Holder.
*
* @param <T> the type parameter.
*/
//用于緩存的包裝類,可能緩存子類實(shí)現(xiàn)類的實(shí)例對(duì)象重斑,或者所有子類的class對(duì)象map
public static class Holder<T> {
// 使用volatile熬荆,保證其他線程可見性
private volatile T value;
/**
* Gets value.
*
* @return the value
*/
public T getValue() {
return value;
}
/**
* Sets value.
*
* @param value the value
*/
public void setValue(final T value) {
this.value = value;
}
}
}
總結(jié)
- apache-shenyu基本沿用dubbo的spi機(jī)制,通過@SPI,@Join + META-INF/指定名稱 的目錄加載卤恳,但是文件內(nèi)容使用kv形式累盗,也提供多實(shí)現(xiàn)通過文件配置中的不同key區(qū)別
- 有一個(gè)spi工廠提供可能存在更多的spi實(shí)現(xiàn)
- 使用泛型保證類型安全,不同的抽象父類突琳,實(shí)例化出不同的ExtensionLoader加載器來處理
- 使用緩存若债,線程安全容器,雙重校驗(yàn)等保證單例