Dubbo的SPI之Filter实现

前面我们了解过了Java的SPI扩展机制,对于Java扩展机制的原理以及优缺点也有了大概的了解,这里继续深入一下Dubbo的扩展点加载机制。玩过Dubbo框架的同学都知道,Dubbo框架最强大的地方就是他的SPI机制,可以满足使用者天马行空的扩展性需求。
本文主要讨论2点:Dubbo的spi机制实现原理;基于SPI思想的Filter实现。

Dubbo的spi机制实现原理

这里以Protocol 协议接口来讲解,先上一张图来帮助理解:


ExtensionLoader加载过程.jpg
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

ExtensionLoader类的实现思想参考了JDK中的ServiceLoader类,也是用来加载指定路径下的接口实现,具体实现细节比JDK的复杂了很多。
首先看ExtensionLoader的静态方法getExtensionLoader。

public static  ExtensionLoader getExtensionLoader(Class 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类
        ExtensionLoader loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);
        if (loader == null) {
            //如果为空保存接口类对应的 新建的ExtensionLoader对象
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));
            loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }

有4个点需要注意:
1.EXTENSION_LOADERS这个Map中以接口为key,以ExtensionLoader对象为value。
2.判断Map中根据接口get对象,如果没有就new个ExtensionLoader对象保存进去。并返回该ExtensionLoader对象。
3.注意创建ExtensionLoader对象的构造函数代码,将传入的接口type属性赋值给了ExtensionLoader类的type属性
4.创建ExtensionFactory objectFactory对象

@SPI("dubbo")
public interface Protocol {

      int getDefaultPort();

      @Adaptive
     Exporter export(Invoker invoker) throws RpcException;

     @Adaptive
     Invoker refer(Class type, URL url) throws RpcException;

    void destroy();

}

ExtensionLoader使用loadExtensionClasses方法读取扩展点中的实现类

loadExtensionClasses先读取SPI注解的value值,如果value有值,就把这个值作为默认扩展实现的key。然后再以此读取META-INF/dubbo/internal/,META-INF/dubbo/,META-INF/services/下对应的文件。

  loadFile逐行读取com.alibaba.dubbo.rpc.Protocol文件中的内容,每行内容以key/value形式存储。先判断实现类上是否打上了@Adaptive注解,如果打上了该注解,将此类作为Protocol协议的设配类缓存起来,读取下一行。如果实现类上没有打上@Adaptive注解,判断实现类是否存在参数为该接口的构造器,有的话作为包装类存储在该ExtensionLoader的Set> cachedWrapperClasses;集合中,这里用到了装饰器模式。如果该类既不是设配类,也不是wrapper对象,那就是扩展点的具体实现对象,查找实现类上是否打了@Activate注解,有缓存到变量cachedActivates的map中将实现类缓存到cachedClasses中,以便于使用时获取。如ProtocolFilterWrapper的实现如下:
public class ProtocolFilterWrapper implements Protocol {

    private final Protocol protocol;

    public ProtocolFilterWrapper(Protocol protocol) {
        if (protocol == null) {
            throw new IllegalArgumentException("protocol == null");
        }
        this.protocol = protocol;
    }
  ..........      
}

获取或则创建设配对象getAdaptiveExtension

如果cachedAdaptiveClass有值,说明有且仅有一个实现类打了@Adaptive, 实例化这个对象返回。如果cachedAdaptiveClass为空, 创建设配类字节码。

为什么要创建设配类,一个接口多种实现,SPI机制也是如此,这是策略模式,但是我们在代码执行过程中选择哪种具体的策略呢。Dubbo采用统一数据模式com.alibaba.dubbo.common.URL(它是dubbo定义的数据模型不是jdk的类),它会穿插于系统的整个执行过程,URL中定义的协议类型字段protocol,会根据具体业务设置不同的协议。url.getProtocol()值可以是dubbo也是可以webservice, 可以是zookeeper也可以是redis。

设配类的作用是根据url.getProtocol()的值extName,去ExtensionLoader. getExtension( extName)选取具体的扩展点实现。

有上述的分析可知,能够使用javasist生成设配类的条件:

1)接口方法中必须至少有一个方法打上了@Adaptive注解

2)打上了@Adaptive注解的方法参数必须有URL类型参数或者有参数中存在getURL()方法
仔细看看Protocol接口代理的具体实现,在使用接口代理中的方法时,都会根据URL来确定接口的具体实现,因为URL中携带了用户大部分的参数配置,根据里面的属性来获取。里面关键代码:

com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);

看到这里思路应该比较清晰了!所有的接口代理中,并没有给定具体的实现,全部根据用户的参数配置来动态创建接口的具体实现。这样做让程序非常的灵活,让接口的实现插拔更加方便。如果想增加一个接口的实现,只需要按照SPI的配置方式增加配置文件,xml标签配置指定新接口实现的标记即可。

基于SPI思想的Filter实现

在微服务场景下,一次调用过程常常会涉及多个应用,在定位问题时,往往需要在多个应用中查看某一次调用链路上的日志,为了达到这个目的,一种常见的做法是在调用入口处生成一个traceId,并基于RpcContext来实现traceId的透传。下面来看一下怎么通过filter实现traceId的跟踪记录。
1.创建Dubbo框架的api项目,创建类FilterTest

