SpringAOP Advice 通知例子

本文详细介绍了Spring AOP中Advice的各类应用,包括Before、After、AfterReturning、AfterThrowing和Around等,通过实例演示了如何在实际项目中运用这些Advice,以及它们之间的执行顺序。

前言

前些天写了【详解什么是Spring AOP】虽然叫详解,但是里面其实还是留了很多内容没有说。于是就想着还是写几个辅助的文章,把前面博客里面的坑给填上,既然要做了就要做好对不对。那么这次要填的坑就是Spring AOP里面的Advice语义中的方法都应该怎么用,这部分的内容在官网【Declaring Advice】这部分的章节里也能够找到。更多Spring内容进入【Spring解读系列目录】

Advice Sample

概念的正文已经在详解这个博客中详细的说过了,所以这里就不多罗嗦了。还是老样子,我们首先要引入必要的依赖,构建一个小demo。假设我们有一个接口Dao,有一个实现类DemoDao,有一个切面类MyAspect,一个测试类MainTest,一个配置类AppConfig。

<!-- Spring上下文是一个配置,向Spring框架提供上下文信息。 -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.2.8.RELEASE</version>
</dependency>
<!--aspectj语法支持-->
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.9.6</version>
</dependency>
@Configuration
@ComponentScan("com")
@EnableAspectJAutoProxy //开启AspectJ语法支持
public class AppConfig {
}
@Component
@Aspect
public class MyAspect {
    @Pointcut("within(com.demo.dao.*)")
    public void myPointCutWithin(){}
}
public interface Dao {
	void print();
    String printString(String str) throws Exception;
}
@Repository("demoDao")
public class DemoDao implements Dao{
    @Override
    public void print() {
        System.out.println("print Empty");
    }
    @Override
    public String printString(String str) throws Exception{  //throws抛出异常
        System.out.println("print String:"+str);
        return str;
    }
}
public class MainTest {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext anno=new AnnotationConfigApplicationContext(AppConfig.class);
        Dao dao= (Dao) anno.getBean("demoDao");
        dao.printString("parameter");
    }
}

Advice Type Sample

Before Advice

添加Before语句到MyAspect中,运行main()方法:

@Before("myPointCutWithin()")
public void beforeDao(){
	System.out.println("this is a beforeDao");
}

运行结果,在方法执行前打印了。
this is a beforeDao
print String:param

After (Finally) Advice

添加After语句到MyAspect中,运行main()方法:

@After("myPointCutWithin()")
public void afterFinally(){
	System.out.println("this is a afterFinally");
}

运行结果,在方法执行后打印了。
print String:param
this is a afterFinally

After Returning Advice

添加AfterReturning语句到MyAspect中,运行main()方法:

@AfterReturning("myPointCutWithin()")
public void afterWithReturn(){
	System.out.println("this is a afterWithReturn");
}

运行结果,在方法执行后打印了。这个效果不容易和After (Finally)区别开来,
	因为即便是void类型的也是返回了空,所以无论是什么都会打印的。
print String:param
this is a afterWithReturn

还有更进一步的方法,我们可以拿到返回值:

@AfterReturning(pointcut="myPointCutWithin()", returning = "str") //获取返回值
public void afterWithReturnValue(Object str){
	System.out.println(str.toString()); //打印返回值
	System.out.println("this is a afterWithReturnValue");
}
运行结果:
print String:param
param	//连接点返回的值
this is a afterWithReturnValue

After Throwing Advice

添加AfterThrowing语句到MyAspect中,但是这个还是有些不一样的,因为我们必须要用throws语句抛出这个异常,才能被通知到,所以我们要改造一下DemoDao,让其能报一个空指针异常。

@AfterThrowing("myPointCutWithin()")
public void afterWithException(){
	System.out.println("this is a afterWithException");
}
@Repository("demoDao")
public class DemoDao implements Dao{
    Dao dao;
    /****/
    @Override
    public String printString(String str) throws Exception{
        System.out.println("print String:"+str);
        dao.print();  //调用到这里抛出异常
        return "String";
    }
}
运行结果,在方法执行抛出异常以后被通知到了。做个提醒:有异常最终一定要try-catch处理,千万不要让你的代码裸奔,
	养成良好的编码习惯是非常重要的。这里为了让大家看清楚,做了一个demo,实际上应该改造为下面的样子。
