设计模式 - 动态代理之拦截器

377 阅读5分钟

​ Java强大的动态代理 ,给与Java强大的活力 ,例如AOP 的实现 , 其实就是基于java的动态代理来实现的, 获取一个增强后的对象, 动态代理依靠字节码技术 , 可以运行时进行代码生成, 而不入侵原有的代码,

​ 例如Spring的AOP , Mybatis 的 Intercept 许多都是基于这个实现的 ,

​ 为什么要说拦截器 , 拦截器可以让用户拓展功能实现, 在运行关键期可以可以实现可见性 , 让开发者可以更加轻松的完成各种操作

1. 设计一个简单的拦截器

1. 开始

我们简易设计了一个拦截器对象

public interface Interceptor {

    /**
     * 方法参数
     *
     * @param args
     */
    void before(Object[] args);


    /**
     * 方法调用完成后
     *
     * @param result 方法的返回结果
     * @return Object 增强后的结果
     */
    Object after(Object result);

    /**
     * 执行出现异常
     *
     * @param e 异常
     */
    void error(Exception e);
}

设计一个代理类

public class MyProxy implements InvocationHandler {
	// 拦截器对象
    private final Interceptor interceptor;

    // 增强的类
    private final Object target;

    public MyProxy(Interceptor interceptor, Object target) {
        this.interceptor = interceptor;
        this.target = target;
    }
	
    // 返回一个代理对象
    public static Object getInstance(Object target, Interceptor interceptor) {
        MyProxy proxy = new MyProxy(interceptor, target);
        return Proxy.newProxyInstance(proxy.target.getClass().getClassLoader(), proxy.target.getClass().getInterfaces(),
                proxy);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object methodResult = null;
        Object proxyResult = null;
        try {
            // 事前
            interceptor.before(args);
            methodResult = method.invoke(target, args);
        } catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException e) {
            // 异常
            interceptor.error(e);
        } finally {
            //事后
            proxyResult = interceptor.after(methodResult);
        }
        return proxyResult;
    }
}

实现接口

public interface IEchoService {

    String echo(String msg);

}

我们的实现方法

public class EchoServiceImpl implements IEchoService {

    @Override
    public String echo(String msg) {
        return null;
    }
}

我们的测试类 :

public class Demo {
    public static void main(String[] args) {

        IEchoService echoService = new EchoServiceImpl();

        IEchoService instance = (IEchoService) MyProxy.getInstance(echoService, new Interceptor() {

            @Override
            public void before(Object[] args) {
                for (Object arg : args) {
                    System.out.printf("args : %s \t", arg);
                }
                System.out.printf("\r\n");
            }
            @Override
            public Object after(Object result) {
                if (result == null) {
                    return "hello world";
                }
                return result;
            }

            @Override
            public void error(Exception e) {
                System.out.println("exception : "+(e == null ? "null" : e));
            }
        });

        String result = instance.echo("tom");
        System.out.println(result);
    }
}

输出 :

args : tom 	
hello world

2. 总结

我们设计的有什么优点和缺点 :

优点 : 可拓展性很强 , 对于一个方法的参数和返回值,异常等都进行了拓展

缺点 ; 没有泛型支持 , 对于方法没有进行一个抽象封装 , Java默认提供的几个参数我们需要加以封装变得更加实用性 , 拓展不是用户管理而是我们管理 ,我们需要进一步优化 ,

2. 继续改进

1. 主体改造部分

1. 设计一个 Invocation 类

public class Invocation<T> {

    /**
     * 增强的对象
     */
    private final T target;
    /**
     * 方法对象
     */
    private final Method method;

    /**
     * 方法参数
     */
    private final Object[] args;
    
    
    public Invocation(T target, Method method, Object[] args) {
        this.target = target;
        this.method = method;
        this.args = args;
    }

    public T getTarget() {
        return target;
    }

    public Method getMethod() {
        return method;
    }

    public Object[] getArgs() {
        return args;
    }

    /**
     * 如果你不想修改可以 .... ,或者 try - catch 都可以
     * @return
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    public Object proceed() throws InvocationTargetException, IllegalAccessException {
        return method.invoke(target, args);
    }
}

2. 接口类

public interface Interceptor<T> {
    Object intercept(Invocation<T> invocation) throws Throwable;
}

3. 代理实现类

package com.intercept.java;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * TODO
 *
 * @date:2019/12/12 13:10
 * @author: <a href='mailto:fanhaodong516@qq.com'>Anthony</a>
 */

public class MyProxy<T> implements InvocationHandler {
	
