SPI
SPI是一種擴展機制,在java中SPI機制被廣泛應用,比如Spring中的SpringServletContainerInitializer 使得容器啟動的時候SpringServletContainerInitializer 執行onStartup方法。在dubbo中,dubbo實現了自己的spi擴展機制,下面詳細的講解下,dubbo的擴展機制。
dubbo SPI使用-Filter的定義使用
dubbo中的fiter是使用spi機制實現的典型應用,現在使用filter作為例子,展示先dubbo的spi機制。
首先定義一個filter。
public class MyFilter implements Filter {
public Result invoke(Invoker<?> invoker, Invocation invocation)
throws RpcException {
System.out.println("開始調用 MyFilter");
Result result = invoker.invoke(invocation);
System.out.println("結束調用 MyFilter");
return result;
}
}
將filter加到dubbo默認的filter
@Activate(group = {Constants.CONSUMER})
在消費端使用filter
在消費者的resource文件下建
/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Filter
內容為:
myFilter=com.lenny.sample.dubbo.common.filter.MyFilter
在消費端指定filter
@Reference(version = "1.0.0",filter = "myFilter")
private UserDubboService userDubboService;
在調用userDubboService的服務器時在控制台打印如下內容:
在服務端使用filter
在服務端的resource文件下建
/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Filter
內容為:
myFilter=com.lenny.sample.dubbo.common.filter.MyFilter
在服務提供者指定filter:
@Service(version = "1.0.0",filter = "myFilter")
同樣在控制台打印如下內容:
dubbo默認filter
dubbo默認提供一些filter。
dubbo filter源碼分析
Filter的加載源碼位於ProtocolFilterWrapper類,具體實現過程
// 服務端暴露
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
return protocol.export(invoker);
}
return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
}
// 消費端
@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
return protocol.refer(type, url);
}
return buildInvokerChain(protocol.refer(type, url), Constants.REFERENCE_FILTER_KEY, Constants.CONSUMER);
}
主要看buildInvokerChain。
private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
Invoker<T> last = invoker;
// 獲取全部的filter
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
if (!filters.isEmpty()) {
for (int i = filters.size() - 1; i >= 0; i--) {
final Filter filter = filters.get(i);
final Invoker<T> next = last;
last = new Invoker<T>() {
@Override
public Class<T> getInterface() {
return invoker.getInterface();
}
@Override
public URL getUrl() {
return invoker.getUrl();
}
@Override
public boolean isAvailable() {
return invoker.isAvailable();
}
@Override
public Result invoke(Invocation invocation) throws RpcException {
// 執行filter的invoke方法
return filter.invoke(next, invocation);
}
@Override
public void destroy() {
invoker.destroy();
}
@Override
public String toString() {
return invoker.toString();
}
};
}
}
return last;
}
在執行export的時候獲取全部的filter,綁定到要執行的方法上面,在要執行的方面的時候,執行filter方法。
Spi的加載原理
在ExtensionLoader中有loadExtensionClasses方法,加載全部的擴展類。
private static final String SERVICES_DIRECTORY = "META-INF/services/";
private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");
private Map<String, Class<?>> loadExtensionClasses() {
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if (defaultAnnotation != null) {
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];
}
}
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
// 去相應的文件路徑下掃描文件
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
loadDirectory(extensionClasses, DUBBO_DIRECTORY);
loadDirectory(extensionClasses, SERVICES_DIRECTORY);
return extensionClasses;
}
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) {
// 文件路徑加上全類名
String fileName = dir + type.getName();
try {
Enumeration<java.net.URL> urls;
ClassLoader classLoader = findClassLoader();
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
if (urls != null) {
while (urls.hasMoreElements()) {
java.net.URL resourceURL = urls.nextElement();
// 加載資源文件
loadResource(extensionClasses, classLoader, resourceURL);
}
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " +
type + ", description file: " + fileName + ").", t);
}
}
根據使用@SPI的注解的類全類名去如下路徑,找到相應問文件,解析文件內容
- META-INF/dubbo/internal/全類名
- META-INF/dubbo/全類名
- META-INF/services/全類名
如以filter為例:
package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.SPI;
@SPI
public interface Filter {
Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException;
}
Filter的全類名是com.alibaba.dubbo.rpc.Filter。所以可以去掃描如下路徑
-
META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Filter
-
META-INF/dubbo/com.alibaba.dubbo.rpc.Filter
-
META-INF/services/com.alibaba.dubbo.rpc.Filter
在上面的例子中將MyFilter的文件定義到了META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Filter路徑下。
當然也可以放到META-INF/dubbo/com.alibaba.dubbo.rpc.Filter和META-INF/services/com.alibaba.dubbo.rpc.Filter。
如圖:放到下面三個位置任何一個位置都是可以的。