Spring:AOP

简介: AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构

1、什么是AOP

  • AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构
  • 作用:在不惊动原始设计的基础上为其进行功能增强
  • Spring理念:无入侵式

2、AOP中的核心概念

image-20210730144903209

  • 连接点(JoinPoint):正在执行的方法,例如:update()、delete()、select()等都是连接点。
  • 切入点(Pointcut):进行功能增强了的方法,例如:update()、delete()方法,select()方法没有被增强所以不是切入点,但是是连接点。
    • 在SpringAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法
      • 一个具体方法:com.demo.dao包下的BookDao接口中的无形参无返回值的save方法
      • 匹配多个方法:所有的save方法,所有的get开头的方法,所有以Dao结尾的接口中的任意方法,所有带有一个参数的方法
  • 通知(Advice):在切入点前后执行的操作,也就是增强的共性功能
    • 在SpringAOP中,功能最终以方法的形式呈现
  • 通知类:通知方法所在的类叫做通知类
  • 切面(Aspect):描述通知与切入点的对应关系,也就是哪些通知方法对应哪些切入点方法。

3、AOP入门案例

  • 案例设定:测定接口执行效率
  • 简化设定:在接口执行前输出当前系统时间
  • 开发模式:注解
  • 思路分析:
    1. 导入坐标(pom.xml)
    2. 制作连接点方法(原始操作,dao接口与实现类)
    3. 制作共性功能(通知类与通知)
    4. 定义切入点
    5. 绑定切入点与通知关系(切面)

【第一步】导入aop相关坐标

<dependencies>
    <!--spring核心依赖,会将spring-aop传递进来-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
    <!--切入点表达式依赖,目的是找到切入点方法,也就是找到要增强的方法-->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.4</version>
    </dependency>

    <!-- junit4 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13</version>
        <scope>test</scope>
    </dependency>

    <!-- spring整合junit包 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.2.10.RELEASE</version>
        <scope>test</scope>
    </dependency>
</dependencies>

image-20210730150138448

【第二步】定义dao接口与实现类

public interface BookDao {
   
    void save();
    void update();
}
@Repository
public class BookDaoImpl implements BookDao {
   

    public void save() {
   
        System.out.println(System.currentTimeMillis());
        System.out.println("book dao save ...");
    }
    public void update(){
   
        System.out.println("book dao update ...");
    }
}

【第三步】定义通知类,制作通知方法

//通知类必须配置成Spring管理的bean
@Component
public class MyAdvice {
   
    public void method(){
   
        System.out.println(System.currentTimeMillis());
    }
}

