静态代理: 由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
动态代理: 在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象。
情境
假设,有个汽车类具有移动和停止两个方法,我们要怎么在不改动源码的情况下:
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(调用父类中的方法)前后加入打印日志的代码
运行截图:
静态代理_继承_日志.png
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事务提交"); } }
运行结果:
静态代理_继承_事务.png
很明显,对于事务的做法与日志的做法一致
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完成"); } }
运行结果:
静态代理_继承_先日志后事务.png
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事务提交"); } }
运行结果:
静态代理_继承_先事务后日志.png
从上面代码可以看出如果我们添加功能的话,就要创建新的类
情境: 有四辆汽车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; }@Overridepublic void move() { System.out.println("move事务开始"); movable.move(); System.out.println("move事务提交"); }@Overridepublic 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(); } }
运行结果:
动态代理.PNG
从运行结果来看,我们使用动态代理实现了上面静态代理的例子,且没有编写多余的类
从上面的代码可以看出,要使用动态代理就必须要有目标接口
在 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还有较多疑惑,之后会进行总结更新)
作者:请叫我张懂
链接:https://www.jianshu.com/p/f59a37984b27
共同学习,写下你的评论
评论加载中...
作者其他优质文章