代理模式的实际运用-以mybatis拦截器实现原理为例

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lovejj1994/article/details/77221436

之前在写mybatis拦截器的时候,因为不懂原理,琢磨了很久,不知道怎么写,在网上找了很多资料,才知道mybatis的拦截器主要还是通过代理实现的,而且我在之前的博文中刚好学习了代理模式。更精细的是,在mybatis对代理的应用上,不管是封装易用性,减少代码耦合度上,都可以让我之前写的代理模式demo进一步改进,也让我加深了对代理模式的理解。

之前代理模式博文地址:http://blog.csdn.net/lovejj1994/article/details/74932311,上一篇博文中,我们讨论了静态代理和动态代理的区别,静态代理需要自己写代理类,比较麻烦,代理的东西一多就很不方便,动态代理只要简单的实现InvocationHandler接口,让jvm自己在运行时生成所需的代理类.

但是第一个问题就是逻辑代码在InvocationHandler的invoke方法里被写死了,不同的被代理类可以有不同的逻辑,逻辑代码被写死就无法保证代码的可拓展性。所以我们可以定义一个Interceptor接口,把需要的逻辑放在接口的intercept方法中。

public interface Interceptor {
    Object intercept() throws Throwable;
}

随后我们将Interceptor 放到代理类中,让Interceptor 的intercept方法在invoke里执行。

/**
 * 动态代理,新增Interceptor接口,让拦截逻辑分离出来
 * Created by panqian on 2017/7/31.
 */
public class DynamicProxy implements InvocationHandler {

    private Interceptor interceptor;

    private DynamicProxy(Object object, Interceptor interceptor) {
        super();
        this.interceptor = interceptor;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object intercept = interceptor.intercept();
        System.out.println("我收到了:" + intercept);
        return intercept;
    }
}

编写测试类,查看效果:

public class DynamicProxyTest {
    public static void main(String[] args) {
        Sourceable source = new Source();

        //lambda表达式直接实现,返回 [0,9] 之间的数
        Interceptor interceptor = () -> new Double(Math.random() * 10).intValue();

        DynamicProxy dynamicProxy = new DynamicProxy(source, interceptor);
        Sourceable sourceable = (Sourceable) Proxy.newProxyInstance(Source.class.getClassLoader(), Source.class.getInterfaces(), dynamicProxy);

        sourceable.method();
        System.out.println("=========");
        sourceable.method1();
    }
}
我收到了:9
=========
我收到了:4

通过上面对逻辑代码的封装,作为客户端程序员就可以随意改变代码逻辑,不需要直接在InvocationHandler接口上定义代码逻辑。

现在引出第二个问题,Interceptor 的引入对我们操作代理类还不是很自由,因为在invoke方法中,还有三个参数,除了第一个proxy对象用的很少,其余两个对于代理操作的灵活性非常重要,method可以返回当前的方法对象,args则是方法的参数数组。那这些参数是否也可以封装进Interceptor接口供 客户端程序员使用呢?

invoke(Object proxy, Method method, Object[] args)

先新建一个Invocation类,里面有method和args。

public class Invocation {
    private Method method;
    private Object[] args;

    public Invocation(Method method, Object[] args) {
        this.method = method;
        this.args = args;
    }

    public Method getMethod() {
        return method;
    }

    public void setMethod(Method method) {
        this.method = method;
    }

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

    public void setArgs(Object[] args) {
        this.args = args;
    }

    @Override
    public String toString() {
        return "Invocation{" +
                "method=" + method +
                ", args=" + Arrays.toString(args) +
                '}';
    }
}

Interceptor 接口做个改造,intercept方法放入Invocation对象。

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

最后在代理类将Invocation 对象封装好并传入intercept方法,这样客户端程序员就对这个拦截器操作有更大的灵活性。

public class DynamicProxy implements InvocationHandler {

    private Interceptor interceptor;

    private DynamicProxy(Object object, Interceptor interceptor) {
        super();
        this.interceptor = interceptor;
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //method和args通过intercept()方法传给了客户端程序员
        Object intercept = interceptor.intercept(new Invocation(method, args));
        System.out.println("我收到了:" + intercept);
        return intercept;
    }
}

最后的测试类,这里为了打印方法参数,将被代理类的method1方法多加了一个参数,用来演示效果。

扫描二维码关注公众号,回复: 3638733 查看本文章
public static void main(String[] args) {
        Sourceable source = new Source();

        //lambda表达式直接实现,返回 [0,9] 之间的数
        Interceptor interceptor = (invocation) -> {
            System.out.println("invocation :" + invocation.toString());
            return new Double(Math.random() * 10).intValue();
        };

        DynamicProxy dynamicProxy = new DynamicProxy(source, interceptor);
        Sourceable sourceable = (Sourceable) Proxy.newProxyInstance(Source.class.getClassLoader(), Source.class.getInterfaces(), dynamicProxy);

        sourceable.method();
        System.out.println("=========");
        sourceable.method1(666);
    }
invocation :Invocation{method=public abstract void designmode.代理模式.evolution02.Sourceable.method(), args=null}
我收到了:2
=========
invocation :Invocation{method=public abstract void designmode.代理模式.evolution02.Sourceable.method1(int), args=[666]}
我收到了:0