print String:param
this is a afterWithException
Exception in thread "main" java.lang.NullPointerException
	at com.demo.dao.DemoDao.printString(DemoDao.java:17)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344)
	
******************捕获你的异常进行处理,杜绝裸奔。******************
public class MainTest {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext anno=new AnnotationConfigApplicationContext(AppConfig.class);
        Dao dao= (Dao) anno.getBean("demoDao");
        try {
            dao.printString("param");
        }catch (Exception e){
            System.out.println("Do something");
        }
    }
}
输出结果:
print String:param
this is a afterWithException
Do something

这个方法也有一个升级版,可以允许你指定想要捕获的异常,比如这里指定捕获空指针异常被通知。这里如果写了别的异常比如官网提供的DataAccessException,通知就无法调用了:

//使用NullPointerException作为参数传递进来,或者使用更广泛的Exception,不过
@AfterThrowing(pointcut="myPointCutWithin()", throwing="ex") //指定空指针异常
public void afterWithException2(NullPointerException ex){ 
	System.out.println("this is a target afterWithException2");
}
输出结果:
print String:param
this is a target afterWithException2
Do something

Around Advice

添加Around语句到MyAspect中:

@Around("myPointCutWithin()")
public void aroundNormal(ProceedingJoinPoint pjp){
    System.out.println("this is a aroundNormal before");
    try {
        pjp.proceed();
    } catch (Throwable throwable) {
        throwable.printStackTrace();
    }
    System.out.println("this is a aroundNormal after");
}
运行结果:
this is a aroundNormal before
print String:param
this is a aroundNormal after

可以看到,这里面连接点是能够在通知中启动的,也就是说能够在通知中修改连接点的内容,这也是为什么官网说Around是最强大的通知,怎么修改呢?ProceedingJoinPoint 这个类已经把连接点描述清楚了,它继承了JoinPoint,所以拥有JoinPoint的一切方法。而JoinPoint正是Spring描述连接点的类,所以ProceedingJoinPoint 对象就能够获取到整个连接点的内容。比如getArgs()这个方法,就可以获取到连接点的参数,并封装成一个数组。那么我们要做的就很简单了,改变传参,修改数组的内容就好了啊。

@Around("myPointCutWithin()")
public void aroundChange(ProceedingJoinPoint pjp){
    System.out.println("this is a aroundChange before");
    Object[] objects=pjp.getArgs();
    for (int i = 0; i < objects.length; i++) {
        objects[i]="bbb"; //修改内容param为bbb
    }
    try {
        pjp.proceed(objects);
    } catch (Throwable throwable) {
        throwable.printStackTrace();
    }
    System.out.println("this is a aroundChange after");
}
运行结果:
this is a aroundChange before
print String:bbb
this is a aroundChange after

Advice的执行顺序

我们有这么多的Advice类型可以执行,那么他们的执行顺序怎么界定呢?全部都打开执行一下,除了After Throwing是必须异常通知外,其他的都是不影响的。

输出结果如下,可以很明显看出:Around before->before->after return->after->Around after.
this is a aroundChange before
this is a beforeDao
print String:bbb
this is a afterWithReturn
this is a afterFinally
this is a aroundChange after

Advice 使用表达式

我们之前的例子都是使用的切点的方法作为Advice的作用域,其实如果不想写切点方法也是可以的。Advice获取的只是切点方法的注解内容而已,所以这里是能够直接用表达式替代的,例如:

@Before("within(com.demo.dao.*)")  //直接用表达式
public void beforeDao(){
	System.out.println("this is a beforeDao");
}
输出:
this is a beforeDao
print String:param
和使用@Before("myPointCutWithin()")效果是一模一样的。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值