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 mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
他为每个mapper创建了一个动态代理,这个动态代理就是实现了interface的对象,可以直接调用. 而通过注解可以获取mysql的具体信息,所以根本不需要具体的实现类.