Spring AOP

1.Spring AOP简介

1.1 AOP概述

1.1.1 AOP是什么

AOP是一种设计思想,是软件设计领域中的面向切面编程1,它是面向对象编程(OOP)的一种补充和完善。它以通过预编译方式和运行期动态代理方式,实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。如图所示:
在这里插入图片描述
AOP与OOP字面意思相近,但其实两者完全是面向不同领域的设计思想。实际项目中我们通常将面向对象理解为一个静态过程(例如一个系统有多少个模块,一个模块有哪些对象,对象有哪些属性),面向切面的运行期代理方式,理解为一个动态过程,可以在对象运行时动态织入一些扩展功能或控制对象执行

1.1.2 AOP应用场景分析

实际项目中通常会将系统分为两大部分,一部分是核心业务,一部分是非核业务。在编程实现时我们首先要完成的是核心业务的实现,非核心业务一般是通过特定方式切入到系统中,这种特定方式一般就是借助AOP进行实现
AOP就是要基于OCP(开闭原则),在不改变原有系统核心业务代码的基础上动态添加一些扩展功能并可以"控制"对象的执行。例如AOP应用于项目中的日志处理,事务处理,权限处理,缓存处理等等。如图所示:
在这里插入图片描述
思考:现有一业务,在没有 AOP 编程时,如何基于 OCP 原则实现功能扩展?

1.1.3 Spring AOP应用原理分析(先了解)

Spring AOP 底层基于代理机制(动态方式)实现功能扩展:

  1. 假如目标对象(被代理对象)实现接口,则底层可以采用 JDK 动态代理机制为目标
    对象创建代理对象(目标类和代理类会实现共同接口)。
  2. 假如目标对象(被代理对象)没有实现接口,则底层可以采用 CGLIB 代理机制为目
    标对象创建代理对象(默认创建的代理类会继承目标对象类型)
    Spring AOP 原理分析,如图所示:
    在这里插入图片描述
    说明:Spring boot2.x 中 AOP 现在默认使用的 CGLIB 代理,假如需要使用 JDK 动态
    代理可以在配置文件(applicatiion.properties)中进行如下配置:
spring.aop.proxy-target-class=false

1.2 Spring中AOP相关术语分析

切面(aspect): 横切面对象,一般为一个具体类对象(可以借助@Aspect 声明)。
通 知 (Advice): 在 切 面 的 某 个 特 定 连 接 点 上 执 行 的 动 作 ( 扩 展 功 能 ) , 例 如
around,before,after 等。
连接点(joinpoint):程序执行过程中某个特定的点,一般指向被拦截到的目标方法。切入点(pointcut):对多个连接点(Joinpoint)一种定义,一般可以理解为多个连接点的集合。
连接点与切入点定义如图所示:
在这里插入图片描述
说明:我们可以简单的将机场的一个安检口理解为连接点,多个安检口为切入点,安全检查过程看成是通知。总之,概念很晦涩难懂,多做例子,做完就会清晰。先可以按白话去理解

2.Spring AOP快速实践

2.1 业务描述

基于项目中的核心业务,添加简单的日志操作,借助 SLF4J 日志 API 输出目标方法的
执行时长。(前提,不能修改目标方法代码-遵循 OCP 原则)

2.2 项目创建及配置

创建 maven 项目或在已有项目基础上添加 AOP 启动依赖:

<dependency>
	<groupId>org.springframework.boot</groupId>
 	<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

说明:基于此依赖spring可以整合AspectJ框架快速完成AOP的基本实现。AspectJ
是一个面向切面的框架,他定义了 AOP 的一些语法,有一个专门的字节码生成器来生成遵
守 java 规范的 class 文件

2.3 扩展业务分析及实现

2.3.1 创建日志切面类对象

package com.cy.pj.common.aspect;
@Aspect
@Slf4j
@Component
public class SysLogAspect {
	@Pointcut("bean(sysUserServiceImpl)")
	public void doLogPointCut() {}
	@Around("doLogPointCut()")
	public Object around(ProceedingJoinPoint jp) throws Throwable{
		try {
 			log.info("start:{}"+System.currentTimeMillis());
 			Object result=jp.proceed();//最终会调用目标方法
 			log.info("after:{}"+System.currentTimeMillis());
			return result;
		}catch(Throwable e) {
 			log.error("after:{}",e.getMessage());
 			throw e;
		}
	}
}

