一、动态代理

代理对象 可以对对象的行为进行定制和扩展。它在代码重用、解耦和业务逻辑分离、性能优化以及系统架构中起到了重要的作用。

代理对象的创建方式

实现业务扩展(日志记录)

下列是一个学生类, 实现BaseStudent接口, 现在有需求, 需要在调用Student下的方法前后记录日志
// 基础学生类
@Data
public class Student implements BaseStudent{

    private Long id;
    private String name;
    private String sex;
    private Integer age;

    @MyLogAnnotation
    public void study() {
        System.out.println("开始学习......");
    }
}

// 接口
public interface BaseStudent {
    void study();
}

JDK 实现

/**
 * 使用 Jdk方式创建代理对象
 * @param obj 被代理的目标对象
 * @return Object
 */
private static Object getInstanceByJdk(Object obj) {

    InvocationHandler handler = new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object result;
            // 在方法执行前添加自定义逻辑
            System.out.println("开始记录日志...... Jdk");

            result = method.invoke(obj, args);

            // 在方法执行后添加自定义逻辑
            System.out.println("方法执行完成.... Jdk");
            return result;
        }
    };
    // 返回的对象id还是那个id,但是对象变了。
    // 通过动态代理获得代理对象
    return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),handler);
}

测试:

public static void main(String[] args) {
    BaseStudent st = new Student();
    BaseStudent obj = (BaseStudent) getInstanceByJdk(st);
    obj.study();
}

执行结果: 
----------------------------------------------------------------
开始记录日志...... Jdk
开始学习......
方法执行完成.... Jdk

Process finished with exit code 0
----------------------------------------------------------------

CGLib 实现

/**
 * 使用 Cglib方式创建代理对象
 * @param obj obj
 * @return Object
 */
private static Object getInstanceByCglib(Object obj) {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(obj.getClass());
    enhancer.setCallback(new MethodInterceptor() {
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
                throws Throwable {

            // 调用方法前的逻辑
            System.out.println("开始记录日志.... Cglib");

            Object o1 = methodProxy.invokeSuper(o, objects);

            // 调用方法后的逻辑
            System.out.println("方法执行完成.... Cglib");

            return o1;
        }
    });

    return enhancer.create();
}

测试

public static void main(String[] args) {
    BaseStudent st = new Student();
    BaseStudent obj = (BaseStudent) getInstanceByCglib(st);
    obj.study();
}

执行结果: 
----------------------------------------------------------------
开始记录日志.... Cglib
开始学习......
方法执行完成.... Cglib

Process finished with exit code 0
----------------------------------------------------------------

两种实现优缺点

二、Spring AOP

1. 概念

AOP(Aspect Oriented Programming)面向切面编程

  • Spring AOP 是 Spring 框架的核心模块之一
  • Spring 采用 AspectJ 作为 其 AOP 框架
  • 通过 AOP 可以实现主从业务逻辑的隔离和解耦
  • AOP 是对 OOP(面向对象编程)的补充和完善
  • 作用

    • 分离系统中的主从业务,将主业务和从业务分离开来
    • 能够保证开发者在不修改源代码的前提下,为系统中的业务组件添加某种通用功能
  • 应用场景

    • 事务管理
    • 日志记录
    • 权限验证
    • 性能监测
  • 相关名词术语

    • 横切关注点
    • 切面(Aspect)
    • 连接点(JoinPoint)
    • 切入点(PointCut)
    • 通知(Advice)
    • 目标对象(Target)
    • 织入(Weaving)
    • 代理(Proxy)

2. AOP 通知类型

  • 前置通知(Before)
  • 后置通知(After)
  • 返回值通知(AfterReturning)
  • 异常通知(AfterThrowing)
  • 环绕通知(Around)
  • 引入通知(Introduction)

3. 完整代码示例:

基础类

/**
 * <p>
 *
 * @author dpl
 * @since 2023/12/8 11:11
 */
public interface BizService {
    // 插入方法
    void insert();
    // 保存方法
    void save();
    // 删除方法
    void delete();
}

/**
 * <p>
 *
 * @author dpl
 * @since 2023/12/8 11:11
 */
