SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。SPI 机制在第三方框架中也有所应用,比如 Dubbo 就是通过 SPI 机制加载所有的组件。不过,Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。如果大家想要学习 Dubbo 的源码,SPI 机制务必弄懂。接下来,我们先来了解一下 Java SPI 与 Dubbo SPI 的用法,然后再来分析 Dubbo SPI 的源码。
前面简单介绍了 SPI 机制的原理,本节通过一个示例演示 Java SPI 的使用方法。首先,我们定义一个接口,名称为 Robot。
public interface Robot {
void sayHello();
}
接下来定义两个实现类,分别为 OptimusPrime 和 Bumblebee。
public class OptimusPrime implements Robot {
@Override
public void sayHello() {
System.out.println("Hello, I am Optimus Prime.");
}
}
public class Bumblebee implements Robot {
@Override
public void sayHello() {
System.out.println("Hello, I am Bumblebee.");
}
}
接下来 META-INF/services 文件夹下创建一个文件,名称为 Robot 的全限定名 org.apache.spi.Robot。文件内容为实现类的全限定的类名,如下:
org.apache.spi.OptimusPrime
org.apache.spi.Bumblebee
做好所需的准备工作,接下来编写代码进行测试。
public class JavaSPITest {
@Test
public void sayHello() throws Exception {
ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);
System.out.println("Java SPI");
serviceLoader.forEach(Robot::sayHello);
}
}
从测试结果可以看出,我们的两个实现类被成功的加载,并输出了相应的内容。关于 Java SPI 的演示先到这里,接下来演示 Dubbo SPI。
Dubbo 并未使用 Java SPI,而是重新实现了一套功能更强的 SPI 机制。Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下,配置内容如下。
optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee
与 Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。另外,在测试 Dubbo SPI 时,需要在 Robot 接口上标注 @SPI 注解。下面来演示 Dubbo SPI 的用法:
public class DubboSPITest {
@Test
public void sayHello() throws Exception {
ExtensionLoader<Robot> extensionLoader =
ExtensionLoader.getExtensionLoader(Robot.class);
Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
optimusPrime.sayHello();
Robot bumblebee = extensionLoader.getExtension("bumblebee");
bumblebee.sayHello();
}
}
SPI机制的实现类是
ExtensionLoader
;所以主要本篇文章主要分析这个类的源码;
如果你有留心dubbo使用SPI机制的时候,无非大部分都是通过一个static静态方法来调用的,而且有很多的静态属性来保存全局的SPI实例;我们先了解一下这些静态方法和属性
静态属性
//文件路径-> (以接口类名为文件名,文件内容为实现类) 一般这个里面存放自定义服务相关类
private static final String SERVICES_DIRECTORY = "META-INF/services/";
//文件路径-> (以接口类名为文件名,文件内容为实现类) 一般这个里面存放dubbo相关的类
private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
//文件路径-> (以接口类名为文件名,文件内容为实现类) 这个存放的就是dubbo框架自身的类
private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
//分隔符
private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");
//存放所有需要扩展的接口类名,和对应的ExtensionLoader扩展加载器
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
SERVICES_DIRECTORY
、DUBBO_DIRECTORY
、DUBBO_INTERNAL_DIRECTORY
本质上没有区别,因为这三个路径都会被dubbo扫描一遍,把这些文件夹下面的文件全部加载到内存中;
ConcurrentMap, ExtensionLoader> EXTENSION_LOADERS
这个Map是用来存放所有的SPI的管理的Class
和ExtensionLoader
扩展器的;
比如dubbo内置的
上面的这些文件名都是一个interface
的全类名路径;那么我们的EXTENSION_LOADERS
中的key就对应这些interface
,value就对应一个单独的ExtensionLoader
扩展器;
ConcurrentMap, Object> EXTENSION_INSTANCES
TODO…
静态方法
//是否有SPI注解
private static <T> boolean withExtensionAnnotation(Class<T> type) {
return type.isAnnotationPresent(SPI.class);
}
@SuppressWarnings("unchecked")
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
if (type == null)
throw new IllegalArgumentException("Extension type == null");
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
}
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type(" + type +
") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
}
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;
}
//获取ClassLoader
private static ClassLoader findClassLoader() {
return ExtensionLoader.class.getClassLoader();
}
getExtensionLoader
方法,这个是非常关键的一个方法,因为dubbo想要获取对应Class
的一个实例,那么需要先获取这个Class
的ExtensionLoader扩展加载器,这个方法就是对外提供的一个入口;Class
必须是一个interface,必须打上了SPI
的注解ExtensionLoader扩展加载器
是从全局静态变量EXTENSION_LOADERS
获取的;但是一开始没有的情况,需要先实例化一个 ExtensionLoader扩展加载器
出来 new ExtensionLoader(type)
;为了方便分析源码,我们一起启动服务,开启Debug;Dubbo最先调用ExtensionLoader的是
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
以首先被加载的Protocol
为例
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
最终是执行了new ExtensionLoader(type)
的方法;并且保存到了静态属性EXTENSION_LOADERS
中;
我们看看是怎么实例化的
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
当前的type=interface com.alibaba.dubbo.rpc.Protocol
; 那么这个ExtensionFactory objectFactory;
属性又是什么呢?
TODO…
那么这里要先去执行
ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
这个时候的type=interface com.alibaba.dubbo.common.extension.ExtensionFactory
当ExtensionFactory
也new了一个ExtensionLoader
之后,然后去调用方法getAdaptiveExtension()
; 这个方法的作用是
获取自适应扩展 ;
public T getAdaptiveExtension() {
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if (createAdaptiveInstanceError == null) {
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
}
}
}
} else {
throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
}
}
return (T) instance;
}
cachedAdaptiveInstance
缓存了自适应扩展的实例类;createAdaptiveExtension()
方法创建了自适应扩展的实例,并存放入cachedAdaptiveInstance
@SuppressWarnings("unchecked")
private T createAdaptiveExtension() {
try {
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
简而言之
Class
Class
的newInstance()
方法来实例化对象injectExtension
依赖注入;那么问题又来了
如何获取自适应扩展的Class
?
实例化之后,如何依赖注入?
Class
?获取自适应扩展的Class
, 得先加载文件夹下面的文件啊,自适应扩展也是SPI机制中管理的其中一个比较特殊的类而已;
private Class<?> getAdaptiveExtensionClass() {
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
type
中所有的扩展类,(加载的具体详情请看下面)
如果扩展类中有带有注解@Adaptive
,说明是自适应扩展类,直接返回
一个type
有且只有一个自适应扩展类type
中所有的扩展类中没有找到带有注解@Adaptive
自适应扩展类的话,就会主动去创建一个自适应扩展类自适应扩展类
private Class<?> createAdaptiveExtensionClass() {
String code = createAdaptiveExtensionClassCode();
ClassLoader classLoader = findClassLoader();
com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader);
}
如果没有自适应扩展类,则dubbo会自动生成一个; 先拼接类
type
的所有方法中是否有注解@Adaptive
(是方法中的注解),如果一个都没有的话,那么就会抛出异常;Protocol
为例子package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
public void destroy() {
throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
public int getDefaultPort() {
throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1)
throws com.alibaba.dubbo.rpc.RpcException {
if (arg1 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg1;
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) " +
"name from url(" + url.toString() + ") use keys([protocol])");
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);
}
public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
com.alibaba.dubbo.common.URL url = arg0.getUrl();
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol)" +
" name from url(" + url.toString() + ") use keys([protocol])");
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)
ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0);
}
}
ExtensionLoader.getExtensionLoader(T).getExtension(extName);
; 这个extName
究竟是多少,拼接的逻辑在下面,那个getNameCode
就是最终的extNameCompiler
来生产字节码;选择Compiler
也是通过SPI选择的;上面已经分析完如何获取自适应扩展类; 实例完了之后还没有完成;因为扩展类 里面可能还有设置一些属性;所有还有一个依赖注入的过程
private T injectExtension(T instance) {
try {
if (objectFactory != null) {
for (Method method : instance.getClass().getMethods()) {
if (method.getName().startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers())) {
Class<?> pt = method.getParameterTypes()[0];
try {
String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("fail to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
objectFactory==null
的情况就直接返回了,不需要依赖注入;什么情况下这个值是null?,只有type=com.alibaba.dubbo.common.extension.ExtensionFactory
;情况下这个才直接返回了;set
开头的并且只有一个入参,并且是public
权限的,就可以依赖注入了我们可以看到是从Object object = objectFactory.getExtension(pt, property);
得到的注入属性,然后执行method.invoke(instance, object);
进行注入;
从扩展工厂类中获取 扩展实例
这个objectFactory=ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()
;
dubbo中的自适应扩展类是AdaptiveExtensionFactory
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
private final List<ExtensionFactory> factories;
public AdaptiveExtensionFactory() {
ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
for (String name : loader.getSupportedExtensions()) {
list.add(loader.getExtension(name));
}
factories = Collections.unmodifiableList(list);
}
@Override
public <T> T getExtension(Class<T> type, String name) {
for (ExtensionFactory factory : factories) {
T extension = factory.getExtension(type, name);
if (extension != null) {
return extension;
}
}
return null;
}
}
loader.getSupportedExtensions()
获取到的是除了自适应类、包装类之外的扩展类;那么这个方法得到的名字有两个,①SpiExtensionFactory
②SpringExtensionFactory
, 拿到的只是名字而已,那么还要通过loader.getExtension(name)
来拿到对应的实例对象! 具体的创建实例对象细节看后面
在这里的情况,那么factories
中就有两个扩展实例
1.SpiExtensionFactory
SPI的扩展工厂
2.SpringExtensionFactory
Spirng的扩展工厂Object object = objectFactory.getExtension(pt, property);
执行的方法就是
可以看到遍历执行SpiExtensionFactory
、SpringExtensionFactory
两个扩展类的getExtension
方法;
例如SpiExtensionFactory
;
所以这个扩展工厂类,我们也可以写自己的扩展工厂类来生成对应的对象来依赖注入对象!
Type
中所有的扩展类加载扩展类比较重要,所以我单独拉出来细说
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() {
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;
}
cachedClasses
不为空直接返回;说明已经加载过了,这个就是用来保存当前Class
中的所有扩展类名;
cachedClasses
的key是左边值,value是右边对应的Class
type
上的@SPI
有默认值,例如@SPI("dubbo")
,则将其设置到属性cachedDefaultName
中;DUBBO_INTERNAL_DIRECTORY
、DUBBO_DIRECTORY
、SERVICES_DIRECTORY
下面的对应type
文件中的具体实现类;private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error when load extension class(interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + "is not subtype of interface.");
}
if (clazz.isAnnotationPresent(Adaptive.class)) {
if (cachedAdaptiveClass == null) {
cachedAdaptiveClass = clazz;
} else if (!cachedAdaptiveClass.equals(clazz)) {
throw new IllegalStateException("More than 1 adaptive class found: "
+ cachedAdaptiveClass.getClass().getName()
+ ", " + clazz.getClass().getName());
}
} else if (isWrapperClass(clazz)) {
Set<Class<?>> wrappers = cachedWrapperClasses;
if (wrappers == null) {
cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
wrappers = cachedWrapperClasses;
}
wrappers.add(clazz);
} else {
clazz.getConstructor();
if (name == null || name.length() == 0) {
name = findAnnotationName(clazz);
if (name == null || name.length() == 0) {
if (clazz.getSimpleName().length() > type.getSimpleName().length()
&& clazz.getSimpleName().endsWith(type.getSimpleName())) {
name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase();
} else {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
}
String[] names = NAME_SEPARATOR.split(name);
if (names != null && names.length > 0) {
Activate activate = clazz.getAnnotation(Activate.class);
if (activate != null) {
cachedActivates.put(names[0], activate);
}
for (String n : names) {
if (!cachedNames.containsKey(clazz)) {
cachedNames.put(clazz, n);
}
Class<?> c = extensionClasses.get(n);
if (c == null) {
extensionClasses.put(n, clazz);
} else if (c != clazz) {
throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
}
}
}
}
}
type
的实现类@Adaptive
则表示这个是自适应扩展类;并且缓存到属性cachedAdaptiveClass
中;如果文件中的扩展类有多个@Adaptive
,则会抛异常,最多只有一个自适应扩展类type
的情况),则将这个包装类加入的一个Map属性cachedWrapperClasses
中; 这个属性保存了所有的包装类;cachedNames
中;key是扩展类的Class
,value是name
;这个就是用来维护一个扩展类有几个名字的;因为这个左边的name可以用逗号来分割;
@Activate
,则放入map属性cachedActivates
中;key是name, value是注解Activate
前面讲了 自适应扩展类的实例化,还有将各个Class
加载到内存中;但是这个时候其他的扩展类还没有实例化的;
那么 在加载完扩展类之后,具体是如何将这些扩展类实例化的呢?
可以看到,dubbo只是加载这些扩展类而已,这个时候并没有去加载这里类并且实例化;只有在需要这些扩展类实例的时候,才会去主动实例化;
public T getExtension(String name) {
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Extension name == null");
if ("true".equals(name)) {
return getDefaultExtension();
}
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<Object>());
holder = cachedInstances.get(name);
}
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
private T createExtension(String name) {
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.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
type + ") could not be instantiated: " + t.getMessage(), t);
}
}
cachedDefaultName
的扩展类,也就是@SPI("默认")
里面的值createExtension
,通过name找到对应的Class
,然后调用clazz.newInstance()
进行实例化;将实例化对象存到静态全局变量EXTENSION_INSTANCES
中;injectExtension
进行依赖注入;上面分析过,这里不做过多分析instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
,可以看到遍历了所有的包装类,并且每实例化成功一个包装类并且镜像依赖注入的操作之后,这个新的实例就成为下一个包装类实例化时候的入参; 反正就是可以一层一层的往下包装下去;cachedInstances
中TODO…
throw new UnsupportedOperationException
load.balance
。load.balance
, 然后从URL中取获取对应需要自适应扩展的实现类名;用url.getParameter(value,defaultvalue)
得到extName
需要要使用的扩展类;(com.alibaba.dubbo.rpc.Protocol) ExtensionLoader .getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName)
拿到了正真要使用的扩展类,然后用这个实例类去调用 被@Adaptive
修饰的方法名;
例如return extension.refer(arg0, arg1);
injectExtension
;import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.Filter;
@Activate // 无条件自动激活
public class XxxFilter implements Filter {
// ...
}
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.Filter;
@Activate("xxx") // 当配置了xxx参数,并且参数为有效值时激活,比如配了cache="lru",自动激活CacheFilter。
public class XxxFilter implements Filter {
// ...
}
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.Filter;
@Activate(group = "provider", value = "xxx") // 只对提供方激活,group可选"provider"或"consumer"
public class XxxFilter implements Filter {
// ...
}