Spring5源码--Spring AOP源码分析一

简介: Spring5源码--Spring AOP源码分析一

一. 认识AOP



1.1 什么是AOP


aop是面向切面编程,相比传统oop,aop能够在方法的前置,中置,后置中插入逻辑代码,对于项目中大量逻辑重复的代码,使用aop能很好的收口逻辑,将逻辑独立于业务代码之外,一处编写,多处使用。


AOP是Object Oriented Programming(OOP)的补充.

OOP能够很好地解决对象的数据和封装的问题,却不能很好的解决Aspect("方面")分离的问题。下面举例具体说明。


比如,我们有一个Bank(银行)类。Bank有两个方法,save(存钱)和withdraw(取钱)。


类和方法的定义如下:

package com.lxl.www.aop;
public class Bank {
  /**
   * 存钱
   */
  public Float save(Account account, float money) {
    // 增加account账户的钱数,返回账户里当前的钱数
    return null;
  }
  /**
   * 取钱
   */
  public Float withdraw(Account account, float money) {
    // 减少account账户的钱数,返回取出的钱数
    return null;
  }
};

这两个方法涉及到用户的账户资金等重要信息,必须要非常小心,所以编写完上面的商业逻辑之后,项目负责人又提出了新的要求--给Bank类的每个重要方法加上安全认证特性。


于是, 我们在两个方法上增加安全代码


改后的类和方法如下:

public class Bank {
  /**
   * 存钱
   */
  public Float save(Account account, float money) {
    // 验证account是否为合法用户
    // 增加account账户的钱数,返回账户里当前的钱数
    return null;
  }
  /**
   * 取钱
   */
  public Float withdraw(Account account, float money) {
    // 验证account是否为合法用户
    // 减少account账户的钱数,返回取出的钱数
    return null;
  }
};

这两个方法都需要操作数据库,为了保持数据完整性,项目负责人又提出了新的要求--给Bank类的每个操作数据库的方法加上事务控制。


于是,我们不得不分别在上面的两个方法中加入安全认证的代码。

类和方法的定义如下:

package com.lxl.www.aop;
public class Bank {
  /**
   * 存钱
   */
  public Float save(Account account, float money) {
    // 验证account是否为合法用户
    // begin Transaction
    // 增加account账户的钱数,返回账户里当前的钱数
    // end Transaction
    return null;
  }
  /**
   * 取钱
   */
  public Float withdraw(Account account, float money) {
    // 验证account是否为合法用户
    // begin Transaction
    // 减少account账户的钱数,返回取出的钱数
    // end Transaction 
    return null;
  }
};

我们看到,这些与商业逻辑无关的重复代码遍布在整个程序中。实际的工程项目中涉及到的类和函数,远远不止两个。如何解决这种问题?


AOP就是为了解决这种问题而出现的。在不修改代码的情况下达到增强的效果


1.2 AOP的相关概念


  • 切面(Aspect):封装通用业务逻辑的组件,即我们想要插入的代码内容. 在spring AOP中, 切面可以使用通用类基于模式的方式, 或者在普通类中标注@Aspect注解来实现
  • 连接点(Join point): 连接点是在应用执行过程中能够插入切面的点。简单理解, 可以理解为需要增强的方法.
  • 通知(Advice): 用于指定具体产生作用的位置,是方法之前或之后等等
  • 前置通知(before) - 在目标方法被调用之前调用通知功能
  • 后置通知(after) - 在目标方法完成之后调用通知(不论程序是否出现异常),此时不会关心方法的输出是什么
  • 返回通知(after-returning) - 在目标方法成功执行之后调用通知
  • 异常通知(after-throwing) - 在目标方法抛出异常后调用通知
  • 环绕通知(around) - 通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为
  • 目标对象(target): 目标对象是指要被增强的对象, 即包含主业务逻辑的类对象
  • 切点(PointCut): 指定哪些Bean组件的哪些方法使用切面组件. 例如:当执行某个特定名称的方法时.我们定义一个切点(execution com.lxl.www.aop.*.*(..)) . 切点表达式如何和连接点匹配是AOP的核心. spring默认使用AspectJ切点语义.
  • 织入(Weaving):通知切入连接点过程叫做织入
  • 引入(Introductions): 可以将其它接口或者实现动态引入到targetClass中

1187916-20201206051603448-1460066005.png

对照上图, 来对应每一个区域,看看其具体含义

1187916-20201201095349132-1085847623.png

那么在Spring中使用AOP就意味着你需要哪些东西呢?我们来举个例子, 就实现上面银行的例子.


  • 首先有一个bank银行类
package com.lxl.www.aop.bank;
public interface Bank {
  /**
   * 存钱
   */
  Float save(Account account, float money) ;
  /**
   * 取钱
   */
  Float withdraw(Account account, float money);
};