package com.enjoy.filter;

import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.extension.Activate;
import com.alibaba.dubbo.rpc.*;

@Activate(group = Constants.CONSUMER)
public class FilterSpi implements Filter{


    @Override
    public Result invoke(Invoker invoker, Invocation invocation) throws RpcException {
      String traceId = String.valueOf(System.currentTimeMillis());
        RpcContext.getContext().setAttachment("tracdId",traceId);
        System.out.println("traceId:"+traceId);

        Result result = invoker.invoke(invocation);
        return result;
    }
}

项目结构图为


api项目结构.JPG

2.创建server项目,提供服务,创建订单类

package com.enjoy.service.impl;

import com.enjoy.dao.OrderDao;
import com.enjoy.entity.OrderEntiry;
import com.enjoy.service.OrderService;
import com.enjoy.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;

public class OrderServiceImpl implements OrderService {

    @Autowired
    private OrderDao orderDao;
    @Autowired
    private ProductService productService;


    @Override
    public OrderEntiry getDetail(String id) {
        OrderEntiry orderEntiry =  orderDao.getDetail(id);
        orderEntiry.addProduct(productService.getDetail("P001"));
        orderEntiry.addProduct(productService.getDetail("P002"));
        System.out.println(super.getClass().getName()+"被调用一次:"+System.currentTimeMillis());
        return orderEntiry;
    }

    @Override
    public OrderEntiry submit(OrderEntiry order) {
        try {
            Thread.currentThread().sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (1==order.getStatus()){
            System.out.println("警告:订单重复提交!");
            throw new RuntimeException("订单重复提交!");
        }
        System.out.println(super.getClass().getName()+"被调用一次:"+System.currentTimeMillis());
        return orderDao.submit(order);
    }

    @Override
    public String cancel(OrderEntiry order) {
        try {
            Thread.currentThread().sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(super.getClass().getName()+"被调用一次:"+System.currentTimeMillis());
        return orderDao.cancel(order);
    }
}

配置dubbo.xm文件





    

    
    

    
    

    
    

    

    
    
    
    
    
    

    

    
    
    
    
    
    


server项目结构.JPG

3.创建消费端

package com.enjoy.controller;

import com.alibaba.dubbo.rpc.RpcContext;
import com.enjoy.entity.OrderEntiry;
import com.enjoy.service.OrderService;
import com.enjoy.service.PayService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;


@Controller
public class OrderController {

    @Autowired
    private PayService payService;

    @Autowired
    private OrderService orderService;

    @RequestMapping(value = "/order", method = RequestMethod.GET)
    public String getDetail(HttpServletRequest request, HttpServletResponse response){
        OrderEntiry orderView = orderService.getDetail("1");

        request.setAttribute("order", orderView);
        return "order";
    }

    /**
     * 异步并发调用
     * @param request
     * @param response
     * @return
     */
    @RequestMapping(value = "/cancel", method = RequestMethod.GET)
    public String cancel(HttpServletRequest request, HttpServletResponse response)  {
        OrderEntiry orderView = orderService.getDetail("1");

        String cancel_order = null,cancel_pay = null;
        long start = System.currentTimeMillis();

        //若设置了async=true,方法立即返回null
        cancel_order = orderService.cancel(orderView);
        //只有async=true,才能得到此对象,否则为null
        Future cancelOrder = RpcContext.getContext().getFuture();
        cancel_pay = payService.cancelPay(orderView.getMoney());
        Future cancelpay = RpcContext.getContext().getFuture();

        /**
         * Future模式
         *
         */

        try {
            cancel_order = cancelOrder.get();
            cancel_pay = cancelpay.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        request.setAttribute("cancelOrder", cancel_order);
        request.setAttribute("cancelpay", cancel_pay);

        long time = System.currentTimeMillis() - start;
        request.setAttribute("time", time);

        return "/cancel";
    }

    /**
     * 事件通知
     * @param request
     * @param response
     * @return
     */
    @RequestMapping(value = "/order/submit", method = RequestMethod.GET)
    public String submit(HttpServletRequest request, HttpServletResponse response){
        OrderEntiry orderView = orderService.getDetail("1");
        orderView.setStatus(1);
        orderService.submit(orderView);

        request.setAttribute("order", orderView);
        return "/order";
    }

}
消费端项目结构

4.启动服务提供方和消费端项目,其中消费端项目控制台信息:


控制台.JPG

这个信息就是我们在api中定义的fiter过滤器的具体实现,即在微服务跨域调用过程中,traceId的追踪,方便后续排查日志。

终于写完了,接口代理的生成是不是有点动态代理的感觉。然后用户在XML中配置的dubbo标签属性都保存在了URL中,URL携带的参数贯穿了整个dubbo架构,所有的组件调用都根据URL中配置的参数做处理。其实SPI技术在很多地方都有用到,比如数据库的驱动,日志的处理,原理不是很复杂,仔细研究下就明白了。

你可能感兴趣的:(Dubbo的SPI之Filter实现)