jannal(无名小宝)

没有失败,只有缓慢的成功

导航

Dubbo之Filter

dubbo版本

  1. dubbo版本2.6.7

Filter

  1. Dubbo中的过滤器与Web应用中的过滤器类似,提供了在服务调用前后插入自定义逻辑的途径。默认启用的过滤

    过滤器 使用方 作用
    AccessLogFilter Provider 打印每一次请求的访问日志。如果需要访问的日志只出现在指定的appender中,可以在log的配置文件中配置additivity
    ActiveLimitFilter Consumer 用于限制消费者对服务端的最大并行调用数
    ExecuteLimitFilter Provider 用于服务端的最大并行调用数
    ClassLoaderFilter Provider 用于切换不同线程的类加载器,服务调用完成后还原回去
    CompatibleFilter 用于使返回值与调用程序的对象版本兼容,默认不开启。如果启用,则会把JSON的返回值转换为Map。如果返回类型与本地接口中定义的不同,则会做POJO的转换
    ConsumerContextFilter Consumer 为消费者把一些上下文信息设置到当前线程的RpcContext对象中,包括invocation、localhost、remote host等
    ContextFilter Provider 为服务提供者把一些上下文信息设置到当前线程的RpcContext对象中,包括invocation、localhost、remote host等
    DeprecatedFilter Consumer 如果调用的方法被标记为弃用,则记录一个错误信息
    EchoFilter Provider 回声测试
    ExceptionFilter Provider 用于统一的异常处理,防止出现序列化失败
    GenericFilter Provider 用于服务提供者实现泛化调用,实现序列化的检查和处理
    GenericImplFilter Consumer 用于消费者实现泛化调用,实现序列化的检查和处理
    TimeoutFilter Provider 如果某些服务调用超时,自动记录告警日志
    TokenFilter Provider 服务提供者下发令牌给消费者,通常用于防止消费者绕过注册中心直接调用Provider
    TpsLimitFilter Provider 用于服务端的限流
    MonitorFilter Provider
    Consumer
    监控并统计所有的接口调用情况:成功、失败、耗时
    TraceFilter Provider Trace指令的使用
    FutureFilter Consumer 在发起invoke或得到返回值、出现异常的时候触发回调事件
  2. @Activate注解可以设置过滤器的条件和顺序

    表示在服务提供者有效
    @Activate(group = Constants.PROVIDER, order = -110000)
    表示在消费者有效
    @Activate(group = Constants.CONSUMER, value = Constants.ACTIVES_KEY)  
    

配置

  1. 过滤器配置

    <!-- 消费方调用过程拦截 -->
    <dubbo:reference filter="xxx,yyy" />
    <!-- 消费方调用过程缺省拦截器,将拦截所有reference -->
    <dubbo:consumer filter="xxx,yyy"/>
    <!-- 提供方调用过程拦截 -->
    <dubbo:service filter="xxx,yyy" />
    <!-- 提供方调用过程缺省拦截器,将拦截所有service -->
    <dubbo:provider filter="xxx,yyy"/>
    
  2. 用户自定义的过滤器顺序默认会在内置过滤器之后,可以使用如下方式,让自定义的过滤器靠前,即写在前面的先执行

    filter="xxx,default"
    
  3. 对于默认的过滤器或自动激活的过滤器,如果不想让其生效,可以按照如下配置

    filter="-XxxFilter"
    如果不想使用所有默认启动的过滤器
    filter="-default"
    
  4. 如果Provider和Consumer都配置了过滤器,则两边的过滤器不会相互覆盖,而是相互叠加,即都会生效。可以通过剔除的方式避免叠加

