本文是写给自己,搞清楚每个流程,参数怎么传递的,做提醒作用。
工具
说到动态代理,离不开的就是2个工具
- Interface InvocationHandler
- Class Proxy
一个是接口,一个是类
- 定义:
- 真实对象->委托对象
- 动态代理->代理对象
- 接口->要实现功能
- 肯定要实现以下步骤:
- 动态代理必须要实现委托对象的接口,并把接口义为属性
InvocationHandler
这是个接口,所以肯定得自己写个实现类来使用,例如:
public class MyInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
method.invoke(obj,args)
return null;
}
}
关键方法invoke,参数解释:
invoke(Object proxy, Method method, Object[] args) throws Throwable
1、proxy 动态代理调用方法时,传入的真实委托对象 //很多人这么解释吧,其实是错的。
2、method 动态代理调用方法时,被调用的那个方法
3、args 动态代理调用方法时,传入被调用方法的参数
4、method.invoke(obj,args) 在方法体里必须要实现的,在代理对象调用方法时使用
上述方法参数,最重要的就是proxy,因为他被误用了,后面解释
Proxy
这个类在动态代理里面主要就是用到newProxyInstance,用来动态生成代理类,以下称为动态代理类:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
1、loader 代理的的类加载器
2、interfaces 委托对象的接口
3、h 在生成动态代理类时关联的MyInvocationHandler的实例化对象h
4、object 生成的动态代理类
1、为什么要有loader,因为此时生成的objerct动态代理类没被加载到JVM,所以用类加载器,可以用MyInvocationHandler的实例化对象的类加载器
2、为什么要传interfaces 因为只有代理类也实现了同一个接口,才可以实现委托类的功能啊—__—
3、那为什么又要传入h呢,是因为在生成动态代理类时用到了h的invoke方法吗? 前排提示并没有
流程解析
好了,做了这么多铺垫,开始看动态代理如何实现,为了更为直观的展示每个步骤参数的作用,我代码耦合度非常低。。。
首先那必须是定义接口和实现类(委托对象)
public interface Phone {
public void sell();
}
public class Apple implements Phone {
@Override
public void sell() {
System.out.println("卖苹果手机");
}
}
public class Huawei implements Phone {
@Override
public void sell() {
System.out.println("卖华为手机");
}
}
这里以卖手机的sell()方法为例
接着我们实现InvocationHandler接口,并把像开篇说的把接口定义为属性、实现有参构造
public class MyInvocationHandler implements InvocationHandler {
private Phone target;
public MyInvocationHandler(Phone phone) {
this.target = phone;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("确认调用了invoke方法");
method.invoke(target,args);
return null;
}
}
Q&A
- 为什么我们要先实现InvocationHandler而不是先proxy呢?
因为proxy要关联InvocationHandler的实用类的实例化对象h。 - target是委托对象,那为什么要把它传入呢?是创建动态代理类时要用吗?
因为method.invoke(target,args),这里的target用的上,和创建动态代理对象无关。
\后面重点解释\
然后我们写一个测试方法,通过代理来卖苹果手机
public class Test {
public static void main(String[] args) {
// 1、实例化对象
Phone apple = new Apple();
// 2、实例化MyInvocationHandler,这里把apple作为委托对象
MyInvocationHandler handleraApple = new MyInvocationHandler(apple);
// 3、生成代理对象
/**
* 1、这里因为我耦合度太低了,所以要改变代理对象得改2个参数,后附一个耦合度高一点的做法
* 2、加载器用handler的,不多解释
* 3、传入委托对象的接口,这是为了让代理类也实现相同接口,这样代理才能实现委托对象的功能啊。
* 4、传入handler,每动态代理对象必须和一个handler关联,后面会用到
* 5、为什么可以用Phone接收,因为代理对象不是也实现了Phone接口吗?
*/
Phone proxy = (Phone) Proxy.newProxyInstance(handlerApple.getClass().getClassLoader(),apple.getClass().getInterfaces(),handlerApple);
// 4、实现代理功能
proxy.sell();
//确认调用了invoke方法
//卖苹果手机
}
}
结果与预期一致,输出:卖苹果手机。
但为什么会也输出:调用了MyInvocationHandler的invoke方法?我没有用handler.invoke操作啊
- 第一,为什么会卖苹果手机:
是因为new MyInvocationHandler()传入了真实对象apple?
还是Proxy.newProxyInstance()中传入了真实对象apple的接口?
答案是:第一个,但不完全是。让我们来验证,我们把MyInvocationHandler参数改一下
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("调用了MyInvocationHandler的invoke方法");
// 把target改为new Huawei()
method.invoke(new Huawei(),args);
return null;
}
再运行一次Test,结果是:
调用了MyInvocationHandler的invoke方法
卖华为手机
这就有意思了,我传入的一直是apple啊,只是方法体里面改为了new Huawei(),怎么会影响invoke传入参数?
public class Test {
public static void main(String[] args) {
Phone apple = new Apple();
MyInvocationHandler handleraApple = new MyInvocationHandler(apple);
Phone proxy = (Phone) Proxy.newProxyInstance(handlerApple.getClass().getClassLoader(),apple.getClass().getInterfaces(),handlerApple);
proxy.sell();
//确认调用了invoke方法
//卖卖华为手机
}
}
为了方便看,我把注释去掉,可以看到handlerApple接收的真实对象明明是apple,那么改方法体的参数,怎么会影响结果呢,通过debugger
可以看到proxy居然是null,不是说这是要传入的真实对象吗,那他怎么知道我要代理的真实对象是谁,应该调用哪个真实对象的方法?
这个就涉及到另外一个问题,这个invoke怎么会被调用,在哪一步被调用?
public class Test {
public static void main(String[] args) {
Phone apple = new Apple();
MyInvocationHandler handlerApple = new MyInvocationHandler(apple);
Phone proxy = (Phone) Proxy.newProxyInstance(handlerApple.getClass().getClassLoader(),apple.getClass().getInterfaces(),handlerApple);
// proxy.sell();
}
}
当我把proxy.sell()注释掉,没有输出结果,这就证明,在生成动态代理对象时,invoke没有被调用,而是proxy调用方法时,自动跳转到handlerApple.invoke方法。
那么为什么会自动跳转,留个坑
同时再看另外一张截图,把proxy展开发现,他的居然属性还是APPLE!!
- 这其实说明了,无论动态代理对象proxy关联的是哪个handlerApple,无论传入的代理proxy是谁,最终都用不上之前传入的委托对象。这个proxy更不是很多人说的真实委托对象,而是一个代理对象而已。
- 在跳转到他的invoke方法时,他始终运行的是方法体中method.invoke(new Huawei(),args),第一个参数就是委托者啊,就是个反射而已。
好了,到这里应该很明白动态代理是怎么实现的了吧,一定要切记,委托对象一定要传入method.invoke(obj,args),这样不管你怎么改,一定可以实现真的的委托对象方法。所以大家都把他定义为传入的真实委托者。
这篇文章主要是我在CSDN找了很多动态代理的文章,都是抄来抄去,而且有错误,所以写了一个给自己看看。纠正了我在CSDN看到的问题:
1、很多文章说invoke是在newProxyInstance时调用了,其实没有。
2、invoke中的proxy也不是真实传入对象。
留了坑,怎么自动跳转的;这就和截图里面那个$Proxy0有关了。