SSM框架学习-AOP学习笔记
一、AOP入门简介
AOP(Aspect Oriented Programming)面向切面编程,是一种编程范式,可以知道开发者如何组织程序结构
作用:在不惊动原始设计的基础上为其进行功能增强。(无侵入式编程)
AOP的核心概念:
- 连接点(JoinPoint):程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等。
- 在SpringAOP中可以理解为方法的执行。
- 切入点(PointCut):匹配连接点的式子
- 在SpringAOP中,一个切入点只可以描述 一个具体的方法,也可以匹配多个方法。
- 一个具体方法:com.huawei.dao包下的BookDao接口中的无形参无返回值的save方法。
- 匹配多个方法:所有的save方法,所有的get开头的方法,所有以Dao结尾的接口中的任意方法,所有带有一个参数的方法。
- 在SpringAOP中,一个切入点只可以描述 一个具体的方法,也可以匹配多个方法。
- 通知(Advice):在切入点处执行的操作,也就是共性功能。
- 在SpringAOP中,功能最终会以 方法的形式呈现出来。
- 通知类:定义通知的类。
- 切面(Aspect):描述通知与切入点的对应关系。
连接点代表所有的方法,切入点代表要追加功能的方法,在范围上连接点是包含切入点的。
二、AOP工作流程
1、Spring容器启动
2、读取所有切面配置中的切入点
3、初始化bean,判断bean对应的类中的方法是否匹配到任意切入点
- 匹配失败、创建对象
- 匹配成功,创建原始对象(目标对象)的代理对象
4、获取bea执行方法
- 获取bean,调用方法并执行,完成操作
- 获取到的bean是代理对象的时候,根据代理对象的运行模式运行原始方法与增强的内容来完成操作
AOP的核心概念
- 目标对象(Target):原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的;
- 代理(Proxy):目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现。
三、AOP切入点表达式
切入点:要增强的方法
切入点表达式:要进行增强的方法的描述方式
描述方式一:执行com.huawei.dao包下的BookDao接口中的无参数update方法
execution(void com.huawei.dao.BookDao.update())
描述方式二:执行com.huawei.dao.impl包下的BookDaoImpl类中的无参数update方法
execution(void com.huawei.dao.impl.BookDaoImpl.update())
切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数)异常名)
可以使用通配符描述切入点,快速描述
*
:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
execution(public * com.huawei.*.UserService.find*(*))
..
:多个连续的任意符号,可以独立出现,常用于简化包名和参数的书写
execution(public User com..UserService.findById(..))
+
:专用于匹配子类的类型
execution(* *..*Service+.*(..))
书写技巧
- 所有代码按照规范标准开发,否则以下的这些技巧会全部失效
- 描述切入点通常描述接口,而不去描述实现类
- 访问修饰符对接口开发均采用public描述(可以省略访问控制修饰符描述)
- 返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用*通配快速描述
- 包名书写尽量不要使用..来匹配,效率过低,通常用*来做单个包的描述匹配,或者精准匹配
- 接口名/类名书写名称与模块相关的采用
*
匹配,例如UserService书写成*
Service,绑定业务接口名 - 方法名书写应该以动词进行精确匹配,名词采用
*
匹配,例如getById书写成getBy*
,selectAll写成selectAll - 参数规则较为复杂,根据业务方法灵活调整
- 通常不使用异常作为匹配规则
四、AOP通知类型
AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码的时候需要将其加入到合理的位置。
AOP通知可以分为5种类型:
- 前置通知
- 后置通知
- 环绕通知(重点)
- 返回后通知(了解)
- 抛出异常后通知(了解)
4.1 前置通知
@Before("pt()")
public void before() {
System.out.println("before advice...");
}
4.2 后置通知
@After("pt()")
public void after() {
System.out.println("after advice...");
}
4.3 环绕通知(重点)
@Around("pt()")
public void around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around before advice...");
// 表示对原始程序的调用
pjp.proceed();
System.out.println("around after advice...");
}
如果我们采取环绕通知的原函数是有返回值的,那么我们也需要采取对应的写法完善这部分内容:
@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;
}
@Around注意事项:
1、环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知。
2、通知中如果未使用ProceedingJoinPoing对原始方法进行调用的话就会直接跳过原始方法的执行。
3、对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果需要接收返回值的话,必须设置成Object类型。
4、原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成Object。
5、由于无法预知原始方法运行之后是否会抛出异常,因此环绕通知方法必须抛出Throwable对象。
4.4 返回后通知
与正常的后置通知的区别是,返回后通知只有在源程序没有抛出异常,正常结束的情况下才会使用。(了解即可,实际并不常用)
4.5 抛出异常后通知
只有在源程序抛出异常的情况下才会被调用,如果源程序正常结束而没有抛出任何异常的话,则不会被调用。
五、AOP通知获取数据
- 获取切入点方法的参数
- JoinPoint:适用于前置、后置、返回后、抛出异常后通知
- ProceedJointPoint:适用于环绕通知
- 获取切入点方法的返回值
- 返回后通知
- 环绕通知
- 获取切入点方法的运行异常信息
- 抛出异常后通知
- 环绕通知
5.1 对于参数的获取
前置后置方法获取函数参数的demo:
@Before("pt()")
public void before(JoinPoint pj) {
Object[] args = pj.getArgs();
system.out.println(Arrays.toString(args));
System.out.println("before dao ...");
}
环绕通知方法可以拿到参数之后进行一系列处理再传入,具体实现如下:
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
System.out.println(Arrays.toString(args));
// 可以在此处对数据进行清洗或者进一步操作
args[0] == 666;
Object ret = pjp.proceed(args); // 此时就是使用我们新传入的参数进行相关的业务操作了
return ret;
}
5.2 对于返回值的获取
采用环绕通知获取返回值的方法上面已经展示了,因此此处不再赘述。
返回后通知demo:
@AfterReturning(value="pt()", returning="ret") // 此处returning属性就是代表接收返回值的
public void afterReturning(Object ret) {
System.out.println("afterReturning result is : " + ret);
}
需要注意的是,如果afterReturning方法中需要连接点的话,参数位置必须是第一个,也可以直接省略不写,
public void afterReturning(JoinPoint jp, Object ret)
5.3 对于异常的获取
环绕通知中只需要使用try-catch
块包围即可,之后我们可以在catch中对捕获到的异常做出对应的操作。
抛出异常后通知demo:
@AfterThrowing(value="pt()", throwing="t")
public void afterThrowing(Throwable t) {
System.out.println("afterThrowing dao ...");
}
六、案例及总结
6.1 案例
需求:对百度网盘分享链接输入密码时尾部多输入的空格进行兼容性处理
分析:
- 在业务方法执行之前对所有的输入参数进行格式处理
- 适用处理后的参数再去调用原始方法 —— 环绕通知中存在对原始方法的调用
核心实现代码:
@Around("servicePt()")
public Object trimStr(ProceedingJoinPoint pjp) throw Throwable {
Object[] args = pjp.getArgs();
// 循环判断是否需要去除空格
for (int i = 0; i < args.length; i++) {
// 如果判定当前参数是字符串的话,则执行去空格操作
if (args[i].getClass.equals(String.getClass())) {
args[i] = args[i].toSrting().trim();
}
}
Object ret = pjp.proceed(args);
return ret;
}
6.2 AOP学习总结
在AOP的实际实现中,我们比较常用的是环绕通知:
- 环绕通知依赖形参ProceedingJoinPoint才能够实现对原始方法的调用
- 环绕通知可以隔离原始方法的调用执行
- 环绕通知的返回值设置为Object类型
- 环绕通知可以对原始方法调用过程中出现的异常进行处理。