【第四步】定义切入点表达式、配置切面(绑定切入点与通知关系

//通知类必须配置成Spring管理的bean
@Component
//设置当前类为切面类类
@Aspect
public class MyAdvice {
   
    //设置切入点,@Pointcut注解要求配置在方法上方
    @Pointcut("execution(void com.demo.dao.BookDao.update())")
    private void pt(){
   }

    //设置在切入点pt()的前面运行当前操作(前置通知)
    @Before("pt()")
    public void method(){
   
        System.out.println(System.currentTimeMillis());
    }
}

【第五步】在配置类中进行Spring注解包扫描和开启AOP功能

@Configuration
@ComponentScan("com.demo")
//开启注解开发AOP功能
@EnableAspectJAutoProxy
public class SpringConfig {
   
}

测试类和运行结果

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class BookAopTest {
   

    @Autowired
    private BookDao bookDao;  //注入dao

    @Test
    public void testAop() {
   
        bookDao.update();
    }
}

image-20210730151347354

4、AOP工作流程

AOP工作流程

  1. Spring容器启动
  2. 读取所有切面配置中的切入点
  3. 初始化bean,判定bean对应的类中的方法是否匹配到任意切入点
    • 匹配失败,创建原始对象
    • 匹配成功,创建原始对象(目标对象)的代理对象
  4. 获取bean执行方法
    • 获取的bean是原始对象时,调用方法并执行,完成操作
    • 获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作

5、AOP切入点表达式

5.1、语法格式
  • 切入点:要进行增强的方法

  • 切入点表达式:要进行增强的方法的描述方式

    • 描述方式一:执行com.demo.dao包下的BookDao接口中的无参数update方法
    execution(void com.demo.dao.BookDao.update())
    
    • 描述方式二:执行com.demo.dao.impl包下的BookDaoImpl类中的无参数update方法
    execution(void com.demo.dao.impl.BookDaoImpl.update())
    
  • 切入点表达式标准格式:切点函数(访问修饰符 返回类型 类全名.方法名(参数类型) 异常类型)

    execution(public User com.demo.service.UserService.findById(int))
    
    • 切点函数:描述切入点的行为动作,例如execution表示执行到指定切入点
    • 访问修饰符:public,private等,可以省略
    • 返回值类型
    • 包名:多级包使用点连接
    • 类全名:包名+类名/接口名
    • 参数:直接写参数的类型,多个类型用逗号隔开
    • 异常名:方法定义中抛出指定异常,可以省略
5.2、 通配符

目的:可以使用通配符描述切入点,快速描述。

* 单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现

匹配com.itheima包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法

execution(public * com.demo.*.UserService.find*(*))

.. 多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写

匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法

execution(public User com.demo.UserService.findById(..))

+ 专用于匹配子类类型

execution(* *..*Service+.*(..))
5.3、 书写技巧
  • 所有代码按照标准规范开发,否则以下技巧全部失效
  • 描述切入点通常描述接口,而不描述实现类
  • 访问控制修饰符针对接口开发均采用public描述(可省略访问控制修饰符描述
  • 返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用*通配快速描述
  • 包名书写尽量不使用..匹配,效率过低,常用*做单个包描述匹配,或精准匹配
  • 接口名/类名书写名称与模块相关的采用*匹配,例如UserService书写成*Service,绑定业务层接口名
  • 方法名书写以动词进行精准匹配,名词采用*匹配,例如getById书写成getBy*,selectAllBook书写成selectAll*
  • 参数规则较为复杂,根据业务方法灵活调整
  • 通常不使用异常作为匹配规则

6、AOP通知类型

  • AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要将其加入到合理的位置
  • AOP通知共分为5种类型
    • 前置通知:在切入点方法执行之前执行
    • 最终通知:在切入点方法执行之后执行,无论切入点方法内部是否出现异常,最终通知都会执行。
    • 环绕通知(重点):手动调用切入点方法并对其进行增强的通知方式。
    • 后置通知(了解):在切入点方法执行之后执行,如果切入点方法内部出现异常将不会执行。
    • 异常通知(了解):在切入点方法执行之后执行,只有当切入点方法内部出现异常之后才执行。
6.1、前置通知
  • 名称:@Before
  • 类型:方法注解
  • 位置:通知方法定义上方
  • 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行
  • 范例:
@Before("pt()")
public void before() {
   
    System.out.println("before advice ...");
}
6.2、 最终通知
  • 名称:@After
  • 类型:方法注解
  • 位置:通知方法定义上方
  • 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法后运行
  • 范例:
@After("pt()")
public void after() {
   
    System.out.println("after advice ...");
}
6.3、后置通知
  • 名称:@AfterReturning(了解)
  • 类型:方法注解
  • 位置:通知方法定义上方
  • 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法正常执行完毕后运行
  • 范例:
@AfterReturning("pt()")
public void afterReturning() {
   
    System.out.println("afterReturning advice ...");
}
6.4、 异常通知
  • 名称:@AfterThrowing(了解)
  • 类型:方法注解
  • 位置:通知方法定义上方
  • 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法运行抛出异常后执行
  • 范例:
@AfterThrowing("pt()")
public void afterThrowing() {
   
    System.out.println("afterThrowing advice ...");
}
6.5、 环绕通知
  • 名称:@Around(重点,常用)
  • 类型:方法注解
  • 位置:通知方法定义上方
  • 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前后运行
  • 范例::
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
   
    System.out.println("around before advice ...");
    Object ret = pjp.proceed();
    System.out.println("around after advice ...");
    return ret;
}

环绕通知注意事项

  1. 环绕通知方法形参必须是ProceedingJoinPoint,表示正在执行的连接点,使用该对象的proceed()方法表示对原始对象方法进行调用,返回值为原始对象方法的返回值。
  2. 环绕通知方法的返回值建议写成Object类型,用于将原始对象方法的返回值进行返回,哪里使用代理对象就返回到哪里

7、AOP切入点数据获取

获取参数

说明:在前置通知和环绕通知中都可以获取到连接点方法的参数们

  • JoinPoint对象描述了连接点方法的运行状态,可以获取到原始方法的调用参数
@Before("pt()")
public void before(JoinPoint jp) {
   
    Object[] args = jp.getArgs(); //获取连接点方法的参数们
    System.out.println(Arrays.toString(args));
}
  • ProccedJointPoint是JoinPoint的子类
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
   
    Object[] args = pjp.getArgs(); //获取连接点方法的参数们
    System.out.println(Arrays.toString(args));
    Object ret = pjp.proceed();
    return ret;
}

获取返回值

说明:在后置通知和环绕通知中都可以获取到连接点方法的返回值

  • 异常通知可以获取切入点方法中出现的异常信息,使用形参可以接收对应的异常对象