ProtocolFilterWrapper

  1. 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);
    }
    
  2. buildInvokerChain方法的逻辑

    • 通过ExtensionLoader#getActivateExtension获取所有过滤器,并遍历
    • 使用装饰器增强原有的Invoke,组装过滤器链。
  3. buildInvokerChain源码:通过从里到外构造Invoker,假设有AFilter、BFilter、CFilter和Invoker。会按照CFilter、BFilter、AFilter倒序遍历,过滤器链的构建顺序为CFilter->InvokerBFilter->CFilter->InvokerAFilter->BFilter->CFilter-Invoker。最终的调用顺序就是AFilter->BFilter->CFilter->Invoker

    private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
        Invoker<T> last = invoker;
        //获取所有的过滤器
        List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
        if (!filters.isEmpty()) {
            // A->B->C->invoke
            for (int i = filters.size() - 1; i >= 0; i--) {
                final Filter filter = filters.get(i);
                final Invoker<T> next = last;
                //装饰器增强原有的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;
    }
    

AccessLogFilter(Provider)

  1. AccessLogFilter日志过滤器,默认会被激活,但是需要配置来开启日志打印

    1. 将日志输出到本身的log中
    <dubbo:protocol accesslog="true" />
    将日志输出到文件
    <dubbo:protocol accesslog="accesslog.log" />
    2. 只是某个服务提供者或消费者打印日志  
    <dubbo:provider accesslog="default">  
    <dubbo:service accesslog="default">    
    
  2. 在构造方法中初始化一个ScheduledThreadPool,用于accesslog配置为文件时,定时输出日志到文件。打印日志的逻辑在invoke方法中,默认如果不配置accesslog,则不会有任何额外的逻辑

    public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException {
        try {
            String accesslog = invoker.getUrl().getParameter(Constants.ACCESS_LOG_KEY);
            if (ConfigUtils.isNotEmpty(accesslog)) {
                ...省略(获取参数、版本、组、上下文等)...
                //日志字符串msg  
                String msg = sn.toString();
                //如果accesslog是default或者true,则直接打印INFO级别日志,否则输出到文件中
                if (ConfigUtils.isDefault(accesslog)) {
                    LoggerFactory.getLogger(ACCESS_LOG_KEY + "." + invoker.getInterface().getName()).info(msg);
                } else {
                  	//放到日志队列,定期输出到文件
                    log(accesslog, msg);
                }
            }
        } catch (Throwable t) {
            logger.warn("Exception in AcessLogFilter of service(" + invoker + " -> " + inv + ")", t);
        }
        return invoker.invoke(inv);
    }
    
  3. 由于日志队列是ConcurrentHashSet即无序的,因此输出到文件也是无序的。又因为是定期刷新到磁盘,所以如果突然宕机会丢失部分日志

    private final ConcurrentMap<String/*日志文件名*/, Set<String>> logQueue = new ConcurrentHashMap<String, Set<String>>();
    

ExecuteLimitFilter(Provider)

  1. ExecuteLimitFilter用于限制每个服务中每个方法的最大并发数(或占用线程池的线程数),有接口级别和方法级别的配置方式。如果不配置,默认不限制

    <dubbo:service interface="xxxx.DemoService" executes="10" />
    <dubbo:service interface="xxxx.DemoService" executes="10" >
    	<dubbo:method name="say" executes="10"/>
    </dubbo:service>
    
  2. 为每个URL生成一个IdentityString和RpcStatus对象,存放到ConcurrentHashMap中。RpcStatus用于记录对应的并发数。ExecuteLimitFilter#invoke核心逻辑

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        URL url = invoker.getUrl();
        String methodName = invocation.getMethodName();
        Semaphore executesLimit = null;
        boolean acquireResult = false;
        int max = url.getMethodParameter(methodName, Constants.EXECUTES_KEY, 0);
        //只有配置大于0时才会限制,否则不限制
        if (max > 0) {
            RpcStatus count = RpcStatus.getStatus(url, invocation.getMethodName());
        //            if (count.getActive() >= max) {
            /**
             * http://manzhizhen.iteye.com/blog/2386408
             * use semaphore for concurrency control (to limit thread number)
             * 获取许可
             */
            executesLimit = count.getSemaphore(max);
            if (executesLimit != null && !(acquireResult = executesLimit.tryAcquire())) {
                throw new RpcException("Failed to invoke method " + invocation.getMethodName() + " in provider " + url + ", cause: The service using threads greater than <dubbo:service executes=\"" + max + "\" /> limited.");
            }
        }
        long begin = System.currentTimeMillis();
        boolean isSuccess = true;
        RpcStatus.beginCount(url, methodName);
        try {
            Result result = invoker.invoke(invocation);
            return result;
        } catch (Throwable t) {
            isSuccess = false;
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new RpcException("unexpected exception when ExecuteLimitFilter", t);
            }
        } finally {
            RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, isSuccess);
            if (acquireResult) {
                executesLimit.release();
            }
        }
    }
    
  3. RpcStatus#getSemaphore

    private volatile Semaphore executesLimit;
    private volatile int executesPermits;  
    public Semaphore getSemaphore(int maxThreadNum) {
      if(maxThreadNum <= 0) {
          return null;
      }
    
      if (executesLimit == null || executesPermits != maxThreadNum) {
          synchronized (this) {
              if (executesLimit == null || executesPermits != maxThreadNum) {
                  executesLimit = new Semaphore(maxThreadNum);
                  executesPermits = maxThreadNum;
              }
          }
      }
    
      return executesLimit;
    }
    

