简介

为什么会有动态代理?

举个例子,当前有一个用户操作类,要求每个方法执行前打印访问日志。

这里可以采用两种方式:

第一种,静态代理。即通过继承原有类来对方法进行扩展。

当然,这种方式可以实现需求,但是当类的方法很多时,我们需要逐个添加打印日志的代码,非常繁琐。此时,如果要求加入权限校验,这个时候又需要再创建一个代理类。

第二种,动态代理。即通过拦截器的方式来对方法进行扩展。

动态代理只需要重写拦截器的一个方法,相比静态代理,可以减少很多代码。而且,动态代理要实现不同的代理类,只要选择不同的拦截器就可以了(可以选择多个),代理类不需要我们自己实现,可以有效实现代码解耦和可重用。

不限于以上优点,动态代理被广泛应用于日志记录、性能统计、安全控制、事务处理、异常处理等等,是spring实现AOP的重要支持。

常见的动态代理有哪些?

常用的动态代理有:JDK动态代理、cglib。

感兴趣的可以研究下aspectJ

什么是cglib

cglib基于asm字节码生成框架,用于动态生成代理类。与JDK动态代理不同,有以下几点不同:

  1. JDK动态代理要求被代理类实现某个接口,而cglib无该要求。

  2. JDK动态代理生成的代理类是该接口实现类,也就是说,不能代理接口中没有的方法,而cglib生成的代理类继承被代理类。

  3. 在字节码的生成和类的创建上,JDK的动态代理效率更高。

  4. 在代理方法的执行效率上,由于采用了FastClass,cglib的效率更高(以空间换时间)。

注:因为JDK动态代理中代理类中的方法是通过反射调用的,而cglib因为引入了FastClass,可以直接调用代理类对象的方法。

使用例子

需求

模拟对用户数据进行增删改前打印访问日志

工程环境

JDK:1.8

maven:3.6.1

IDE:STS4

主要步骤

  1. 创建Enhancer对象:Enhancer是cglib代理的对外接口,以下操作都是调用这个类的方法
  2. setSuperclass(Class superclass):代理谁?
  3. setCallback(final Callback callback):怎么代理?(我们需要实现Callback的子接口MethodInterceptor,重写其中的intercept方法,该方法定义了代理规则)
  4. create():获得代理类
  5. 使用代理类

创建项目

项目类型Maven Project,打包方式jar

引入依赖

    <!-- cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>

编写被代理类

包路径:cn.zzs.cglib

这里只是简单地测试,不再引入复杂的业务逻辑。

public class UserController {
public void save() {
System.out.println("增加用户");
}
public void delete() {
System.out.println("删除用户");
}
public void update() {
System.out.println("修改用户");
}
public void find() {
System.out.println("查找用户");
}
}

编写MethodInterceptor接口实现类

包路径:cn.zzs.cglib

public class LogInterceptor implements MethodInterceptor {

    @Override
public Object intercept( Object obj, Method method, Object[] args, MethodProxy proxy ) throws Throwable {
// 设置需要代理拦截的方法
HashSet<String> set = new HashSet<String>( 6 );
set.add( "save" );
set.add( "delete" );
set.add( "update" );
// 进行日志记录
if( method != null && set.contains( method.getName() ) ) {
System.out.println( "进行" + method.getName() + "的日志记录" );
}
// 执行被代理类的方法
Object obj2 = proxy.invokeSuper( obj, args );
return obj2;
}
}

编写测试类

这里的输出代理类的class文件,方便后面分析。

包路径:test下的cn.zzs.cglib

public class CglibTest {
@Test
public void test01() {
// 设置输出代理类到指定路径
System.setProperty( DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:/growUp/test" );
// 创建Enhancer对象,用于生成代理类
Enhancer enhancer = new Enhancer();
// 设置哪个类需要代理
enhancer.setSuperclass( UserController.class );
// 设置怎么代理,这里传入的是Callback对象-MethodInterceptor父类
LogInterceptor logInterceptor = new LogInterceptor();
enhancer.setCallback( logInterceptor );
// 获取代理类实例
UserController userController = ( UserController )enhancer.create();
// 测试代理类
System.out.println( "-------------" );
userController.save();
System.out.println( "-------------" );
userController.delete();
System.out.println( "-------------" );
userController.update();
System.out.println( "-------------" );
userController.find();
}
}

运行结果

CGLIB debugging enabled, writing to 'D:/growUp/test'
-------------
进行save的日志记录
增加用户
-------------
进行delete的日志记录
删除用户
-------------
进行update的日志记录
修改用户
-------------
查找用户

源码分析-获得代理类的过程

主要步骤

这里先简单说下过程:

  1. 根据当前Enhancer实例生成一个唯一标识key

  2. 用key去缓存中找代理类的Class实例

  3. 找到了就返回代理类实例

  4. 找不到就生成后放入map,再返回代理类实例

获得key

接下来具体介绍下。

首先,一进来就先调用了createHelper()

    public Object create() {
classOnly = false;
argumentTypes = null;
return createHelper();
}

createHelper()中,创建了key,这个用于唯一标识当前类及相关配置,用于在缓存中存取代理类的Class实例。接着调用父类AbstractClassGeneratorcreate(Object key)方法获取代理类实例。

    private Object createHelper() {
preValidate();
Object key = KEY_FACTORY.newInstance((superclass != null) ? superclass.getName() : null,
ReflectUtils.getNames(interfaces),
filter == ALL_ZERO ? null : new WeakCacheKey<CallbackFilter>(filter),
callbackTypes,
useFactory,
interceptDuringConstruction,
serialVersionUID);
this.currentKey = key;
//获取代理类实例
Object result = super.create(key);
return result;
}

create(Object key)中,会调用内部类ClassLoaderDataget(AbstractClassGenerator gen, boolean useCache)方法获取代理类的Class实例。

    protected Object create(Object key) {
try {
ClassLoader loader = getClassLoader();
Map<ClassLoader, ClassLoaderData> cache = CACHE;
ClassLoaderData data = cache.get(loader);
if (data == null) {
synchronized (AbstractClassGenerator.class) {
cache = CACHE;
data = cache.get(loader);
if (data == null) {
Map<ClassLoader, ClassLoaderData> newCache = new WeakHashMap<ClassLoader, ClassLoaderData>(cache);
data = new ClassLoaderData(loader);
newCache.put(loader, data);
CACHE = newCache;
}
}
}
this.key = key;
//获取代理类的Class类实例
Object obj = data.get(this, getUseCache());
//获取代理类实例
if (obj instanceof Class) {
return firstInstance((Class) obj);
}
return nextInstance(obj);
} catch (RuntimeException e) {
throw e;
} catch (Error e) {
throw e;
} catch (Exception e) {
throw new CodeGenerationException(e);
}
}

利用key从缓存中获取Class

ClassLoaderData有一个重要字段generatedClasses,是一个LoadingCache缓存对象,存放着当前类加载器加载的代理类的Class类实例。在以下方法中,就是从它里面寻找,通过key匹配查找。

        //这个对象存放着当前类加载器加载的代理类的Class类实例
private final LoadingCache<AbstractClassGenerator, Object, Object> generatedClasses;
public Object get(AbstractClassGenerator gen, boolean useCache) {
if (!useCache) {
return gen.generate(ClassLoaderData.this);
} else {
//获取代理类的Class类实例
Object cachedValue = generatedClasses.get(gen);
return gen.unwrapCachedValue(cachedValue);
}
}

下面重点看下LoadingCache这个类,需要重点理解三个字段的意思:

//K:AbstractClassGenerator 这里指Enhancer类
//KK:Object 这里指前面生成key的类
//V:Object 这里指代理类的Class类
public class LoadingCache<K, KK, V> {
//通过key可以拿到代理类的Class实例
protected final ConcurrentMap<KK, Object> map;
//通过loader.apply(Enhancer实例)可以获得代理类的Class实例
protected final Function<K, V> loader;
//通过keyMapper.apply(Enhancer实例)可以获得key
protected final Function<K, KK> keyMapper;
·······
}

这里通过key去map里找代理类的Class实例,如果找不到,会重新生成后放入map中。

    public V get(K key) {
final KK cacheKey = keyMapper.apply(key);
Object v = map.get(cacheKey);
if (v != null && !(v instanceof FutureTask)) {
return (V) v;
} return createEntry(key, cacheKey, v);
}

生成代理类Class

以上基本说完如何从缓存中拿到代理类实例的方法,接下来简单看下生成代理类的过程,即loader.apply(Enhancer实例),里面的generate会生成所需的Class对象,比较复杂,后面有时间再研究吧。