@AfterReturning(value = "pt()",returning = "ret")
public void afterReturning(Object ret) {
    //变量名要和returning="ret"的属性值一致
    System.out.println("afterReturning advice ..."+ret);
}
  • 环绕通知中可以手工书写对原始方法的调用,得到的结果即为原始方法的返回值
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
   
    // 手动调用连接点方法,返回值就是连接点方法的返回值
    Object ret = pjp.proceed();
    return ret;
}

获取异常

说明:在异常通知和环绕通知中都可以获取到连接点方法中出现的异常

  • 异常通知可以获取切入点方法中出现的异常信息,使用形参可以接收对应的异常对象
@AfterThrowing(value = "pt()",throwing = "t")
public void afterThrowing(Throwable t) {
   //变量名要和throwing = "t"的属性值一致
    System.out.println("afterThrowing advice ..."+ t);
}
  • 异常通知可以获取切入点方法运行的异常信息,使用形参可以接收运行时抛出的异常对象
@Around("pt()")
public Object around(ProceedingJoinPoint pjp)  {
   
    Object ret = null;
    //此处需要try...catch处理,catch中捕获到的异常就是连接点方法中抛出的异常
    try {
   
        ret = pjp.proceed();
    } catch (Throwable t) {
   
        t.printStackTrace();
    }
    return ret;
}
相关文章
|
3月前
|
Java
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
这篇文章是Spring5框架的实战教程,深入讲解了AOP的基本概念、如何利用动态代理实现AOP,特别是通过JDK动态代理机制在不修改源代码的情况下为业务逻辑添加新功能,降低代码耦合度,并通过具体代码示例演示了JDK动态代理的实现过程。
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
|
12天前
|
存储 缓存 Java
Spring高手之路23——AOP触发机制与代理逻辑的执行
本篇文章深入解析了Spring AOP代理的触发机制和执行流程,从源码角度详细讲解了Bean如何被AOP代理,包括代理对象的创建、配置与执行逻辑,帮助读者全面掌握Spring AOP的核心技术。
25 3
Spring高手之路23——AOP触发机制与代理逻辑的执行
|
2月前
|
设计模式 Java 测试技术
spring复习04,静态代理动态代理,AOP
这篇文章讲解了Java代理模式的相关知识,包括静态代理和动态代理(JDK动态代理和CGLIB),以及AOP(面向切面编程)的概念和在Spring框架中的应用。文章还提供了详细的示例代码,演示了如何使用Spring AOP进行方法增强和代理对象的创建。
spring复习04,静态代理动态代理,AOP
|
4月前
|
Java Spring
在Spring Boot中使用AOP实现日志切面
在Spring Boot中使用AOP实现日志切面
|
22天前
|
Java 编译器 Spring
Spring AOP 和 AspectJ 的区别
Spring AOP和AspectJ AOP都是面向切面编程(AOP)的实现,但它们在实现方式、灵活性、依赖性、性能和使用场景等方面存在显著区别。‌
43 2
|
30天前
|
Java Spring 容器
Spring IOC、AOP与事务管理底层原理及源码解析
【10月更文挑战第1天】Spring框架以其强大的控制反转(IOC)和面向切面编程(AOP)功能,成为Java企业级开发中的首选框架。本文将深入探讨Spring IOC和AOP的底层原理,并通过源码解析来揭示其实现机制。同时,我们还将探讨Spring事务管理的核心原理,并给出相应的源码示例。
110 9
|
22天前
|
XML Java 数据格式
Spring的IOC和AOP
Spring的IOC和AOP
37 0
|
2月前
|
Java 数据库连接 数据库
Spring基础3——AOP,事务管理
AOP简介、入门案例、工作流程、切入点表达式、环绕通知、通知获取参数或返回值或异常、事务管理
Spring基础3——AOP,事务管理
|
3月前
|
XML Java 数据格式
Spring5入门到实战------11、使用XML方式实现AOP切面编程。具体代码+讲解
这篇文章是Spring5框架的AOP切面编程教程,通过XML配置方式,详细讲解了如何创建被增强类和增强类,如何在Spring配置文件中定义切入点和切面,以及如何将增强逻辑应用到具体方法上。文章通过具体的代码示例和测试结果,展示了使用XML配置实现AOP的过程,并强调了虽然注解开发更为便捷,但掌握XML配置也是非常重要的。
Spring5入门到实战------11、使用XML方式实现AOP切面编程。具体代码+讲解
|
3月前
|
缓存 Java 开发者
Spring高手之路22——AOP切面类的封装与解析
本篇文章深入解析了Spring AOP的工作机制,包括Advisor和TargetSource的构建与作用。通过详尽的源码分析和实际案例,帮助开发者全面理解AOP的核心技术,提升在实际项目中的应用能力。
40 0
Spring高手之路22——AOP切面类的封装与解析