ClassLoaderFilter(Provider)

  1. ClassLoaderFilter主要目的是临时切换当前工作线程的类加载到接口的类加载器,以便和接口的类加载器的上线文一起工作。当前框架线程的类加载器可能和Invoker接口的类加载器不是同一个,而当前框架线程中又需要获取Invoker的类加载器中的一些Class,为了避免出现ClassNotFoundException,需要使用线程上下文类加载

    @Activate(group = Constants.PROVIDER, order = -30000)
    public class ClassLoaderFilter implements Filter {
    
        @Override
        public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
            ClassLoader ocl = Thread.currentThread().getContextClassLoader();
            //切换上下文类加载器
            Thread.currentThread().setContextClassLoader(invoker.getInterface().getClassLoader());
            try {
                return invoker.invoke(invocation);
            } finally {
                Thread.currentThread().setContextClassLoader(ocl);
            }
        }
    
    }
    
  2. DubboProtocol#optimizeSerialization会根据Invoker中配置的optimizer参数获取扩展的自定义序列化处理类,这些外部引用的序列化类在框架的类加载器中肯定没有,因此需要使用Invoker的类加载器获取对应的类

ContextFilter(Provider)

  1. ContextFilter主要记录每个请求的调用上下文(收到的请求上下文信息)。ContextFilter为每个请求维护一个RpcContext对象,改对象维护两个InternalThreadLocal

    //local上下文
    private static final InternalThreadLocal<RpcContext> LOCAL = new InternalThreadLocal<RpcContext>() {
        @Override
        protected RpcContext initialValue() {
            return new RpcContext();
        }
    };
    //server上下文
    private static final InternalThreadLocal<RpcContext> SERVER_LOCAL = new InternalThreadLocal<RpcContext>() {
        @Override
        protected RpcContext initialValue() {
            return new RpcContext();
        }
    };
    
  2. ContextFilter核心逻辑

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        Map<String, String> attachments = invocation.getAttachments();
        if (attachments != null) {
            attachments = new HashMap<String, String>(attachments);
            attachments.remove(Constants.PATH_KEY);
            attachments.remove(Constants.GROUP_KEY);
            attachments.remove(Constants.VERSION_KEY);
            attachments.remove(Constants.DUBBO_VERSION_KEY);
            attachments.remove(Constants.TOKEN_KEY);
            attachments.remove(Constants.TIMEOUT_KEY);
            //移除异步属性,避免异步属性传到过滤链的下一个节点
            attachments.remove(Constants.ASYNC_KEY);// Remove async property to avoid being passed to the following invoke chain.
        }
        //设置当前请求的上下文Invoker、host、port
        RpcContext.getContext()
                .setInvoker(invoker)
                .setInvocation(invocation)
        //                .setAttachments(attachments)  // merged from dubbox
                .setLocalAddress(invoker.getUrl().getHost(),
                        invoker.getUrl().getPort());
    
        // mreged from dubbox
        // we may already added some attachments into RpcContext before this filter (e.g. in rest protocol)
        if (attachments != null) {
            //合并前面已经在上线文中设置的附加信息
            if (RpcContext.getContext().getAttachments() != null) {
                RpcContext.getContext().getAttachments().putAll(attachments);
            } else {
                RpcContext.getContext().setAttachments(attachments);
            }
        }
    
        if (invocation instanceof RpcInvocation) {
            ((RpcInvocation) invocation).setInvoker(invoker);
        }
        try {
            RpcResult result = (RpcResult) invoker.invoke(invocation);
            // pass attachments to result
            result.addAttachments(RpcContext.getServerContext().getAttachments());
            return result;
        } finally {
            //清除上下文信息
            RpcContext.removeContext();
            RpcContext.getServerContext().clearAttachments();
        }
    }
    

