哈喽,咱们如约而至,今天咱一起看下dubbo的SPI扩展部分,说起这个不禁感慨当初开发dubbo的人,脑子怎么长得做出来这么优秀的产品。
我们可以基于SPI做过滤器、负载均衡器、日志打印、协议扩展等等操作,非常的方便,而且相对于jdk原生SPI做了不少优化。
咱们先实战一波,看下具体是如何使用的,然后再分析下源码。
dubbo支持的SPI扩展有:协议扩展、调用拦截扩展、引用监听扩展、暴露监听扩展、集群扩展、路由扩展、负载均衡扩展、合并结果扩展、注册中心扩展、监控中心扩展、扩展点加载扩展、动态代理扩展、编译器扩展、配置中心扩展、消息派发扩展、线程池扩展、序列化扩展、网络传输扩展、信息交换扩展、组网扩展、Telnet 命令扩展、状态检查扩展、容器扩展、缓存扩展、验证扩展、日志适配扩展 等等。
咱们今天实现定义日志过滤器,记录rpc执行耗时,首先在消费者项目中创建LogMonitorFilter类实现Filter,代码如下:
public class LogMonitorFilter implements Filter {
@Override
public Result invoke(Invoker> invoker, Invocation invocation) throws RpcException {
Long startTime=System.currentTimeMillis();
Result invoke = invoker.invoke(invocation);
Long endTime=System.currentTimeMillis();
System.out.println(String.format("----------------------------%s类的%s方法rpc耗时:%s",invocation.getServiceName(),invocation.getMethodName(),(endTime-startTime)));
return invoke;
}
}
创建 META-INF.dubbo.internal/org.apache.dubbo.rpc.Filter
文件内容如下:log=org.apache.dubbo.demo.consumer.comp.LogMonitorFilter
在rpc接口上通过注解指定用那个filter:
@Component("demoServiceComponent")
public class DemoServiceComponent implements DemoService {
@DubboReference(interfaceClass = org.apache.dubbo.demo.DemoService.class,filter = "log")
private DemoService demoService;
@Override
public String sayHello(String name) {
return demoService.sayHello(name);
}
@Override
public CompletableFuture sayHelloAsync(String name) {
return null;
}
}
运行程序在控制台完美打印了我们的过滤器:
接下来咱们从源码层面分析下它是如何加载我们的SPI过滤器的
先看下包结构:
org.apache.dubbo.common.extension包下的都是关于SPI的实现,重点是ExtensionLoader这个类。
这个类有近1200行的代码,看起来不是很方便,所以我们还是采用debug的模式一步步的跟踪。
咱们先创建3个类(Car、Bmw、Test),Car为接口 Bmw为具体实现通过test做测试,并在META-INF下创建文件如图:
Car代码如下:
@SPI
public interface Car {
void toHome();
}
Bmw代码如下:
public class Bmw implements Car{
@Override
public void toHome() {
System.out.println("开着宝马回老家!");
}
}
Test类代码:
public class Test {
public static void main(String[] args) {
Car bmw = ExtensionLoader.getExtensionLoader(Car.class).getExtension("bmw");
bmw.toHome();
}
}
直接在getExtension方法设置条件断点到了这里:
public T getExtension(String name, boolean wrap) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
return getDefaultExtension();
}
String cacheKey = name;
if (!wrap) {
cacheKey += "_origin";
}
final Holder
此时是没有实例化Bmw的如图:
然后进过后再看instance已经有了Bmw的对象实例了:
说明Bmw对象是由dubbo为我们创建的,可见dubbo实现了IOC的功能,而且是懒加载模式的,走完断点顺利开车回家了。
经过刚才调试我们知道了,重点在instance= createExtension(name, wrap);这行代码,再次断点进去看看具体实现方式,我们看下这个方法中的代码:
private T createExtension(String name, boolean wrap) {
Class> clazz = getExtensionClasses().get(name);
if (clazz == null || unacceptableExceptions.contains(name)) {
throw findException(name);
}
try {
T instance = (T) extensionInstances.get(clazz);
if (instance == null) {
extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));
instance = (T) extensionInstances.get(clazz);
//看到这里这方法是不是很熟悉
instance = postProcessBeforeInitialization(instance, name);
injectExtension(instance);
instance = postProcessAfterInitialization(instance, name);
}
if (wrap) {
List> 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));
instance = postProcessAfterInitialization(instance, name);
}
}
}
}
// Warning: After an instance of Lifecycle is wrapped by cachedWrapperClasses, it may not still be Lifecycle instance, this application may not invoke the lifecycle.initialize hook.
initExtension(instance);
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
细心的这里已经发现了熟悉的地方postProcessBeforeInitialization、postProcessAfterInitialization 这是典型的spring的后置处理器的思想,通过这个来干扰bean的实例化过程,dubbo这ioc照搬的spring的思想啊这是,不信咱们看下这个类更为熟悉的东西:
不禁感叹dubbo yyds!继续写一步果然还是空的,接下来就要实例化我们的类了:
咱们看下实例化对象的代码:
public T instantiate(Class type) throws ReflectiveOperationException {
// should not use default constructor directly, maybe also has another constructor matched scope model arguments
// 1. try get default constructor
Constructor defaultConstructor = null;
try {
defaultConstructor = type.getConstructor();
} catch (NoSuchMethodException e) {
// ignore no default constructor
}
// 2. use matched constructor if found
List matchedConstructors = new ArrayList<>();
Constructor>[] declaredConstructors = type.getConstructors();
for (Constructor> constructor : declaredConstructors) {
if (isMatched(constructor)) {
matchedConstructors.add(constructor);
}
}
// remove default constructor from matchedConstructors
if (defaultConstructor != null) {
matchedConstructors.remove(defaultConstructor);
}
// match order:
// 1. the only matched constructor with parameters
// 2. default constructor if absent
Constructor targetConstructor;
if (matchedConstructors.size() > 1) {
throw new IllegalArgumentException("Expect only one but found " +
matchedConstructors.size() + " matched constructors for type: " + type.getName() +
", matched constructors: " + matchedConstructors);
} else if (matchedConstructors.size() == 1) {
targetConstructor = matchedConstructors.get(0);
} else if (defaultConstructor != null) {
targetConstructor = defaultConstructor;
} else {
throw new IllegalArgumentException("None matched constructor was found for type: " + type.getName());
}
// create instance with arguments
Class[] parameterTypes = targetConstructor.getParameterTypes();
Object[] args = new Object[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
args[i] = getArgumentValueForType(parameterTypes[i]);
}
//通过构造方法反射获取对象
return (T) targetConstructor.newInstance(args);
}
通过构造方法反射获取对象,在回过来看
instance = postProcessBeforeInitialization(instance, name);
instance = postProcessAfterInitialization(instance, name);
这2行代码,实际跟下去发现postProcessBeforeInitialization并没有实际做什么,postProcessAfterInitialization中对bean的作用于做了一下处理,到此我们有发现一个扩展项就是ExtensionPostProcessor可以通过它来干扰bean。
那它怎么获取的bmw的Class的呢?关键代码在createExtension Class> clazz = getExtensionClasses().get(name);一路跟踪来到如下代码处:
private Map> loadExtensionClasses() {
cacheDefaultExtensionName();
Map> extensionClasses = new HashMap<>();
//通过jdk的spi加载3个dubbo的LoadingStrategy
for (LoadingStrategy strategy : strategies) {
loadDirectory(extensionClasses, strategy, type.getName());
// compatible with old ExtensionFactory
if (this.type == ExtensionInjector.class) {
loadDirectory(extensionClasses, strategy, ExtensionFactory.class.getName());
}
}
return extensionClasses;
}
private void loadDirectory(Map> extensionClasses, LoadingStrategy strategy, String type) {
loadDirectory(extensionClasses, strategy.directory(), type, strategy.preferExtensionClassLoader(),
strategy.overridden(), strategy.excludedPackages(), strategy.onlyExtensionClassLoaderPackages());
String oldType = type.replace("org.apache", "com.alibaba");
loadDirectory(extensionClasses, strategy.directory(), oldType, strategy.preferExtensionClassLoader(),
strategy.overridden(), strategy.excludedPackages(), strategy.onlyExtensionClassLoaderPackages());
}
这里处理我们的类就在循环strategies的时候,这个是dubbo的三个默认处理SPI的类如图:
这个三个类源码如下:
public class DubboInternalLoadingStrategy implements LoadingStrategy {
@Override
public String directory() {
return "META-INF/dubbo/internal/";
}
@Override
public int getPriority() {
return MAX_PRIORITY;
}
}
public class DubboLoadingStrategy implements LoadingStrategy {
@Override
public String directory() {
return "META-INF/dubbo/";
}
@Override
public boolean overridden() {
return true;
}
@Override
public int getPriority() {
return NORMAL_PRIORITY;
}
}
public class ServicesLoadingStrategy implements LoadingStrategy {
@Override
public String directory() {
return "META-INF/services/";
}
@Override
public boolean overridden() {
return true;
}
@Override
public int getPriority() {
return MIN_PRIORITY;
}
}
可以清楚的看到就是这3个类加载META-INF下的dubbo扩展的,这3个类是由JDK的SPI来加载的,代码如下:
/**
* Load all {@link Prioritized prioritized} {@link LoadingStrategy Loading Strategies} via {@link ServiceLoader}
* 通过此处从jdk实例化的接口中拿出实例
* @return non-null
* @since 2.7.7
*/
private static LoadingStrategy[] loadLoadingStrategies() {
return stream(load(LoadingStrategy.class).spliterator(), false)
.sorted()
.toArray(LoadingStrategy[]::new);
}
继续debug来到了如下方法:
这个方法通过获取classLoader来加载META-INF下的数据,继续往下:
private void loadResource(Map> extensionClasses, ClassLoader classLoader,
java.net.URL resourceURL, boolean overridden, String[] excludedPackages, String[] onlyExtensionClassLoaderPackages) {
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)
&& !isExcludedByClassLoader(clazz, classLoader, onlyExtensionClassLoaderPackages)) {
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);
}
}
看到真实面幕了通过 Class.forName(clazz, true, classLoader)获取得到真实实现类的class,到了这里我们的思路也清洗了。
首先通过JDK的SPI来实例化dubbo自己的处理类,然后通过懒加载的方式去实例化具体的SPI扩展对象,实例化的时候先通过DubboInternalLoadingStrategy扫描指定路径下的SPI文件,或得到class再通过反射得到整个bean对象,下次直接从ioc容器中获取对象实例即可。
咱们再看实际运行中是在哪里执行的filter,是通过
StubProxyFactoryWrapper--->export--->
ProtocolFilterWrapper---->export--->
DefaultFilterChainBuilder ----> buildInvokerChain
buildInvokerChain方法源码如下:
@Override
public Invoker buildInvokerChain(final Invoker originalInvoker, String key, String group) {
Invoker last = originalInvoker;
URL url = originalInvoker.getUrl();
//获取filter
List filters = ScopeModelUtil.getExtensionLoader(Filter.class, url.getScopeModel()).getActivateExtension(url, key, group);
if (!filters.isEmpty()) {
for (int i = filters.size() - 1; i >= 0; i--) {
final Filter filter = filters.get(i);
final Invoker next = last;
last = new FilterChainNode<>(originalInvoker, next, filter);
}
}
return last;
}
参照dubbo的十层架构图看起来会更加清晰:
咱们能够基于 Dubbo 提供的扩展能力,很方便基于自身需求扩展其他协议、过滤器、路由等。
按需加载。Dubbo 的扩展能力不会一次性实例化所有实现,而是用那个扩展类则实例化那个扩展类,减少资源浪费。
增加扩展类的 IOC 能力。Dubbo 的扩展能力并不仅仅只是发现扩展服务实现类,而是在此基础上更进一步,如果该扩展类的属性依赖其他对象,则 Dubbo 会自动的完成该依赖对象的注入功能。
增加扩展类的 AOP 能力。Dubbo 扩展能力会自动的发现扩展类的包装类,完成包装类的构造,增强扩展类的功能。
具备动态选择扩展实现的能力。Dubbo 扩展会基于参数,在运行时动态选择对应的扩展类,提高了 Dubbo 的扩展能力。
可以对扩展实现进行排序。能够基于用户需求,指定扩展实现的执行顺序。
提供扩展点的 Adaptive 能力。该能力可以使的一些扩展类在 consumer 端生效,一些扩展类在 provider 端生效。
dubbo作者果然牛逼IOC、AOP的思想人家玩的溜溜的,源码中还有不少处用了弱引用等作为cache都非常值得我们借鉴,好了这期就到这了咱们下期见!
MYSQL系列经典文章
MYSQl 深入探索系列一 redo log
MYSQl深入探索系列二 undo log
MYSQl深入探索系列三 MVCC机制
MYSQl深入探索系列四 服务端优化
MYSQL深入探索系列之五 buffer_pool