有一个银行类的实现方法. 这里面save, withdraw就是连接点. 最终会将各种通知插入到连接点中

package com.lxl.www.aop.bank;
import org.springframework.stereotype.Service;
/**
 * 工商银行
 *
 *
 * DATE 2020/12/6.
 *
 * @author lxl.
 */
@Service
public class IcbcBank implements Bank{
    @Override
    public Float save(Account account, float money) {        // 主业务逻辑: 增加account账户的钱数,返回账户里当前的钱数
        System.out.println(account.getName() + "账户存入" + money);
        return null;
    }
    @Override
    public Float withdraw(Account account, float money) {        // 主业务逻辑: 减少account账户的钱数,返回取出的钱数
        System.out.println(account.getName() + "账户取出" + money);
        return null;
    }
}

接下来, 要有一个切面, 切面是一个类. 切面类里面定义了切点, 通知, 引用

package com.lxl.www.aop.bank;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.DeclareParents;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
 * 切面
 */
@Aspect // 标记这是一个切面
@Order
@Component // 将其放到ioc容器管理
public class BankLogAspect {
  /**
   * 引入
   *
   * 这段话可以理解为, 为com.lxl.www.aop.bank.IcbcBank 引入了一个接口 EnhanceFunctionOfBank,
   * 同时, 引入了默认的实现类 IcbcEnhanceFunctionOfBank
   */
  @DeclareParents(value = "com.lxl.www.aop.bank.IcbcBank",    // 引入的目标类. 也就是需要引入动态实现的类
          defaultImpl = IcbcEnhanceFunctionOfBank.class) // 引入的接口的默认实现
  public static EnhanceFunctionOfBank enhanceFunctionOfBank; // 引入的接口
  /**
   * 定义一个切点
   */
  @Pointcut("execution(* com.lxl.www.aop.bank.IcbcBank.*(..))")
  public void pointCut() {}
  /**
   * 定义一个前置通知
   * @param joinPoint
   */
  @Before(value = "pointCut()")
  public void beforeAdvice(JoinPoint joinPoint) {
    String methodName = joinPoint.getSignature().getName();
    System.out.println("执行目标方法"+methodName+"的前置通知");
  }
  /**
   * 定义了一个后置通知
   */
  @After(value = "pointCut()")
  public void afterAdvice(JoinPoint joinPoint) {
    String methodName = joinPoint.getSignature().getName();
    System.out.println("执行目标方法"+methodName+"的后置通知");
  }
  /**
   * 定义了一个返回通知
   */
  @AfterReturning(value = "pointCut()", returning = "result")
  public void returningAdvice(JoinPoint joinPoint, Object result) {
    String methodName = joinPoint.getSignature().getName();
    System.out.println("执行目标方法"+methodName+"的返回通知");
  }
  /**
   * 定义了一个异常通知
   */
  @AfterThrowing(value = "pointCut()")
  public void throwingAdvice(JoinPoint joinPoint) {
    String methodName = joinPoint.getSignature().getName();
    System.out.println("执行目标方法"+methodName+"的异常通知");
  }
}
  • 那么这里的目标对象是谁呢? 就是我们的IcbcBank类. 这里需要注意的是引入: 引入的概念是将一个接口动态的让另一个类实现了. 这样实现了接口的类, 就可以动态的拥有接口实现类的功能.
  • 银行的额外功能. 也就是银行除了可以存钱, 取钱. 还有一个额外的功能. 比如理财. 不是每个银行都有的.
package com.lxl.www.aop.bank;
/**
 * 增强的功能
 */
public interface EnhanceFunctionOfBank {
    void Financialanagement(Account account);
}

具体银行额外功能的实现类

package com.lxl.www.aop.bank;
import org.springframework.stereotype.Service;
/**
 * Description
 */
@Service
public class IcbcEnhanceFunctionOfBank implements EnhanceFunctionOfBank {
    /**
     * 理财功能
     * @param account
     */
    @Override
    public void Financialanagement(Account account) {
        System.out.println(account.getName() +"的账户 增加 理财功能");
    }
}
  • 这个功能我们可以通过引入,动态增加到IcbcBank类中, 原本IcbcBank只有存钱和取钱的功能. 这样, 就可以增加理财功能了. 这就是引入.
  • 整体配置类
package com.lxl.www.aop.bank;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configurable
// 使用注解的方式引入AOP
@EnableAspectJAutoProxy
@ComponentScan("com.lxl.www.aop.bank")
public class BankMainConfig {
}
  • 使用aop,需要引入AOP, 这里使用的注解的方式引入的.
  • main入口方法