    public Object apply(AbstractClassGenerator gen) {
Class klass = gen.generate(ClassLoaderData.this);
return gen.wrapCachedClass(klass);
}

代理类代码分析

cglib生成文件

在一开始指定的路径下,可以看到生成了三个文件,前面简介里说到在代理类的生成上,cglib的效率低于JDK动态代理,主要原因在于多生成了两个FastClass文件,至于这两个文件有什么用呢?接下来会重点分析:

代理类源码

本文采用Luyten作为反编译工具,一开始用jd-gui解析,但错误太多。

下面看看代理类的源码。

在初始化时,代理类的字段都会被初始化,这里涉及到MethodProxycreate方法。

在实际调用update方法是会调用MethodInterceptor对象的intercept方法,执行我们自定义的代码后,最终会调用的是MethodProxyinvokeSuper方法。下面重点看看这些方法。

注:考虑篇幅问题,这里仅展示update方法。

//生成类的名字规则是:被代理classname + "$$"+classgeneratorname+"ByCGLIB"+"$$"+key的hashcode
public class UserController$$EnhancerByCGLIB$$e6f193aa extends UserController implements Factory {
private boolean CGLIB$BOUND;
public static Object CGLIB$FACTORY_DATA;
private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
private static final Callback[] CGLIB$STATIC_CALLBACKS; //我们一开始传入的MethodInterceptor对象
private MethodInterceptor CGLIB$CALLBACK_0;
private static Object CGLIB$CALLBACK_FILTER;
//被代理类update方法
private static final Method CGLIB$update$0$Method;
//代理类update方法
private static final MethodProxy CGLIB$update$0$Proxy;
private static final Object[] CGLIB$emptyArgs; static void CGLIB$STATICHOOK1() {
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
//代理类Class对象
final Class<?> forName = Class.forName("cn.zzs.cglib.UserController$$EnhancerByCGLIB$$e6f193aa");
//被代理类Class对象
final Class<?> forName2;
final Method[] methods = ReflectUtils.findMethods(new String[] { "update", "()V", "find", "()V", "delete", "()V", "save", "()V" },
(forName2 = Class.forName("cn.zzs.cglib.UserController")).getDeclaredMethods());
//初始化被代理类update方法
CGLIB$update$0$Method = methods[0];
//初始化代理类update方法
CGLIB$update$0$Proxy = MethodProxy.create((Class)forName2, (Class)forName, "()V", "update", "CGLIB$update$0");
} final void CGLIB$update$0() {
super.update();
} public final void update() {
MethodInterceptor cglib$CALLBACK_2;
MethodInterceptor cglib$CALLBACK_0;
if ((cglib$CALLBACK_0 = (cglib$CALLBACK_2 = this.CGLIB$CALLBACK_0)) == null) {
CGLIB$BIND_CALLBACKS(this);
cglib$CALLBACK_2 = (cglib$CALLBACK_0 = this.CGLIB$CALLBACK_0);
}
//一般走这里,即调用我们传入MethodInterceptor对象的intercept方法
if (cglib$CALLBACK_0 != null) {
cglib$CALLBACK_2.intercept((Object)this, UserController$$EnhancerByCGLIB$$e6f193aa.CGLIB$update$0$Method, UserController$$EnhancerByCGLIB$$e6f193aa.CGLIB$emptyArgs, UserController$$EnhancerByCGLIB$$e6f193aa.CGLIB$update$0$Proxy);
return;
}
super.update();
}

MethodProxy.create

通过以下代码可以知道,MethodProxy对象CGLIB$update$0$Proxy持有了代理类和被代理类的Class实例,以及代理方法和被代理方法的符号表示,这两个sig用于后面获取方法索引。

    //Class c1, 被代理对象
//Class c2, 代理对象
//String desc, 参数列表描述
//String name1, 被代理方法
//String name2,代理方法
public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
MethodProxy proxy = new MethodProxy();
//创建方法签名
proxy.sig1 = new Signature(name1, desc);
proxy.sig2 = new Signature(name2, desc);
//创建createInfo
proxy.createInfo = new CreateInfo(c1, c2);
return proxy;
}

MethodProxy.invokeSuper

以下方法中会去创建两个FastClass文件,也就是我们看到的另外两个文件。当然,它们只会创建一次。

另外,通过原来的方法签名获得了update的方法索引。

