java动态代理简单讲解

作者:liuguobing   阅读 (3921)  |  收藏 (0)  |  点赞 (0)

摘要

动态代理算是java反射中比较重要的一环


原文链接:java动态代理简单讲解

昨天有人问我为什么mybatis能够只写mapper.xml文件和对应接口,而不用去写实现类,我说到用到了动态代理,然后他说他没学过这个,看来培训班真的如我所料教其然不教其所以然,今天就写篇文章讲一下动态代理吧。

动态代理两个关键字,动态与代理,代理这词应该能让人联想到代理模式,没错动态代理是代理模式的一种,还有一种是静态代理。动态呢,自然是跟静态相对的,先给出个静态代理的例子吧,这样好理解些。

public class StaticProxy {

    public static void main(String[] args) {
        Base base = new BaseProxy(new Base());
        base.hello();
    }

    static class BaseProxy extends Base{

        private Base base;

        public BaseProxy(Base base) {
            this.base = base;
        }

        @Override
        public void hello() {
            System.out.println("在"+new Date()+"开始执行");
            super.hello();
            System.out.println("在"+new Date()+"执行结束");
        }
    }

    static class Base {

        public void hello() {
         System.out.println("Hello");
         try {
                    Thread.sleep(1000);
                  } catch (InterruptedException e) {
                        e.printStackTrace();
                  }
        }

    }

}

没错这就是静态代理,代理类接受一个原有的类对象,然后实现原有类所有需要代理的方法,实现方式是将那个对象执行一次然后加点辅助代码,想象一下如果你需要对很多个类代理,然后这些类还有很多个方法,但你的辅助代码都是相似的,比如都是前后打印执行时间,那不是很浪费代码量吗?于是动态代理出场。

先写出例子:

public class DynamicProxy {

    public static void main(String[] args) {

        BaseInterface b  = (BaseInterface) Proxy.newProxyInstance(DynamicProxy.class.getClassLoader(),new Class[]{BaseInterface.class},new BaseProxyHandler(new Base()));
        b.hello();
    }

    interface BaseInterface {
        void hello();
    }

    static class Base implements BaseInterface {

        @Override
        public void hello() {
            System.out.println("hello");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    static class BaseProxyHandler implements InvocationHandler {

        private Base base;

        public BaseProxyHandler(Base base) {
            this.base = base;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("开始执行时间:"+new Date());
            Object r = method.invoke(base,args);
            System.out.println("结束执行时间:"+new Date());
            return r;
        }
    }

}

这代码量比前一个多很多,因为简单的情况往往越高级的东西代码量反而越大。java.lang.reflect包里有个类Proxy,它有一个静态方法newProxyInstance(ClassLoader, Class[], InvocationHandler),第一个参数是类加载器,第二个参数是一个类数组,这个类数组包含的是接口(记住是接口),InvocationHandler是一个接口,它有一个抽象方法invoke,通过Proxy的那个方法生成的对象执行的方法都会交给这个invoke方法处理,这样我没就可以在这个invoke方法添加一些辅助方法。

有没有觉得跟aop的功能很像,aop的一种实现方式就是用了动态代理,大部分培训班学aop的应该都是在spring里学的,那我就讲讲spring中的吧,spring aop有两种实现方式,一种是这里的动态代理,它会生成一个内部类继承Proxy并动态实现接口的方法,一种是利用cglib操作字节码生成类(实际上应该还有第三种,使用了AspectJ框架,这个框架会在编译期就把切面方法写进代码中)。后两种我不讲,我只说为何要用后两种,因为动态代理有局限性,它只能用于接口,cglib的局限在于它是生成字节码产生的类是继承原类,但如果类是final的就无法进行继承的,AspectJ因为是编译时修改方法,就没有这些局限,它并不会产生代理类对象,而是直接修改类方法。后两种我并没有做详细研究分析,所以不讲了。

现在回到文章开始那个哥们的问题,mybatis为何不需要写实现类呢。相信聪明的人应该很快想到怎么做到的,我给出一个类似代码:

Runnable runnable = (Runnable) Proxy.newProxyInstance(DynamicProxy.class.getClassLoader(), 
new Class[]{Runnable.class}, new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("hello");
        return null;
    }
});
runnable.run();

我并没有写一个Runnable的实现类,但这里runnable可以使用,并且执行打印"hello",mybatis就是类似的实现方式,它会在app运行前生成mapper.xml对应mapper接口的代理类,invoke里则对xml文件中方法对应的sql语句进行处理,所以我们可以不用写mapper的实现类,这个xml文件相当于一个实现类了,如果使用intellij的哥们在使用这些接口方法的地方Ctrl+Alt+B,它会跳到这个xml文件的。

java动态代理就讲到这里,并没有做出很详细的分析,只是希望给还未知道动态代理的人点亮一盏灯,让他们知道这个。

分类   默认分组
字数   3462

博客标签    动态代理  

评论