基本介绍
代理
就是让代理角色帮助真实角色完成一件事情。比如说,我叫我们朋友帮我完成博客,那么这个朋友就是代理,朋友不是我,但是和我完成同样的事,并且还可以对这件事加入他的行为逻辑。
不论是什么代理方法,其根本目的就是为了拓展一个类的方法,也就是想要加入自己的行为逻辑。
当然,我们可以直接修改类的方法,但是一旦类的方法多了起来,就会造成代码的冗余,而且极难进行维护。
静态代理
静态代理相当于是多写了一个代理类,在调用的时候调用的是代理类,在代理类中的处理还是原生的处理逻辑,不过在前后添加上需要添加的代码。
其最根本的特征就是这个代理类需要程序员自己写出来。即在编译阶段就确定了代理对象是谁。
实例说明:
我们编写如下的测试案例:
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)