    private final Interceptor<T> interceptor;
    private final T target;

    // 私有是因为不能开发者进行实例化
    private MyProxy(Interceptor<T> interceptor, T target) {
        this.interceptor = interceptor;
        this.target = target;
    }

    // 写成V 是因为让大家区分开 静态方法的泛型和非静态的不同(类声明的T) , 
	public static <V> V getInstance(final V target, final Interceptor<V> interceptor) {
        // 不是接口直接返回
        Class<?>[] interfaces = target.getClass().getInterfaces();
        if (interfaces == null) {
            return target;
        }

        final MyProxy<V> proxy = new MyProxy<V>(interceptor, target);
        return (V) Proxy.newProxyInstance(proxy.target.getClass().getClassLoader(), proxy.target.getClass().getInterfaces(),proxy);
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return interceptor.intercept(new Invocation<T>(target, method, args));
    }
}

2. 简单使用

接口

public interface EchoService { void echo(String msg);}

接口实现

public class EchoServiceImpl implements EchoService{

    @Override
    public void echo(String msg) {
        // 模拟异常
        int x = 1 / 0;
        // 输出
        System.out.println(msg);
    }
}

测试类 :

public class Demo {
    public static void main(String[] args) {
        EchoService echoService = new EchoServiceImpl();

        Interceptor<EchoService> interceptor = new Interceptor<EchoService>() {
            @Override
            public Object intercept(Invocation<EchoService> invocation) throws Throwable {
                try {
                    invocation.getTarget().echo((String) invocation.getArgs()[0]);
                } catch (Exception e) {
                    System.out.println("异常信息 : " + e.getMessage());
                }
                return null;
            }
        };

        EchoService proxyEchoService = MyProxy.<EchoService>getInstance(echoService, interceptor);
        proxyEchoService.echo("hello");
    }
}

结果 :

异常信息 : / by zero

3. 总结

我们发现如果接口有多个实现类, 如果我们的拦截器是面向方法实现的改如何做呢 ?

比如我这个拦截器只拦截这个方法, 其他方法不拦截 , 那么我们改如何做 ?

其实很简单 ,当我们生成代理对象的时候对于invoke 那里进行判断就行了 , 我简单实现一个例子

接口有两个方法

public interface EchoService {
    void echo(String msg);

    void say(String msg);
}

接口实现类 :

public class EchoServiceImpl implements EchoService {

    @Override
    public void echo(String msg) {
        System.out.println("echo : " + msg);
    }

    @Override
    public void say(String msg) {
        System.out.println("say : " + msg);
    }
}

代理类 : 修改这里 MyProxy<T> #invoke

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

    if (method.getName().equals("echo")) {
        return method.invoke(target, args);
    }

    return interceptor.intercept(new Invocation<T>(target, method, args));
}

测试类 ;

public class Demo {
    public static void main(String[] args) {

        EchoService echoService = new EchoServiceImpl();

        Interceptor<EchoService> interceptor = new Interceptor<EchoService>() {
            @Override
            public Object intercept(Invocation<EchoService> invocation) throws Throwable {
                System.out.println("time  : " + new Date());
                return invocation.proceed();
            }
        };
        EchoService proxyEchoService = MyProxy.<EchoService>getInstance(echoService, interceptor);

        System.out.println("=========echo===========");
        proxyEchoService.echo("hello");

        System.out.println("=========say===========");
        proxyEchoService.say("hello world");
    }
}

执行结果 :

=========echo===========
echo : hello
=========say===========
time  : Thu Dec 12 14:41:53 CST 2019
say : hello world

发现只增强了 say

4. 再次总结

我整个是基于 Mybatis的拦截器实现的 ,基本上和他的思想是一样的 , 我的MyProxy类对应的就是他的 Plugin

我的 InterceptorInvocation 跟他的一样 , 所以颗粒度的降低需要再次细化,比如注解之类的.... 所以Java的强大还是很厉害的

3. 总结

其实拦截器无法就是动态代理去实现, 他可以实现无入侵式的进行方法的拦截, 增强之类的,