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 机制是什么?
文中找答案。
共同學習,寫下你的評論
評論加載中...
作者其他優質文章



