zoukankan      html  css  js  c++  java
  • 【Mybtais】Mybatis 插件 Plugin开发(一)动态代理步步解析

    需求:

      对原有系统中的方法进行‘拦截’,在方法执行的前后添加新的处理逻辑。

    分析:

      不是办法的办法就是,对原有的每个方法进行修改,添加上新的逻辑;如果需要拦截的方法比较少,选择此方法到是会节省成本。但是面对成百上千的方法怎么办?此时需要用到动态代理来实现。

    场景:

      例如:对原有的系统添加日志记录、添加性能分析等等。。。

    举例:

      如下,需要对Sleep对象的sleep方法进行“拦截”,并在此方法的执行前后添加新的逻辑。想知道‘睡觉前干了什么?睡觉后干了什么?’

    interface Sleep {
        public void sleep();
    }
    public class SleepImpl implements Sleep{
        public void sleep() {
            System.out.println("我于"+new SimpleDateFormat("yyyy-MM-dd HH:mm").format(new Date())+"开始睡觉");
        }
    }

      创建动态代理类,实现InvocationHandler接口即可。下面的wrap方法:传入要被代理的对象target。返回包装后的代理对象。$Proxy 打断点会看到这样的对象。针对下面的sleepProxy对象,sleepProxy.sleep()调用需要拦截的方法。实际上调用的是Plugin中的invoke方法。invoke方法中的method.invoke(target,args)是真是的调用被代理对象的sleep方法。所以直接在此语句的前后添加相应的逻辑即可满足需要。

    public class Plugin implements InvocationHandler {
    
        private Object target;
        Plugin(Object target){
            this.target = target;
        }    
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            //睡觉前做的事
            Object result = method.invoke(target, args);
            //睡觉后做的事
            return result;
        }    
        public static Object wrap(Object target){
            return Proxy.newProxyInstance(target.getClass().getClassLoader(), 
                    target.getClass().getInterfaces(), 
                    new Plugin(target));
        }
    
    }
    public class Main {
    
        public static void main(String[] args) {
            //要被代理的对象
            Sleep sleep = new SleepImpl();
            //代理对象
            Sleep sleepProxy = (Sleep)Plugin.wrap(sleep);
            sleepProxy.sleep();
        }
    }

    到此,你以为就结束了?不 ,这个仅仅是 说了在睡觉 前后做了什么事,加入还想知道,你在睡觉前后吃了什么东西?当然睡觉后吃东西有点说不通。但 意会就可以了。还有其他巴拉巴拉的需求。你该怎么做?是不是要把所有的 新的逻辑都方法 Plugin中invoke方法中去?这样不合适吧!乱 乱 乱 这样。那咱们能不能抽象出来一个拦截接口,接口中有拦截后要做什么的方法。各种需求只需要实现这个拦截接口即可!

    interface Interceptor {
    
        public void interceptBefore()throws Exception;
        
        public void interceptAfter()throws Exception;
    }
    public class SleepBeforeAndAfter implements Interceptor {
    
        public void interceptBefore() throws Exception {
            System.out.println("之前。。。");
        }
    
        public void interceptAfter() throws Exception {
            System.out.println("之后。。。");
            
        }
    
    }

    然后动态代理类Plugin需要修改

    /**
     * 动态代理
     * 
     * @author 魏正迪
     * 2018年10月13日
     */
    public class Plugin implements InvocationHandler {
    
        private Object target;
        private List<Interceptor> iList = new ArrayList<Interceptor>();
    
        Plugin(Object target , List<Interceptor> iList){
            this.target = target;
            this.iList = iList;
        }
        
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {        
            for(Interceptor i :iList){
                i.interceptBefore();
            }
            Object result = method.invoke(target, args);
            for(Interceptor i :iList){
                i.interceptAfter();
            }
            return result;
        }
        
        public static Object wrap(Object target,List<Interceptor> iList){
            return Proxy.newProxyInstance(target.getClass().getClassLoader(), 
                    target.getClass().getInterfaces(), 
                    new Plugin(target,iList)
                    );
        }
    
    }
    public class Main {
        public static void main(String[] args) {
            Sleep sleep = new SleepImpl();
            List<Interceptor> iList = new ArrayList<Interceptor>();
            iList.add(new SleepBeforeAndAfter());
            Sleep sleepProxy = (Sleep)Plugin.wrap(sleep,iList);
            sleepProxy.sleep();
        }
    }

    现在想对每个对象的方法进行拦截,直接实现Interceptor接口即可!实现其中的两个方法。此时我们新加的逻辑和原有的逻辑并没有什么交集。假如我们想在interceptor中的两个方法中使用被代理对象的各种属性,此时该怎么做?首先想到是将interceptor接口的两个方法添加参数。

    public class SleepBeforeAndAfter implements Interceptor {
    
        public void interceptBefore(Object target, Method method, Object[] args)
                throws Exception {
            System.out.println("之前。。。interceptBefore(Object target, Method method, Object[] args)");
            
        }
    
        public void interceptAfter(Object target, Method method, Object[] args)
                throws Exception {        
            System.out.println("之后。。。interceptAfter(Object target, Method method, Object[] args)");
        }
    
    }

    到此,个人感觉没啥问题了【大牛如发现明显不符的请指出】。但但但但是我们奔着简单明了、面向对象的思想(其实就是mybatis源码插件设计)。我们做出进一步的精简。于是Invocation对象产生了。看到Method对象传进来了。我们是不是可以想到,我们不再 在Plugin中的invoke方法中调用method.invoke(target,args);了,而是在Intercetpor中处理完前后逻辑后进行调用。这样分工明确了。

    /**
     * 拦截对象的包装
     * @author 魏正迪
     * 2018年10月13日
     */
    public class Invocation {
        
        private Object target;
        
        private Object []args;
        
        private Method method;
        
        Invocation(Object target,Method method,Object[] args){
            this.target = target;
            this.args = args;
            this.method = method;
            
        }
        /**
         * 执行拦截对象的对应的方法
         * @return
         * @throws Exception
         */
        public Object process() throws Exception{
            return method.invoke(target, args);
        }
        
    }

    此时拦截器Interceptor应该是这样的

    interface Interceptor {
        public Object intercept(Invocation invocation)throws Exception;
    }
    public class SleepBeforeAndAfter implements Interceptor {
    
        public Object intercept(Invocation invocation) throws Exception{
            System.out.println("拦截sleep方法要执行的方法之前");
            Object result = invocation.process();
            System.out.println("拦截sleep方法要执行的方法之后");
            return result;
        }
    
    }

    此时Plugin应该是这样的

    public class Plugin implements InvocationHandler {
    
        private Object target;
        private Interceptor interceptor;
        
        Plugin(Object target,Interceptor interceptor){
            this.target = target;
            this.interceptor = interceptor;
        }
        
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {        
            Invocation invocation = new Invocation(target,method,args);
            return interceptor.intercept(invocation);
        }
        
        public static Object wrap(Object target,Interceptor interceptor){
            return Proxy.newProxyInstance(target.getClass().getClassLoader(), 
                    target.getClass().getInterfaces(), 
                    new Plugin(target,interceptor)
                    );
        }
    
    }
    public class Main {
    
        public static void main(String[] args) {
            Sleep sleep = new SleepImpl();
            SleepBeforeAndAfter s = new SleepBeforeAndAfter();
            Sleep sleepProxy1 = (Sleep)Plugin.wrap(sleep,s);
            sleepProxy1.sleep();
            Sleep sleepProxy2 = (Sleep)Plugin.wrap(sleepProxy1, s);
            sleepProxy2.sleep();
        }
    
    }

    到此,mybatis插件开发的引言完毕!其实是使用了动态代理和责任链结合的方式。

      

  • 相关阅读:
    关于题目中的内存限制
    手动实现最小堆和最大堆(优先队列)
    线性筛素数(欧拉筛)+前缀和优化
    并查集
    快速排序

    字典按中文姓名排序
    oc程序代码
    学生字典计算年龄差 随机50个数
    nsset
  • 原文地址:https://www.cnblogs.com/oldwei/p/9784708.html
Copyright © 2011-2022 走看看