package com.lxl.www.aop.bank;
import com.lxl.www.aop.Calculate;
import com.lxl.www.aop.MainConfig;
import com.lxl.www.aop.ProgramCalculate;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class BankMainClass {
  public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(BankMainConfig.class);
    Account account = new Account("张三");
    Bank bank = (Bank) ctx.getBean("icbcBank");
    bank.save(account, 100);
    System.out.println();
    EnhanceFunctionOfBank enhanceFunctionOfBank = (EnhanceFunctionOfBank) ctx.getBean("icbcBank");
    enhanceFunctionOfBank.Financialanagement(account);
  }
}

如上, 运行结果:

1187916-20201206053731637-2065464154.png

需要注意的地方: 是.gradle配置文件. 通常, 我们在引入AspectJ的jar包的时候, 会引入到父类项目的build.gradle中. 如下所示

1187916-20201206053937763-1846219510.png


  • 最后我们还需要引入到指定的项目中

 1187916-20201206054041477-359691.png

以上就是对整个AOP的理解. 接下来, 分析AOP的源码.

相关文章
|
1月前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
54 2
|
2月前
|
监控 安全 Java
什么是AOP?如何与Spring Boot一起使用?
什么是AOP?如何与Spring Boot一起使用?
71 5
|
2月前
|
Java 开发者 Spring
Spring AOP 底层原理技术分享
Spring AOP(面向切面编程)是Spring框架中一个强大的功能,它允许开发者在不修改业务逻辑代码的情况下,增加额外的功能,如日志记录、事务管理等。本文将深入探讨Spring AOP的底层原理,包括其核心概念、实现方式以及如何与Spring框架协同工作。
|
2月前
|
XML 监控 安全
深入调查研究Spring AOP
【11月更文挑战第15天】
49 5
|
4月前
Micronaut AOP与代理机制:实现应用功能增强,无需侵入式编程的秘诀
AOP(面向切面编程)能够帮助我们在不修改现有代码的前提下,为应用程序添加新的功能或行为。Micronaut框架中的AOP模块通过动态代理机制实现了这一目标。AOP将横切关注点(如日志记录、事务管理等)从业务逻辑中分离出来,提高模块化程度。在Micronaut中,带有特定注解的类会在启动时生成代理对象,在运行时拦截方法调用并执行额外逻辑。例如,可以通过创建切面类并在目标类上添加注解来记录方法调用信息,从而在不侵入原有代码的情况下增强应用功能,提高代码的可维护性和可扩展性。
84 1
|
2月前
|
安全 Java 编译器
什么是AOP面向切面编程?怎么简单理解?
本文介绍了面向切面编程(AOP)的基本概念和原理,解释了如何通过分离横切关注点(如日志、事务管理等)来增强代码的模块化和可维护性。AOP的核心概念包括切面、连接点、切入点、通知和织入。文章还提供了一个使用Spring AOP的简单示例,展示了如何定义和应用切面。
191 1
什么是AOP面向切面编程?怎么简单理解?
|
2月前
|
XML Java 开发者
论面向方面的编程技术及其应用(AOP)
【11月更文挑战第2天】随着软件系统的规模和复杂度不断增加,传统的面向过程编程和面向对象编程(OOP)在应对横切关注点(如日志记录、事务管理、安全性检查等)时显得力不从心。面向方面的编程(Aspect-Oriented Programming,简称AOP)作为一种新的编程范式,通过将横切关注点与业务逻辑分离,提高了代码的可维护性、可重用性和可读性。本文首先概述了AOP的基本概念和技术原理,然后结合一个实际项目,详细阐述了在项目实践中使用AOP技术开发的具体步骤,最后分析了使用AOP的原因、开发过程中存在的问题及所使用的技术带来的实际应用效果。
72 5
|
4月前
Micronaut AOP与代理机制:实现应用功能增强,无需侵入式编程的秘诀
【9月更文挑战第9天】AOP(面向切面编程)通过分离横切关注点提高模块化程度,如日志记录、事务管理等。Micronaut AOP基于动态代理机制,在应用启动时为带有特定注解的类生成代理对象,实现在运行时拦截方法调用并执行额外逻辑。通过简单示例展示了如何在不修改 `CalculatorService` 类的情况下记录 `add` 方法的参数和结果,仅需添加 `@Loggable` 注解即可。这不仅提高了代码的可维护性和可扩展性,还降低了引入新错误的风险。
54 13
|
3月前
|
Java 容器
AOP面向切面编程
AOP面向切面编程
52 0
|
5月前
|
XML Java 数据格式
Spring5入门到实战------11、使用XML方式实现AOP切面编程。具体代码+讲解
这篇文章是Spring5框架的AOP切面编程教程,通过XML配置方式,详细讲解了如何创建被增强类和增强类,如何在Spring配置文件中定义切入点和切面,以及如何将增强逻辑应用到具体方法上。文章通过具体的代码示例和测试结果,展示了使用XML配置实现AOP的过程,并强调了虽然注解开发更为便捷,但掌握XML配置也是非常重要的。
Spring5入门到实战------11、使用XML方式实现AOP切面编程。具体代码+讲解