文章目录
- AOP是什么?
- 一、AOP术语名词介绍
- 二、Spring AOP框架介绍和关系梳理
- 三、Spring AOP基于注解方式实现和细节
- 3.1 Spring AOP底层技术组成
- 3.2 初步实现
- 3.3 获取通知细节信息
- 3.4 切点表达式语法
- 3.5 重用(提取)切点表达式
- 3.6 环绕通知
- 3.7 切面优先级设置
- 3.8 CGLib动态代理生效
- 3.9 注解实现小结
- 四、Spring AOP对获取Bean的影响理解
- 总结
- 代理介绍:
- AOP名词理解:
- AOP思维以及与代理的关系:
AOP是什么?
AOP:Aspect Oriented Programming面向切面编程,解决非核心代码的冗余。
- AOP利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。
- 所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
- 使用AOP,可以在不修改原来代码的基础上添加新功能。
AOP思想主要的应用场景:
- 日志记录:在系统中记录日志是非常重要的,可以使用AOP来实现日志记录的功能,可以在方法执行前、执行后或异常抛出时记录日志。
- 事务处理:在数据库操作中使用事务可以保证数据的一致性,可以使用AOP来实现事务处理的功能,可以在方法开始前开启事务,在方法执行完毕后提交或回滚事务。
- 安全控制:在系统中包含某些需要安全控制的操作,如登录、修改密码、授权等,可以使用AOP来实现安全控制的功能。可以在方法执行前进行权限判断,如果用户没有权限,则抛出异常或转向到错误页面,以防止未经授权的访问。
- 性能监控:在系统运行过程中,有时需要对某些方法的性能进行监控,以找到系统的瓶颈并进行优化。可以使用AOP来实现性能监控的功能,可以在方法执行前记录时间戳,在方法执行完毕后计算方法执行时间并输出到日志中。
- 异常处理:系统中可能出现各种异常情况,如空指针异常、数据库连接异常等,可以使用AOP来实现异常处理的功能,在方法执行过程中,如果出现异常,则进行异常处理(如记录日志、发送邮件等)。
- 缓存控制:在系统中有些数据可以缓存起来以提高访问速度,可以使用AOP来实现缓存控制的功能,可以在方法执行前查询缓存中是否有数据,如果有则返回,否则执行方法并将方法返回值存入缓存中。
- 动态代理:AOP的实现方式之一是通过动态代理,可以代理某个类的所有方法,用于实现各种功能。
- 综上所述,AOP可以应用于各种场景,它的作用是将通用的横切关注点与业务逻辑分离,使得代码更加清晰、简洁、易于维护。
一、AOP术语名词介绍
横切关注点
- 从每个方法中抽取出来的同一类非核心业务。
- 在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强。
- 这个概念不是语法层面天然存在的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点。
-
AOP
把软件系统分为两个部分:核心关注点
和横切关注点
。- 业务处理的主要流程是核心关注点,非核心代码是横切关注点。
横切关注点
的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事务、异常等。- AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。(分离核心业务代码与非核心代码
-
通知(增强)
- 每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。
- 前置通知:在被代理的目标方法前执行
- 返回通知:在被代理的目标方法成功结束后执行(寿终正寝)
- 异常通知:在被代理的目标方法异常结束后执行(死于非命)
- 后置通知:在被代理的目标方法最终结束后执行(盖棺定论)
- 环绕通知:使用
try...catch...finally
结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
- 每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。
-
切入点 pointcut
定位连接点的方式,或者可以理解成被选中的连接点!
是一个表达式,比如execution(* com.spring.service.impl..(…))。
符合条件的每个方法都是一个具体的连接点。 -
切面 aspect
切入点和通知的结合。是一个类。
-
目标 target
被代理的目标对象。 -
代理 proxy
向目标对象应用通知之后创建的代理对象。 -
织入 weave
指把通知应用到目标上,生成代理对象的过程。
可以在编译期织入,也可以在运行期织入,Spring采用后者。
二、Spring AOP框架介绍和关系梳理
AOP
一种区别于OOP
的编程思维,用来完善和解决OOP的非核心代码冗余和不方便统一维护问题!- 代理技术(动态代理|静态代理)是实现
AOP思维编程
的具体技术,但是自己使用动态代理实现代码比较繁琐! Spring AOP框架
,基于AOP编程思维,封装动态代理技术,简化动态代理技术实现的框架!- SpringAOP内部帮助我们实现动态代理,
- 我们只需写少量的配置,指定生效范围即可完成面向切面思维编程的实现!
三、Spring AOP基于注解方式实现和细节
3.1 Spring AOP底层技术组成
- 动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。
- 因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。
- cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。
- AspectJ:早期的AOP实现的框架,SpringAOP借用了AspectJ中的AOP注解。
3.2 初步实现
- 导入依赖
<!-- spring-aspects会帮我们传递过来aspectjweaver -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>6.0.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.0.6</version>
</dependency>
- 准备接口
public interface Calculator {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
- 纯净实现类
/**
* @Description: 实现计算接口,单纯添加加减乘除 实现,掺杂其它功能
* AOP -》 只针对IOC容器对象 - 创建代理对象 -> 将代理对象存储到IOC容器
*/
@Component
public class CalculatorImpl implements Calculator {
@Override
public int add(int i, int j) {
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
return result;
}
}
- 声明切面类
package com.doug.advice;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
/**
* @Description: 增强类 实现增强方法
*/
@Component
@Aspect // @Aspect表示这个类是一个切面类
public class LogAspect {
/*
* 1. 定义方法存储增强代码
* 具体定义几个方法,根据插入的位置决定!
* 2. 使用配置注解 指定插入目标方法的位置
* 前置 @Before
* 后置 @AfterReturning
* 异常 @AfterThrowing
* 最后 @After
* 环绕 @Around
*
* try{
* 前置
* 目标方法执行
* 后置
* }catch(){
* 异常
* }finally{
* 最后
* }
*
* 3.配置切点表达式 [选择要插入的方法 切点
* 4.注解补全
* 加入IOC容器 @Component
* 配置切面 @Aspect
* 5.开启Aspect注解的支持
* */
@Before("execution(* com.doug.aop.impl.*.*(..))")
public void start(){
System.out.println("方法开始了");
}
@After("execution(* com.doug.aop.impl.*.*(..))")
public void after(){
System.out.println("方法结束了");
}
@AfterThrowing("execution(* com.doug.aop.impl.*.*(..))")
public void error(){
System.out.println("方法报错了");
}
}
- 配置类
@Configuration
@ComponentScan("com.doug")
@EnableAspectJAutoProxy // 开启Aspectj的注解 xml: <aop:aspectj-autoproxy />
public class MyConfiguration {
}
- 测试
@SpringJUnitConfig(value = MyConfiguration.class)
public class TestAop {
@Autowired
private Calculator calculator;
@Test
public void aopTest(){
int add = calculator.add(1, 0);
System.out.println(add);
}
}
3.3 获取通知细节信息
- JointPoint接口
- JoinPoint 接口通过
getSignature()
方法获取目标方法的签名(方法声明时的完整信息)
- JoinPoint 接口通过
- 方法返回值
- 在返回通知中,通过 @AfterReturning注解的
returning
属性获取目标方法的返回值!
- 在返回通知中,通过 @AfterReturning注解的
- 异常对象捕捉
- 在异常通知中,通过**@AfterThrowing**注解的
throwing
属性获取目标方法抛出的异常对象
- 在异常通知中,通过**@AfterThrowing**注解的
package com.doug.advice;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.lang.reflect.Modifier;
/**
* @Description: 定义四个增强方法,获取目标方法的信息 返回值 异常
* 1. 定义方法 - 增强代码
* 2. 使用注解指定对应的位置
* 3. 配置切点表达式选中方法
* 4. 切面和IOC的配置
* 5. 开启aspectj注解的支持
*/
@Component
@Aspect
public class MyAdvice {
/*
* 增强方法中获取目标方法信息:
*
* 1. 全部增强方法中,获取目标方法的信息(方法名,参数,访问修饰符,所属类的信息...)
* (JoinPoint joinPoint) import org.aspectj.lang.JoinPoint;
*
* 2. 返回结果 - @AfterReturning
* (Object result) result 接受返回的结果
* @AfterReturning(value = "execution(* com..impl.*.*(..))",returning = "result")
*
* 3. 异常信息 - @AfterThrowing
* (Throwing t) t接收异常信息
* @AfterThrowing(value = "execution(* com..impl.*.*(..))",throwing = "result")
* */
@Before("execution(* com..impl.*.*(..))")
public void start(JoinPoint joinPoint){
// 1. 获取方法属于类的信息
String simpleName = joinPoint.getTarget().getClass().getSimpleName();
// 2. 获取方法名称
int modifiers = joinPoint.getSignature().getModifiers();
String s = Modifier.toString(modifiers);
String name = joinPoint.getSignature().getName();//获取方法名
// 3. 获取参数列表
Object[] args = joinPoint.getArgs();
}
@AfterReturning(value = "execution(* com..impl.*.*(..))",returning = "result")
public void afterReturn(JoinPoint joinPoint,Object result){
}
@After("execution(* com..impl.*.*(..))")
public void after(){
}
@AfterThrowing(value = "execution(* com..impl.*.*(..))",throwing = "result")
public void afterThrowing(Throwable result){
}
}
3.4 切点表达式语法
切点表达式作用
AOP切点表达式(Pointcut Expression)是一种用于指定切点的语言,它可以通过定义匹配规则,来选择需要被切入的目标对象。
切点表达式语法
语法细节:
- 实战:
1.查询某包某类下,访问修饰符是公有,返回值是int的全部方法
execution(public int com.doug.xClass.*(..))
2.查询某包下类中第一个参数是String的方法
execution(* com.doug.xClass.*(String..))
3.查询全部包下,无参数的方法!
execution(* *..*.*())
4.查询com包下,以int参数类型结尾的方法
execution(* com..*.*(..int))
5.查询指定包下,Service开头类的私有返回值int的无参数方法
execution(private int com.doug.Service*.*())
3.5 重用(提取)切点表达式
提取重复的切点表达式
切点统一管理:
将切点表达式统一存储到一个类中进行集中管理和维护!
3.6 环绕通知
三合一(四合一)
3.7 切面优先级设置
相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。
- 优先级高的切面:外面
- 优先级低的切面:里面
使用 @Order 注解可以控制切面的优先级:
- @Order(较小的数):优先级高
- @Order(较大的数):优先级低
实际意义:
实际开发时,如果有多个切面嵌套的情况,要慎重考虑。例如:如果事务切面优先级高,那么在缓存中命中数据的情况下,事务切面的操作都浪费了。
此时应该将缓存切面的优先级提高,在事务操作之前先检查缓存中是否存在目标数据。
3.8 CGLib动态代理生效
在目标类没有实现任何接口的情况下,Spring会自动使用cglib技术实现代理。
总结:
a. 如果目标类有接口,选择使用jdk动态代理
b. 如果目标类没有接口,选择cglib动态代理
c. 如果有接口,接口接值
d. 如果没有接口,类进行接值
3.9 注解实现小结
四、Spring AOP对获取Bean的影响理解
对实现了接口的类应用切面
对没实现接口的类应用切面new
如果使用AOP技术,目标类有接口,必须使用接口类型接收IoC容器中代理组件!