为了账号安全,请及时绑定邮箱和手机立即绑定

Spring之路(44)–Spring AOP通知类型详解与实例展示

标签:
Spring

通知是干啥的

上一篇我们演示了一种通知,即使用@Before标识的在接入点执行的方法。通知就是切面要执行的特定行为。

实际上通知很灵活,还有其他种类的通知,具体如下:

注解 名称 说明
前置通知 @Before 在实际方法调用之前调用被注解的通知方法
正常返回通知 @AfterReturning 实际方法执行完毕后执行该通知,注意抛出异常则不会执行该通知
异常返回通知 @AfterThrowing 实际执行方法抛出异常执行该通知
返回通知 @After 实际方法调用之后执行该通知,不论是否发生异常
环绕通知 @Around 方法执行之前和之后都可以执行通知指定动作,这个比较强大

前置通知演示

还是车辆出门前登记这个场景,注意通知参数可以携带JoinPoint参数,该参数中包含被通知的方法信息、还有目标对象的信息,这样便于我们操作。

第一,货车类和轿车类,提供容器中bean的类型信息。

package org.maoge.aopdemo.useaop;
/**
 * 货车
 */
public class Truck {
	public void out() {
		System.out.println("卡车出门");
	}
	public void in() {
		System.out.println("卡车进门");
	}
}
package org.maoge.aopdemo.useaop;
/**
 * 轿车
 */
public class Car {
	public void out() {
		System.out.println("轿车出门");
	}
	public void in() {
		System.out.println("轿车进门");
	}
}

第二,在配置类中将类型注册为bean,同时开启AOP,开启指定包的bean扫描。

package org.maoge.aopdemo.useaop;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
 * 配置类
 */
@Configuration // 配置类,用来配置容器
@EnableAspectJAutoProxy // 开启AOP
@ComponentScan(basePackages = { "org.maoge.aopdemo.useaop" }) // 扫描包以便发现注解配置的bean
public class SpringConfig {
	@Bean // 注册卡车bean
	public Truck Truck() {
		Truck truck = new Truck();
		return truck;
	}
	@Bean // 注册轿车bean
	public Car car() {
		Car car = new Car();
		return car;
	}
}

第三,配置切面,并编写前置通知

package org.maoge.aopdemo.useaop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
/**
* 车辆出门切面
*/
@Component // 切面也是Spring的bean
@Aspect // 使用该注解标志此类是一切面
public class OutAspect {
   // 前置通知
   @Before("execution(public void out())")//在public void out()方法之前执行通知
   public void outNote(JoinPoint joinPoint) {
   	System.out.println("出门登记信息");
   	System.out.println("joinPoint.signature:" + joinPoint.getSignature());// 接入方法信息
   	System.out.println("joinPoint.target.class.name:" + joinPoint.getTarget().getClass().getName());// 接入目标对象的类型信息
   }
}

第四,测试类

package org.maoge.aopdemo.useaop;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
   public static void main(String[] args) {
   	// 获取容器
   	AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
   	// 取出bean
   	Truck truck = (Truck) context.getBean(Truck.class);
   	// 执行bean方法
   	truck.out();
   	Car car = (Car) context.getBean(Car.class);
   	car.out();
   }
}

执行结果如下,可见前置通知执行成功,且已输出接入点方法信息和目标对象信息。

出门登记信息
joinPoint.kind:method-execution
joinPoint.signature:void org.maoge.aopdemo.useaop.Truck.out()
joinPoint.target.class.name:org.maoge.aopdemo.useaop.Truck
卡车出门
出门登记信息
joinPoint.kind:method-execution
joinPoint.signature:void org.maoge.aopdemo.useaop.Car.out()
joinPoint.target.class.name:org.maoge.aopdemo.useaop.Car
轿车出门

正常返回通知演示

