静态代理: 由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
动态代理: 在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象。
情境
假设,有个汽车类具有移动和停止两个方法,我们要怎么在不改动源码的情况下:
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