Java教程

Spring 之 AOP 详解

本文主要是介绍Spring 之 AOP 详解,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

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 使用 AOP:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-ataspectj


    • 核心(声明切入点):https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-pointcuts

  • 基于 XML 模式使用 AOP:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-schema

    • 核心(声明切入点):https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-schema-pointcuts


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

  • https://www.eclipse.org/aspectj/doc/released/progguide/language-joinPoints.html#pointcut-best-practice

  • https://www.eclipse.org/aspectj/doc/released/progguide/language-joinPoints.html#some-example-pointcuts


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

https://img4.sycdn.imooc.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());
        }
    }
}
  • 动态代理类缓存池

https://img3.sycdn.imooc.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 代理对象创建的时序图

https://img3.sycdn.imooc.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 不同代理模式对比

https://img1.sycdn.imooc.com/619cfcfa000126b621060897.jpg




五、高频面试题

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

文中找答案。

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

文中找答案。

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

文中找答案。

代理模式有什么优缺点?

代理模式具有以下优点:

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

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

  • 起到保护目标的作用

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

缺点:

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

  • 增加了系统的复杂度

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


@CallerSensitive 是干嘛的?

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

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

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

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

FastClass 机制是什么?

文中找答案。

TRANSLATE with x
English
ArabicHebrewPolish
BulgarianHindiPortuguese
CatalanHmong DawRomanian
Chinese SimplifiedHungarianRussian
Chinese TraditionalIndonesianSlovak
CzechItalianSlovenian
DanishJapaneseSpanish
DutchKlingonSwedish
EnglishKoreanThai
EstonianLatvianTurkish
FinnishLithuanianUkrainian
FrenchMalayUrdu
GermanMalteseVietnamese
GreekNorwegianWelsh
Haitian CreolePersian
 
TRANSLATE with
COPY THE URL BELOW
Back
EMBED THE SNIPPET BELOW IN YOUR SITE
Enable collaborative features and customize widget: Bing Webmaster Portal
Back
这篇关于Spring 之 AOP 详解的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!