- 静态代理: 由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
- 动态代理: 在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象。
情境
假设,有个汽车类具有移动和停止两个方法,我们要怎么在不改动源码的情况下:
1.添加日志
2.添加事务
IMovable.java
public interface IMovable {
void move();
void stop();
}
Car.java
public class Car implements IMovable {
@Override
public void move() {
System.out.println("汽车移动");
}
@Override
public void stop() {
System.out.println("汽车停止");
}
}
静态代理
继承
1.添加日志
CarLog.java
public class CarLog extends Car {
@Override
public void move() {
System.out.println("开始执行move");
super.move();
System.out.println("执行move完成");
}
@Override
public void stop() {
System.out.println("开始执行stop");
super.stop();
System.out.println("执行stop完成");
}
}
Client.java
public class Client {
public static void main(String[] args) {
IMovable log = new CarLog();
log.move();
log.stop();
}
}
- 从上面的代码可以看出,我们定义了一个类并且继承于Car
- 重写父类中的方法,在super(调用父类中的方法)前后加入打印日志的代码
运行截图:
2.添加事务
CarTransaction.java
public class CarTransaction extends Car {
@Override
public void move() {
System.out.println("move事务开始");
super.move();
System.out.println("move事务提交");
}
@Override
public void stop() {
System.out.println("stop事务开始");
super.stop();
System.out.println("stop事务提交");
}
}
运行结果:
- 很明显,对于事务的做法与日志的做法一致
3.先添加日志再开启事务
CarLog2Trans.java
public class CarLog2Trans extends CarTransaction{
@Override
public void move() {
System.out.println("开始执行move");
super.move();
System.out.println("执行move完成");
}
@Override
public void stop() {
System.out.println("开始执行stop");
super.stop();
System.out.println("执行stop完成");
}
}
运行结果:
4.先开启事务再添加日志
CarTrans2Log.java
public class CarTrans2Log extends CarLog {
@Override
public void move() {
System.out.println("move事务开始");
super.move();
System.out.println("move事务提交");
}
@Override
public void stop() {
System.out.println("stop事务开始");
super.stop();
System.out.println("stop事务提交");
}
}
运行结果:
- 从上面代码可以看出如果我们添加功能的话,就要创建新的类
情境: 有四辆汽车A,B,C,D,A汽车要做到先添加日志再开启事务,B汽车要做到先开启事务再添加日志,C汽车只需要添加日志,D汽车只需要开启事务
显然为了完成这样的功能使用继承的方式,我们必须要有四个类才能完成,哪有没有更好的方式呢?
接口(聚合)
1.添加日志
CarLogProxy.java
public class CarLogProxy implements IMovable {
private IMovable movable;
public CarLogProxy(IMovable movable) {
this.movable = movable;
}
@Override
public void move() {
System.out.println("开始执行move");
movable.move();
System.out.println("执行move完成");
}
@Override
public void stop() {
System.out.println("开始执行stop");
movable.stop();
System.out.println("执行stop完成");
}
}
Client.java
public class Client {
public static void main(String[] args) {
IMovable movable = new Car();
IMovable log = new CarLogProxy(movable);
log.move();
log.stop();
}
}
- 从上面的代码可以看出,我们实现了IMovable接口(目标接口),并传入了需要被代理的对象
2.添加事务
CarTransactionProxy.java
public class CarTransactionProxy implements IMovable {
private IMovable movable;
public CarTransactionProxy(IMovable movable) {
this.movable = movable;
}
@Override
public void move() {
System.out.println("move事务开始");
movable.move();
System.out.println("move事务提交");
}
@Override
public void stop() {
System.out.println("stop事务开始");
movable.stop();
System.out.println("stop事务提交");
}
}
Client.java
public class Client {
public static void main(String[] args) {
IMovable movable = new Car();
IMovable transaction = new CarTransactionProxy(movable);
transaction.move();
transaction.stop();
}
}
3.先添加日志再开启事务
Client.java
public class Client {
public static void main(String[] args) {
IMovable movable = new Car();
IMovable transaction = new CarTransactionProxy(movable);
IMovable log = new CarLogProxy(transaction);
log.move();
log.stop();
}
}
4.先开启事务再添加日志
Client.java
public class Client {
public static void main(String[] args) {
IMovable movable = new Car();
IMovable log = new CarLogProxy(movable);
IMovable transaction = new CarTransactionProxy(log);
transaction.move();
transaction.stop();
}
}
- 从3与4的Client可以看出,使用聚合的办法就只要用两个类就能实现需求
- 显然,使用实现目标接口的方式进行代理,让代理和被代理对象之间都可以相互灵活转换
- 所以一般静态代理使用聚合的方式进行实现,使用继承的方式多多少少有些过于笨重
想必认真的人都看的出来,静态代理的方式随着功能的增多,必然要生成更多的代理对象,这样不利于维护。而且,就目前的要求来看,对 move() 和 stop() 两个方法添加日志,其中代码出现了冗余的情况,无法复用。那么有什么方式可以解决呢?
动态代理
Client.java
public class Client {
public static void main(String[] args) {
IMovable movable = new Car();
IMovable logProxy = (IMovable) Proxy.newProxyInstance(Car.class.getClassLoader(), Car.class.getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始执行" + method.getName());
Object invoke =method.invoke(movable, args);
System.out.println("执行" + method.getName() + "完成");
return invoke;
}
});
logProxy.move();
logProxy.stop();
System.out.println();
IMovable transProxy = (IMovable) Proxy.newProxyInstance(Car.class.getClassLoader(), Car.class.getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName() + "开启事务");
Object invoke = method.invoke(logProxy, args);
System.out.println(method.getName() + "事务提交");
return invoke;
}
});
transProxy.move();
transProxy.stop();
}
}
运行结果:
- 从运行结果来看,我们使用动态代理实现了上面静态代理的例子,且没有编写多余的类
- 从上面的代码可以看出,要使用动态代理就必须要有目标接口
- 在 InvocationHandler 的方法中可以获取要执行的 Method 实例
- 通过 Method 的实例可以通过反射来执行,不过要传入被代理对象
- 在反射前后可以进行添加日志和事务的操作
- 而且也可以灵活的让进行代理对象与被代理对象之间的转换
- 由于使用了反射,对性能有一定的损耗
动态代理源码解析
对于动态代理的源码其实最重要的就是下面两个方法,我们下面开始对他们进行深入分析,做到知其然知其所以然。
Proxy.newProxyInstance 部分代码
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
...
Class<?> cl = getProxyClass0(loader, intfs);
try {
...
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
...
return cons.newInstance(new Object[]{h});
}catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
}
...
}
- loader 定义代理类的类加载器
- interfaces 代理类要实现的接口列表
- h 指派方法调用的调用处理程序(注:动态代理的关键)
- 将其他多余的部分代码忽略,找核心的代码(因为有些偏底层我也看不懂 -.- )
- getProxyClass0(loader, intfs); 获得代理类
- cl.getConstructor(constructorParams); 获得代理类的构造方法
- cons.newInstance(new Object[]{h}); 反射生成代理对象,并传入 InvocationHandler
所以我们往下看看它是如何得到代理对象的
Proxy.getProxyClass0 代码
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
return proxyClassCache.get(loader, interfaces);
- 注释翻译: 如果存在给定接口的给定装入器定义的代理类存在,则只返回缓存的副本;否则,它将通过proxyclassfactory创建代理类
所以我们就要进一步分析 (proxyClassCache)WeakCache 类是怎么进行缓存的。(个人能力有限对于WeakCache还有较多疑惑,之后会进行总结更新)
学习资料:
知道了是得到创建代理类,我们继续往下分析
InvocationHandler.java
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
- proxy 在其上调用方法的代理实例
- method 对应于在代理实例上调用的接口方法的 Method 实例,目标对象被调用的方法
- args 包含传入代理实例上方法调用的参数值的对象数组
InvocationHandler用来连接代理对象与目标对象
分析代理类对象
我们可以使用如下代码获取代理类$Proxy0.class文件
public static void main(String[] args) {
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
System.out.println("$Proxy0.class: "+Proxy.getProxyClass(Inter.class.getClassLoader(), Inter.class));
//
IMovable movable = new Car();
IMovable logProxy = (IMovable) Proxy.newProxyInstance(Car.class.getClassLoader(), Car.class.getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始执行" + method.getName());
Object invoke =method.invoke(movable, args);
System.out.println("执行" + method.getName() + "完成");
return invoke;
}
});
logProxy.move();
logProxy.stop();
}
运行结果:
- System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); 打开保存开关
- System.out.println("$Proxy0.class全名: "+Proxy.getProxyClass(IMovable.class.getClassLoader(), IMovable.class)); 可以通过打印信息获取class在项目中路径
- 关于这个类是如何生成的,需要往下跟踪 Proxy.getProxyClass0方法中的proxyClassCache.get(loader, interfaces),这与缓存相挂钩未进行详细分析(之后还会更新文章)。
在得到 $Proxy0.class 之后我们可以使用一些工具将class进行反编译,这里我使用了JD_GUI
$Proxy0.java部分代码
public final class $Proxy0
extends Proxy
implements IMovable
{
private static Method m1;
private static Method m4;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler paramInvocationHandler)
throws
{
super(paramInvocationHandler);
}
...
public final void move()
throws
{
try
{
this.h.invoke(this, m4, null);
return;
}
catch ...
}
public final void stop()
throws
{
try
{
this.h.invoke(this, m3, null);
return;
}
catch ...
}
...
static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m4 = Class.forName("com.zzz.proxy.IMovable").getMethod("move", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("com.zzz.proxy.IMovable").getMethod("stop", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch ...
}
}
- 构造方法传入InvocationHandler
- 在静态代码块中,可以看到我们接口定义的 move 和 stop 分别为 m4 和 m3
- 所以在我们调用代理对象时,就使用 InvocationHandler 回调出去,而 invoke 方法正是由我们实现的。