Java的动态代理有JDK原生的代理和cglib两种代理模式, 在我们分析源代码的时候,经常可以看到动态代理的身影,所以这部分是必需要理解并且要记住的一个知识点和常识。
一、代理模式
想要了解java的动态代理,我首先复习一下代理模式. 代理模式的意思是,当你有一个事情要做的时候,不自己去做而交给一个代理去做,这样做的好处是,在做这件事情之前和之后,能够添加一些业务逻辑。这样做的好处是,将代理和核心逻辑分离,也就是说之前和之后的逻辑以及要去做的核心逻辑是分离的,可以分别交给不同的人去写这个代码,改动也不会相互影响,举例如下:
a. 首先是本来的类
/** * Created by longan.rtb on 15/6/2. */ public class Test { public void test() { System.out.print("main method!"); } }
b. 然后代理的类
/** * Created by longan.rtb on 16/11/23. */ public class TestProxy { private Test test; TestProxy(Test test) { this.test = test; } public void ProxyTest() { System.out.print("before call method!"); test.test(); System.out.print("after call method!"); } }
c. 最后是客户端的使用方法
/** * Created by longan.rtb on 15/6/2. */ public class TestTest { public static void main(String[] args) { Test test = new Test(); TestProxy testProxy = new TestProxy(test); testProxy.ProxyTest(); } }
二、如果我们的运行模式before\after是相同的,例如打日志, 仅仅是持有的Test对象不同,调用的方法不同,那么我们是不是每一个类都必须得有一个绑定的代理类呢? 这样就出现了大量的重复代码,java的动态代理就是解决这个问题的,他的实现一共有两种方式。
- JDK的动态代理实现方法,这个需要靠你记忆住了.
a. 首先是定义一个接口
/** * Created by longan.rtb on 16/11/23. */ public interface TestInterFace { void test(); }
b. 接着是这个接口的实现
/** * Created by longan.rtb on 15/6/2. */ public class Test implements TestInterFace{ public void test() { System.out.print("main method!"); } }
c. 规定:需要一个动态代理的实现(你要在代理之前、之后干啥事情),必须实现InvocationHandler接口
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * Created by longan.rtb on 16/11/23. */ public class TestProxy implements InvocationHandler { private Object target; TestProxy(Object target) { this.target = target; } @Override public Object invoke(Object object, Method method, Object[] args) throws Throwable { System.out.print("before call method!"); method.invoke(target, args); System.out.print("after call method!"); return null; } }
d. 最后是调用的方法client看下
import java.lang.reflect.Proxy; /** * Created by longan.rtb on 15/6/2. */ public class TestTest { public static void main(String[] args) { Test test = new Test(); TestProxy testProxy = new TestProxy(test); TestInterFace subject = (TestInterFace) Proxy.newProxyInstance( testProxy.getClass().getClassLoader(), test.getClass().getInterfaces(), testProxy); subject.test(); } }
那么回到我们刚刚提出的那个问题,这里是Test,你也可以代理Test1, Test2, Test3, TestN....等等,而不用写那么多的代理类了,我这里一个代理类足够了
那么proxy.newProxyInstance的方法返回的是个啥玩意儿呢?他是Test对象吗?显然不是,那么他是TestProxy对象吗?也不是,否则的强制转换都要出错,他是由jvm生成的一个动态对象,该对象实现了TestInterface接口当然就可以直接调用test方法喽,这要得益于newProxyInstance这个方法中的第二个参数,明确的告诉他,被代理的target对象是个什么玩意儿的接口.
三、 我们继续提出下一个问题,你这个Test类必须得实现接口,才能工作,那么我可不可以就针对一个类来创建一个动态代理,不需要传入接口类型呢? 答案是肯定的,他就是cglib干的事情
a. 一个类,没有实现任何接口
/** * Created by longan.rtb on 15/6/2. */ public class Test { public void test() { System.out.print("main method!"); } }
b. 一个代理类, 必须按规定写
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * Created by longan.rtb on 16/11/23. */ public class TestProxy implements MethodInterceptor { private Object target; public Object getProxyObject(Object target) { this.target = target; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.target.getClass()); // 回调方法 enhancer.setCallback(this); // 创建代理对象 return enhancer.create(); } @Override // 回调方法 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("before method call"); proxy.invokeSuper(obj, args); System.out.println("after method call"); return null; } }
getProxyObject是为了获得一个对象,这个对象则是最终执行方法的主体,这个对象是你传入的对象(并不关心代理的到底是谁)
intercept则是你必须要实现的方法,你可以在这个方法里做你想做的前和后逻辑
c. 客户端的实现
/** * Created by longan.rtb on 15/6/2. */ public class TestTest { public static void main(String[] args) { Test test = new Test(); TestProxy testProxy = new TestProxy(); Test bookCglib=(Test)test.getProxyObject(test); test.test(); } }
将test传给了testProxy就可以得到一个Test对象,因为他返回的是Test的一个子类,这样你在调用test的方法的时候,其实是偷偷的执行了动态代理里面的代码
四、 具体引用
实际上动态代理在很多地方都有用,例如Spring的AOP的原理就是动态代理,我们看看mybatis的mapper发现他只有接口没有方法,他是怎么工作的呢?
我们看下这个类MapperProxyFactory,里面有个方法
@SuppressWarnings("unchecked") protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }
他为每个mapper创建了一个动态代理,这个动态代理就是实现了interface的对象,可以直接调用. 而通过注解可以获取mysql的具体信息,所以根本不需要具体的实现类.