@Service("bizService")
public class BizServiceImpl implements BizService {

    @SneakyThrows
    public void insert() {
        System.out.println("insert 插入数据......");
        
        // 此处手动抛出异常
        throw new RuntimeException("");
    }

    @Override
    public void save() {
        System.out.println("save 保存数据......");
    }

    @Override
    public void delete() {
        System.out.println("delete 删除数据......");
    }
}

配置类

/**
 * <p>
 *
 * @author dpl
 * @since 2023/12/12 14:46
 */
@Configuration
@EnableAspectJAutoProxy // 启用AOP
@ComponentScan("com.dpl.spring.aop")
public class AopConfig {

}

切面类

/**
 * <p>
 * 定义切面类
 * @author dpl
 * @since 2023/12/12 14:59
 */
@Aspect
@Component
public class AopAspect {


    /**
     * 定义一个可复用的切入点表达式
     * 使用方法名来引用当前的切入点表达式:
     *      本类中调用、 直接调用方法名:@Before("baseExecution()")
     *      跨类调用、加类名:@Before("AopAspect.baseExecution()")
     *      跨包调用、加全类名:@Before("com.xx.yy.AopAspect.baseExecution()")
     */
    @Pointcut("execution(* com.dpl.spring.aop..BizServiceImpl.*(..))")
    public void baseExecution() {

    }

    /**
     * 前置通知(Before)
     * 
     * @param point 可以加上 `JoinPoint` 对象,Spring自动注入,
     *              它可以访问到连接点信息,如方法签名、方法参数等。
     */
    @Before("baseExecution()")
    public void before(JoinPoint point) {
        String name = point.getSignature().getName();
        System.out.println("开始调用 执行" + name + "方法前的业务逻辑......");
    }

    // 后置通知(After)
    @After("baseExecution()")
    public void after(JoinPoint point) {
        System.out.println("方法调用完成, 执行结束逻辑......");
    }

    // 返回值通知(AfterReturning)
    @AfterReturning("baseExecution()")
    public void afterReturning(JoinPoint point) {
        System.out.println("获取到方法返回值, 执行其他业务逻辑......");
    }

    // 异常通知(AfterThrowing)
    @AfterThrowing("baseExecution()")
    public void afterThrowing(JoinPoint point) {
        System.out.println("方法调用异常, 执行异常处理业务逻辑......");
    }
    
    /**
     * 环绕通知(Around)
     * 环绕通知是最强大的通知类型,它可以将目标方法的执行过程完全控制,
     * 相当于 `其他几种通知的集合`,可以完成任何通知类型的操作。
     * 一般在使用环绕通知后, 不会再使用其他通知类型。
     *
     * @param pjp 在使用环绕通知时`ProceedingJoinPoint`用于获取连接点信息,Spring自动注入
     */
    @Around("baseExecution()")
    public Object around(ProceedingJoinPoint pjp) {
        Object obj = null;

        try {
            String name = pjp.getSignature().getName();
            System.out.println("1. 开始调用方法前的逻辑, 打印方法名: " + name);

            obj = pjp.proceed();

            System.out.println("2. 方法调用完成, 得到返回值: " + obj);

        } catch (Throwable e) {
            System.out.println("3. 捕获到异常, 异常处理逻辑...");
        } finally {
            System.out.println("4. 整个方法调用结束后的逻辑....");
        }
        return obj;
    }
}

测试

  1. 其他几种通知组合 ( 将代码注释掉 环绕通知 的部分 )

    public class AopTest {
    
        @Test
        public void test() {
            ApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class);
            BizService bizService = (BizService)context.getBean("bizService");
            bizService.insert();
        }
    }
    
    测试结果: 
    
    ------------------------------------------------
    开始调用 执行insert方法前的业务逻辑......
    insert 插入数据......
    方法调用异常, 执行异常处理业务逻辑......
    方法调用完成, 执行结束逻辑......
    
    java.lang.RuntimeException: 
        at com.dpl.spring.aop.BizServiceImpl.insert(BizServiceImpl.java:18)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344)
        at 
    ------------------------------------------------
  2. 使用 环绕通知 ( 自行注释掉其他通知类型的代码 )

    public class AopTest {
    
        @Test
        public void test() {
            ApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class);
            BizService bizService = (BizService)context.getBean("bizService");
            bizService.insert();
        }
    }
    
    测试结果: 
    
    ------------------------------------------------
    1. 开始调用方法前的逻辑, 打印方法名: insert
    insert 插入数据......
    3. 捕获到异常, 异常处理逻辑...
    4. 整个方法调用结束后的逻辑....
    
    Process finished with exit code 0
    ------------------------------------------------

