Spring 最核心的概念是IOC、AOP,AOP的核心功能底层实现机制就是动态代理。
本文使用一个案例逐步讲解动态代理的底层原理。
备注:本文内容核心是韩顺平老师课程内容,这是我做的笔记外加个人理解和补充。
案例需求说明
- 我们有一个 Vehicle 接口,其中有一个 run 方法;这个接口下有两个实现类 Car 和 Ship
- 当运行 Car 对象的 run 方法和 Ship 对象的 run 方法时,输出如下内容:
交通工具开始运行了...
小汽车在公路 running..
交通工具停止运行了...
交通工具开始运行了...
大轮船在公路 running..
交通工具停止运行了...
- 思考如何完成
案例解决方案-传统方式
传统按OOP的思想,这个需求解决方法很简单,就是分别创建好接口,再创建对应的实现类,测试即可,主要代码如下:
- Vehicle 接口
package com.example.proxy2;
public interface Vehicle {
public void run();
}
- Car 实现类
package com.example.proxy2;
public class Car implements Vehicle{
public void run(){
System.out.println("交通工具开始运行了...");
System.out.println("小汽车在公路 running..");
System.out.println("交通工具停止运行了...");
}
}
- Ship 实现类
package com.example.proxy2;
public class Ship implements Vehicle{
public void run(){
System.out.println("交通工具开始运行了...");
System.out.println("轮船在海上航行..");
System.out.println("交通工具停止运行了...");
}
}
- Test ,这里用到了动态绑定机制,可以阅读粗解动态绑定机制
package com.example.proxy2;
public class Test {
public static void main(String[] args) {
Vehicle car = new Car();
car.run();
Vehicle ship = new Ship();
ship.run();
}
}
传统方法好吗,会明显发现有代码冗余,在这个案例中虽然只是输出语句,看起来不算冗余,但是扩展一下如果是某个复杂的实现方法呢,比如某个校验方法,那就显得很冗余了,而且冗余也还好,更主要的是无法对这些方法进行统一管理,修改起来很麻烦,所以解决了需求但是没有很好地解决。
案例解决方案-动态代理
动态代理的思路是在调用方法时,利用反射机制,根据方法去决定调用哪个对象方法
直接上代码:
- 我们先写一个类 VehicleProxyProvider
- 该类可以返回一个代理对象
- 定义一个属性 target_vehicle,将来真正要执行的对象赋值给它,当然这个对象肯定要实现 Vehicle 接口
- 定义构造器
- 接下来,我们要写一个方法 getProxy ,用来返回一个代理对象
- 开始写,随便设置一个返回值null
public class VehicleProxyProvider {
// 定义一个属性
private Vehicle target_vehicle;
// 构造器
public VehicleProxyProvider(Vehicle target_vehicle) {
this.target_vehicle = target_vehicle;
}
// 返回代理对象方法
public Vehicle getProxy(){
return null;
}
}
- 因为 getProxy 方法很重要,我们单独拿出来细讲
- 在 java.lang.reflect 包中有一个类 Proxy,它有一个方法 newProxyInstance() 用来创建代理对象/实例,源码如注释所示
- 初始我们对 newProxyInstance() 所需要的三个参数都没有,所以都设置成null,proxy就是代理对象,我们返回它
- 初始是 Object proxy = Proxy.newProxyInstance(null, null, null);,需要强转一下类型,才能返回
public Vehicle getProxy(){
/**
* Proxy.newProxyInstance() 方法
* public static Object newProxyInstance(ClassLoader loader, // 类加载器
* Class<?>[] interfaces, // 将来要代理的对象的接口信息
* InvocationHandler h) // 调用处理程序/对象,有一个很重要的方法invoke
*/
Vehicle proxy = (Vehicle) Proxy.newProxyInstance(null, null, null);
return proxy;
}
- 我们继续完善Proxy.newProxyInstance(null, null, null)所需要的三个参数
- ClassLoader loader 类加载器,相关知识可以查看粗解Java类加载器
- Class<?>[] interfaces 要执行的类的接口信息
- 创建 InvocationHandler 对象
- InvocationHandler 本身是个接口(可以查阅源码)
- 接口不能实例化,但是我们就是要得到这个对象,该怎么办?使用匿名对象可以解决。简单理解就是在实现接口方法的同时创建对象。
- 这个接口中有个非常重要的方法,
public Object invoke(Object proxy, Method method, Object[] args)
,这个invoke方法就是我们将来执行 target_vehicle 对象的方法时会调用到
// 类加载器
ClassLoader classLoader = target_vehicle.getClass().getClassLoader();
// 要执行的类的接口,我们要代理的是target_vehicle,所以要获取target_vehicle的接口Vehicle
Class<?>[] interfaces = target_vehicle.getClass().getInterfaces();
// 调用处理程序
InvocationHandler invocationHandler = new InvocationHandler() {
/**
* public Object invoke(Object proxy, Method method, Object[] args) * proxy: 代理对象
* method: 代理对象被调用的方法,我们的例子中是 .run()方法
* args: 代理对象调用方法的参数,我们的例子中是 .run()方法中的参数,不过我们的例子中没有参数
* return: 返回代理对象调用方法的返回值,我们的例子中是,。run()的返回结果,不过没有返回值只有打印
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在调用目标对象的方法之前,我们可以添加一些自己的操作
System.out.println("交通工具开始运行了...");
// 调用目标对象的方法,反射
Object result = method.invoke(target_vehicle, args);
// 在调用目标对象的方法之后,我们可以添加一些自己的操作
System.out.println("交通工具停止运行了...");
return result;
}
};
- 创建好三个需要的参数后,放入到 Proxy.newProxyInstance 中
Vehicle proxy = (Vehicle) Proxy.newProxyInstance(loader, interfaces, invocationHandler);
- 测试
- 创建一个对象,Car或者Ship,我们这里用
Vehicle ship = new Ship();
- new VehicleProxyProvider(ship),将要代理的对象传入进去
- 接下来如果还是
ship.run()
,那么调用的还是 ship 自身的run方法,没有走代理对象,可以测试一下,我们将Ship类中的第一个和第三个输出注释掉,结果输出轮船在海上航行..
- 所以,第三步是要获取一个代理对象
Vehicle proxy = vehicleProxyProvider.getProxy();
- 最后,运行代理对象的执行方法
proxy.run();
- 创建一个对象,Car或者Ship,我们这里用
public class Test {
public static void main(String[] args) {
// 1. 创建一个轮船对象
Vehicle ship = new Ship();
// 2. 创建一个 VehicleProxyProvider 对象
VehicleProxyProvider vehicleProxyProvider = new VehicleProxyProvider(ship);
// 3. 获取代理对象,该对象可以代理执行方法
Vehicle proxy = vehicleProxyProvider.getProxy();
// 4. 代理对象执行方法
proxy.run();
}
}
输出:
交通工具开始运行了...
轮船在海上航行..
交通工具停止运行了...
- 最后值得一提是:代理对象 proxy 在编译时是 Vehicle 类型,运行时是 Proxy 类型,这个结论比较容易理解,下面主要讲解代码具体的执行过程
- 既然我们输出了“交通工具开始运行了…”和“交通工具停止运行了…“,说明一定是执行了
invoke(Object proxy, Method method, Object[] args)
方法 - 在这个方法中,先输出“交通工具开始运行了…”,再通过反射执行
method.invoke(target_vehicle, args)
方法,回来继续输出“交通工具停止运行了…”
- 既然我们输出了“交通工具开始运行了…”和“交通工具停止运行了…“,说明一定是执行了
总结
动态代理,在哪里体现了动态呢?
执行的对象是动态的,我们创建谁就用谁的方法 Vehicle ship = new Ship();
,这里可以创建Ship也可以创建Car;执行的方法是动态的。
完整代码
- Vehicle 接口
public interface Vehicle {
public void run();
}
- Vehicle 实现类 Ship
public class Ship implements Vehicle{
public void run(){
// System.out.println("交通工具开始运行了...");
System.out.println("轮船在海上航行..");
// System.out.println("交通工具停止运行了...");
}
}
- VehicleProxyProvider
public class VehicleProxyProvider {
// 定义一个属性
private Vehicle target_vehicle;
public VehicleProxyProvider(Vehicle target_vehicle) {
this.target_vehicle = target_vehicle;
}
public Vehicle getProxy(){
// 类加载器
ClassLoader loader = target_vehicle.getClass().getClassLoader();
/**
* Proxy.newProxyInstance() 方法
* public static Object newProxyInstance(ClassLoader loader, // 类加载器
* Class<?>[] interfaces, // 要执行的类的接口
* InvocationHandler h) // 调用处理程序
*/
// 类加载器
ClassLoader classLoader = target_vehicle.getClass().getClassLoader();
// 要执行的类的接口,我们要代理的是target_vehicle,所以要获取target_vehicle的接口Vehicle
Class<?>[] interfaces = target_vehicle.getClass().getInterfaces();
// 调用处理程序
InvocationHandler invocationHandler = new InvocationHandler() {
/**
* public Object invoke(Object proxy, Method method, Object[] args) * proxy: 代理对象
* method: 代理对象被调用的方法,我们的例子中是 .run()方法
* args: 代理对象调用方法的参数,我们的例子中是 .run()方法中的参数,不过我们的例子中没有参数
* return: 返回代理对象调用方法的返回值,我们的例子中是,。run()的返回结果,不过没有返回值只有打印
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在调用目标对象的方法之前,我们可以添加一些自己的操作
System.out.println("交通工具开始运行了...");
// 调用目标对象的方法,反射
Object result = method.invoke(target_vehicle, args);
// 在调用目标对象的方法之后,我们可以添加一些自己的操作
System.out.println("交通工具停止运行了...");
return result;
}
};
Vehicle proxy = (Vehicle) Proxy.newProxyInstance(loader, interfaces, invocationHandler);
return proxy;
}
}