直接在切面类中添加通知即可,非常简单,注意如果抛出异常,该通知是不执行的。

   // 正常返回通知
   @AfterReturning("execution(public void out())")
   public void AfterReturning(JoinPoint joinPoint) {
   	System.out.println("车辆已出门");
   }

异常返回通知演示

在切面中定义异常返回通知,如下:

	// 异常返回通知
	@AfterThrowing("execution(public void out())")
	public void afterThrowing(JoinPoint joinPoint) {
		System.out.println(joinPoint.getSignature()+"发生异常");// 接入方法信息
	}

注意必须抛出才能执行该通知,如果方法内部处理了异常,则不会执行异常返回通知,如下:

package org.maoge.aopdemo.useaop;
/**
 * 轿车
 */
public class Car {
	//抛出异常,会执行异常通知
	public void out() {
		System.out.println("轿车出门");
		int a=1/0;
	}
	public void in() {
		System.out.println("轿车进门");
	}
}
package org.maoge.aopdemo.useaop;
/**
 * 货车
 */
public class Truck {
	//已处理异常,不会执行异常通知
	public void out() {
		System.out.println("卡车出门");
		try {
			int a=1/0;
		}catch(Exception e) {
			
		}
	}
	public void in() {
		System.out.println("卡车进门");
	}
}

返回通知演示

返回通知是不管是否发生异常,都会执行的,示例如下:

// 返回通知(不论是否有异常都会执行)
   @After("execution(public void out())")
   public void after(JoinPoint joinPoint) {
   	System.out.println("车辆出门这个事我知道了");// 接入方法信息
   }

环绕通知演示

环绕通知能够在目标方法执行之前、之后启动,如果要记录一个方法的执行时间,那么使用环绕通知是很合适的,如下:

	// 环绕通知,记录方法执行时间
   @Around("execution(public void out())")
   public void around(ProceedingJoinPoint joinPoint) throws Throwable {
   	long startTime = System.currentTimeMillis();//开始时间
   	joinPoint.proceed();//这一行代码表示执行目标方法
   	System.out.println(joinPoint.getSignature() + "运行时间(毫秒)为:" + (System.currentTimeMillis() - startTime));
   }

我们来看下输出:

出门登记信息
joinPoint.signature:void org.maoge.aopdemo.useaop.Truck.out()
joinPoint.target.class.name:org.maoge.aopdemo.useaop.Truck
卡车出门
void org.maoge.aopdemo.useaop.Truck.out()运行时间(毫秒)为:7
车辆出门这个事我知道了
车辆已出门
出门登记信息
joinPoint.signature:void org.maoge.aopdemo.useaop.Car.out()
joinPoint.target.class.name:org.maoge.aopdemo.useaop.Car
轿车出门
车辆出门这个事我知道了
void org.maoge.aopdemo.useaop.Car.out()发生异常
Exception in thread "main" java.lang.ArithmeticException: / by zero

可见没有异常的时候,统计运行时间成功了,当发生异常时,环绕通知中抛出异常,未能执行到打印运行时间那一行,所以改为:

	// 环绕通知,记录方法执行时间
   @Around("execution(public void out())")
   public void around(ProceedingJoinPoint joinPoint) {
   	long startTime = System.currentTimeMillis();// 开始时间
   	try {
   		joinPoint.proceed();// 这一行代码表示执行目标方法
   	} catch (Throwable e) {
   		e.printStackTrace();
   	}
   	System.out.println(joinPoint.getSignature() + "运行时间(毫秒)为:" + (System.currentTimeMillis() - startTime));
   }

总结

看到这里,想必大家也能深深体会AOP的强大和作用了,例如我们完全可以针对一些指定的方法启用事务,利用环绕通知在方法开始前开启事务,在方法执行后提交事务,发生异常时回滚。

Spring AOP是功能封装的利器,当项目中越来越多的使用到AOP时,说明已逐渐从初级的Java工程师向中级进阶啦,恭喜!

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
软件工程师
手记
粉丝
1.5万
获赞与收藏
1523

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消