4. 切入点表达式

  • 1、execution(public void com.test.service.Impl.AccountServiceImpl.save(int,java.lang.String))
  • 2、execution( com.test..ServiceImpl.*(..))
  • 3、execution(* com.test.service.Impl.AccountServiceImpl.save())
  • 4、execution(void com..*.save())
  • 5、execution( save()) execution( *())
  • 6、!execution( save()) not execution( save())
  • 7、execution( save()) || execution( update())) execution( save()) or execution( update()))
  • 8、execution( (*))
  • 9、execution( (..))
  • 10、bean(*Service)

5. 切面优先级

  • 在 AOP 编程中,@Order 可以用于控制切面的执行顺序
  • @Order注解指定切面优先级,值越小优先级越高、优先级越高越先执行
@Aspect
@Order(1) // 值越小优先级越高
@Component
public class AopAspect1 {
    @Before("execution(* com.dpl.spring.aop..BizServiceImpl.*(..))")
    public void before(JoinPoint point) {
        String name = point.getSignature().getName();
        System.out.println("开始调用 执行" + name + "方法前的业务逻辑......1");
    }
}

@Aspect
@Order(2)
@Component
public class AopAspect2 {
    @Before("execution(* com.dpl.spring.aop..BizServiceImpl.*(..))")
    public void before(JoinPoint point) {
        String name = point.getSignature().getName();
        System.out.println("开始调用 执行" + name + "方法前的业务逻辑......2");
    }
}

测试结果:
---------------------------------------------------------
开始调用 执行insert方法前的业务逻辑......1
开始调用 执行insert方法前的业务逻辑......2
insert 插入数据......
---------------------------------------------------------

三、Spring AOP 源码分析

1. 完整调用流程 - 下载

流程简述

  • 在启动容器时,把切面织入的方法与执行链的关系做了绑定
  • 在调用时,通过方法名可以获取到执行链
  • 在启动AOP过程中,做好很多关系的映射
  • 在AOP调用过程中,通过“索引+递归”完成方法的顺序逻辑
  • 多切面支持

2. 源码分析

1)、AOP 动作在源码中的位置

Spring AOP动作在源码中的位置,位于:初始化方法的后置处理器中

代码位置:

AbstractAutowireCapableBeanFactory

进入 initializeBean()

进入 applyBeanPostProcessorsAfterInitialization 初始化的后置处理器方法

查看 processor.postProcessAfterInitialization(result, beanName) 的实现: postProcessAfterInitialization

为何wrapIfNecessary() 方法会走AbstractAutoProxyCreator类的实现?下一步解答.

2)、理解 @EnableAspectJAutoProxy 注解

进一步查看 AspectJAutoProxyRegistrar.class 类关系

进一步看做了什么

AnnotationAwareAspectJAutoProxyCreator.class 注册到了容器中, 它是自动代理创建器组件,是AOP的核心

故前置的问题:为何wrapIfNecessary() 方法会走 AbstractAutoProxyCreator 类的实现?

3)、AnnotationAwareAspectJAutoProxyCreator 执行时机

此类自动创建代理,发生的位置和时间节点 有两次

  • 第一次,目标类创建之前尝试生成代理

    **AbstractAutowireCapableBeanFactory.createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)**

  • 第二次,目标类完成初始化被增强生成代理类

    AbstractAutowireCapableBeanFactory.initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd)

    4)、如何生成代理对象

    5)、代理对象的调用流程

最后修改:2023 年 12 月 26 日
如果觉得我的文章对你有用,请点个赞吧~