从代理模式说起
「代理模式」是设计模式的一种,代理模式中有两个关键的成员:「代理类」(Proxy)和「被代理类」(RealSubject)
那Proxy有啥用呢,直接访问RealSubject不行嘛?
- 如果「被代理类」十分庞大(消耗内存空间),但真正需要它的时候很少,我们不希望立即初始化「被代理类」从而占用内存,交给轻量级的「代理类」Proxy完成任务即可。
- 我们希望对「被代理类」进行一些增强,比如说在方法开始执行前后打印参数的变化,执行结果等等信息,为了解耦「被代理类」的业务实现和这种与业务无关的行为逻辑,我们需要将这些与业务无关的行为逻辑剥离出来,就可以封装在「代理类」中
- 往往这种与业务无关的行为逻辑有很多共性,这些逻辑可以被抽象为「切面」,也就是AOP,面向切面编程,通过代理模式可以极大地减少重复代码。
代理模式的实现方法
代理模式一般有两种实现方法:静态代理和动态代理。
静态代理
静态代理就是上述UML图的实现方法,可以看到「代理类」Proxy内聚一个RealObject,实现共同的接口,可以很轻松地在这个方法上做加强。
接口:
public interface Subject {
void dosth();
}
被代理类:
public class RealSubject implements Subject{
@Override
public void dosth(){
System.out.println("dosth...");
}
}
代理类:
public class Proxy implements Subject{
private Subject subject;
@Override
public void dosth(){
System.out.println("before-------执行前增强逻辑");
subject.dosth();
System.out.println("after--------执行后增强逻辑");
}
}
静态代理的局限性
- 每需要一个类被代理,就需要为之编写一个代理类,这会导致文件数量膨胀。
- 上文提到的Proxy的第三个好处,即AOP的功能还没有实现,我们希望一个代理类能代理多个类,并且被代理的方法是我们可以指定的,这就需要动态代理来解决了。
动态代理
动态代理又有两种常见的实现:JDK动态代理和CGLIB动态代理。
JDK动态代理
基本使用
JDK动态代理中:「代理类」Proxy不再实现Subject接口,而是implements InvocationHandler
,但仍聚合了被代理类(通过构造函数传入被代理类,Object类型)
代理类implements InvocationHandler
,重写invoke方法
public class TestInterceptor implements InvocationHandler {
private Object target;//目标对象的引用,这里设计成Object类型,更具通用性
public TestInterceptor(Object target){
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] arg) throws Throwable {
System.out.println("before-------执行前逻辑");
Object result = method.invoke(target, arg);//调用目标对象的方法
System.out.println("Before return:"+result);
return result;
}
}
利用Proxy类.newProxyInstance方法
public class Main {
public static void main(String[] args) {
RealSubject target = new RealSubject();//生成目标对象
//接下来创建代理对象
Subject proxy = (Subject) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(), new TestInterceptor(target));
//调用代理类的方法,有切面逻辑
proxy.dosth();
}
}
获取proxy的名称,发现是$Proxy0
实现原理
通过前文的基本使用,我们了解到了两个关键点:InvocationHandler接口和Proxy代理类。
InvocationHandler只有一个invoke方法
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
那么重点就是Proxy.newProxyInstance方法
public static Object newProxyInstance(ClassLoader loader,
Class[] interfaces,
InvocationHandler h)
{
// 1. 获取到代理类的class对象
Class cl = getProxyClass0(loader, intfs);
// 2. 拿到代理类的构造器,通过反射创建出一个代理类实例
final Constructor cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
}
那getProxyClass0这个方法是如何拿到代理的class的呢
其实这个方法做了参数的校验,然后直接调用proxyClassCache.get方法
return proxyClassCache.get(loader, interfaces);
这个Cache也很好理解,我们两次获取代理对象,没必要生成两个class对象,因此要复用代理类的class,我们来关注cache未命中的情况,proxyClass是如何生成的。
这个proxyClassCache是Proxy内部的一个字段如下(注意两个入参):
private static final WeakCache>
proxyClassCache = new WeakCache(new KeyFactory(), new ProxyClassFactory());
get方法最重要的部分如下:
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
// 最终这个class对象就是supplier.get方法返回的
// Supplier是一个lambda表达式
Supplier supplier = valuesMap.get(subKey);
Factory factory = null;
while (true) {
// 如果有supplier了 直接get,正常就返回(一般缓存命中就在这里直接return了)
if (supplier != null) {
V value = supplier.get();
if (value != null) {
return value;
}
}
// 一般缓存未命中 会先创建一个Factory , Factory是一个实现了supplier的内部类
if (factory == null) {
factory = new Factory(key, parameter, subKey, valuesMap);
}
// 尝试放入缓存
if (supplier == null) {
supplier = valuesMap.putIfAbsent(subKey, factory);
if (supplier == null) {
supplier = factory;
}
} else {
if (valuesMap.replace(subKey, supplier, factory)) {
supplier = factory;
} else {
supplier = valuesMap.get(subKey);
}
}
}
那么核心就是Factory.get方法了:
value = Objects.requireNonNull(valueFactory.apply(key, parameter));
return value;
兜兜转转又回到了proxyClassCache的构造函数的第二个参数了,就是ProxyClassFactory
核心:ProxyClassFactory
代理类名的由来
// 前缀
private static final String proxyClassNamePrefix = "$Proxy";
// 原子long
private static final AtomicLong nextUniqueNumber = new AtomicLong();
// 代理类名
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
最后调用到:
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
发现generateProxyClass非常晦涩难懂,并且defineClass0是个native方法,总之是生成了字节码文件并加载了class对象,只能反编译查看代理类的实现。
发现代理类继承了Proxy,被代理的方法以Method的形式存储,最终通过反射的方式来调用。
CGLIB动态代理
cglib不再需要被代理类实现一个接口,这是和JDK动态代理与静态代理的不同点,这可以帮助我们远离必须实现接口的困扰。
基本使用
被代理类:
public class RealSubject{
public void dosth(){
System.out.println("dosth...");
}
}
代理类, 实现 MethodInterceptor
接口
public class CglibProxy implements MethodInterceptor {
@Override
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("xxx");
methodProxy.invokeSuper(object, args);
System.out.println("xxx");
return null;
}
}
利用Enhancer创建代理对象。
public class CglibTest {
public static void main(String[] args) {
CglibProxy cglibproxy = new CglibProxy();
Enhancer enhancer = new Enhancer();
// 设置enhancer对象的父类(被代理类)
enhancer.setSuperclass(RealSubject.class);
// 设置enhancer的回调对象(代理类)
enhancer.setCallback(cglibproxy);
// 创建代理对象
RealSubject proxy = (RealSubject) enhancer.create();
proxy.dosth();
}
}
实现原理
如果要从ASM开始了解太费精力了,一种做法是直接看cglib生成的所有类反向推。
cglib主要通过ASM直接修改字节码文件,再通过类加载器加载生成一个class对象,就是我们的代理类Proxy,最后依然通过反射拿到Proxy的构造方法并创建一个实例对象并返回。
cglib的代理类Proxy实际上是继承被代理类RealSubject的,并且实现了Factory接口,因此cglib的局限性是:final类是无法被代理的。
cglib实际会生成五个字节码文件,比较重要的有三个,代理类,以及两个FastClass分别对应代理类和被代理类,所以在生成代理对象时会慢一些。cglib调用原始方法是通过FastClass的下标进行调用的。而JDK动态代理是通过反射进行调用的。
建议自己动手反编译代理类看一下,篇幅原因这里就不贴了,实在不想动手也可以参考:分析cglib动态代理的实现
如何理解静态与动态的区别
静态代理,是一对一的关系。是确定了「被代理类」,专为此「被代理类」创建了一个代理类。
而动态代理,是多对多关系。解耦了「代理逻辑」与「被代理类」,彼此不相干。静态代理的主体是「被代理类」,而动态代理的主体既是「被代理类」,也是「代理逻辑」。只是我们在真正需要代理的时候,才把他们结合到一起。这里说「代理逻辑」,是因为真正的「代理类」是动态生成的,在此之前并不知道会用什么「代理逻辑」。而静态代理的「代理类」是静态的,代理逻辑是确定的。所以也可以说,静态代理我们是在编写「代理类」,而动态代理我们是在编写「代理逻辑」。
JDK与cglib的区别
- JDK的核心是反射,cglib的核心是ASM
- 通常JDK的效率更高,一种佐证是:Spring默认使用JDK,没有实现接口才用cglib
- JDK是委托机制,cglib是继承关系
- JDK要求被代理类必须实现某个接口,而cglib要求被代理类不被final修饰
SpringBoot默认cglib实现AOP
SpringBoot关于Spring AOP的配置类:AopAutoConfiguration,默认使用 Cglib 来实现AOP。
这样设置的原因是:避免在没有实现接口时报错(无法使用JDK动态代理)
可以通过在配置文件中输入如下命令关闭cglib
spring.aop.proxy-target-class=false
参考文档
分析cglib动态代理的实现