一.前言

  虽然平时日常开发很少用到动态代理,但是动态代理在底层框架等有着非常重要的意义。比如Spring AOP使用cglib和JDK动态代理,Hibernate底层使用了javassit和cglib动态代理,Dubbo使用javassist字节码(具体可以看Dubbo SPI)。

  本文主要介绍什么是动态代理及原理,下文将介绍Spring AOP

  我们先思考一个问题:如何统计一个类各个方法的执行时间?可能你心里有好多答案都可以解决问题。

  那么如果是这个项目的多个不同类呢?可能心里也有答案,但是代码改动量不少。那么有什么其他的方法么?

  这时候动态代理就出来了,它可以灵活的在方法、代码点上切入我们想要实现的逻辑。如图示:

二.体验动态代理

2.1 JDK动态代理

  在Java的java.lang.reflect包下提供了Proxy类和InvocationHandler接口,通过使用这两个类可以生成JDK动态代理类或者JDK动态代理对象

  JDK动态代理只能针对实现了接口的类进行拓展,我们还是就上面的问题来结局。所以这里我们先创建一个接口,叫Developer(开发),里面有个方法是develop方法。有RookieDeveloper(新生开发)、PrimaryDeveloper(初级开发)和AdvancedDeveloper(高级开发)实现其接口。类图如下:

  我现在对AdvancedDeveloper进行动态代理,先来看一下AdvancedDeveloper的代码:

  接下来请看如何使用Proxy和InvocationHandler生成动态代理的:

 

  运行结果如下:

2.2 CGLib动态代理

  CGLIB代理的核心是net.sf.cglib.proxy.Enhancer类。我们可以将自定义的net.sf.cglib.proxy.MethodInterceptor实现类来得到强大的代理。代理的所有方法调用都会被分派给net.sf.cglib.proxy.MethodInterceptor的intercept方法。intercept方法然后调用底层对象。

  我们看一下Cglib动态代理的例子,先看下PrimaryDeveloper类:

  再看下CGLib动态代理测试类:

  简而言之,proxy.invoke方法调用的对象不是代理后的子类,proxy.invokeSuper方法调用的对象是代理后的子类(已增强),所以会再走一遍 MyMethodInterceptor的 interceptor方法,如果是个拦截器链条,就会重新在走一次拦截器链;最后看一下执行结果:

三.动态代理原理

3.1 JDK动态代理

  我们首先看一下java.lang.reflect.Proxy#newProxyInstance这个方法:

  这里还有一个关键点,在java.lang.reflect.Proxy.ProxyClassFactory#apply方法里,有一段代码生产对应的class字节码文件:

  简单总结一下上面的代码:

  1. 生成一个实现interfaces所有接口且继承Proxy类的代理类
  2. 使用Proxy(InvocationHandler h)构造一个代理类实例
  3. 传入我们定义的InvocationHandler(例子中是匿名内部类),构造器实例化了代理对象

  最后我们看一下生成的类的代码,我使用的是Bytecode Viewer,github地址:https://github.com/Konloch/bytecode-viewer。我们用debug evaluate获取到代理类的class文件,然后用Bytecode Viewer瞅瞅是啥样子:

  生产类的代码出来啦,继承Proxy类,实现Developer接口,调用所有方法都转换成了实际调用InvocationHandler接口的invoke方法:

3.2 CGLib动态代理

  我们从生成的动态代理类长啥样开始研究。上面的例子,添加System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/miaojiaxing/Downloads");后运行会生成几个.class文件:

  • PrimaryDeveloper$$EnhancerByCGLIB$$bda772e0:CGLib生成的代理类
  • PrimaryDeveloper$$EnhancerByCGLIB$$bda772e0$$FastClassByCGLIB$$aeaf1210:代理类的FastClass
  • PrimaryDeveloper$$FastClassByCGLIB$$de1a7774:被代理类的FastClass(有点绕口)

  首先用反编译工具查看一下PrimaryDeveloper$$EnhancerByCGLIB$$bda772e0这个类的代码:

 package com.mjx.java.proxy;

 import java.lang.reflect.Method;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy; public class PrimaryDeveloper$$EnhancerByCGLIB$$bda772e0 extends PrimaryDeveloper 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;
