rpc简单框架-添加拦截器链

我们先来看下架构图:


image.png

增加新的功能点,用户可以添加自定义拦截器,实现rpc层面的AOP功能。

拦截器的作用很强大,它提供RPC层面的AOP功能,比如进行日志记录,rpc调用次数统计,service提供方添加白名单等等。很多框架都会实现这样的功能,比如:Servlet中的Filter,Struts中的Interceptor,Netty中的PipelineChannel和ChannelHandler,Tomcat中的Realm等等。这其实是软件可扩展性的一种体现,那么我们的rpc框架必须也要紧跟时代潮流! 不同框架对于这一功能的实现略有不同,但是思路基本一致:责任链+命令模式。

责任链模式的最大作用就是可以让请求在链上传播,那么链上每个节点都可以对请求进行处理。这里的请求是什么?当然就是一次rpc调用了,它肯定包含调用的方法名,调用方法参数列表、参数值、调用方法所属的类等等信息。可以看到,这里的方法的调用者invoker,和方法调用的接受者receiver并不是直接交互的,这就很容易想到了命令模式,命令模式不正是将调用者和接受者进行解耦吗?

MethodInvocation:代表每次方法调用
RpcMethodInvocation:MethodInvocation子接口,代表一次rpc调用
DefaultMethodInvocation:MethodInvocation的默认实现
Interceptor:拦截器接口
InterceptorChain:Interceptor容器,其实叫InterceptorManager貌似更合适。

实现

/**
 * 抽象出方法的执行
 *  *
 */
public interface MethodInvocation {
    
    /**
     * 获取方法
     * 
     * @return
     */
    public Method getMethod();
    
    /**
     * 
     * @return
     */
    public Object[] getParameters();
    
    /**
     * invoke next Interceptor
     * 
     * @return
     * @throws Exception
     */
    Object executeNext() throws Exception;
    
}
/**
 * RpcMethodInvocation
 * 
 *
 */
public interface RpcMethodInvocation extends MethodInvocation {
    
    /**
     * 是否是rpc调用
     * 
     * @return
     */
    public boolean isRpcInvocation();
    
    
}

下面看下MethodInvocation的默认实现类:DefaultMethodInvocation

public class DefaultMethodInvocation implements RpcMethodInvocation {

    private Object target;
    private Object proxy;
    private Method method;
    private Object[] parameters;
    
    private boolean isRpcInvocation;
    
    //拦截器
    private List interceptors;
    
    //当前Interceptor索引值,初始值:-1,范围:0-interceptors.size()-1
    private int currentIndex = -1;
    
    public DefaultMethodInvocation(Object target,Object proxy,Method method,Object[] parameters, List interceptors){
        this.target = target;
        this.proxy = proxy;
        this.method = method;
        this.parameters = parameters;
        this.interceptors = interceptors;
    }
    
    @Override
    public Object executeNext() throws Exception {
        if(this.currentIndex == this.interceptors.size() - 1){
            if(this.isRpcInvocation){
                RpcRequest request = new RpcRequest(target.getClass().getName(), method.getName(),method.getParameterTypes(),parameters
                        ,RpcContext.getAttributes());
                return ((RpcConsumer)target).sendRequest(request);
            }else{
                method.setAccessible(true);
                return method.invoke(target, parameters);               
            }
        }
        Object interceptor = this.interceptors.get(++this.currentIndex);
        return ((Interceptor)interceptor).intercept(this);
    }
    
    //getter setter。。。
    
    @Override
    public boolean isRpcInvocation() {
        return isRpcInvocation;
    }

}

InterceptorChain中维护了一个List类型的拦截器列表,还有一个指向当前拦截器的索引变量currentIndex,下面重点看下executeNext方法。
首先,判断拦截器链是否执行完,如下:

if(this.currentIndex == this.interceptors.size() - 1){}

如果拦截器链没有执行完,那么进行2,3 两句,思路很简单,获取下一个拦截器对象,并将索引值加1,然后调用下一个拦截器的intercept方法。

2.Object interceptor = this.interceptors.get(++this.currentIndex);
3.return ((Interceptor)interceptor).intercept(this);

如果拦截器链已经执行完了,那么需要判断isRpcInvocation的值:

  1. isRpcInvocation=true:说明这是客户端的调用,将方法调用信息封装到RpcRequest中,通过sendRequest发送到server端。
  2. isRpcInvocation=false:这是server端本地调用,通过反射执行方法即可。
