目标:

1.什么是AOP, 什么是AspectJ,

2. 什么是Spring AOP

3. Spring AOP注解版实现原理

4. 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中

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

那么在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);
    }
    }

    如上, 运行结果:

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

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

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

详见第二篇文章

as

5.1 Spring5源码--Spring AOP源码分析一的更多相关文章

  1. 5.2 Spring5源码--Spring AOP源码分析二

    目标: 1. 什么是AOP, 什么是AspectJ 2. 什么是Spring AOP 3. Spring AOP注解版实现原理 4. Spring AOP切面原理解析 一. 认识AOP及其使用 详见博 ...

  2. 5.2 spring5源码--spring AOP源码分析二--切面的配置方式

    目标: 1. 什么是AOP, 什么是AspectJ 2. 什么是Spring AOP 3. Spring AOP注解版实现原理 4. Spring AOP切面原理解析 一. 认识AOP及其使用 详见博 ...

  3. 5.2 spring5源码--spring AOP源码分析三---切面源码分析

    一. AOP切面源码分析 源码分析分为三部分 1. 解析切面 2. 创建动态代理 3. 调用 源码的入口 源码分析的入口, 从注解开始: 组件的入口是一个注解, 比如启用AOP的注解@EnableAs ...

  4. spring AOP源码分析(三)

    在上一篇文章 spring AOP源码分析(二)中,我们已经知道如何生成一个代理对象了,那么当代理对象调用代理方法时,增强行为也就是拦截器是如何发挥作用的呢?接下来我们将介绍JDK动态代理和cglib ...

  5. Spring AOP 源码分析 - 拦截器链的执行过程

    1.简介 本篇文章是 AOP 源码分析系列文章的最后一篇文章,在前面的两篇文章中,我分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器,以及如何创建代理对象的过程.现在我们的得 ...

  6. 框架源码系列十:Spring AOP(AOP的核心概念回顾、Spring中AOP的用法、Spring AOP 源码学习)

    一.AOP的核心概念回顾 https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/core.html#a ...

  7. Spring AOP 源码分析 - 创建代理对象

    1.简介 在上一篇文章中,我分析了 Spring 是如何为目标 bean 筛选合适的通知器的.现在通知器选好了,接下来就要通过代理的方式将通知器(Advisor)所持有的通知(Advice)织入到 b ...

  8. Spring AOP 源码分析 - 筛选合适的通知器

    1.简介 从本篇文章开始,我将会对 Spring AOP 部分的源码进行分析.本文是 Spring AOP 源码分析系列文章的第二篇,本文主要分析 Spring AOP 是如何为目标 bean 筛选出 ...

  9. Spring AOP 源码分析系列文章导读

    1. 简介 前一段时间,我学习了 Spring IOC 容器方面的源码,并写了数篇文章对此进行讲解.在写完 Spring IOC 容器源码分析系列文章中的最后一篇后,没敢懈怠,趁热打铁,花了3天时间阅 ...

随机推荐

  1. 对比JAVA、Python、C、Go运行时间,我惊呆了!!!

    对比JAVA.Python.C.Go运行时间,我惊呆了!!! 周末在寝室刷完算法,想放松一下,于是做了一个实验:用现在主流的几种编程语言对0 - (10000000 - 1)求和,结果我惊呆了,话不多 ...

  2. 关于C中指针的引用,解引用与脱去解引用

    *,& 在指针操作中的意义 (1)* 大家都知道在写int *p 时,*可以声明一个指针.很少人知道*在C/C++中还有一个名字就是"解引用".他的意思就是解释引用,说的通 ...

  3. C/C++四种取整函数floor,ceil,trunc,round

    处理浮点数操作常用到取整函数,C/C++提供了四种取整函数 floor函数 floor函数:向下取整函数,或称为向负无穷取整 double floor(double x); floor(-5.5) = ...

  4. RBAC设计前期设计

    //s用户表 create table userinfo( id int(18) primaryk key auto_increment, username varchar(50) not null ...

  5. 懂了!国际算法体系对称算法DES原理

    概念 加密领域主要有国际算法和国密算法两种体系.国密算法是国家密码局认定的国产密码算法.国际算法是由美国安全局发布的算法.由于国密算法安全性高等一系列原因.国内的银行和支付机构都推荐使用国密算法. 从 ...

  6. 经典c程序100例==71--80

    [程序71] 题目:编写input()和output()函数输入,输出5个学生的数据记录. 1.程序分析: 2.程序源代码: #define N 5 struct student { char num ...

  7. 【日拱一卒】链表——如何实现lru

    LRU Redis的内存淘汰机制好几种,如ttl.random.lru. lru(less recently used)即最近最少使用策略,表示在最近一段时间内最少被使用到的Redis键,如果遇到内存 ...

  8. Adaboost算法的一个简单实现——基于《统计学习方法(李航)》第八章

    最近阅读了李航的<统计学习方法(第二版)>,对AdaBoost算法进行了学习. 在第八章的8.1.3小节中,举了一个具体的算法计算实例.美中不足的是书上只给出了数值解,这里用代码将它实现一 ...

  9. 一些 git 常用的命令

    1.本地命令 查看状态 -git status 添加文件 -git add . 提交文件 -git commit -m "(comment)" 查看历史key -git reflo ...

  10. PHP核心配置基础解读

    PHP核心配置 原为引用 <代码审计企业级web代码安全架构>尹毅  第一章内容 Register_globals(全局变量开关) 会直接把用户GET.POST等方式提交上来的参数注册成为 ...