private MethodInterceptor CGLIB$CALLBACK_0;
private static Object CGLIB$CALLBACK_FILTER; // 代理类会获得所有在父类继承来的方法,并且会有MethodProxy与之对应
private static final Method CGLIB$laugh$0$Method;
private static final MethodProxy CGLIB$laugh$0$Proxy;
private static final Object[] CGLIB$emptyArgs; // 代理类会获得所有在父类继承来的方法,并且会有MethodProxy与之对应
private static final Method CGLIB$develop$1$Method;
private static final MethodProxy CGLIB$develop$1$Proxy; private static final Method CGLIB$say$2$Method;
private static final MethodProxy CGLIB$say$2$Proxy;
private static final Method CGLIB$equals$3$Method;
private static final MethodProxy CGLIB$equals$3$Proxy;
private static final Method CGLIB$toString$4$Method;
private static final MethodProxy CGLIB$toString$4$Proxy;
private static final Method CGLIB$hashCode$5$Method;
private static final MethodProxy CGLIB$hashCode$5$Proxy;
private static final Method CGLIB$clone$6$Method;
private static final MethodProxy CGLIB$clone$6$Proxy; static void CGLIB$STATICHOOK1() {
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
Class var0 = Class.forName("com.mjx.java.proxy.PrimaryDeveloper$$EnhancerByCGLIB$$bda772e0");
Class var1;
Method[] var10000 = ReflectUtils.findMethods(new String[]{"laugh", "()V", "develop", "()V", "say", "()V"}, (var1 = Class.forName("com.mjx.java.proxy.PrimaryDeveloper")).getDeclaredMethods());
CGLIB$laugh$0$Method = var10000[0];
CGLIB$laugh$0$Proxy = MethodProxy.create(var1, var0, "()V", "laugh", "CGLIB$laugh$0");
CGLIB$develop$1$Method = var10000[1];
CGLIB$develop$1$Proxy = MethodProxy.create(var1, var0, "()V", "develop", "CGLIB$develop$1");
CGLIB$say$2$Method = var10000[2];
CGLIB$say$2$Proxy = MethodProxy.create(var1, var0, "()V", "say", "CGLIB$say$2");
var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
CGLIB$equals$3$Method = var10000[0];
CGLIB$equals$3$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$3");
CGLIB$toString$4$Method = var10000[1];
CGLIB$toString$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$4");
CGLIB$hashCode$5$Method = var10000[2];
CGLIB$hashCode$5$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$5");
CGLIB$clone$6$Method = var10000[3];
CGLIB$clone$6$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$6");
} final void CGLIB$laugh$0() {
super.laugh();
} public final void laugh() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
} if (var10000 != null) {
// 拦截器
var10000.intercept(this, CGLIB$laugh$0$Method, CGLIB$emptyArgs, CGLIB$laugh$0$Proxy);
} else {
super.laugh();
}
} // methodProxy.invokeSuper会调用
final void CGLIB$develop$1() throws Exception {
super.develop();
} public final void develop() throws Exception {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
} // 拦截
if (var10000 != null) {
var10000.intercept(this, CGLIB$develop$1$Method, CGLIB$emptyArgs, CGLIB$develop$1$Proxy);
} else {
super.develop();
}
}

  我们可以看到代理类会获得所有在父类继承来的方法,并且会有MethodProxy与之对应,这个非常关键,上面已经用红色标注。