    //传入参数obj:代理类实例
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
try {
//初始化,创建了两个FastClass类对象,并根据原来的方法签名得到方法索引
init();
//这个对象持有两个FastClass类对象和方法的索引
FastClassInfo fci = fastClassInfo;
//调用了代理对象FastClass的invoke方法
return fci.f2.invoke(fci.i2, obj, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
private void init(){
if (fastClassInfo == null){
synchronized (initLock){
if (fastClassInfo == null){
CreateInfo ci = createInfo;
FastClassInfo fci = new FastClassInfo();
//helper方法用ASM框架去生成了两个FastClass类
fci.f1 = helper(ci, ci.c1);
fci.f2 = helper(ci, ci.c2);
fci.i1 = fci.f1.getIndex(sig1);
fci.i2 = fci.f2.getIndex(sig2);
fastClassInfo = fci;
createInfo = null;
}
}
}
}
private static class FastClassInfo{
FastClass f1;//被代理对象FastClass
FastClass f2;//代理对象FastClass
int i1;//被代理update方法的索引
int i2; //代理update方法的索引
}

FastClass.invoke

根据方法索引进行匹配,可以直接调用代理类实例的方法,而不需要像JDK动态代理一样采用反射的方式,所以在方法执行上,cglib的效率会更高。

    //传入参数:
//n:方法索引
//o:代理类实例
//array:方法输入参数
public Object invoke(final int n, final Object o, final Object[] array) throws InvocationTargetException {
final UserController$$EnhancerByCGLIB$$e6f193aa userController$$EnhancerByCGLIB$$e6f193aa = (UserController$$EnhancerByCGLIB$$e6f193aa)o;
try {
switch (n) {
case 0: {
return new Boolean(userController$$EnhancerByCGLIB$$e6f193aa.equals(array[0]));
}
case 1: {
return userController$$EnhancerByCGLIB$$e6f193aa.toString();
}
case 2: {
return new Integer(userController$$EnhancerByCGLIB$$e6f193aa.hashCode());
}
case 3: {
return userController$$EnhancerByCGLIB$$e6f193aa.clone();
}
case 4: {
//通过匹配方法索引,直接调用该方法
userController$$EnhancerByCGLIB$$e6f193aa.update();
return null;
}
······· }
catch (Throwable t) {
throw new InvocationTargetException(t);
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
}

相关源码请移步:https://github.com/ZhangZiSheng001/cglib-demo

本文为原创文章,转载请附上原文出处链接:https://www.cnblogs.com/ZhangZiSheng001/p/11917086.html

最新文章

  1. bzoj 4695: 最假女选手
  2. JS中获得当前时间的年月
  3. Ionic Lab下载地址
  4. KindEditor 编辑器使用方法
  5. Highcharts 在低版本 IE 上使用注意事项
  6. 使用Kindle4rss推送自己感兴趣的博文
  7. tp框架做留言板
  8. Intent(二)
  9. 大作业 XXX大学 课程管理系统
  10. paip.使用WORD进行拆分段落单个汉字转表格.txt
  11. 阻碍android程序员发展的几个原因
  12. STM32F407VG (三)ADC
  13. 深度解析:Android在Mms设置页面更改短信中心号码流程
  14. Nginx 反向代理 负载均衡 虚拟主机配置
  15. C语言第一次博客作业---顺序机构基础练习
  16. LeetCode之旅(16)-Climbing Stairs
  17. PHP 安装扩展 phpize
  18. react问题解决的一些方法
  19. 小tip: transition与visibility
  20. string基本字符序列容器(竞赛时常用的使用方法总结)

热门文章

  1. mybatis 使用redis实现二级缓存(spring boot)
  2. 我的第一个Python爬虫——谈心得
  3. 9、pytest -- 集成文档测试
  4. mac os 10.15 virtualBox6.0.12崩溃
  5. 在虚拟机上的关于FTP FTP访问模式(本地用户模式)
  6. Angular开发规范
  7. 安装requests遇到的坑
  8. super()函数的作用
  9. [考试反思]0829NOIP模拟测试33:仰望
  10. 大厂面试经:说一下你们线上JVM是如何优化的?