动态代理及其实现原理
代理模式概述
代理模式是一种比较好的理解的设计模式。简单来说就是我们使用代理对象来代替对真实对象的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能(开闭原则: 对扩展开放,对修改关闭)。
代理模式的主要作用是扩展目标对象的功能,比如说在目标对象的某个方法执行前后你可以增加一些自定义的操作。
代理模式有静态代理和动态代理两种实现方式。
静态代理
所谓的静态代理就是在代码运行之前,代理类就已经存在,通常情况下, 静态代理中的代理类和委托类会实现同一接口或是派生自相同的父类。
- 委托类 即指的是代理模式中的被代理对象
- 代理类 指的是生成的代表委托类的一个角色
静态代理中,我们对目标对象的每个方法的增强都是手动完成的,非常不灵活(比如接口一旦新增加方法,目标对象和代理对象都要进行修改)且麻烦(需要对每个目标类都单独写一个代理类)。 实际应用场景非常非常少,日常开发几乎看不到使用静态代理的场景。
从 JVM 层面来说, 静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。
动态代理
代理类在程序运行时创建的代理方式被成为动态代理,也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指令”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。
相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,我们可以直接代理实现类( CGLIB 动态代理机制)。
动态代理其实是一种方便运行时候动态的处理代理方法的调用机制,通过代理可以让调用者和实现者之间解耦。
从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
动态代理实现方式:
- 静态代理,工程师编辑代理类代码,实现代理模式;在编译期就生成了代理类。
- 基于 JDK 实现动态代理,通过jdk提供的工具方法Proxy.newProxyInstance动态构建全新的代理类(继承Proxy类,并持有InvocationHandler接口引用 )字节码文件并实例化对象返回。(jdk动态代理是由java内部的反射机制来实例化代理对象,并代理的调用委托类方法)
- 基于CGlib 动态代理模式 基于继承被代理类生成代理子类,不用实现接口。只需要被代理类是非final 类即可。(cglib动态代理底层是借助asm字节码技术
- 基于 Aspectj 实现动态代理(修改目标类的字节,织入代理的字节码,在程序编译的时候 插入动态代理的字节码,不会生成全新的Class )
- 基于 instrumentation 实现动态代理(修改目标类的字节码、类装载的时候动态拦截去修改,基于javaagent)
-javaagent:spring-instrument-4.3.8.RELEASE.jar
(类装载的时候 插入动态代理的字节码,不会生成全新的Class )
JDK动态代理
JDK动态代理实现机制
java的java.lang.reflect包下提供了Proxy类和一个 InvocationHandler 接口,这个类Proxy定义了生成JDK动态代理类的方法
getProxyClass(ClassLoader loader,Class<?>... interfaces)
生成动态代理类,返回class实例代表一个class文件。
首先来看看他的具体实现机制主要两个类:
-
InvocationHandler
-
Proxy
根据注解描述可知,InvocationHandler作用就是,当代理对象的原本方法被调用的时候,会绑定执行一个方法,这个方法就是InvocationHandler里面定义的内容,同时会替代原本方法的结果返回。
InvocationHandler接口,它就只有一个方法invoke。
每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler
这个接口的invoke
方法来进行调用,该方法一共有三个参数:
- proxy - 在其上调用方法的代理实例也就是代理的真实对象
- method - 指的是我们所要调用真实对象的某个方法的Method对象
- args - 指的是调用真实对象某个方法时接受的参数
// JDK 动态代理
public Object getProxyInstance() {
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
System.out.println("JDK代理开始~~");
//反射机制调用目标对象的方法
Object returnVal = method.invoke(target, args);
System.out.println("JDK代理提交");
return returnVal;
}
});
}
其实大概就是把接口复制出来,通过这些接口和类加载器,拿到这个代理类cl
。然后通过反射的技术复制拿到代理类的构造函数(这部分代码在Class类中的getConstructor0
方法),最后通过这个构造函数new个一对象出来,同时用InvocationHandler绑定这个对象。
public class Proxy implements java.io.Serializable {
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
ClaCss<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
// 获取系统安全管理器 是否有创建代理类的权限
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
// 生成代理类
Class<?> cl = getProxyClass0(loader, intfs);
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
// 调用代理的构造方法
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
}
进去getProxyClass0可以看到代理类从proxyClassCache缓存中获取,代码如下:
/**
* a cache of proxy classes
*/
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()
;
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
其中最重要的就是ProxyGenerator.generateProxyClass这个生成代理类字节码。(通过ProxyGenerator.generateProxyClass来手动生成一个代码类)
$Proxy0.class
$Proxy0.class是内存中生成代理类的源码。
JDK动态代理主要是基于反射,使用反射解析目标对象的属性、方法等
根据解析的内容生成proxy.class,说白了就是把要生成的class按照字符串的形式拼接,最终通过ClassLoader加载。
JDK动态代理只能代理接口:已经继承了Proxy类,可变部分只有implements
每个生成的动态代理实例都会关联一个调用处理器对象,可以通过 Proxy 提供的静态方法 getInvocationHandler
去获得代理类实例的调用处理器对象。⭐️在代理类实例上调用其代理的接口中所声明的方法时,这些方法最终都会由调用处理器的 invoke 方法执行
代理类的根类 java.lang.Object 中有三个方法也同样会被分派到调用处理器的 invoke 方法执行,它们是 hashCode,equals 和 toString
小结
一个典型的基于JDK动态代理创建对象过程可分为以下四个步骤:
-
通过实现InvocationHandler接口创建自己的调用处理器 InvocationHandler handler = new InvocationHandlerImpl(…);
-
通过为Proxy类指定ClassLoader对象和一组代理类需要实现的接口,创建动态代理类类文件,默认JDK并不会保存这个文件到文件中;可以这样观察生成并保存起来的代理类结构:Class clazz = Proxy.getProxyClass(classLoader,new Class[]{…});
-
通过上面新建的代理clazz的反射机制获取动态代理类的一个构造函数,其构造函数入参类型是调用处理器接口
IvocationHandler
类型Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class}); -
通过构造函数实例创建代理类实例,此时需将调用处理器对象作为参数被传入 Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler)); 为了简化对象创建过程,Proxy类中的newInstance工具方法封装了2~4,只需两步即可完成代理对象的创建。
其实大概就是通过接口和类加载器拿到这个代理类,·然后通过反射的技术复制拿到代理类的构造函数(这部分代码在Class类中的
getConstructor0
方法),最后通过这个构造函数new个一对象出来,同时用InvocationHandler绑定这个对象。
JDK动态代理特点总结
- 生成的代理类:$Proxy0 extends Proxy implements Person,我们看到代理类继承了Proxy类,所以也就决定了java动态代理只能对接口进行代理。
- 每个生成的动态代理实例都会关联一个调用处理器对象,可以通过 Proxy 提供的静态方法 getInvocationHandler 去获得代理类实例的调用处理器对象。在代理类实例上调用其代理的接口中所声明的方法时,这些方法最终都会由调用处理器的 invoke 方法执行
- 代理类的根类 java.lang.Object 中有三个方法也同样会被分派到调用处理器的 invoke 方法执行,它们是 hashCode,equals 和 toString,可能的原因有:一是因为这些方法为 public 且非 final 类型,能够被代理类覆盖; 二是因为这些方法往往呈现出一个类的某种特征属性,具有一定的区分度,所以为了保证代理类与委托类对外的一致性,这三个方法也应该被调用处理器分派到委托类执行。
JDK动态代理不足
- JDK动态代理的代理类字节码在创建时,需要实现业务实现类所实现的接口作为参数。如果业务实现类是没有实现接口而是直接定义业务方法的话,就无法使用JDK动态代理了。(JDK动态代理重要特点是代理接口) 并且,如果业务实现类中新增了接口中没有的方法,这些方法是无法被代理的(因为无法被调用)。
- 动态代理只能对接口产生代理,不能对类产生代理
CGLIB动态代理
JDK 动态代理有一个最致命的问题是只能代理实现了接口的类。
CGLIB是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。很多知名的开源框架都使用到了CGLIB, 例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。
CGLIB是针对类来实现代理的,他的原理是对代理的目标类生成一个子类,并覆盖其中方法实现增强,因为底层是基于创建被代理类的一个子类,所以它避免了JDK动态代理类的缺陷。
// cglib 动态代理
public class ProxyFactory implements MethodInterceptor {
//维护一个目标对象
private Object target;
//构造器,传入一个被代理的对象
public ProxyFactory(Object target) {
this.target = target;
}
//返回一个代理对象: 是 target 对象的代理对象
public Object getProxyInstance() {
//1. 创建一个工具类
Enhancer enhancer = new Enhancer();
//2. 设置父类
enhancer.setSuperclass(target.getClass());
//3. 设置回调函数
enhancer.setCallback(this);
//4. 创建子类对象,即代理对象
return enhancer.create();
}
//重写 intercept 方法,会调用目标对象的方法
@Override
public Object intercept(Object arg0, Method method, Object[] args, MethodProxy arg3) throws Throwable {
// TODO Auto-generated method stub
System.out.println("Cglib代理模式 ~~ 开始");
Object returnVal = method.invoke(target, args);
System.out.println("Cglib代理模式 ~~ 提交");
return returnVal;
}
}
通过“继承”可以继承父类所有的公开方法,然后可以重写这些方法,在重写时对这些方法增强,这就是CGLIB的思想。根据里氏代换原则(LSP 子类可以扩展父类的功能,但不能改变父类原有的功能),所以CGLIB实现的代理也是可以被正常使用的。
但因为采用的是继承,所以不能对final修饰的类进行代理,final修饰的类不可继承。
- CGlib可以传入接口也可以传入普通的类,接口使用实现的方式,普通类使用会使用继承的方式生成代理类.
- 由于是继承方式,如果是 static方法,private方法,final方法等描述的方法是不能被代理的
- 做了方法访问优化,使用建立方法索引的方式避免了传统JDK动态代理需要通过Method方法反射调用.
- 提供callback 和filter设计,可以灵活地给不同的方法绑定不同的callback。编码更方便灵活。
- CGLIB会默认代理Object中equals,toString,hashCode,clone等方法。比JDK代理多了clone。
AOP中的动态代理
AOP的源码中用到了两种动态代理来实现拦截切入功能:jdk动态代理和cglib动态代理。两种方法同时存在,各有优劣。 jdk动态代理是由java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的。 总的来说,反射机制在生成类的过程中比较高效,执行时候通过反射调用委托类接口方法比较慢;而asm在生成类之后的相关代理类执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。
还有一点必须注意:jdk动态代理的应用前提,必须是委托类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。 由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。
参考
既已览卷至此,何不品评一二: