Spring 之 AOP
《参考资料》
Aspect Oriented Programming with Spring: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop
Spring AOP APIs: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-api
一、什么是 AOP ?
OOP - Object-oriented Programming 面向对象编程
AOP - Aspect-oriented Programming 面向切面编程
当需要为多个不具有继承关系的对象引入同一个公共行为时(例如日志、权限校验、事务管理、性能监控等),OOP 只能在每个对象里引用公共行为(方法),这样程序中就产生了大量的重复代码。
于是就有了 AOP,它是对 OOP 的一种补充。
AOP 通过预编译和运行时动态代理,实现在不修改源代码的情况下给程序添加统一的功能。
Spring AOP 是一种编程范式,主要目的是将非功能性需求从功能性需求中分离出来,达到解耦的目的。
代码示例
比如,下面这个类,我们想要统计每个方法的执行时间
package org.springframework.zero.study.aop.example; import org.springframework.stereotype.Service; import java.util.concurrent.TimeUnit; @Service public class RandomBusiness { public String businessA() throws InterruptedException { TimeUnit.MILLISECONDS.sleep((long) (Math.random() * 1000)); return "success"; } public String businessB() throws InterruptedException { TimeUnit.MILLISECONDS.sleep((long) (Math.random() * 1000)); return "success"; } public String businessC() throws InterruptedException { TimeUnit.MILLISECONDS.sleep((long) (Math.random() * 1000)); return "success"; } }
只需创建如下配置类
package org.springframework.zero.study.aop.example; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Slf4j @Aspect @Component public class LogAopSample { /** * 配置切入点 —— org.springframework.zero.study.aop.example 这个路径下的所有类的方法 */ @Pointcut("execution(* org.springframework.zero.study.aop.example..*(..))") public void aspect() { } /** * 配置环绕通知 * * @param joinPoint 连接点 */ @Around("aspect()") public void executionTime(ProceedingJoinPoint joinPoint) throws Throwable { long start = System.currentTimeMillis(); joinPoint.proceed(); long cost = System.currentTimeMillis() - start; log.info(joinPoint.getSignature().getDeclaringTypeName() + "#" + joinPoint.getSignature().getName() + " --- 执行耗时:" + cost); } }
编写一个注册类和测试类,验证结果
package org.springframework.zero.study.aop; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.zero.study.aop.example.LogAopSample; import org.springframework.zero.study.aop.example.RandomBusiness; @Configuration @EnableAspectJAutoProxy(proxyTargetClass = true) public class AopExampleConfig { @Bean public LogAopSample logAopSample() { return new LogAopSample(); } @Bean public RandomBusiness helloService() { return new RandomBusiness(); } }
package org.springframework.test; import org.junit.Test; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.zero.study.aop.AopExampleConfig; import org.springframework.zero.study.aop.example.RandomBusiness; public class AopTests { @Test public void annotationBasedAopTest() throws InterruptedException { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AopExampleConfig.class); RandomBusiness randomBusiness = applicationContext.getBean(RandomBusiness.class); randomBusiness.businessA(); randomBusiness.businessB(); randomBusiness.businessC(); } }
执行结果:
org.springframework.zero.study.aop.example.RandomBusiness#businessA --- 执行耗时:335 org.springframework.zero.study.aop.example.RandomBusiness#businessB --- 执行耗时:850 org.springframework.zero.study.aop.example.RandomBusiness#businessC --- 执行耗时:754
通过这个 Demo,可以想象 AOP 的应用场景有多广泛,几乎所有非业务代码都能够用它来处理,如:
事务管理
日志
权限校验
分布式锁
分布式限流
缓存管理
...
二、AOP 核心概念
在 OOP 中模块化的关键单元是类(Class),而 AOP 中模块化的关键单元是切面(Aspect)。
学习 AOP 必须牢记并理解这些概念
https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-introduction-defn
切面(Aspect)
切面是一个关注点的模块化,这个关注点横跨多个类。比如:应用程序中的事务管理就是一个横切关注点。
切面在 Spring 中的实践 | |
---|---|
注解声明式 | 用 @Aspect 标记一个类,在这个类中通过一系列配置实现 AOP |
XML 声明式 | <aop:aspect id=""></aop:aspect> |
连接点(Join point)
在 Spring AOP 中,连接点总是表示方法的执行。
org.aspectj.lang.JoinPoint
通知(Advice)
通知是指一个切面(Aspect)在特定连接点执行的动作,使用拦截器来实现。
Spring AOP 包含以下五种通知:
注解 | 通知 | 说明 |
---|---|---|
@Before | 前置通知 | 方法执行之前执行的通知 |
@AfterReturning | 后置通知 | 方法正常结束执行的通知 |
@AfterThrowing | 异常抛出后通知 | 方法抛出异常后执行的通知 |
@After | 最终通知 | 连接点结束后执行的通知,异常退出也要执行 |
@Around | 环绕通知 | 最强大的通知,可以实现其他所有通知,甚至可以连方法都不执行 |
切入点(Pointcut)
切入点是 AOP 的核心,它是匹配连接点的断言,类比正则表达式。Spring 默认使用 “AspectJ 切入点表达式”。
目标对象(Target object)
目标对象也称为通知对象,在 Spring 中该对象始终是一个代理对象,代理连接点所在的类实例。
引入(Introduction)
引入能够为目标对象附加额外的方法或字段,具体怎么做呢?
首先,将要附加的方法或字段用一个接口维护起来
然后,在切面中声明这个接口变量
最后,使用
@DeclareParents
注解标记这个接口变量,并指定接口实现类
package org.springframework.zero.study.aop.introduction; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.DeclareParents; import org.springframework.stereotype.Component; /** * 为目标对象附加额外的方法或字段. * <p> * * @author ship * @date 2021/10/25 0025 5:25 */ @Slf4j @Aspect @Component public class AdditaionalAspect { /** * 把变量(必须是接口)声明为目标对象的父亲。 * <p> * 目标对象 - 表达式匹配到的所有类 * defaultImpl - 实现类必须指定 * <p> * Note: 表达式中 + 号表示包含子类 */ @DeclareParents(value = "org.springframework.zero.study.aop.introduction.service.*+", defaultImpl = TencentServiceImpl.class) public static TencentService tencentService; }
AOP Proxy【底层】
为了实现面向切面编程而创建的对象,在 Spring 中使用 JDK 动态代理或 CGLIB 动态代理。
织入(Weaving)【底层】
将切面与切入点所在的类或对象链接,以创建通知对象。Spring 在运行时执行织入操作。
三、Spring AOP 使用详解
关于 Spring 中 AOP 的使用,直接参考官方手册:
基于 @AspectJ 使用 AOP:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-ataspectj
基于 XML 模式使用 AOP:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-schema
AspectJ Pointcut 相关文档:
https://www.eclipse.org/aspectj/doc/released/progguide/quick.html#quick-pointcuts
https://www.eclipse.org/aspectj/doc/released/progguide/quick-typePatterns.html
https://www.eclipse.org/aspectj/doc/released/progguide/semantics-pointcuts.html
四、AOP 原理
前面说了 AOP 是通过运行时动态代理实现的,先来看看什么是代理。
代理模式
为其他对象提供一种代理,以控制对这个对象的访问。
代理模式的定义超简单,可以类比生活中的
房产中介、车票代售、婚介、经纪人、...
使用代理模式的主要目的:
增强目标对象(主要是对已有行为进行增强。这里注意区别于装饰器模式:主要是附加行为)
代理模式的优点:
能够保护目标对象
能够降低系统耦合度,扩展性好
4.1 静态代理
声明一个订单服务接口
public interface IOrderService { int createOrder(int orderId); }
实现订单服务接口
public class OrderService implements IOrderService { @Override public int createOrder(int orderId) { System.out.println("新增订单成功,订单编号:" + orderId); return 1; } }
下面使用静态代理的方式对 OrderService 的 createOrder 方法进行增强,创建代理类:
/** * 订单服务代理类. * <p> * 1. 和目标对象实现相同接口 * 2. 声明一个变量,用于引用目标对象 * 3. 代理类的构造函数需要一个参数来接收目标对象 * 4. 实现从接口继承过来的方法 * * @author ship * @date 2021/10/26 0026 23:20 */ public class OrderServiceProxy implements IOrderService { private IOrderService target; public OrderServiceProxy(IOrderService target) { this.target = target; } /** * 增强 createOrder 方法 * 场景模拟:将奇数订单编号和偶数订单编号分别存入不同数据库 */ @Override public int createOrder(int orderId) { int dbSeq = orderId % 2; // 代理模式 --- 原则上不会丢弃目标对象的行为,只是原有行为进行增强 this.target.createOrder(orderId); System.out.println("订单被保存到 >>> database-" + dbSeq); return 0; } }
测试 main 方法
public static void main(String[] args) { int orderId = (int) (Math.random() * 10); IOrderService orderService = new OrderService(); System.out.println("================= 未增强的 createOrder ================="); orderService.createOrder(orderId); // ------------------------------- 代理 orderService 对象 OrderServiceProxy proxy = new OrderServiceProxy(orderService); System.out.println("================= 增强后的 createOrder ================="); proxy.createOrder(orderId); }
输出结果:
================= 未增强的 createOrder ================= 新增订单成功,订单编号:5 ================= 增强后的 createOrder ================= 新增订单成功,订单编号:5 订单被保存到 >>> database-1
4.2 动态代理
静态代理的问题
大量重复代码(一个类很多个方法需要增强)
需要创建大量代理类(同样的增强逻辑,需要应用到多个类时)
使用动态代理,可以避免静态代理引起的类爆炸和大量重复代码的问题。
下面介绍两种动态代理模式:
JDK 动态代理
Cglib (Code Generation Library)
JDK 动态代理
JDK 动态代理要求被代理对象必须实现一个接口
public interface IPerson { void findLove(); } public class Customer implements IPerson { @Override public void findLove() { System.out.println("白富美"); System.out.println("身高 165 cm"); System.out.println("前凸后翘"); } }
代理类需要实现
java.lang.reflect.InvocationHandler
/** * 代理类 —— 媒婆(中介). * <p> * JDK 动态代理要求代理类实现 {@link java.lang.reflect.InvocationHandler} 接口 * * @author ship * @date 2021/10/27 0027 0:14 */ public class JDKMeipo implements InvocationHandler { /** * 与静态代理不同,目标对象声明为 Object */ private Object target; /** * 使用 Proxy.newProxyInstance 获取创建代理对象并返回 */ public Object getInstance(Object target) { this.target = target; Class<?> clazz = target.getClass(); // 参数说明:目标对象的类加载器,目标对象父接口,InvocationHandler(在 invoke 方法中定义增强逻辑) return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this); // 根据方法入参,可以推测一下 Proxy.newProxyInstance 内部做了什么事情 // 1. 返回的是代理对象,又传入了 InvocationHandler,那么代理对象的方法逻辑肯定被替换成 invoke 中的逻辑 // 2. 传入了目标对象的父接口,那么代理对象应该是要实现这些父接口的 // 3. 还传入了一个目标对象的类加载器,类加载器是用来将 class 字节码文件加载到内存中的,字节码文件很明显就是动态代理类的 class 文件了 // 4. 接口给了,方法逻辑给了,我自己创建个 java 文件,代码拷一下,编译生成 class 文件然后用 ClassLoader 加载到内存就好了 // 5. JVM 这么高级肯定不需要通过 java 文件再转 class 文件了,直接在运行时生成 class 文件,直接存到元空间,连生成文件的步骤都省了 } /** * invoke 方法中调用目标对象的方法,在此之前/之后想加什么代码都行 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); Object obj = method.invoke(this.target, args); after(); return obj; } private void before() { System.out.println("我是媒婆,我要给你找对象,现在已经确认你的需求: "); } private void after() { System.out.println("开始物色......"); } }
原理解析
通过上面代码可以知道,JDK 动态代理的核心就是
java.lang.reflect.Proxy#newProxyInstance
下面通过源码调试,来探索 JDK 动态代理的原理。
java.lang.reflect.Proxy
详解
/** * Proxy 提供了一些静态方法来创建动态代理类和实例,■■■■■ 所有动态代理类都会继承 Proxy ■■■■■ * * 想要创建一个 Proxy 至少需要提供一个接口(如:Foo)和一个 InvocationHandler: * <pre> * // Foo 称为代理接口,■■■■■ 代理类必须实现它 ■■■■■ * Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(), new Class[]{ Foo.class }, handler); * </pre> * * 动态代理类是一个 ■■■■■ 在运行时被创建的、实现了指定接口 ■■■■■ 的类。 * * ■■■■■ 每个代理实例都会关联一个 InvocationHandler 对象 ■■■■■ * ■■■■■ Proxy 实例执行代理方法,其实就是去调用 InvocationHandler#invoke ■■■■■ * ■■■■■ 传递参数为 —— (Object proxy, Method method, Object[] args) ■■■■■ * * ---------------------------------------【一个代理类具有以下属性】--------------------------------------- * ● 如果所有代理接口都是 public 的,那么代理类就是 public final 的。 * ● 如果代理接口不是 public 的,那么代理类就不是 public 的,但仍然是 final 的。 * ● ■■■■■ 虽然没有指定代理类的非法名称,但还是应该遵循规范,以字符串“$Proxy”开头。■■■■■ * ● 代理类继承了 java.lang.reflect.Proxy * ● 代理类以相同的顺序精确地实现了创建时传入的接口列表。 * ● 如果代理类实现了一个非 public 接口,那么它将在与该接口相同的包中定义。否则,代理类的包是未指定的。 * ● 代理类的保护域(java.security.ProtectionDomain)与由 bootstrap 类加载器加载的系统类相同。 * ● 因为代理类是由受信任的系统代码生成的,保护域通常被授予 java.security.AllPermission * ● 每个代理类都有一个公共构造函数,该构造函数接收一个参数 InvocationHandler, 可以把它理解为代理类的调用处理器。 * * ---------------------------------------【代理实例具有以下属性】--------------------------------------- * ● 代理实例和代理接口的关系:(proxy instanceof Foo) 结果为 true,意味着可以直接类型强转 (Foo) proxy * ● 每个代理实例都关联一个 InvocationHandler,可通过静态方法 Proxy.getInvocationHandler(proxy) 获得。 * ● 代理实例的接口方法将会被编码、并分派 InvocationHandler 的 invoke 方法。 * ● hashCode、equals、toString 方法都会被编码、并分派 InvocationHandler 的 invoke 方法。 * ● notify、wait、clone、finalize 方法不会被覆盖,还是保持 Object 的行为。 * * -------------------------------------【多个代理接口存在重复方法】------------------------------------- * ● 由接口顺序决定,第一个接口的 Method 会被传递给 InvocationHandler#invoke * ● 如果是 hashCode、equals、toString 重复了,那 Object 优先于所有接口 */ public class Proxy implements java.io.Serializable { @CallerSensitive public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { //... } }
Proxy#newProxyInstance
详解
生成的 class 文件(反编译后,根据结构继续分析
Constructor#newInstance
)
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package com.sun.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; import structurepattern.proxy.jdkdynamicproxy.IPerson; // final 类,继承 Proxy,实现了目标对象的父接口 public final class $Proxy0 extends Proxy implements IPerson { private static Method m1; private static Method m3; private static Method m2; private static Method m0; // 构造函数接收一个 InvocationHandler 参数 // Proxy.newProxyInstance 方法接收的 InvocationHandler 就是为了传给此处的 public $Proxy0(InvocationHandler var1) throws { super(var1); } // 接口继承的方法全部分派了 InvocationHandler 的 invoke 方法 public final void findLove() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } // Object 继承过来的 equals、toString、hashCode 也被覆盖了,全部去调用 InvocationHandler#invoke public final boolean equals(Object var1) throws { ... } public final String toString() throws { ... } public final int hashCode() throws { ... } // 静态代码块:通过 Class.forName("类的全限定名") 的方法得到类对象再 getMethod 得到 Method 对象 // Method 对象作为 InvocationHandler 的 invoke 方法的入参 static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m3 = Class.forName("structurepattern.proxy.jdkdynamicproxy.IPerson").getMethod("findLove"); m2 = Class.forName("java.lang.Object").getMethod("toString"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
动态代理类缓存池
这里的 Supplier 是一个
java.lang.reflect.WeakCache.Factory
,动态生成代理类的关键元素(类加载器、代理接口列表)都在这里,最后生成的代理类也存放在此处。
Cglib 动态代理
直接上代码(与 JDK 动态代理进行对比)
public class Guest { public void findLove(String str) { System.out.println("肤白貌美大长腿"); } } /** * ● Cglib 需要创建一个 MethodInterceptor 接口实现类,它与 JDK 动态代理的 InvocationHandler 接口实现类大同小异 * ● Cglib 不要求目标对象必须有父接口,只需要提供目标对象的 Class * ● 创建代理对象使用 Enhancer#create (JDK 动态代理使用 Proxy#newProxyInstance) */ public class CglibMeipo implements MethodInterceptor { public Object getInstance(Class<?> clazz) throws Exception { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(clazz); enhancer.setCallback(this); // 有了 JDK 动态代理的分析经验,这里要推测 enhancer.create() 做了什么就很简单了 // 1. 已知要代理的目标 Class,意味着 class 文件的信息 // 2. 当前对象也交给 enhancer 了,增强逻辑已知 // 3. JVM 编写新的 class 文件作为代理类 -> 类加载 -> 构造实例 return enhancer.create(); } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { before(); Object obj = methodProxy.invokeSuper(o, objects); after(); return obj; } private void before() { System.out.println("我是媒婆,我要给你找对象,现在已经确认你的需求: "); } private void after() { System.out.println("开始物色......"); } }
原理解析
关于 Cglib 如何生成 class 文件的源码,这里提供一个非常有用的断点:
net.sf.cglib.reflect.FastClass.Generator#generateClass
这个方法是一堆操作字节码生成 class 文件的代码,断点后,通过线程调用栈可以看到
enhancer.create()
如何一步步走到这里。
下面是
Enhancer#create
代理对象创建的时序图
直接看生成的 class 文件(反编译后,有 3 个文件)
生成文件 说明 Guest$$EnhancerByCGLIB$$13aa3b67 代理类 Guest$$EnhancerByCGLIB$$13aa3b67$$FastClassByCGLIB$$d5923b6f 代理类的 FastClass Guest$$FastClassByCGLIB$$5b758309 被代理类的 FastClass
Cglib 不光生成动态代理类,还为代理类和被代理类各生成了一个 FastClass。
下面开始分析这三个类的作用:
代理类
/** * 继承被代理类,且 Cglib 生成的代理类都会实现 Factory 接口。 * * {@link net.sf.cglib.proxy.Factory} 接口定义三类方法: * ● newInstance(Callback), newInstance(Callback[]), newInstance(Class[], Object[], Callback[]) * ● setCallback * ● getCallback * 动态代理类需要实现这些方法,主要关注创建实例的方法 * ■■■■■ newInstance 根据传入的 Callback 来创建代理实例,Callback 就是我们自定义的 MethodInterceptor ■■■■■ * ■■■■■ 创建代理实例时,使用 ThreadLocal 解决了 Callback 的传递问题 ■■■■■ * */ public class Guest$$EnhancerByCGLIB$$13aa3b67 extends Guest implements Factory { // 代理的实例是否已经绑定 Callback(MethodInterceptor) private boolean CGLIB$BOUND; // 这就是用来绑定 Callback 的 private MethodInterceptor CGLIB$CALLBACK_0; // 代理类生成过程使用的数据 public static Object CGLIB$FACTORY_DATA; // 临时存放 Callback,代理实例创建完成就清理 private static final ThreadLocal CGLIB$THREAD_CALLBACKS; // 提供了一个 CGLIB$SET_STATIC_CALLBACKS 静态方法来设置该值 private static final Callback[] CGLIB$STATIC_CALLBACKS; /** * 动态代理类缓存池 {@link AbstractClassGenerator.ClassLoaderData#generatedClasses} 中,缓存键是弱引用, * CallbackFilter 就是其中一个缓存键,为了防止代理类活动状态下它被回收,所以生成一个静态字段来保持强引用 */ private static Object CGLIB$CALLBACK_FILTER; // MethodInterceptor#intercept 必须传一个代表方法参数的 Object 数组 // 无参的方法都用这个字段传 private static final Object[] CGLIB$emptyArgs = new Object[0]; // 下面是所有方法的 Method 和 MethodProxy 对象,在 static 代码块中会对它们进行初始化 private static final Method CGLIB$findLove$0$Method; private static final MethodProxy CGLIB$findLove$0$Proxy; private static final Method CGLIB$equals$1$Method; private static final MethodProxy CGLIB$equals$1$Proxy; private static final Method CGLIB$toString$2$Method; private static final MethodProxy CGLIB$toString$2$Proxy; private static final Method CGLIB$hashCode$3$Method; private static final MethodProxy CGLIB$hashCode$3$Proxy; private static final Method CGLIB$clone$4$Method; private static final MethodProxy CGLIB$clone$4$Proxy; /** * 静态代码块:变量初始化 */ static void CGLIB$STATICHOOK1() { // 存放 Callback 的,方便数据传递 CGLIB$THREAD_CALLBACKS = new ThreadLocal(); // 代理类 Class var0 = Class.forName("包名太长省略..Guest$$EnhancerByCGLIB$$13aa3b67"); // 临时变量,用来引用被代理对象的 Class 和 Object.class Class var1; // 反射获取被代理对象的 Method,并加载被代理类,用 var1 引用 CGLIB$findLove$0$Method = ReflectUtils.findMethods( new String[]{"findLove", "(Ljava/lang/String;)V"}, (var1 = Class.forName("包名太长省略..Guest")).getDeclaredMethods() )[0]; // 创建 MethodProxy 实例,就是把代理类、被代理类、代理方法、被代理方法的信息组装起来 // FastClass 的信息也在 MethodProxy 中存放,但不是现在 CGLIB$findLove$0$Proxy = MethodProxy.create( var1, var0, "(Ljava/lang/String;)V", "findLove", "CGLIB$findLove$0" ); /** * Object 中的方法(equals、toString、hashCode、clone)参考上面操作,这里就不列出来了 */ } // final 修饰,被代理对象原方法 final void CGLIB$findLove$0(String var1) { super.findLove(var1); } /** * final 修饰,Cglib 与 JDK 动态代理的差异,关键就看方法调用 * ● JDK 动态代理:InvocationHandler#invoke -> Method#invoke(反射) * ● Cglib 动态代理:通过 FastClass 进行方法调用,性能更高 */ public final void findLove(String var1) { // 绑定 Callback,就是我们自定义的 MethodInterceptor,这是创建代理对象(newInstance)时必需传的参数 MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; if (var10000 == null) { // MethodInterceptor 在 newInstance 的时候存进了 ThreadLocal,就是为了这里方便取用 CGLIB$BIND_CALLBACKS(this); var10000 = this.CGLIB$CALLBACK_0; } if (var10000 != null) { // 调用 MethodInterceptor#intercept 执行增强逻辑 var10000.intercept(this, // 代理对象 CGLIB$findLove$0$Method, // Method 对象 new Object[]{var1}, // 方法参数 CGLIB$findLove$0$Proxy); // MethodProxy } else { // 没有方法拦截的还是执行原对象的逻辑 super.findLove(var1); } } // Factory 接口方法实现(newInstance、setCallback、getCallback) // 其他方法实现 ... ... }
了解了 Cglib 代理类的结构,也知道代理对象的方法调用是去执行 MethodInterceptor#intercept 方法。
但是 Cglib 总共生成了 3 个文件,另外两个 FastClass 是怎么起作用的?
下面通过
net.sf.cglib.proxy.MethodProxy#invokeSuper
继续探索。
FastClass 机制
/** * MethodProxy 的 invokeSuper 的工作: * 1. 初始化 FastClassInfo * 2. 使用 FastClassInfo 调用方法 */ public Object invokeSuper(Object obj, Object[] args) throws Throwable { init(); return fastClassInfo.f2.invoke(fci.i2, obj, args); } // volatile 修饰,保证可见性(发生变化后立刻同步到主内存,要使用时必须从主内存取),禁止重排序 private volatile FastClassInfo fastClassInfo; private static class FastClassInfo { FastClass f1;// 被代理对象的 FastClass FastClass f2;// 代理对象的 FastClass int i1;// 被代理对象方法的索引 int i2;// 代理对象方法的索引 } // 双重检测锁,防止并发时重复创建 fastClassInfo private final Object initLock = new Object(); private void init() { if (fastClassInfo == null) { synchronized (initLock) { if (fastClassInfo == null) { CreateInfo ci = createInfo; FastClassInfo fci = new FastClassInfo(); // 加载 Guest$$FastClassByCGLIB$$5b758309.class 并创建实例 fci.f1 = helper(ci, ci.c1); // 加载 Guest$$EnhancerByCGLIB$$13aa3b67$$FastClassByCGLIB$$d5923b6f.class 并创建实例 fci.f2 = helper(ci, ci.c2); /** * FastClass 为每个方法都分配了一个 ID,就是在 getIndex 方法中实现的 * 与之对应的,调用 FastClass#invoke 的时候传入这个ID,用 switch ... case 匹配 * 比如:0 调用 equals 方法,1 调用 toString 方法,... 全都写死了,不需要再通过反射确认方法 */ fci.i1 = fci.f1.getIndex(sig1); fci.i2 = fci.f2.getIndex(sig2); fastClassInfo = fci; // 创建 MethodProxy 实例的时候创建的信息,用完释放 createInfo = null; } } } }
4.3 不同代理模式对比
五、高频面试题
代理模式和装饰者模式的区别?
文中找答案。
动态代理和静态代理有什么区别?
文中找答案。
JDK 动态代理和 Cglib 动态代理的区别?
文中找答案。
代理模式有什么优缺点?
代理模式具有以下优点:
能将代理对象与真实被调用的目标对象分离(静态代理做不到,关联关系)
降低系统耦合性、扩展性好
起到保护目标的作用
可以增强目标对象的功能
缺点:
造成系统设计中类的数量增加
增加了系统的复杂度
客户端和目标对象之间增加了一个代理,请求速度变慢
@CallerSensitive 是干嘛的?
当方法体中出现 Reflection.getCallerClass()
,这个方法就必须加上此注解。
执行 Reflection.getCallerClass()
获取调用者的 Class 对象,一般都是为了检查调用者的访问权限,但它只会检查固定深度的调用者。
假设固定检查两层调用者,那我只要利用双重反射,那它检查到的调用者就是 java.lang.reflect 包下的类,反射包下的类权限是很高的,检查就会通过,所以固定深度检查是一个严重的漏洞。
于是引入 @CallserSensitive
,反射包中的方法只要调用了 Reflection.getCallerClass()
就加个 @CallserSensitive
注解,那么固定深度检查的时候,发现这个注解就跳过(深度不变),这样就能找到真正的调用者,判断访问权限的时候才不会出问题。
FastClass 机制是什么?
文中找答案。