@Aspect 注解用于标识或者描述 AOP 中的切面类型,基于切面类型构建的对象用于为目标对象进行功能扩展或控制目标对象的执行。
@Pointcut 注解用于描述切面中的方法,并定义切面中的切入点(基于特定表达式的方式进行描述),在本案例中切入点表达式用的是 bean 表达式,这个表达式以 bean开头,bean 括号中的内容为一个 spring 管理的某个 bean 对象的名字
@Around 注解用于描述切面中方法,这样的方法会被认为是一个环绕通知(核心业务方法执行之前和之后要执行的一个动作),@Aournd 注解内部 value 属性的值为一个切入点表达式或者是切入点表达式的一个引用(这个引用为一个@PointCut 注解描述的方法的方法名)。
ProceedingJoinPoint 类为一个连接点类型,此类型的对象用于封装要执行的目标方法相关的一些信息。只能用于@Around 注解描述的方法参数
当我们切入点引入不正确时,会出现如图所示错误:
在这里插入图片描述

2.3.2 业务切面测试实现

启动项目测试或者进行单元测试,其中 Spring Boot 项目中的单元测试代码如下:

@SpringBootTest
public class AopTests {
	@Autowired
	private SysUserService userService;
	@Test
	public void testSysUserService() {
		PageObject<SysUserDeptVo> po=
		userService.findPageObjects("admin",1);
		System.out.println("rowCount:"+po.getRowCount());
	}
}

对于测试类中的 userService 对象而言,它有可能指向 JDK 代理,也有可能指向 CGLIB代理,具体是什么类型的代理对象,要看 application.yml 配置文件中的配置

2.3.3 应用总结分析

在业务应用,AOP 相关对象分析,如图所示:
在这里插入图片描述

2.4 扩展业务织入增强分析

2.4.1 基于JDK代理方式实现

假如目标对象有实现接口,则可以基于 JDK 为目标对象创建代理对象,然后为目标对象
进行功能扩展,如图所示:
在这里插入图片描述
说明:假如目标对象类型没有实现接口,则不允许使用 JDK 代理

2.4.2 基于CGLIB代理方式实现

假如目标对象没有实现接口(当然实现了接口也是可以的),可以基于 CGLIB 代理方式
为目标对象织入功能扩展,如图所示:
在这里插入图片描述
说明:目标对象实现了接口也可以基于 CGLIB 为目标对象创建代理对象。但是目标对象类型假如使用了 final 修饰,则不可以使用 CGBLIB

3.Spring AOP编程增强

3.1 切面通知应用增强

3.1.1 通知类型

在基于 Spring AOP 编程的过程中,基于 AspectJ 框架标准,spring 中定义了五种类型的通知(通知-Advice 描述的是一种扩展业务),它们分别是:
@Before。(目标方法执行之前执行)
@AfterReturning。(目标方法成功结束时执行)
@AfterThrowing。(目标方法异常结束时执行)
@After。(目标方法结束时执行)
@Around.(重点掌握,目标方法执行前后都可以做业务拓展)(优先级最高)
说明:在切面类中使用什么通知,由业务决定,并不是说,在切面中要把所有通知都写上。代码实践分析如下:

package com.cy.pj.common.aspect;
@Component
@Aspect
public class SysTimeAspect {
	@Pointcut("bean(sysUserServiceImpl)")
	public void doTime(){}
	@Before("doTime()")
	public void doBefore(){
		System.out.println("time doBefore()");
	}
	@After("doTime()")
	public void doAfter(){
		System.out.println("time doAfter()");
	}
	/**核心业务正常结束时执行* 说明:假如有 after,先执行 after,再执行
	returning*/
	@AfterReturning("doTime()")
	public void doAfterReturning(){
		System.out.println("time doAfterReturning");
	}
	/**核心业务出现异常时执行说明:假如有 after,先执行 after,再执行
	Throwing*/
	@AfterThrowing("doTime()")
	public void doAfterThrowing(){
		System.out.println("time doAfterThrowing");
	}
	@Around("doTime()")
	public Object doAround(ProceedingJoinPoint jp) throws Throwable{
		System.out.println("doAround.before");
 		try{
			Object obj=jp.proceed();
 			System.out.println("doAround.after");
 			return obj;
		}catch(Throwable e){
			 System.out.println(e.getMessage());
 			 throw e;
 		}
	}
}

3.2 切入点表达式增强

Spring 中通过切入点表达式定义具体切入点,其常用 AOP 切入点表达式定义及说明:

指示符作用
bean用于匹配指定bean对象的所有方法
within用于匹配指定包下所有类内的所有方法
execution用于按指定语法规则匹配到具体方法
@annotation用于匹配指定注解修饰的方法

3.2.1 bean表达式