3.2.1 MethodProxy

  CGLIB$develop$1$Proxy = MethodProxy.create(var1, var0, "()V", "develop", "CGLIB$develop$1");

  我们先看下创建MethodProxy:

 public class MethodProxy {
private Signature sig1;
private Signature sig2;
private CreateInfo createInfo; private final Object initLock = new Object();
private volatile FastClassInfo fastClassInfo; /**
* For internal use by {@link Enhancer} only; see the {@link net.sf.cglib.reflect.FastMethod} class
* for similar functionality.
*/
// c1:被代理对象Class
// c2:代理对象Class
// desc:入参类型
// name1:被代理方法名
// 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);
proxy.createInfo = new CreateInfo(c1, c2);
return proxy;
} private static class CreateInfo
{
Class c1;
Class c2;
NamingPolicy namingPolicy;
GeneratorStrategy strategy;
boolean attemptLoad; public CreateInfo(Class c1, Class c2)
{
this.c1 = c1;
this.c2 = c2;
AbstractClassGenerator fromEnhancer = AbstractClassGenerator.getCurrent();
if (fromEnhancer != null) {
namingPolicy = fromEnhancer.getNamingPolicy();
strategy = fromEnhancer.getStrategy();
attemptLoad = fromEnhancer.getAttemptLoad();
}
}
}
}

  创建代理之后,执行方法这里走到红色的代码:var10000.intercept(this, CGLIB$develop$1$Method, CGLIB$emptyArgs, CGLIB$develop$1$Proxy);走到了我们写的CglibInterceptor类的intercept方法,里面调用了proxy.invokeSuper(obj,args);

 public Object invokeSuper(Object obj, Object[] args) throws Throwable {
try {
init();
FastClassInfo fci = fastClassInfo;
// 这里的f2就是PrimaryDeveloper$$EnhancerByCGLIB$$bda772e0$$FastClassByCGLIB$$aeaf1210
return fci.f2.invoke(fci.i2, obj, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
} private static class FastClassInfo{
FastClass f1;//被代理类FastClass,这里就是PrimaryDeveloper$$FastClassByCGLIB$$de1a7774
FastClass f2;//代理类FastClass,这里就是PrimaryDeveloper$$EnhancerByCGLIB$$bda772e0$$FastClassByCGLIB$$aeaf1210
int i1;//被代理方法index
int i2;//代理方法index

3.2.1 FastClass机制  

  Cglib动态代理执行代理方法效率之所以比JDK的高是因为Cglib采用了FastClass机制,它为代理类和被代理类各生成一个Class(就是上面的f1和f2),它会为代理类或被代理类的方法分配一个index(int类型)。这个index是用签名的hashCode来计算出来的Index(下面代码有),FastClass就可以直接定位要调用的方法直接进行调用,那么就省去了反射,所以调用效率比JDK动态代理通过反射调用高。f2我们已经知道是PrimaryDeveloper$$EnhancerByCGLIB$$bda772e0$$FastClassByCGLIB$$aeaf1210,下面我们反编译一下f2看看:

  继续看下f2的invoke方法,直接调用PrimaryDeveloper$$EnhancerByCGLIB$$bda772e0的方法,无需反射。

  最后我们整理一下调用过程,这样比较清晰了吧,如图:

四.总结

JDK动态代理:

  • 首先这个类要实现了某接口
  • 其核心就是克隆interfaces的所有接口,继承Proxy类,生成一个心类作为代理类,这个类里有我们定义的实现InvocationHandler接口的类进行代理逻辑处理

CGLib动态代理:

  • JDK动态代理有个重大缺陷,必须要实现接口才可以使用,而CGLib动态代理只要有个类就行,动态生成子类。如果是private方法,final方法等描述的方法是不能被代理的
  • Cglib动态代理执行代理方法效率之所以比JDK的高是因为Cglib采用了FastClass机制

从动态代理到Spring AOP(上)的更多相关文章

  1. java:struts框架2(方法的动态和静态调用,获取Servlet API三种方式(推荐IOC(控制反转)),拦截器,静态代理和动态代理(Spring AOP))

    1.方法的静态和动态调用: struts.xml: <?xml version="1.0" encoding="UTF-8"?> <!DOCT ...

  2. java中代理,静态代理,动态代理以及spring aop代理方式,实现原理统一汇总

    若代理类在程序运行前就已经存在,那么这种代理方式被成为 静态代理 ,这种情况下的代理类通常都是我们在Java代码中定义的. 通常情况下, 静态代理中的代理类和委托类会实现同一接口或是派生自相同的父类. ...

  3. 从动态代理到Spring AOP(中)

    一.前言 上一章节主要介绍了JDK动态代理和CGLIB动态代理:https://www.cnblogs.com/GrimMjx/p/11194283.html 这一章主要结合我们之前学习的动态代理的基 ...

  4. 反射实现 AOP 动态代理模式(Spring AOP 的实现原理)

    枚举 在某些情况下,一个类的对象是有限而且固定的,比如季节类,它只有4个对象.这种实例有限而且固定的类,在Java里被称为枚举类. 枚举就是要让某个类型的变量的取值只能为若干个固定值中的一个,否则,编 ...

  5. 反射实现 AOP 动态代理模式(Spring AOP 的实现 原理)

    好长时间没有用过Spring了. 突然拿起书.我都发现自己对AOP都不熟悉了. 其实AOP的意思就是面向切面编程. OO注重的是我们解决问题的方法(封装成Method),而AOP注重的是许多解决解决问 ...

  6. 通过JDK动态代理实现 Spring AOP

    1.新建一个目标类 接口:public interface IUserService //切面编程 public void addUser(); public void updateUser( ); ...

  7. JDK动态代理给Spring事务埋下的坑!

    一.场景分析 最近做项目遇到了一个很奇怪的问题,大致的业务场景是这样的:我们首先设定两个事务,事务parent和事务child,在Controller里边同时调用这两个方法,示例代码如下: 1.场景A ...

  8. (转)面试必备技能:JDK动态代理给Spring事务埋下的坑!

    一.场景分析 最近做项目遇到了一个很奇怪的问题,大致的业务场景是这样的:我们首先设定两个事务,事务parent和事务child,在Controller里边同时调用这两个方法,示例代码如下: 1.场景A ...

  9. aop学习总结二------使用cglib动态代理简单实现aop功能

    aop学习总结二------使用cglib动态代理简单实现aop功能 模拟业务需求: 1.拦截所有业务方法 2.判断用户是否有权限,有权限就允许用户执行业务方法,无权限不允许用户执行业务方法 (判断是 ...

随机推荐

  1. git 笔记-初始化

    进入项目所在的文件夹 git init git remote add origin https://github.com/enderlu/test.gitgit push -u origin mast ...

  2. Win10《芒果TV - Preview》更新v3.1.31.0,全新播放页蜕变,预加载提速技术

    Win10<芒果TV - Preview>(商店内测版) v3.1.31.0 于2016年11月21日星期一晚上九点半登陆商店 主要是全面升级改造桌面播放页,新增观看互动评论.猜你喜欢功能 ...

  3. ARTS 12.24 - 12.28

    从陈皓博主的专栏里学到一个概念,争取可以坚持下去: 每周一个 Algorithm,Review 一篇英文文章,总结一个工作中的技术 Tip,以及 Share 一个传递价值观的东西! 一个 Algori ...

  4. 领域驱动设计(DDD)的实践经验分享之持久化透明

    原文:领域驱动设计(DDD)的实践经验分享之持久化透明 前一篇文章中,我谈到了领域驱动设计中,关于ORM工具该如何使用的问题.谈了很多我心里的想法,大家也对我的观点做了一些回复,或多或少让我深深感觉到 ...

  5. Dependency Injection 筆記 (1)

    <.NET 依賴注入>連載 (1) 本文从一个基本的问题开始,点出软件需求变动的常态,以说明为什么我们需要学习「依赖注入」(dependency injection:简称 DI)来改善设计 ...

  6. winpcap在VS2012 Qt5 X64下的配置

    最近在学网络编程,想在windows下用Qt做个网络抓包工具,就要用到WinPcap,而我的电脑的系统是Win7 64位,qt版本是Qt 5.3.1 for Windows 64-bit (VS 20 ...

  7. 层次关系表格,不用递归,快速检索。HierarchyId

    最近这几天写了个T4自动实现EF code first和Ado的存储过程.使用过程中发现了一个Sql的类型为HierarchyId.看到时真是百思不得齐姐.算了查一下MSDN吧.从微软官网找到了Hie ...

  8. MongoDB自学日记3——架构及HA

    在对mongoDB的操作有了一定基础后,终于可以扯扯HA和架构这两个高大上的概念了.在这之前当然还得弄清楚mongoDB的Key feature:Sharding. 1. Sharding Shard ...

  9. Terminator快捷键

    窗口相关 窗口开关 上下开新窗口   Ctrl+Shift+O垂直开新窗口   Ctrl+Shift+E关闭当前窗口   Ctrl+Shift+W 改变当前激活窗口 逆时针改变当前窗口 Ctrl+Sh ...

  10. http 报错码对应的错误原因

    转:http://blog.csdn.net/cutbug/article/details/4024818 1xx - 信息提示这些状态代码表示临时的响应.客户端在收到常规响应之前,应准备接收一个或多 ...