ExceptionFilter(Provider)

  1. 如果provider中抛出了自定义的异常,而如果consumer不存在此异常类,那么就会导致序列化异常。ExceptionFilter就是防止当异常类不在consumer时序列化失败

  2. ExceptionFilter处理规则

    • 判断是否是泛化调用(GenericService),如果是则不会处理
    • 如果是JDK内部的异常、方法签名上的异常、dubbo的RpcException则直接抛出
    • 将不能处理的异常转换为字符串封装为RuntimeException,放在RpcResult中返回
  3. 源码逻辑

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        try {
            Result result = invoker.invoke(invocation);
            // 如果有异常或者是泛化调用,没有异常或者是泛化调用,则直接返回
            if (result.hasException() && GenericService.class != invoker.getInterface()) {
                try {
                    Throwable exception = result.getException();
    
                    // directly throw if it's checked exception
                    if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) {
                        return result;
                    }
                    // directly throw if the exception appears in the signature
                    try {
                        Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
                        //获取方法参数声明的异常
                        Class<?>[] exceptionClassses = method.getExceptionTypes();
                        for (Class<?> exceptionClass : exceptionClassses) {
                            if (exception.getClass().equals(exceptionClass)) {
                                return result;
                            }
                        }
                    } catch (NoSuchMethodException e) {
                        return result;
                    }
    
                  	...省略错误日志打印...
                    String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
                    String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
                    if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)) {
                        return result;
                    }
                    // directly throw if it's JDK exception
                    //如果JDK异常就直接抛出,这样判断是不是有些草率?万一consumer和provider的JDK版本不一致
                    //而抛出的异常在旧JDK版本里没有岂不是找不到?
                    String className = exception.getClass().getName();
                    if (className.startsWith("java.") || className.startsWith("javax.")) {
                        return result;
                    }
                    // directly throw if it's dubbo exception
                    if (exception instanceof RpcException) {
                        return result;
                    }
    
                    // otherwise, wrap with RuntimeException and throw back to the client
                    //将自定义异常封装为RuntimeException避免consumer序列化异常
                    return new RpcResult(new RuntimeException(StringUtils.toString(exception)));
                } catch (Throwable e) {
                   ...省略错误日志打印...
                    return result;
                }
            }
            return result;
        } catch (RuntimeException e) {
            ...省略错误日志打印...
            throw e;
        }
    }
    

TimeoutFilter(Provider)

  1. TimeoutFilter:记录每个Invoker的调用时间,如果超过了接口设置的timeout之,则会打印警告日志

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        long start = System.currentTimeMillis();
        Result result = invoker.invoke(invocation);
        long elapsed = System.currentTimeMillis() - start;
        if (invoker.getUrl() != null
                && elapsed > invoker.getUrl().getMethodParameter(invocation.getMethodName(),
                "timeout", Integer.MAX_VALUE)) {
            if (logger.isWarnEnabled()) {
                logger.warn("invoke time out. method: " + invocation.getMethodName()
                        + " arguments: " + Arrays.toString(invocation.getArguments()) + " , url is "
                        + invoker.getUrl() + ", invoke elapsed " + elapsed + " ms.");
            }
        }
        return result;
    }
    
    

TokenFilter(Provider)

  1. 如果不想让消费者绕过注册中直连自己,则可以使用令牌验证。即服务提供者在发布自己的服务时会生成令牌并注册到注册中心。消费者必须通过注册中心才能获取有令牌的URL。

    1. 随机Token令牌,使用UUID生成
    <dubbo:provider interface="xxx.DemoService" token="true"/>
    <dubbo:service interface="xxx.DemoService" token="true"/>  
    <dubbo:protocol interface="xxx.DemoService" token="true"/>  
    2. 固定token令牌,相当于密码
    <dubbo:provider interface="xxx.DemoService" token="123456789"/>  
    <dubbo:service interface="xxx.DemoService" token="123456789"/>  
    <dubbo:protocol interface="xxx.DemoService" token="123456789"/>    
    
  2. 令牌的工作流程

    • 消费者从注册中心获取服务提供者包含令牌的URL
    • 消费者RPC调用设置令牌(附加信息中)
    • 服务提供者认证令牌
  3. 源码

     @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();
             //客户端请求带过来的token
             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);
     }
    

TpsLimitFilter(Provider)

  1. TpsLimitFilter 主要用于服务提供者端的限流。TpsLimitFilter并没有在META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Filter文件中配置。如果需要使用,需要自己配置。

    1. 每次发放1000个令牌
    <dubbo:parameter key="tps" value="1000">
    2. 令牌刷新时间间隔为1秒,不配置,默认是60s  
    <dubbo:parameter key="tps.interval" value="1000">  
    
  2. TpsLimitFilter限流是基于令牌,同一时间内只分配N个令牌(使用AtomicInteger实现),每个请求过来都会消耗一个令牌,消耗完后面的请求会被拒绝。主要委托DefaultTPSLimiter实现逻辑

    @Activate(group = Constants.PROVIDER, value = Constants.TPS_LIMIT_RATE_KEY)
    public class TpsLimitFilter implements Filter {
    
        private final TPSLimiter tpsLimiter = new DefaultTPSLimiter();
    
        @Override
        public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
    
            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);
        }
    
    }
    