bean 表达式一般应用于类级别,实现粗粒度的切入点定义,案例分析:
bean(“userServiceImpl”)指定一个 userServiceImpl 类中所有方法。
bean("*ServiceImpl")指定所有后缀为 ServiceImpl 的类中所有方法。
说明:bean 表达式内部的对象是由 spring 容器管理的一个 bean 对象,表达式内部的名字应该是 spring 容器中某个 bean 的 name。
缺陷:不能精确到具体方法,也不能针对于具体模块包中的方法做切入点设计

3.2.2 within 表达式

within 表达式应用于类级别,实现粗粒度的切入点表达式定义,案例分析:
within(“aop.service.UserServiceImpl”)指定当前包中这个类内部的所有方法。
within(“aop.service.") 指定当前目录下的所有类的所有方法。
within("aop.service…
”) 指定当前目录以及子目录中类的所有方法。
within 表达式应用场景分析:
1)对所有业务 bean 都要进行功能增强,但是 bean 名字又没有规则。
2)按业务模块(不同包下的业务)对 bean 对象进行业务功能增强

3.2.3 execution 表达式

execution 表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析:
语法:execution(返回值类型 包名.类名.方法名(参数列表))。
execution(void aop.service.UserServiceImpl.addUser())匹配 addUser 方法。
execution(void aop.service.PersonServiceImpl.addUser(String)) 方法参数必须为String 的 addUser 方法。
execution(* aop.service….(…)) 万能配置

3.2.4 @annotation 表达式

@annotaion 表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析
@annotation(anno.RequiredLog) 匹配有此注解描述的方法。
@annotation(anno.RequiredCache) 匹配有此注解描述的方法。
其中:RequiredLog 为我们自己定义的注解,当我们使用@RequiredLog 注解修饰业务层方法时,系统底层会在执行此方法时进行日扩展操作。
课堂练习:定义一 Cache 相关切面,使用注解表达式定义切入点,并使用此注解对需要使用 cache 的业务方法进行描述,代码分析如下:
第一步:定义注解 RequiredCache

package com.cy.pj.common.annotation;
/**
* 自定义注解,一个特殊的类,所有注解都默认继承 Annotation 接口
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequiredCache {
	//...
}

第二步:定义SysCacheAspect切面对象

package com.cy.pj.common.aspect;
@Aspect
@Component
public class SysCacheAspect {
	@Pointcut("@annotation(com.cy.pj.common.annotation.RequiredCache)")
 	public void doCache() {}
 	@Around("doCache()")
 	public Object around(ProceedingJoinPoint jp) throws Throwable{
 		System.out.println("Get data from cache");
 		Object obj=jp.proceed();
 		System.out.println("Put data to cache");
 		return obj;
 	}
}

第三步:使用@RequiredCache 注解对特定业务目标对象中的查询方法进行描述

@RequiredCache
@Override
public List<Map<String, Object>> findObjects() {.
	return list;
}

3.3 切面优先级设置实现

切面的优先级需要借助@Order 注解进行描述,数字越小优先级越高,默认优先级比较低。例如:
定义日志切面并指定优先级

@Order(1)
@Aspect
@Component
public class SysLogAspect {}

定义缓存切面并指定优先级:

@Order(2)
@Aspect
@Component
public class SysCacheAspect {}

说明:当多个切面作用于同一个目标对象方法时,这些切面会构建成一个切面链,类似过滤器链、拦截器链,其执行分析如图所示:
在这里插入图片描述

3.4 关键对象与术语总结

Spring 基于 AspectJ 框架实现 AOP 设计的关键对象概览,如图所示:
在这里插入图片描述

4.总结

4.1 重难点分析

AOP 是什么,解决了什么问题,应用场景?
AOP 编程基本步骤及实现过程(以基于 AspectJ 框架实现为例)。
AOP 编程中的核心对象及应用关系。(代理对象,切面对象,通知,切入点)
AOP 思想在 Spring 中的实现原理分析。(基于代理方式进行扩展业务的织入)
AOP 编程中基于注解方式的配置实现。(@Aspect,@PointCut,@Around,…)

4.2 2 FAQ 分析

什么是 OCP 原则(开闭原则)?
什么是 DIP 原则 (依赖倒置)?
什么是单一职责原则(SRP)?
Spring 中 AOP 的有哪些配置方式?(XML,注解)
Spring 中 AOP 的通知有哪些基本类型?(5 种)
Spring 中 AOP 是如何为 Bean 对象创建代理对象的?(JDK,CGLIB)
Spring 中 AOP 切面的执行顺序如何指定?(@Order)

4.3 BUG分析

切入点应用错误,如图所示:
在这里插入图片描述
问题分析:检查切入点的引入是否丢掉了"()"

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值