这时候我们从客户端程序员的角度开始看生成代理对象的代码,我们只需要提供被代理对象(source)和代理逻辑(interceptor),但是我们还写了生成代理对象的逻辑代码(Proxy.newProxyInstance),这不是我们客户端程序员需要干的事,所以我们决定把这部分代码继续提取出来,封装到DynamicProxy代理类中。因为在mybatis拦截器中,Plugin类对应我们的DynamicProxy类,我们也在逐步靠近mybatis拦截器的写法,所以下面直接把DynamicProxy更名为Plugin,便于理解。

对Plugin新增一个静态方法wrap,通过此方法生成代理对象,客户端不需要写过多跟自己业务无关的代码,生成代理对象的任务全部转移到Plugin类。

public class Plugin implements InvocationHandler {

    private Interceptor interceptor;

    private Plugin(Object object, Interceptor interceptor) {
        super();
        this.interceptor = interceptor;
    }
    //wrap方法生成代理对象
    public static Object wrap(Object target, Interceptor interceptor) {
        Class<?> type = target.getClass();
        Class<?>[] interfaces = target.getClass().getInterfaces();
        return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor)) : target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object intercept = interceptor.intercept(new Invocation(method, args));
        System.out.println("我收到了:" + intercept);
        return intercept;
    }
}

测试类部分代码,sourceable 代理对象 依靠Plugin.wrap生成 :

Sourceable sourceable = (Sourceable)Plugin.wrap(source, interceptor);

等等!我们似乎忘记了一个很严重的问题,我们似乎忘了执行 被代理类本身的method代码。。。虽然这也很好解决,只要在Plugin 的invoke方法中执行以下方法即可:

method.invoke(this.target, this.args);

但是现在已经用了Interceptor ,这个也干脆剥离出来 给客户端程序员 自由发挥吧,Invocation做个小小的改造,新增了target被代理对象 和 proceed方法,proceed方法用来执行原方法的逻辑:

public class Invocation {
    private Method method;
    private Object[] args;
    private Object target;

    public Invocation(Method method, Object[] args,Object target) {
        this.method = method;
        this.args = args;
        this.target = target;
    }

....

    public Object proceed() throws InvocationTargetException, IllegalAccessException {
        return this.method.invoke(this.target, this.args);
    }
}

Plugin 也改动一下,新增object被代理对象。

public class Plugin implements InvocationHandler {

    private Object object;
    private Interceptor interceptor;

    private Plugin(Object object, Interceptor interceptor) {
        super();
        this.object = object;
        this.interceptor = interceptor;
    }

    public static Object wrap(Object target, Interceptor interceptor) {
        Class<?> type = target.getClass();
        Class<?>[] interfaces = target.getClass().getInterfaces();
        return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor)) : target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object intercept = interceptor.intercept(new Invocation(method, args, object));
        System.out.println("我收到了:" + intercept);
        return intercept;
    }
}

被代理类也新增一些逻辑代码:

public class Source implements Sourceable {

    @Override
    public void method() {
        System.out.println("the original method!");
    }

    @Override
    public void method1(int i) {
        System.out.println("the original method1!");
    }

}

最后测试类测试一下:

public static void main(String[] args) {
        Sourceable source = new Source();

        //lambda表达式直接实现,返回 [0,9] 之间的数
        Interceptor interceptor = (invocation) -> {

            System.out.println("原方法开始执行");
            //执行原方法的逻辑
            invocation.proceed();
            System.out.println("原方法执行完毕");
            return new Double(Math.random() * 10).intValue();
        };

        Sourceable sourceable = (Sourceable) Plugin.wrap(source, interceptor);

        sourceable.method();
        System.out.println("=========");
        sourceable.method1(666);
    }

测试成功~!

原方法开始执行
the original method!
原方法执行完毕
我收到了:4
=========
原方法开始执行
the original method1!
原方法执行完毕
我收到了:9

到现在为止mybatis拦截器的原理也讲了十之六七,在拦截器里,并不是所有的方法都需要拦截,你可以在拦截逻辑里判断method的方法名,这是一个方法,mybatis用了注解解决这个问题。

先新建一个注解,methods加入要拦截的方法名:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Intercepts {
    String[] methods();
}

新建一个Interceptor实现类,加上注解@Intercepts。

@Intercepts(methods = {"method1"})
public class MyInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Intercepts annotation = this.getClass().getAnnotation(Intercepts.class);
        if (Objects.nonNull(annotation)) {
            List<String> methods = Arrays.asList(annotation.methods());
            if (methods.contains(invocation.getMethod().getName())) {
                System.out.println(invocation.getMethod().getName() + " :该方法可以执行");
            } else {
                System.out.println(invocation.getMethod().getName() + " :该方法不能执行");
            }
        }
        return null;
    }
}

测试类:

public static void main(String[] args) {
        Sourceable source = new Source();

        //lambda表达式貌似不能加注解,所以换成传统实现类
        Interceptor interceptor = new MyInterceptor();

        Sourceable sourceable = (Sourceable) Plugin.wrap(source, interceptor);

        sourceable.method();
        System.out.println("=========");
        sourceable.method1(666);
    }

结果,可以看出拦截起到了效果:

method :该方法不能执行
=========
method1 :该方法可以执行

总结:
这是一篇以mybatis拦截器实现原理为例子的 讲解代理模式运用 的文章,单纯的学习代理模式不用于实战是理解不了设计模式的核心和用法。 在设计模式中,代理模式算是运用的很广泛了,比如spring的aop也运用的很经典,自己以后还要多多学习

猜你喜欢

转载自blog.csdn.net/lovejj1994/article/details/77221436