AOP能干啥?
- 解决代码重复性问题 将公共的事情通过切面去完成
- 解决关注点分离
- 水平分离 展示层 > 服务层 > 持久层
- 垂直分离 模块划分(订单、库存、用户等)
- 切面分离(aop) 分离功能性需求和非功能性需求;
aop到底能做什么呢?
如:权限控制
、缓存控制
、事务控制
、审计日志
、性能监控
、分布式追踪
、异常处理
、数据认证
都可以使用aop;为什么这些可以使用aop呢?
这一部分功能他与业务没有啥关系,但是他们的公用性非常的强,不管啥操作、啥业务,可能都需要这些;举个很常用的例子,当我们做java web开发的时候,如果我想打印出所有接口请求及响应的数据日志,我们要怎么打?这个关注点(通用需求)它与业务无关,任何业务、任何请求都得打;那我们要怎么做?在每个Controller的每一个方法里面都去打印?可是可以,但是这很显然是一个苦逼、无聊且没有任何营养的体力活;这个时候,我们就可以通过AOP去完美的解决这个问题,具体怎么做,后面再说。
支持AOP的常用编程语音
- Java
- Python
- .NET
- c/c
- PHP
- Ruby
- ......
AOP的原理
当我们剥开aop的外衣的时候,其实他的核心设计思想就是代理模式
;spring中大量用到了代理模式;如果你不太了解代理模式,其实也不影响你对aop的使用;这里我举个生活中的例子,带你了解一下什么代理模式;当你在美团、饿了么点餐的时候,其实就是一个典型的代理模式,美团(代理对象
)代理了餐馆(目标对象
)将美食(方法
)卖给你,同时对你的消费进行了增强
(帮你配送、送你优惠券等);帮你配送、送你赠品并不是餐厅做的;而是美团(代理对象)做的;但是这一切并没有影响到你就餐、也没有影响餐厅对商品的销售;aop同样也使用的这个方式,在不影响目标对象的前提下对他的功能进行增强。
Spring AOP与AspectJ的关系
Spring AOP只是引用了AspectJ相关的注解,借鉴了他的编程风格,具体的实现是由Spring自己做的,并没有依赖AspectJ编译器,因此核心的东西他们没有直接的关系。spring和aspect在实现上还存在本质上的区别,spring是在运行时动态生成的代理对象,aspect是在编译的时候就生成了代理对象;所以同样的面容,不同的灵魂...
Spring AOP关键词
- Aspect(切面)
对多个类的一个关注点的表达
;什么是关注点,通俗一点就是说,大家都要做或者都关心的事情;比如说:系统的权限校验,可能登录、下单、操作都涉及到用户权限的校验,那么权限校验就是关注点,在切面中,要明确的表达出切入的点、切入的时机,织入什么等等!。所以切面是对以下所有知识点的一个综合的理解。 - Join point(连接点) 程序执行过程中aop要作用的一个点(如:方法的执行、异常处理)。spring中连接点始终是代表着方法的执行。通俗点理解,连结点就代表这一个方法;如果还是觉得概念有点空可以看了下面的pointcut之后再来理解一下连接点;
- Pointcut(切入点) 通过
表达式
匹配出来的某一类(些)连接点(方法)的集合;切入点和连接点(Join point)是相辅相成的;简单理解就是,切入点通过表达式描述来匹配出对应的方法(连接点);
/**
* 所有的public方法
* 以下public * *(..)这个表达式匹配到的所有方法 都称之为连接点
*/
@Pointcut("execution(public * *(..))")
public void ex_pc1() {
}
- Advice(通知) 通知要分两个维度去理解:1、增强什么?2、什么时候增强(执行时机)?切面在特定的连接点(Spring下表示某个方法的执行)处基于某个时机采取的操作我们称之为通知;通知作用于连接点的时机有以下5种:
Before
(之前)、After
(之后)、AfterReturning
(成功执行之后)、AfterThrowing
(执行异常)、Around
(环绕,该执行时机可以包含前面四种执行时机);实例如下:
/**
* Before的执行时机是在方法执行之前执行
* 这里可以通过运算符去匹配多个切入点
*/
@Before("ex_pc1() || ex_pc2()")
public void advice1() {
System.out.println("aop before..........");
}
- Introduction 用于声明其他的方法或者属性,在Spring Aop中可以通过一个接口及其接口的实现将其添加到目标对象中,让目标对象具备这个接口实现的功能。
- target object(目标对象) 通过切入点的表达式匹配出来的所有类我们称之为目标对象,再由Spring Aop通过运行时代理实现,因此对于使用者来说他看到的始终是一个代理对象。
- Aop Proxy(Aop代理) Aop框架基于目标对象创建的对象称之为目标对象,其目的用于执行通过切面添加的方法;在Spring中AOP代理使用的
JDK动态代理
或者CGLIB代理
- Weaving(织入) 将某个
增强
基于切入点
在特定的时机
作用于连接点
的过程叫做织入;
Spring AOP的使用方式
- 第一种 通过XML配置,目前开发都用的少了,因为用起来没有注解的方式简单,所以也就不推荐了;如果因为历史原因需要用到,可以去官网进行查看;
- 第二种 通过注解的方式进行配置 不管那种方式,其实目的就是切面的表达式、切面的内容
Spring AOP的使用
- mavne依赖(如果使用的springboot 测试)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- spring 依赖(spring源码进行测试)
// 这里使用的是5.1.x的spring源码进行的测试
// 由于spring源码是使用的gradle构建的 所以下面是gradle的引入
compile(project(":spring-aop"))
compile group: 'org.aspectj', name: 'aspectjweaver', version: '1.9.6'
- 所有的测试代码如下
// 自定义注解
package com.lupf.aop.anno;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Lupf {
}
// ==========================================================
// 业务测试bean
package com.lupf.aop.service;
import com.lupf.aop.anno.Lupf;
@Lupf
public class BusiBean {
}
// ==========================================================
// 接口I
package com.lupf.aop.service;
public interface I {
public String busi1();
public void busi2(String arg1,Integer arg2);
}
// ==========================================================
// ServiceA
package com.lupf.aop.service.dir;
import com.lupf.aop.anno.Lupf;
import com.lupf.aop.service.BusiBean;
import org.springframework.stereotype.Component;
@Lupf
@Component
public class ServiceA {
public String busi1(BusiBean busiBean){
System.out.println("ServiceA busi1.....");
return "bbb";
}
public void busi2(@Lupf String arg1){
System.out.println("ServiceA busi2.....");
}
}
// ==========================================================
// Service B
package com.lupf.aop.service;
import com.lupf.aop.anno.Lupf;
import org.springframework.stereotype.Component;
@Component
public class ServiceB implements I{
@Override
@Lupf
public String busi1(){
System.out.println("ServiceB busi1.....");
//System.out.println(1/0);
return "bbb";
}
@Override
public void busi2(String arg1,Integer arg2){
System.out.println("ServiceB busi2.....");
}
}
// ==========================================================
// 配置类
package com.lupf.aop;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
@Component
@ComponentScan("com.lupf.aop")
@EnableAspectJAutoProxy//(proxyTargetClass = true)
public class AppConfig {
}
// ==========================================================
// 项目启动类 及测试代码
package com.lupf.aop;
import com.lupf.aop.service.I;
import com.lupf.aop.service.dir.ServiceA;
import com.lupf.aop.service.ServiceB;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
ac.register(AppConfig.class);
ac.refresh();
ServiceA serviceA = ac.getBean(ServiceA.class);
serviceA.busi1(null);
serviceA.busi2("a");
I serviceB = ac.getBean(I.class);
System.out.println(serviceB.busi1());;
serviceB.busi2("a",);
}
}
- 结构如下,可以在你的项目下创建一个
com.lupf.aop
的目录,将上面的代码按一下的目录结构拷贝到项目中即可用于测试了。
切面表达式(expression)
通配符(wildcards)
*
(用于匹配任意字符)..
(匹配指定类及其子类,类似于java的泛型)
运算符(operators)
用来表达希望同时满足多个条件的表达式
&&
(与-两个都必须满足)||
(或) 有一个满足就行!
(非) 取反
指示器(designators)
主要用于描述,通过什么样的方式去匹配你想要操作的类对应的方法;
- 测试类AspectJDemo 为了方便测试,这里创建一个AspectJDemo的java类用于写测试用例(文末给出了完整的类文件)
@Component // 交由spring管理
@Aspect // 指定当前类为一个aop切面
@Order(0) // 当有多个切面的时候,指定执行顺序,越小优先级越高
public class AspectJDemo {
// 写测试用例
}
exection
/**
* 完整的格式如下
* exection(
* modifier-pattern? // 修饰符 允许不配置
* ret-type-pattern // 返回值
* declaring-type-pattern? // 描述包名 允许不配置
* name-pattern(param-pattern) // 描述方法名 方法参数
* throws-pattern? // 匹配抛出的异常 允许不配置
* )
*/
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
// 以下为测试用例
/**
* 所有的public方法
*/
@Pointcut("execution(public * *(..))")
public void ex_pc1() {
}
/**
* 只有带了特定返回值的类型
*/
@Pointcut("execution( String *(..))")
public void ex_pc2() {
}
/**
* 指定指定的包名 带特定请求参数的
*/
@Pointcut("execution(public * com.lupf.aop..*(String,..))")
public void ex_pc3() {
}
/**
* 匹配执行的方法名 所以这里只有方法名为busi1的才会被代理
*/
@Pointcut("execution(public * com.lupf.aop..*1(..))")
public void ex_pc4() {
}
within
within为execution的一种简化模式 使用起来更便捷 但是他没有execution功能强大;其只能指明具体的类或具体目录下类中的方法
/**
* 指定具体的类ServiceB下的所有方法
*/
@Pointcut("within(com.lupf.aop.service.ServiceB)")
public void w_pc2(){
}
/**
* 通配service目录及子目录的所有类
* com.lupf.aop.service..* 会匹配当前目录及子目录
* com.lupf.aop.service.* 只会匹配当前目录 子目录不会去匹配
*/
@Pointcut("within(com.lupf.aop.service..*)")
public void w_pc1() {
}
args
匹配指定的参数
/**
* 指明包含特定入参的方法,可以通过..进行多个参数的统配
* args(String,Integer) 只能是第一个参数为String 第二个为Integer的才能匹配
* args(String,Integer,..) 只要是前两个参数为String和Integer的都可以匹配上
* args(..,Integer) 最后一个参数为Integer的都可以匹配上
*/
@Pointcut("args(String,Integer)")
public void arg_pc1() {
}
this
用于匹配代理对象的类;这一部分是相对不好理解的,可以参考注释详细的测试一下
/**
* 这里指明了com.lupf.aop.service.ServiceB用于匹配代理后的对象
* <p>
* 为了方便测试 这里定义了一个接口I ServiceB实现了I
* <p>
* 第一次测试,
* 使用 @EnableAspectJAutoProxy 表示默认使用jdk动态代理
* 那么spring获取对象是 I serviceB = ac.getBean(ServiceB.class)
* 这样serviceB对象调用方法的时候,不会被增强
* 原因是因为jdk代理是基于接口,那么生成的代理对象如下
* public $ServiceBByJDK implements I {
* publis ServiceB serviceB;
* public void busi1(){
* service.busi();
* }
*
* // other....
* }
* 所以$ServiceBByJDK无法匹配上ServiceB,增强失败
* <p>
* <p>
* ===============================================================
* <p>
* 第二次测试
*
* @EnableAspectJAutoProxy(proxyTargetClass = true) 强制使用cglib
* 那么就会得到一个基于cglib的代理实现
* 最终的代理对象效果如下
* public ServiceB$$EbhabcerBySpringCGLIB&&xxxx extend ServiceB implements I
* {
* // do something
* }
* 因为cglib代理是基于继承 因此生成的代理对象是ServiceB的子类,所以这里就可以匹配上
* 第二次测试执行的ServiceB中的所有方法都是可以增强的
*/
@Pointcut("this(com.lupf.aop.service.ServiceB)")
public void this_pc1() {
}
target
匹配动态代理之前 目标对象为指定对象的类
/**
* 由于是匹配的目标对象 因此就不会区分使用的什么代理方式了
* 因此jdk和cglib代理的对象都是可以增强的
*/
@Pointcut("target(com.lupf.aop.service.ServiceB)")
public void target_pc1() {
}
@within
匹配加了指定注解的类
/**
* 类上面加了指定注解,那么该类下所有的方法执行都增强
* <p>
* 注意:该方式只作用于类上面 如果注解只写在方法上没加在类上面,那么增强是无法生效的
*/
@Pointcut("@within(com.lupf.aop.anno.Lupf)")
public void anno_within_pc() {
}
@target
目标对象是否添加指定的注解
/**
* 代理对象的时候 目标对象添加了指定注解类的方法执行都会进行增强
*/
@Pointcut("@target(com.lupf.aop.anno.Lupf)")
public void anno_target_pc() {
}
@开头的都是作用于注解
@args
/**
* 方法参数带有指定注解的
* <p>
* 如下测试,定义一个测试对象
*
* @Lupf public class BusiBean {
* }
* <p>
* 如下的方法是会进行增强的
* public String busi1(BusiBean busiBean)
* <p>
* 下面这种方式是不会进行增强的
* public void busi2(@Lupf String arg1)
*/
@Pointcut("@args(com.lupf.aop.anno.Lupf)")
public void anno_args_pc() {
}
@annotation
加了指定注解的方法
/**
* 加了指定注解的方法调用的时候都会进行增强
* <p>
* 注意:这里只有方法,加在类上面是没有用的,和@within是对立的
*/
@Pointcut("@annotation(com.lupf.aop.anno.Lupf)")
public void anno_pc1() {
}
特别说明
需要匹配多个表达式的时候,可以使用运算符进行匹配
// 实例 表示添加了指定注解且只有一个String参数的方法
@Pointcut("@annotation(com.lupf.aop.anno.Lupf) && args(String)")
AOP的5种通知时机
@Before
在目标方法执行之前执行增强的代码
/**
* Before的执行时机是在方法执行之前执行
* 这里可以通过运算符去匹配多个切入点
*/
@Before("w_pc1()")
public void advice1() {
System.out.println("aop before..........");
}
@AfterReturning
在目标方法成功执行
之后执行增强
/**
* 这个通知相对于After就更加明确了
* 只有在执行成功之后才会通知
* <p>
* 如果不需要管结果,那么我们只需要指定好切点就好了
* 如果我们需要拿到想要结果,就可以通过returning指定一个字段名称,并通过名称拿到响应数据
* 同时这里是可以指定具体的响应数据类型,当指定具体类型之后,就只有返回指定类型的数据才会触发通知
* <p>
* 这里是否需要返回 并不会影响到调用方
*
* @param re 响应数据
*/
// @AfterReturning("anno_pc1()")
@AfterReturning(pointcut = "anno_pc1()", returning = "re")
public void advice3(Object re) {
System.out.println("aop after reutrn........re:" re);
}
@AfterThrowing
目标方法执行异常
之后执行增强
/**
* 当方法执行出现异常之后,就会触发这个通知
* <p>
* 如果不需要管异常是什么,那么我们只需要指定好切点就好了
* 如果我们需要拿到异常,就可以通过throwing指定一个字段名称,并通过名称拿到相应的异常
* 同时这里是可以指定具体的异常类型,当指定具体类型之后,就只有抛出指定类型的异常才会触发通知
*
* @param ex
*/
//@AfterThrowing("anno_pc1()")
@AfterThrowing(pointcut = "anno_pc1()", throwing = "ex")
public void advice4(Exception ex) {
System.out.println("aop after exception msg:" ex.getMessage());
}
@After
目标方法不管执行成功还是失败
都会执行增强
/**
* 这个通知相对于After就更加明确了
* 只有在执行成功之后才会通知
* <p>
* 如果不需要管结果,那么我们只需要指定好切点就好了
* 如果我们需要拿到想要结果,就可以通过returning指定一个字段名称,并通过名称拿到响应数据
* 同时这里是可以指定具体的响应数据类型,当指定具体类型之后,就只有返回指定类型的数据才会触发通知
* <p>
* 这里是否需要返回 并不会影响到调用方
*
* @param re 响应数据
*/
// @AfterReturning("anno_pc1()")
@AfterReturning(pointcut = "anno_pc1()", returning = "re")
public void advice3(Object re) {
System.out.println("aop after reutrn........re:" re);
}
@Around
环绕通知囊括了上面的4种情况,包含了所有的执行时机;所以这种方式是最常用的
/**
* 坏绕通知 囊括了上面4种通知时机
*
* @param joinPoint
* @return 响应数据
* @throws Throwable
*/
@Around("anno_pc1()")
public Object advice4(ProceedingJoinPoint joinPoint) throws Throwable {
//在这里相当于@Before
System.out.println("aop around before.........");
// 获取所有参数
Object[] args = joinPoint.getArgs();
try {
// 执行目标方法并获取响应对象
Object o = joinPoint.proceed(args);
// 到这里相当于@AfterReturning
System.out.println("aop around after returning.........");
// 返回数据
// 这里不同于@Before 这里如果不返回,调用方将拿不到响应数据
return o;
} catch (Throwable t) {
// 这里相当于@AfterThrowing
System.out.println("aop around after throwing.........");
throw t;
} finally {
// 到这里相当于@After 异常和正常只会出现其中一种情况
System.out.println("aop around after.........");
}
}
特别说明
,执行时机同样可以通过算术运算符匹配对各切入点,如:
@Before("w_pc1() && w_pc2()")
AOP执行优先级
当多个切面作用于同一个方法的的时候,可以通过在切面类上加@Order(0)
来指定优先级,数越小,优先级越高。
- 完整的测试用例 为了不占用篇幅,放在了最后
package com.lupf.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component // 交由spring管理
@Aspect // 指定当前类为一个aop切面
@Order() // 当有多个切面的时候,指定执行顺序,越小优先级越高
public class AspectJDemo {
/**
* exection(
* modifier-pattern? // 修饰符 允许不配置
* ret-type-pattern // 返回值
* declaring-type-pattern? // 描述包名 允许不配置
* name-pattern(param-pattern) // 描述方法名 方法参数
* throws-pattern? // 匹配抛出的异常 允许不配置
* )
*/
/**
* 所有的public方法
* 以下public * *(..)这个表达式匹配到的所有方法 都称之为连接点
*/
@Pointcut("execution(public * *(..))")
public void ex_pc1() {
}
/**
* 只有带了特定返回值的类型
*/
@Pointcut("execution( String *(..))")
public void ex_pc2() {
}
/**
* 指定指定的包名 带特定请求参数的
*/
@Pointcut("execution(public * com.lupf.aop..*(String,..))")
public void ex_pc3() {
}
/**
* 匹配执行的方法名 所以这里只有方法名为busi1的才会被代理
*/
@Pointcut("execution(public * com.lupf.aop..*1(..))")
public void ex_pc4() {
}
// within 指明具体的类或者具体的包路径
// within 为execution的一种简化模式 使用起来更便捷 但是他没有execution功能强大
/**
* 指定具体的类ServiceB下的所有方法
*/
@Pointcut("within(com.lupf.aop.service.ServiceB)")
public void w_pc1() {
}
/**
* 通配service目录及子目录的所有类
* com.lupf.aop.service..* 会匹配当前目录及子目录
* com.lupf.aop.service.* 只会匹配当前目录 子目录不会去匹配
*/
@Pointcut("within(com.lupf.aop.service..*)")
public void w_pc2() {
}
/**
* 指明包含特定入参的方法,可以通过..进行多个参数的统配
* args(String,Integer) 只能是第一个参数为String 第二个为Integer的才能匹配
* args(String,Integer,..) 只要是前两个参数为String和Integer的都可以匹配上
* args(..,Integer) 最后一个参数为Integer的都可以匹配上
*/
@Pointcut("args(String,Integer)")
public void arg_pc1() {
}
// this 表示匹配代理对象
// spring aop 默认使用的jdk动态代理
// 可以在启动类添加@EnableAspectJAutoProxy(proxyTargetClass = true) 强制使用cglib
/**
* 这里指明了com.lupf.aop.service.ServiceB用于匹配代理后的对象
* <p>
* 为了方便测试 这里定义了一个接口I ServiceB实现了I
* <p>
* 第一次测试,
* 使用 @EnableAspectJAutoProxy 表示默认使用jdk动态代理
* 那么spring获取对象是 I serviceB = ac.getBean(ServiceB.class)
* 这样serviceB对象调用方法的时候,不会被增强
* 原因是因为jdk代理是基于接口,那么生成的代理对象如下
* public $ServiceBByJDK implements I {
* publis ServiceB serviceB;
* public void busi1(){
* service.busi();
* }
* <p>
* // other....
* }
* 所以$ServiceBByJDK无法匹配上ServiceB,增强失败
* <p>
* <p>
* ===============================================================
* <p>
* 第二次测试
*
* @EnableAspectJAutoProxy(proxyTargetClass = true) 强制使用cglib
* 那么就会得到一个基于cglib的代理实现
* 最终的代理对象效果如下
* public ServiceB$$EbhabcerBySpringCGLIB&&xxxx extend ServiceB implements I
* {
* // do something
* }
* 因为cglib代理是基于继承 因此生成的代理对象是ServiceB的子类,所以这里就可以匹配上
* 第二次测试执行的ServiceB中的所有方法都是可以增强的
*/
@Pointcut("this(com.lupf.aop.service.ServiceB)")
public void this_pc1() {
}
// 匹配动态代理之前 目标对象为指定对象的类
/**
* 由于是匹配的目标对象 因此就不会区分使用的什么代理方式了
* 因此jdk和cglib代理的对象都是可以增强的
*/
@Pointcut("target(com.lupf.aop.service.ServiceB)")
public void target_pc1() {
}
/**
* 类上面加了指定注解,那么该类下所有的方法执行都增强
* <p>
* 注意:该方式只作用于类上面 如果注解只写在方法上没加在类上面,那么增强是无法生效的
*/
@Pointcut("@within(com.lupf.aop.anno.Lupf)")
public void anno_within_pc() {
}
/**
* 代理对象的时候 目标对象添加了指定注解类的方法执行都会进行增强
*/
@Pointcut("@target(com.lupf.aop.anno.Lupf)")
public void anno_target_pc() {
}
/**
* 方法参数带有指定注解的
* <p>
* 如下测试,定义一个测试对象
*
* @Lupf public class BusiBean {
* }
* <p>
* 如下的方法是会进行增强的
* public String busi1(BusiBean busiBean)
* <p>
* 下面这种方式是不会进行增强的
* public void busi2(@Lupf String arg1)
*/
@Pointcut("@args(com.lupf.aop.anno.Lupf)")
public void anno_args_pc() {
}
/**
* 加了指定注解的方法调用的时候都会进行增强
* <p>
* 注意:这里只有方法,加在类上面是没有用的,和@within是对立的
*/
@Pointcut("@annotation(com.lupf.aop.anno.Lupf)")
public void anno_pc1() {
}
/**
* Before的执行时机是在方法执行之前执行
* 这里可以通过运算符去匹配多个切入点
*/
@Before("w_pc1() && w_pc2()")
public void advice1() {
System.out.println("aop before..........");
}
/**
* After执行时机是在方法执行完之后
* 这个执行并不会管执行成功还是失败,都会触发这个通知
*/
@After("anno_pc1()")
public void advice2() {
System.out.println("aop after.............");
}
/**
* 这个通知相对于After就更加明确了
* 只有在执行成功之后才会通知
* <p>
* 如果不需要管结果,那么我们只需要指定好切点就好了
* 如果我们需要拿到想要结果,就可以通过returning指定一个字段名称,并通过名称拿到响应数据
* 同时这里是可以指定具体的响应数据类型,当指定具体类型之后,就只有返回指定类型的数据才会触发通知
* <p>
* 这里是否需要返回 并不会影响到调用方
*
* @param re 响应数据
*/
// @AfterReturning("anno_pc1()")
@AfterReturning(pointcut = "anno_pc1()", returning = "re")
public void advice3(Object re) {
System.out.println("aop after reutrn........re:" re);
}
/**
* 当方法执行出现异常之后,就会触发这个通知
* <p>
* 如果不需要管异常是什么,那么我们只需要指定好切点就好了
* 如果我们需要拿到异常,就可以通过throwing指定一个字段名称,并通过名称拿到相应的异常
* 同时这里是可以指定具体的异常类型,当指定具体类型之后,就只有抛出指定类型的异常才会触发通知
*
* @param ex
*/
//@AfterThrowing("anno_pc1()")
@AfterThrowing(pointcut = "anno_pc1()", throwing = "ex")
public void advice4(Exception ex) {
System.out.println("aop after exception msg:" ex.getMessage());
}
/**
* 坏绕通知 囊括了上面4种通知时机
*
* @param joinPoint
* @return 响应数据
* @throws Throwable
*/
@Around("anno_pc1()")
public Object advice4(ProceedingJoinPoint joinPoint) throws Throwable {
//在这里相当于@Before
System.out.println("aop around before.........");
// 获取所有参数
Object[] args = joinPoint.getArgs();
try {
// 执行目标方法并获取响应对象
Object o = joinPoint.proceed(args);
// 到这里相当于@AfterReturning
System.out.println("aop around after returning.........");
// 返回数据
// 这里不同于@Before 这里如果不返回,调用方将拿不到响应数据
return o;
} catch (Throwable t) {
// 这里相当于@AfterThrowing
System.out.println("aop around after throwing.........");
throw t;
} finally {
// 到这里相当于@After 异常和正常只会出现其中一种情况
System.out.println("aop around after.........");
}
}
}
关于理解及使用就写到这里,以上纯属个人理解,如果存在不对的地方,欢迎留言指正!!!后续会一起来分析一下spring是如何去实现aop的;