继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Spring 之 AOP 详解

shipping
关注TA
已关注
手记 1
粉丝 0
获赞 0

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)

引入能够为目标对象附加额外的方法或字段,具体怎么做呢?

  1. 首先,将要附加的方法或字段用一个接口维护起来

  2. 然后,在切面中声明这个接口变量

  3. 最后,使用 @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 Pointcut 相关文档:


四、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 详解

http://img4.mukewang.com/619cfc4f0001773518140983.jpg

  • 生成的 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());
        }
    }
}
  • 动态代理类缓存池

http://img3.mukewang.com/619cfc6c00015ecb15970412.jpg

这里的 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 代理对象创建的时序图

http://img3.mukewang.com/619cfc7e0001ef7f17921288.jpg

  • 直接看生成的 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 不同代理模式对比

http://img.mukewang.com/619cfcfa000126b621060897.jpg




五、高频面试题

代理模式和装饰者模式的区别?

文中找答案。

动态代理和静态代理有什么区别?

文中找答案。

JDK 动态代理和 Cglib 动态代理的区别?

文中找答案。

代理模式有什么优缺点?

代理模式具有以下优点:

  • 能将代理对象与真实被调用的目标对象分离(静态代理做不到,关联关系)

  • 降低系统耦合性、扩展性好

  • 起到保护目标的作用

  • 可以增强目标对象的功能

缺点:

  • 造成系统设计中类的数量增加

  • 增加了系统的复杂度

  • 客户端和目标对象之间增加了一个代理,请求速度变慢


@CallerSensitive 是干嘛的?

当方法体中出现 Reflection.getCallerClass() ,这个方法就必须加上此注解。

执行 Reflection.getCallerClass() 获取调用者的 Class 对象,一般都是为了检查调用者的访问权限,但它只会检查固定深度的调用者。

假设固定检查两层调用者,那我只要利用双重反射,那它检查到的调用者就是 java.lang.reflect 包下的类,反射包下的类权限是很高的,检查就会通过,所以固定深度检查是一个严重的漏洞。

于是引入 @CallserSensitive ,反射包中的方法只要调用了 Reflection.getCallerClass() 就加个 @CallserSensitive 注解,那么固定深度检查的时候,发现这个注解就跳过(深度不变),这样就能找到真正的调用者,判断访问权限的时候才不会出问题。

FastClass 机制是什么?

文中找答案。

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP