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;
}
相关文章
|
6天前
|
XML 监控 安全
Spring特性之一——AOP面向切面编程
Spring特性之一——AOP面向切面编程
16 1
|
6天前
|
运维 Java 程序员
Spring5深入浅出篇:基于注解实现的AOP
# Spring5 AOP 深入理解:注解实现 本文介绍了基于注解的AOP编程步骤,包括原始对象、额外功能、切点和组装切面。步骤1-3旨在构建切面,与传统AOP相似。示例代码展示了如何使用`@Around`定义切面和执行逻辑。配置中,通过`@Aspect`和`@Around`注解定义切点,并在Spring配置中启用AOP自动代理。 进一步讨论了切点复用,避免重复代码以提高代码维护性。通过`@Pointcut`定义通用切点表达式,然后在多个通知中引用。此外,解释了AOP底层实现的两种动态代理方式:JDK动态代理和Cglib字节码增强,默认使用JDK,可通过配置切换到Cglib
|
4天前
|
前端开发 Java 关系型数据库
使用IDEA搭建一个Spring + AOP (权限管理 ) + Spring MVC
使用IDEA搭建一个Spring + AOP (权限管理 ) + Spring MVC
|
6天前
|
JSON 前端开发 Java
【JavaEE】Spring全家桶实现AOP-统一处理
【JavaEE】Spring全家桶实现AOP-统一处理
6 0
|
6天前
|
前端开发 Java 开发者
【JavaEE】面向切面编程AOP是什么-Spring AOP框架的基本使用
【JavaEE】面向切面编程AOP是什么-Spring AOP框架的基本使用
12 0
|
6天前
|
Java Spring 容器
Spring AOP浅谈
Spring AOP浅谈
12 1
|
6天前
|
XML Java 数据格式
Spring高手之路18——从XML配置角度理解Spring AOP
本文是全面解析面向切面编程的实践指南。通过深入讲解切面、连接点、通知等关键概念,以及通过XML配置实现Spring AOP的步骤。
23 6
Spring高手之路18——从XML配置角度理解Spring AOP
|
6天前
|
XML Java 数据格式
Spring使用AOP 的其他方式
Spring使用AOP 的其他方式
17 2
|
6天前
|
XML Java 数据格式
Spring 项目如何使用AOP
Spring 项目如何使用AOP
26 2
|
6天前
|
Java 开发者 Spring
Spring AOP的切点是通过使用AspectJ的切点表达式语言来定义的。
【5月更文挑战第1天】Spring AOP的切点是通过使用AspectJ的切点表达式语言来定义的。
25 5