基本介绍
代理
就是让代理角色帮助真实角色完成一件事情。比如说,我叫我们朋友帮我完成博客,那么这个朋友就是代理,朋友不是我,但是和我完成同样的事,并且还可以对这件事加入他的行为逻辑。
不论是什么代理方法,其根本目的就是为了拓展一个类的方法,也就是想要加入自己的行为逻辑。
当然,我们可以直接修改类的方法,但是一旦类的方法多了起来,就会造成代码的冗余,而且极难进行维护。
静态代理
静态代理相当于是多写了一个代理类,在调用的时候调用的是代理类,在代理类中的处理还是原生的处理逻辑,不过在前后添加上需要添加的代码。
其最根本的特征就是这个代理类需要程序员自己写出来。即在编译阶段就确定了代理对象是谁。
实例说明:
我们编写如下的测试案例:
public class StaticProxyTest {
@Test
public void Test01() {
Vehicle proxyForCar = new ProxyForCar(new Car());
proxyForCar.run("托马斯");
proxyForCar.fly("托马斯");
}
}
编写接口:
public interface Vehicle {
public void run(String s);
public void fly(String s);
}
解读:
- 这里编写接口纯粹是为了符合模板设计模式,没有其他的原因;
- 这样就可以让代理对象调用原类的方法,不会造成原类的浪费。最大限度的保存原类的功能;
编写实现类:
public class Car implements Vehicle {
@Override
public void run(String name) {
System.out.println(name + "is running");
}
@Override
public void fly(String name) {
System.out.println(name + "is flying");
}
}
编写代理类:
public class ProxyForCar implements Vehicle {
Vehicle vehicle;
public ProxyForCar(Vehicle vehicle) {
this.vehicle = vehicle;
}
@Override
public void run(String arg) {
System.out.println("before invoke...");
vehicle.run(arg);
System.out.println("after invoke...");
}
@Override
public void fly(String arg) {
System.out.println("before invoke...");
vehicle.fly(arg);
System.out.println("after invoke...");
}
}
以上就是一个简单的静态代理的实现。可以发现缺点是显然的,静态代理无法实现切面编程,一旦我们需要代理一个新的接口方法,就要重新写一个方法,甚至重新写一个代理类,这样的开销成本无疑是很大的。
于是我们引入了动态代理。
动态代理
我们这里将主要介绍基于接口实现的 JDK 的动态代理方式。
见名知义,动态即我们不需要在自己写代理类了,而是由 JVM 在运行阶段自动的帮我生成一个代理类,在程序结束的时候自动销毁。优势在于:
- 可延展性高,便于实现切面编程。可以批量的代理类的方法;
- 极大的简化了代码工程量;
接下来,我将简单的演示一下动态代理的实现过程,深入分析我们新开一章单独解决。
实例说明
编写测试案例:
public class DynamicProxyTests {
@Test
public void Test01() {
Car car = new Car();
VehicleDynamicProxy vehicleDynamicProxy = new VehicleDynamicProxy(car);
Vehicle proxy = vehicleDynamicProxy.getProxy();
String fly = proxy.fly(10000);
System.out.println("result:\t" + fly);
}
}
编写接口:
public interface Vehicle {
public String fly(int height);
public void run();
}
实现类 ship :
public class Ship implements Vehicle {
@Override
public String fly(int height) {
System.out.println("wdnmd");
return "ship 的高度为" + height;
}
@Override
public void run() {
System.out.println("ship 在run");
}
}
实现类 Car:
public class Car implements Vehicle{
@Override
public String fly(int height) {
System.out.println("wdnmdCar");
return "car 的高度为" + height;
}
@Override
public void run() {
System.out.println("car 在run");
}
}
动态代理类:
public class VehicleDynamicProxy {
/**
* 被代理对象
*/
public Vehicle targetVehicle;
public VehicleDynamicProxy(Vehicle targetVehicle) {
this.targetVehicle = targetVehicle;
}
public Vehicle getProxy() {
//获取类的加载器
ClassLoader classLoader = targetVehicle.getClass().getClassLoader();
//获取类的接口数组
Class<?>[] interfaces = targetVehicle.getClass().getInterfaces();
//调用函数,用来的调用方法
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
System.out.println("前置通知 before");
//调用被代理对象的方法
result = method.invoke(targetVehicle, args);
System.out.println("返回通知 afterReturning");
} catch (Exception e) {
//异常通知 AfterThrowing
throw new RuntimeException(e);
} finally {
//最终通知 After }
return result;
}
};
//此时的proxy对象是实现了Vehicle接口,继承了Proxy类的代理对象
Vehicle proxy = (Vehicle) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
return proxy;
}
}
解读:
- JVM 何以动态的创建动态数组?从以上的代码可以看出,我们获得了被代理对象的类加载器和接口数组。显然底层是根据反射动态生成代理类,并且创建实例的;
- 为了实现调用原对象中的方法,在我们调用代理对象的相应方法的时候,一定会利用 InvocationHandler 中进行调用的转发。也就是说,方法的实际调用不在代理对象中,而由 InvocationHandler 转发到了原对象处理;
- 这样我们就可以在 invoke 方法中实现自己想要拓展的逻辑,在 Spring 中,就有了前置通知,后置通知等切面编程的思想;
总结
- 代理:让代理角色帮助真实角色完成任务,如朋友帮忙完成博客。
- 代理方法目的:拓展类的方法,加入自己的行为逻辑。
- 静态代理:编写代理类,调用时调用代理类,前后添加需要的代码,需要程序员在编译阶段自行编写代理类。
- 缺点:无法实现切面编程,需要代理新接口方法时需要重新写方法或代理类。
- 动态代理:JVM在运行阶段自动生成代理类,程序结束时销毁。
- 优势:高可延展性,便于实现切面编程,简化代码工程量。
- 动态代理实现过程:
- 获取被代理对象的类加载器和接口数组。
- 使用InvocationHandler进行调用转发。
- 实现自己想要拓展的逻辑。
接下来,我们将从源码的角度,详细的看看 JDK 动态代理的机制。
我们再抛出一个问题,那就是为什么在生成代理对象的时候一定要传入接口数组呢?似乎没有接口,我们也可以通过反射,来转发方法的执行。
【源码解析】JDK 动态代理实现 - yelanyanyu - 博客园 (cnblogs.com)