开篇:最近重新学习Spring,看到书中提到 "因为Spring基于动态代理,所以Spring只支持方法连接点。",动态代理熟悉而又陌生的词,还记得大学时候,还花了好打的功夫去了解这个东西,工作快2年了,这方面的东西也基本都快忘没了(真的用不到啊),但是为了更好的去学习Spring,不单单是学会怎么用,也要了解其原理到底如何(面试时候可能问吧?谁知道呢!),也查了好多资料,自己简单的整理一份。
什么是动态代理(dynamic proxy)
就是利用Java的反射技术,在运行时创建一个实现某些给定接口的新类
例如:火车票代售点
应用场景?
1、RPC框架,
2、Spring AOP
3、blablabla...
涉及到哪些接口?
InvocationHandler(接口):
每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。我们来看看InvocationHandler这个接口的唯一一个方法 invoke 方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
proxy: 指代我们所代理的那个真实对象
method: 指代的是我们所要调用真实对象的某个方法的Method对象
args: 指代的是调用真实对象某个方法时接受的参数
Proxy(类):
Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) throws IllegalArgumentException;
loader: 指定一个动态加载代理类的类加载器
interfaces: 表示的是我将要给我需要代理的对象提供一组什么接口
h: 表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个handler对象上
(1)静态代理
先从静态代理开始讲起...
定义一个接口
/**
* 火车站
*/
public interface ITrainStation {
//卖票
public void sellTicket();
//退票
public void returnTicket();
}
实现这个接口
public class ITrainStationImpl implements ITrainStation {
@Override
public void sellTicket() {
System.out.println("卖出去一张票!!!");
}
@Override
public void returnTicket() {
System.out.println("有人退票!!!");
}
}
创建代理类,
/**
* 代售点
*/
public class StaticProxy implements ITrainStation{
private ITrainStation station;
public StaticProxy(ITrainStation station) {
this.station = station;
}
@Override
public void sellTicket() {
System.out.println("代售点、卖票开始");
station.sellTicket();
System.out.println("代售点、卖票结束");
}
@Override
public void returnTicket() {
System.out.println("代售点、退票开始");
station.returnTicket();
System.out.println("代售点、退票结束");
}
public static void main(String[] args) {
//被代理类
ITrainStation station = new ITrainStationImpl();
//代理类
StaticProxy staticProxy = new StaticProxy(station);
staticProxy.sellTicket();
System.out.println("--------------");
staticProxy.returnTicket();
}
}
输出结果:
代售点、卖票开始
卖出去一张票!!!
代售点、卖票结束
--------------
代售点、退票开始
有人退票!!!
代售点、退票结束
代码太简单了,一看就懂,就是创建了一个新类,内部引用被代理类,
静态代理存在如下问题:
1、代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复,如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法,增加了代码维护的复杂度。
2、代理对象只服务于一种类型的对象,如果服务多类型的对象,那就要给每一个对象都进行代理,静态代理在程序规模很大的时候就无法胜任了。
(2)动态代理
创建代理类
public class DynamicProxyHandler implements InvocationHandler {
//要代理的真实对象
private Object subject;
//构造方法,给要代理的真实对象赋初值
public DynamicProxyHandler(Object subject) {
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//proxy: 指代我们所代理的那个真实对象
System.out.println("proxy: " + proxy.getClass().getName());
//method: 指代的是我们所要调用真实对象的某个方法的Method对象
System.out.println("method: " + method.getName());
//args: 指代的是调用真实对象某个方法时接受的参数
if(!StringUtils.isEmpty(args)){
for(Object obj:args){
System.out.println("param: "+ obj);
}
}
System.out.println("在方法执行之前,我要说句话。");
//通过反射调用被代理类
//这里是subject 不是proxy
method.invoke(subject,args);
System.out.println("在方法执行之后,我要说句话。");
return null;
}
}
客户端代码执行代理方法执行
public class Client {
public static void main(String[] args) {
//创建真正的“被代理类”
ITrainStation trainStation = new ITrainStationImpl();
//创建“代理类”
DynamicProxyHandler proxyHandler = new DynamicProxyHandler(trainStation);
//创建一个‘假’的“被代理类”
ITrainStation sellPoint = (ITrainStation)Proxy.newProxyInstance(trainStation.getClass().getClassLoader(),
trainStation.getClass().getInterfaces(),
proxyHandler);
//执行方法
sellPoint.sellTicket();
System.out.println("---------------");
sellPoint.returnTicket();
}
}
输出结果:
proxy: com.sun.proxy.$Proxy0
method: sellTicket
在方法执行之前,我要说句话。
卖出去一张票!!!
在方法执行之后,我要说句话。
---------------
proxy: com.sun.proxy.$Proxy0
method: returnTicket
在方法执行之前,我要说句话。
有人退票!!!
在方法执行之后,我要说句话。
为什么我们可以将返回结果转化为ITrainStation类型的对象?
原因就是在newProxyInstance这个方法的第二个参数上,我们给这个代理对象提供了一组什么接口,那么我这个代理对象就会实现了这组接口,这个时候我们当然可以将这个代理对象强制类型转化为这组接口中的任意一个,因为这里的接口是ITrainStation类型
等一下!输出结果$Proxy0 是什么鬼???
是这句话输出的,System.out.println("proxy: " + proxy.getClass().getName());
正常理解的话应该是 ITrainStation 或 InvocationHandler吗?但是结果却不是。
原因如下:
通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号。
以上就是动态代理的内容了,理解非常重要,在之后学习Spring AOP或者使用RPC框架调用的时候,如会瞬间想到这个模块,就说明真正的掌握了,像我们公司框架,平常用的但是平时没有留意,通过这次学习,瞬间就明白了,啊原来是这么回事!
希望你同样有此感悟,有疑问的话,评论一起讨论
(¦3[▓▓] 晚安
部分内容借鉴:
https://blog.csdn.net/u011784767/article/details/78281384
https://www.cnblogs.com/xiaoluo501395377/p/3383130.html
知乎这个超棒!