35-AOP通知获取数据
目前我们写AOP仅仅是在原始方法前后追加一些操作,接下来我们要说说AOP中数据相关的内容,我们将从获取参数、获取返回值和获取异常三个方面来研究切入点的相关信息。
前面我们介绍通知类型的时候总共讲了五种,那么对于这五种类型都会有参数,返回值和异常吗?
我们先来逐一分析下:
-
获取切入点方法的参数,所有的通知类型都可以获取参数
JoinPoint
:适用于前置、后置、返回后、抛出异常后通知ProceedingJoinPoint
:适用于环绕通知
-
获取切入点方法返回值,前置和抛出异常后通知是没有返回值,后置通知可有可无,所以不做研究
- 返回后通知
- 环绕通知
-
获取切入点方法运行异常信息,前置和返回后通知是不会有,后置通知可有可无,所以不做研究
- 抛出异常后通知
- 环绕通知
环境准备
- 创建一个maven项目
- 添加Spring依赖
<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>
- 添加BookDao和BookDaoImpl类
public interface BookDao {
String findName(int id);
}
@Repository
public class BookDaoImpl implements BookDao {
@Override
public String findName(int id) {
System.out.println("id:" + id);
//返回一个固定值
return "TestName";
}
}
- 创建Spring配置类
@Configuration
@ComponentScan("com.yolo")
@EnableAspectJAutoProxy
public class SpringConfig {
}
- 编写App运行类
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = ctx.getBean(BookDao.class);
String name = bookDao.findName(100);
System.out.println(name);
}
}
运行程序,结果如下
- 编写通知类
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(* com.yolo.dao.BookDao.findName(..))")
public void pt(){}
// @Before("pt()")
public void before(){
System.out.println("before advice ...");
}
// @After("pt()")
public void after(){
System.out.println("after advice ...");
}
// @Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object res = pjp.proceed();
return res;
}
// @AfterReturning("pt()")
public void afterReturning(){
System.out.println("afterReturning advice ...");
}
// @AfterThrowing("pt()")
public void afterThrowing(){
System.out.println("afterThrowing advice ...");
}
}
获取参数
非环绕通知获取方式
在方法上添加JoinPoint
,通过JoinPoint来获取参数
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(* com.yolo.dao.BookDao.findName(..))")
public void pt(){}
@Before("pt()")
public void before(JoinPoint jp){
Object[] args = jp.getArgs();
System.out.println(Arrays.toString(args));
System.out.println("before advice ...");
}
}
运行App类,可以获取如下内容,说明参数100已经被获取
- 思考:方法的参数只有一个,为什么获取的是一个数组?
- 因为参数的个数是不固定的,所以使用数组更通配些。
- 如果将参数改成两个会是什么效果呢?
- 修改BookDao和BookDaoImpl类
public interface BookDao {
String findName(int id, String password);
}
@Repository
public class BookDaoImpl implements BookDao {
@Override
public String findName(int id, String password) {
System.out.println("id:" + id);
//返回一个固定值
return "TestName";
}
}
- 修改App类,调用方法传入多个参数
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = ctx.getBean(BookDao.class);
String name = bookDao.findName(100, "rick666");
System.out.println(name);
}
}
输出结果如下,两个参数都已经被获取到
@After
与@Before
获取参数的方式相同,不再赘述
环绕通知获取方式
环绕通知使用的是ProceedingJoinPoint
,因为ProceedingJoinPoint是JoinPoint类的子类,所以对于ProceedingJoinPoint类中应该也会有对应的getArgs()
方法,我们去验证下
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(* com.yolo.dao.BookDao.findName(..))")
public void pt(){}
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
System.out.println(Arrays.toString(args));
Object res = pjp.proceed();
return res;
}
}
运行App后查看运行结果,说明ProceedingJoinPoint也是可以通过getArgs()获取参数
注意:
pjp.proceed()
方法是有两个构造方法,分别是:proceed()
proceed(Object[] object)
proceed相关源码
Object proceed() throws Throwable;
Object proceed(Object[] var1) throws Throwable;
- 调用无参数的proceed,当原始方法有参数,会在调用的过程中自动传入参数
- 所以调用这两个方法的任意一个都可以完成功能(且运行结果一致)
- 但是当需要修改原始方法的参数时,就只能采用带有参数的方法,如下
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(* com.yolo.dao.BookDao.findName(..))")
public void pt(){}
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
System.out.println(Arrays.toString(args));
//对参数值进行修改
args[0] = 777;
Object res = pjp.proceed(args);
return res;
}
}
运行程序,输出结果如下
有了这个特性后,我们就可以在环绕通知中对原始方法的参数进行拦截过滤,避免由于参数的问题导致程序无法正确运行(比如要求传入字符串,但是传进来int类型,便可以去掉传进来的int型,改用设置的默认字符串内容),还可以根据参数来给予不同的权限,提高代码的健壮性
获取返回值
对于返回值,只有返回后AfterReturing
和环绕Around
这两个通知类型可以获取,具体如何获取?
环绕通知获取返回值
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(* com.yolo.dao.BookDao.findName(..))")
public void pt(){}
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
System.out.println(Arrays.toString(args));
//对参数值进行修改
args[0] = 777;
Object res = pjp.proceed(args);
return res;
}
}
上述代码中,res就是方法的返回值,我们是可以直接获取,不但可以获取,如果需要还可以进行修改。
返回后通知获取返回值
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(* com.yolo.dao.BookDao.findName(..))")
public void pt(){}
@AfterReturning(value = "pt()", returning = "res")
public void afterReturning(Object res){
System.out.println("res = " + res);
System.out.println("afterReturning advice ...");
}
}
运行程序,输出如下,成功获取了返回值
几点注意:
- 参数名的问题
赋给returning的值,必须与Object类型参数名一致,上面的代码中均为res - afterReturning方法参数类型的问题
参数类型可以写成String,但是为了能匹配更多的参数类型,建议写成Object类型 - afterReturning方法参数的顺序问题
如果存在JoinPoint参数,则必须将其放在第一位,否则运行将报错
获取异常
对于获取抛出的异常,只有抛出异常后AfterThrowing和环绕Around这两个通知类型可以获取,具体如何获取?
环绕通知获取异常
这块比较简单,以前我们是抛出异常,现在只需要将异常捕获,就可以获取到原始方法的异常信息了
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) {
Object[] args = pjp.getArgs();
System.out.println(Arrays.toString(args));
//对参数值进行修改
args[0] = 777;
Object res = null;
try {
res = pjp.proceed(args);
} catch (Throwable e) {
e.printStackTrace();
}
return res;
}
在catch方法中就可以获取到异常,至于获取到异常以后该如何处理,这个就和你的业务需求有关了。
抛出异常后通知获取异常
@AfterThrowing(value = "pt()", throwing = "t")
public void afterThrowing(Throwable t){
System.out.println("afterThrowing advice ..." + t);
}
那现在我们只需要让原始方法抛一个异常来看看效果
@Repository
public class BookDaoImpl implements BookDao {
@Override
public String findName(int id, String password) {
System.out.println("id:" + id);
//模拟异常
int i = 1/0;
//返回一个固定值
return "TestName";
}
}
运行程序,输出如下,成功输出了异常
至此,AOP通知如何获取数据就已经讲解完了,数据中包含参数、返回值(了解)、异常(了解)