if(this.isRpcInvocation){
    RpcRequest request = new RpcRequest(target.getClass().getName(), method.getName(),method.getParameterTypes(),parameters
                        ,RpcContext.getAttributes());
    return ((RpcConsumer)target).sendRequest(request);
}else{
    method.setAccessible(true);
    return method.invoke(target, parameters);   
}

MethodInvocation设计完后,再看看拦截器Interceptor和拦截器链InterceptorChain的设计思路,源码如下:

public interface Interceptor {
    
    Object intercept(MethodInvocation invocation) throws Exception;
    
}

public class InterceptorChain {

    private List interceptors;

    private Set registeredNames;

    public InterceptorChain(){
        interceptors = new ArrayList();
        registeredNames = new HashSet();
    }

    public void addLast(String name, Interceptor interceptor){
        synchronized(this){
            checkDuplicateName(name);
            Entry entry = new Entry(name,interceptor);
            register(interceptors.size(), entry);
        }
    }
    public void addFirst(String name, Interceptor interceptor){
        synchronized(this){
            checkDuplicateName(name);
            Entry entry = new Entry(name,interceptor);
            register(0, entry);
        }
    }

    public void addBefore(String baseName, String name, Interceptor interceptor){
        synchronized(this){
            checkDuplicateName(name);
            int index = getInterceptorIndex(baseName);
            if(index == -1)
                throw new NoSuchElementException(baseName);
            Entry entry = new Entry(name,interceptor);
            register(index, entry);
        }
    }
    
    public void addAfter(String baseName, String name, Interceptor interceptor){
        synchronized(this){
            checkDuplicateName(name);
            int index = getInterceptorIndex(baseName);
            if(index == -1)
                throw new NoSuchElementException(baseName);
            Entry entry = new Entry(name,interceptor);
            register(index+1, entry);
        }
    }
    
    private int getInterceptorIndex(String name) {
        List interceptors = this.interceptors;
        for(int i = 0; i < interceptors.size(); i++){
            if(interceptors.get(i).name.equals(name)){
                return i;
            }
        }
        return -1;
    }
    private void register(int index, Entry entry){
        interceptors.add(index, entry);
        registeredNames.add(entry.name);
    }
    private void checkDuplicateName(String name) {
        if (registeredNames.contains(name)) {
            throw new IllegalArgumentException("Duplicate interceptor name: " + name);
        }
    }
    @SuppressWarnings("unchecked")
    public List getInterceptors() {
        if(!CollectionUtils.isEmpty(this.interceptors)){
            List list = new ArrayList<>(this.interceptors.size());
            for(Entry entry: this.interceptors){
                list.add(entry.interceptor);
            }
            return Collections.unmodifiableList(list);
        }else{
            return (List) CollectionUtils.EMPTY_COLLECTION;
        }
    }
    static class Entry{
        String name;
        Interceptor interceptor;

        public Entry(String name, Interceptor interceptor) {
            super();
            this.name = name;
            this.interceptor = interceptor;
        }
    }
}

首先看InterceptorChain的成员变量:

private List interceptors;
private Set registeredNames;
  • 第一个变量是List类型,代表整个拦截器链,不过并没有直接存储Interceptor对象,而是将interceptor和其对应的name封装成Entry对象。

  • 第二个变量是Set类型,用处很简单,保存已经注册过的拦截器,防止重复注册。

下面在分析下InterceptorChain的构造函数和成员方法:

public InterceptorChain(){
    interceptors = new ArrayList();
    registeredNames = new HashSet();
}

主要成员函数:

addLast
addFirst
addBefore
addAfter

上面四个函数是InterceptorChain提供给用户使用的主要API,实现很简单,无非就是向List中的指定位置增加元素罢了。下面重点分析下InterceptorChain的线程安全性。

InterceptorChain是线程安全的吗
要回答某个类是否是线程安全的问题,首先看实例化对象是否是有状态的?无状态的对象一定是线程安全的,InterceptorChain显然是有状态的:有interceptors和registeredNames两个成员变量。若有状态,再分析下该类是否会存在与并发环境中,如果只可能被单线程操作,那么我们就无需考虑线程安全问题了。InterceptorChain明显可能被多个线程同时操作,所以可能存在于并发环境中。好了,既然这样就想想如何解决?在Java中遇到并发问题是很常见的,解决思路其实无非就这么几种:

加锁(典型解决方案:synchronized、ReentrantLock等)
volatile
使用现成的并发容器(ConcurrentHashMap,CopyOnWriteArrayList等)

基本思路都是并发操作串行化,那么这几种方案该如何选择呢?首先,当我们分析一个类的线程安全性的时候,需要找出可能改变类自身状态的操作,然后分析每个操作在多线程环境中是否会造成状态的不一致性。对于InterceptorChain来说,前面提到的

addLast
addFirst
addBefore
addAfter

这四个方法显然会改变其自身的状态,那么拿addAfter来具体分析下:

    public void addAfter(String baseName, String name, Interceptor interceptor){
        synchronized(this){
            checkDuplicateName(name);
            int index = getInterceptorIndex(baseName);
            if(index == -1)
                throw new NoSuchElementException(baseName);
            Entry entry = new Entry(name,interceptor);
            register(index+1, entry);
        }
    }

addAfter的逻辑很简单,首先检查待注册的Interceptor的name是否已经存在,如果不存在,那么获取拦截器baseName的索引,如果不存在抛出NoSuchElementException异常,如果存在,将待注册拦截器插入到baseName之后,下面看下注册操作register:

private void register(int index, Entry entry){
    interceptors.add(index, entry);
    registeredNames.add(entry.name);
}

可见,从checkDuplicateName到最后注册操作register,这是一系列复合操作集合。如果不进行任何额外的同步操作,就会出现错误(两个相同name的拦截器可能被注册了),加了synchronized保证了对interceptors和registeredNames操作的串行化,保证了InterceptorChain的线程安全性。

Test

用户自定义拦截器TimeInterceptor,client端用来记录rpc调用时间

/**
 * TimeInterceptor:记录方法执行时间
 * 
 * @author wqx
 *
 */
public class TimeInterceptor implements Interceptor {

    @Override
    public Object intercept(MethodInvocation invocation) throws Exception {
        long start = System.currentTimeMillis();
        Object retVal = null;
        try{
            retVal = invocation.executeNext();
        }finally{
            long end = System.currentTimeMillis();
            System.out.println("execute time :" + (end-start) + "ms.");
            RpcContext.addAttribute("time", (end-start));
        }
        return retVal;
    }
}

服务端自定义拦截器MethodFilterInterceptor:

public class MethodFilterInterceptor implements Interceptor {
    
    private Set exclusions = new HashSet<>();
    
    public MethodFilterInterceptor(Set exclusions){
        this.exclusions = exclusions;
    }
    @Override
    public Object intercept(MethodInvocation invocation) throws Exception {
        String methodName = invocation.getMethod().getName();
        if(exclusions.contains(methodName)){
            throw new RpcException("method " + methodName + " is not allowed!");
        }
        return invocation.executeNext();
    }
    
}

MethodFilterInterceptor主要进行黑名单过滤,如果client端调用的方法在exclusions集合中,那么直接抛出RpcException。下面看下客户端和服务端测试代码:

server端:
public class ServerTest {

    private static int port = 8888;
    
    public static void main(String[] args) throws IOException {
        UserService userService = new UserServiceImpl();
        
        
        Set exclusions = new HashSet<>();
        exclusions.add("getMap");
        
        //新增拦截器MethodFilterInterceptor
        RpcProvider.interceptorChain.addLast("methodFilter",
                new MethodFilterInterceptor(exclusions));
        
        RpcProvider.publish(userService, port);
    }
}

client端:
public class ClientTest {


    private static RpcConsumer consumer;
    static {
        consumer = new RpcConsumer();
        
        //客户端添加记录rpc调用时间的拦截器
        consumer.getInterceptorChain().addLast("time", new TimeInterceptor());

        userService = (UserService)consumer.targetHostPort(host, port)
                .interfaceClass(UserService.class)
                .timeout(TIMEOUT)
                .newProxy();
    }
    @Test
    public void testInterceptorChain(){
        try{
            Map resultMap = userService.getMap();
        }catch(Exception e){
            System.out.println(e);
            Assert.assertEquals("method getMap is not allowed!", e.getMessage());
        }
    }
}

结果输出:

execute time :103ms.
edu.ouc.rpc.model.RpcException: method getMap is not allowed!

你可能感兴趣的:(rpc简单框架-添加拦截器链)