dubbo里面的过滤器机制能够让用户实现很好的定制扩展,就像tomcat里面的Filter的实现一样。
我们看下在dubbo里面Filter具体是怎么实现的。
在ReferenceConfig类的createProxy方法里面有一句很重要,如下
invoker = refprotocol.refer(interfaceClass, urls.get(0));
而refprotocol是通过spi加载的,如下
private static final Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
其真实生成的instance的源码如下
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.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);
}
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);
}
}
所以这个Protocol真正实现的是如下这句代码
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
而在ExtensionLoader的getExtension方法如下
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;
}
createExtension源码如下
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);
//instance被加载出来之后,使用配置的wrapperClasses对其进行进一步的封装
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);
}
}
如上可以看到当instance被反射出来之后,会继续的使用所有配置的wrapper对其进行进一步的wrapper封装。
在dubbo里面提供了一个ProtocolFilterWrapper,当我们在resource里面进行配置之后,其会自动生效,在这个类里面实现了对所有filrer的加载,源码如下
public class ProtocolFilterWrapper implements Protocol {
//内部wrapper的protocol,典型的装饰模式
private final Protocol protocol;
public ProtocolFilterWrapper(Protocol protocol) {
if (protocol == null) {
throw new IllegalArgumentException("protocol == null");
}
this.protocol = protocol;
}
//这个就是核心了
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;
//使用前一个Invoker封装一个新的Invoker出来
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 {
return filter.invoke(next, invocation);
}
@Override
public void destroy() {
invoker.destroy();
}
@Override
public String toString() {
return invoker.toString();
}
};
}
}
return last;
}
@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));
}
//将protocol refer出来的invoker使用filter进一步的wrapper
@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);
}
}
经过上面的介绍大家应该了解了dubbo里面的源码是如何的实现Filter的逻辑的了。
我们介绍几个常用的例子和dubbo里面几个默认的实现。
1 比如我们需要将消费端的application name 透传到提供端,可以如下实现
//当url的group为consumer时生效,也就是只有消费端的invoker调用的时候此filter才生效
@Activate(group = {Constants.CONSUMER})
public class ConsumerPenetrateFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
//通过url拿到application name
String application = invoker.getUrl().getParameter(Constants.APPLICATION_KEY);
if (application != null) {
//放入RpcContext
RpcContext.getContext().setAttachment(Constants.APPLICATION_KEY, application);
}
//invoker的时候,会自动将attachment一起透传过去
return invoker.invoke(invocation);
}
}
比如对于服务端的调用,我们要限流,一般要设置tps数,在dubbo里面提供了现成的实现,如下
//只对服务端生效,且url里面要配置了tps
@Activate(group = Constants.PROVIDER, value = Constants.TPS_LIMIT_RATE_KEY)
public class TpsLimitFilter implements Filter {
//tps限流器
private final TPSLimiter tpsLimiter = new DefaultTPSLimiter();
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
//如果不满足限流条件,Client端会调用失败,且抛出异常。
if (!tpsLimiter.isAllowable(invoker.getUrl(), invocation)) {
throw new RpcException(
"Failed to invoke service " +
invoker.getInterface().getName() +
"." +
invocation.getMethodName() +
" because exceed max service tps.");
}
return invoker.invoke(invocation);
}
}
我们看下DefaultTPSLimiter的具体的实现
public class DefaultTPSLimiter implements TPSLimiter {
//全局调用状态的缓存
private final ConcurrentMap<String, StatItem> stats
= new ConcurrentHashMap<String, StatItem>();
@Override
public boolean isAllowable(URL url, Invocation invocation) {
//配置的rate,默认是-1,不进行控制
int rate = url.getParameter(Constants.TPS_LIMIT_RATE_KEY, -1);
//时间间隔,默认一分钟
long interval = url.getParameter(Constants.TPS_LIMIT_INTERVAL_KEY,
Constants.DEFAULT_TPS_LIMIT_INTERVAL);
//这个serviceKey就是服务端暴露出的dubbo服务的api名字
String serviceKey = url.getServiceKey();
if (rate > 0) {
//已serviceKey 为key 来记录状态信息
StatItem statItem = stats.get(serviceKey);
if (statItem == null) {
stats.putIfAbsent(serviceKey,
new StatItem(serviceKey, rate, interval));
statItem = stats.get(serviceKey);
}
return statItem.isAllowable();
//默认不进行控制
} else {
StatItem statItem = stats.get(serviceKey);
if (statItem != null) {
stats.remove(serviceKey);
}
}
return true;
}
}
其实上面最主要的设计是StatItem,源码如下
class StatItem {
private String name;
//上次设置的时间
private long lastResetTime;
//间隔
private long interval;
//间隔内的允许访问的速率
private AtomicInteger token;
//间隔内的允许访问的速率
private int rate;
StatItem(String name, int rate, long interval) {
this.name = name;
this.rate = rate;
this.interval = interval;
this.lastResetTime = System.currentTimeMillis();
//用rate来初始化token数
this.token = new AtomicInteger(rate);
}
public boolean isAllowable() {
long now = System.currentTimeMillis();
//如果现在时间超过了设置的时间间隔和上次最近设置时间和,那么重置token数
if (now > lastResetTime + interval) {
token.set(rate);
lastResetTime = now;
}
int value = token.get();
boolean flag = false;
//自旋 将token-1
while (value > 0 && !flag) {
flag = token.compareAndSet(value, value - 1);
value = token.get();
}
//如果token数为非负 返回true
return flag;
}
}
由上我们可以看到就是设置 tps.interval 时间间隔内允许访问的 tps。
dubbo里面也提供了简单的token验证,如下
//针对服务端生效,且url有配置token
@Activate(group = Constants.PROVIDER, value = Constants.TOKEN_KEY)
public class TokenFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation inv)
throws RpcException {
//拿到服务端配置的token值
String token = invoker.getUrl().getParameter(Constants.TOKEN_KEY);
if (ConfigUtils.isNotEmpty(token)) {
Class<?> serviceType = invoker.getInterface();
Map<String, String> attachments = inv.getAttachments();
//拿到消费端在attachments里面传过来的remoteToken
String remoteToken = attachments == null ? null : attachments.get(Constants.TOKEN_KEY);
//是否匹配,不匹配抛出异常
if (!token.equals(remoteToken)) {
throw new RpcException("Invalid token! Forbid invoke remote service " + serviceType + " method " + inv.getMethodName() + "() from consumer " + RpcContext.getContext().getRemoteHost() + " to provider " + RpcContext.getContext().getLocalHost());
}
}
return invoker.invoke(inv);
}
}
done!!