摘要
动态代理算是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动态代理就讲到这里,并没有做出很详细的分析,只是希望给还未知道动态代理的人点亮一盏灯,让他们知道这个。