ActiveLimitFilter(Consumer)

  1. ActiveLimitFilter是限制消费端的并发数,类似服务提供者端的ExecuteLimitFilter

    <dubbo:service interface="xxx.DemoService" actives="10"/>
    <dubbo:reference interface="xxx.DemoService" actives="10" />
    <dubbo:reference interface="xxx.DemoService" actives="10" >
      <dubbo:method name="say" actives="10" />
    </dubbo:reference>  
    
  2. 如果达到限流阈值,并不是直接抛出异常(与服务端的ExecuteLimitFilter不一样),而是先等到直接超时,因为请求是有timeout属性的。当并发数达到阈值时,会先加锁抢占当前接口的RpcStatus对象,然后通过wait方法进行等待。

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        URL url = invoker.getUrl();
        String methodName = invocation.getMethodName();
        int max = invoker.getUrl().getMethodParameter(methodName, Constants.ACTIVES_KEY, 0);
        RpcStatus count = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName());
        if (max > 0) {
            long timeout = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.TIMEOUT_KEY, 0);
            long start = System.currentTimeMillis();
            long remain = timeout;
            int active = count.getActive();
            if (active >= max) {
                synchronized (count) {
                    // 大于阈值就等待,while是避免虚假唤醒,这里使用信号量不是更好?
                    while ((active = count.getActive()) >= max) {
                        try {
                            count.wait(remain);
                        } catch (InterruptedException e) {
                        }
                        long elapsed = System.currentTimeMillis() - start;
                        //先等到超时,再抛出异常(这点与服务端的不一样)
                        remain = timeout - elapsed;
                        if (remain <= 0) {
                            throw new RpcException("Waiting concurrent invoke timeout in client-side for service:  "
                                    + invoker.getInterface().getName() + ", method: "
                                    + invocation.getMethodName() + ", elapsed: " + elapsed
                                    + ", timeout: " + timeout + ". concurrent invokes: " + active
                                    + ". max concurrent invoke limit: " + max);
                        }
                    }
                }
            }
        }
        try {
            long begin = System.currentTimeMillis();
            RpcStatus.beginCount(url, methodName);
            try {
                Result result = invoker.invoke(invocation);
                RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, true);
                return result;
            } catch (RuntimeException t) {
                RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, false);
                throw t;
            }
        } finally {
            if (max > 0) {
                //唤醒另一个线程
                synchronized (count) {
                    count.notify();
                }
            }
        }
    }
    

ConsumerContextFilter(Consumer)

  1. ConsumerContextFilterContextFilter配合使用,在链式调用中(A->B->C->D),收到请求时,当前节点可以被看作是一个服务提供者,在ContextFilter上设置上下文。当发起请求到下一个服务时,当前服务变为一个消费者,由ConsumerContextFilter设置上下文。主要逻辑为
  • 设置当前请求上下文
  • 服务调用,清楚Response上下文,然后继续调用下一个节点
  • 清楚上下文信息。每次调用完成,都会把附加上下文清楚

DeprecatedFilter(Consumer)

  1. 检查所有调用,如果方法已经设置了过时,在会打印ERROR日志,只会打印一次(接口名+方法名,Set保存)
<dubbo:parameter deprecated ="true" />
  1. 源码

    private static final Set<String> logged = new ConcurrentHashSet<String>();
    
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        //为什么不是方法签名呢? 方法名可能重复呀
      	String key = invoker.getInterface().getName() + "." + invocation.getMethodName();
        if (!logged.contains(key)) {
            logged.add(key);
            if (invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.DEPRECATED_KEY, false)) {
                LOGGER.error("The service method " + invoker.getInterface().getName() + "." + getMethodSignature(invocation) + " is DEPRECATED! Declare from " + invoker.getUrl());
            }
        }
        return invoker.invoke(invocation);
    }
    

FutureFilter(Consumer)

  1. FutureFilter主要实现框架在调用前后出现异常时,触发调用用配置的回调方法。oninvoke只对异步调用有效

    <bean id="callback" class="Xxx.CallBack">
    <dubbo:reference id="demoService" interface="xxx.DemoService">
      <dubbo:method name="bar" 
                    onreturn="callback.onreturn"
                    onthrow="callback.onthrow"
                    oninvoke="callback.oninvoke"
       />
        
    </dubbo:referenc> 
    

posted on 2021-12-25 22:59  jannal  阅读(354)  评论(0编辑  收藏  举报