一、单切面-普通通知
目标对象
@Service
public class MyMathCalculator{
/**
* 加法
*/
public int add(int i, int j) {
int result = i + j;
System.out.println("方法内部执行");
return result;
}
/**
* 减法
*/
public int sub(int i, int j) {
int result = i - j;
System.out.println("方法内部执行");
return result;
}
/**
* 乘法
*/
public int mul(int i, int j) {
//方法的兼容性;
int result = i * j;
System.out.println("方法内部执行");
return result;
}
/**
* 除法
*/
public int div(int i, int j) {
int result = i / j;
System.out.println("方法内部执行");
return result;
}
}
@Aspect
@Component
public class LogUtils {
/**
* @Before:在目标方法之前运行; 前置通知
* @After:在目标方法结束之后 后置通知
* @AfterReturning:在目标方法正常返回之后 返回通知
* @AfterThrowing:在目标方法抛出异常之后运行 异常通知
* @Around:环绕 环绕通知
*/
@Pointcut("execution(public int com.atguigu.impl.MyMathCalculator.*(..))")
public void hahaMyPoint(){};
//想在执行目标方法之前运行;写切入点表达式
@Before("hahaMyPoint()")
public static void logStart(JoinPoint joinPoint){
//获取到目标方法运行是使用的参数
Object[] args = joinPoint.getArgs();
//获取到方法签名
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println("[LogUtils-前置]【"+name+"】方法开始执行,用的参数列表【"+Arrays.asList(args)+"】");
}
//想在目标方法正常执行完成之后执行
@AfterReturning(value="hahaMyPoint()",returning="result")
public static void logReturn(JoinPoint joinPoint,Object result){
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println("[LogUtils-返回]【"+name+"】方法正常执行完成,计算结果是:"+result);
}
//想在目标方法出现异常的时候执行
@AfterThrowing(value="hahaMyPoint()",throwing="exception")
public static void logException(JoinPoint joinPoint,Exception exception) {
System.out.println("[LogUtils-异常]【"+joinPoint.getSignature().getName()+"】方法执行出现异常了,异常信息是【"+exception+"】:;这个异常已经通知测试小组进行排查");
}
//想在目标方法结束的时候执行
@After("hahaMyPoint()")
private int logEnd(JoinPoint joinPoint) {
System.out.println("[LogUtils-后置]【"+joinPoint.getSignature().getName()+"】方法最终结束了");
return 0;
}
}
ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
@Test
public void test02(){
MyMathCalculator bean = ioc.getBean(MyMathCalculator.class);
bean.div(1, 1);
}
没有异常的输出:
[LogUtils-前置]【div】方法开始执行,用的参数列表【[1, 1]】
方法内部执行
[LogUtils-后置]【div】方法最终结束了
[LogUtils-返回]【div】方法正常执行完成,计算结果是:1
异常输出:
[LogUtils-前置]【div】方法开始执行,用的参数列表【[1, 0]】
[LogUtils-后置]【div】方法最终结束了
[LogUtils-异常]【div】方法执行出现异常了,异常信息是【java.lang.ArithmeticException: / by zero】:;这个异常已经通知测试小组进行排查java.lang.ArithmeticException: / by zero
at com.atguigu.impl.MyMathCalculator.div(MyMathCalculator.java:39)
注意:这里的 后置通知(不管方法是否发生异常,都会执行) 都是在,返回通知(目标方法正常完成后被织入)和异常通知(目标方法发生异常后被织入)之前和我们的理解可能有点不同。
二、 单切面-环绕通知
@Aspect
@Component
public class LogUtils {
@Pointcut("execution(public int com.atguigu.impl.MyMathCalculator.*(..))")
public void hahaMyPoint(){};
/**
* @throws Throwable
* @Around:环绕 :是Spring中强大的通知;
* @Around:环绕:动态代理;
* try{
* //前置通知
* method.invoke(obj,args);
* //返回通知
* }catch(e){
* //异常通知
* }finally{
* //后置通知
* }
*
* 四合一通知就是环绕通知;
* 环绕通知中有一个参数: ProceedingJoinPoint pjp
*/
@Around("hahaMyPoint()")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable{
Object[] args = pjp.getArgs();
String name = pjp.getSignature().getName();
//args[0] = 100;
Object proceed = null;
try {
//@Before
System.out.println("【环绕前置通知】【"+name+"方法开始】");
//就是利用反射调用目标方法即可,就是method.invoke(obj,args)
proceed = pjp.proceed(args);
//@AfterReturing
System.out.println("【环绕返回通知】【"+name+"方法返回,返回值"+proceed+"】");
} catch (Exception e) {
//@AfterThrowing
System.out.println("【环绕异常通知】【"+name+"】方法出现异常,异常信息:"+e);
//为了让外界能知道这个异常,这个异常一定抛出去
throw new RuntimeException(e);
} finally{
//@After
System.out.println("【环绕后置通知】【"+name+"】方法结束");
}
//反射调用后的返回值也一定返回出去
return proceed;
}
}
没有异常的输出:
【环绕前置通知】【div方法开始】
方法内部执行
【环绕返回通知】【div方法返回,返回值1】
【环绕后置通知】【div】方法结束
异常输出:
【环绕前置通知】【div方法开始】
【环绕异常通知】【div】方法出现异常,异常信息:java.lang.ArithmeticException: / by zero
【环绕后置通知】【div】方法结束java.lang.RuntimeException: java.lang.ArithmeticException: / by zero
at com.atguigu.utils.LogUtils.myAround(LogUtils.java:62)
注:环绕通知是不存在前面的顺序问题的
三、 单切面-环绕通知-普通通知
@Aspect
@Component
public class LogUtils {
/**
* 告诉Spring每个方法都什么时候运行;
* try{
* @Before
* method.invoke(obj,args);
* @AfterReturning
* }catch(e){
* @AfterThrowing
* }finally{
* @After
* }
*
* 5个通知注解
* @Before:在目标方法之前运行; 前置通知
* @After:在目标方法结束之后 后置通知
* @AfterReturning:在目标方法正常返回之后 返回通知
* @AfterThrowing:在目标方法抛出异常之后运行 异常通知
* @Around:环绕 环绕通知
*
*
* 抽取可重用的切入点表达式;
* 1、随便声明一个没有实现的返回void的空方法
* 2、给方法上标注@Pointcut注解
*/
@Pointcut("execution(public int com.atguigu.impl.MyMathCalculator.*(..))")
public void hahaMyPoint(){};
//想在执行目标方法之前运行;写切入点表达式
//execution(访问权限符 返回值类型 方法签名)
@Before("hahaMyPoint()")
public static void logStart(JoinPoint joinPoint){
//获取到目标方法运行是使用的参数
Object[] args = joinPoint.getArgs();
//获取到方法签名
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println("[LogUtils-前置]【"+name+"】方法开始执行,用的参数列表【"+Arrays.asList(args)+"】");
}
//想在目标方法正常执行完成之后执行
@AfterReturning(value="hahaMyPoint()",returning="result")
public static void logReturn(JoinPoint joinPoint,Object result){
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println("[LogUtils-返回]【"+name+"】方法正常执行完成,计算结果是:"+result);
}
//想在目标方法出现异常的时候执行
@AfterThrowing(value="hahaMyPoint()",throwing="exception")
public static void logException(JoinPoint joinPoint,Exception exception) {
System.out.println("[LogUtils-异常]【"+joinPoint.getSignature().getName()+"】方法执行出现异常了,异常信息是【"+exception+"】:;这个异常已经通知测试小组进行排查");
}
//想在目标方法结束的时候执行
@After("hahaMyPoint()")
private int logEnd(JoinPoint joinPoint) {
System.out.println("[LogUtils-后置]【"+joinPoint.getSignature().getName()+"】方法最终结束了");
return 0;
}
/**
* @throws Throwable
* @Around:环绕 :是Spring中强大的通知;
* @Around:环绕:动态代理;
* try{
* //前置通知
* method.invoke(obj,args);
* //返回通知
* }catch(e){
* //异常通知
* }finally{
* //后置通知
* }
*
* 四合一通知就是环绕通知;
* 环绕通知中有一个参数: ProceedingJoinPoint pjp
*
*环绕通知:是优先于普通通知执行,执行顺序;
*
*[普通前置]
*{
* try{
* 环绕前置
* 环绕执行:目标方法执行
* 环绕返回
* }catch(){
* 环绕出现异常
* }finally{
* 环绕后置
* }
*}
*
*
*[普通后置]
*[普通方法返回/方法异常]
*新的顺序:
* (环绕前置---普通前置)----目标方法执行----环绕正常返回/出现异常-----环绕后置----普通后置---普通返回或者异常
*注意:
*/
@Around("hahaMyPoint()")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable{
Object[] args = pjp.getArgs();
String name = pjp.getSignature().getName();
//args[0] = 100;
Object proceed = null;
try {
//@Before
System.out.println("【环绕前置通知】【"+name+"方法开始】");
//就是利用反射调用目标方法即可,就是method.invoke(obj,args)
proceed = pjp.proceed(args);
//@AfterReturing
System.out.println("【环绕返回通知】【"+name+"方法返回,返回值"+proceed+"】");
} catch (Exception e) {
//@AfterThrowing
System.out.println("【环绕异常通知】【"+name+"】方法出现异常,异常信息:"+e);
//为了让外界能知道这个异常,这个异常一定抛出去
throw new RuntimeException(e);
} finally{
//@After
System.out.println("【环绕后置通知】【"+name+"】方法结束");
}
//反射调用后的返回值也一定返回出去
return proceed;
}
}
没有异常的输出:
【环绕前置通知】【div方法开始】
[LogUtils-前置]【div】方法开始执行,用的参数列表【[1, 1]】
方法内部执行
【环绕返回通知】【div方法返回,返回值1】
【环绕后置通知】【div】方法结束
[LogUtils-后置]【div】方法最终结束了
[LogUtils-返回]【div】方法正常执行完成,计算结果是:1
异常输出:
【环绕前置通知】【div方法开始】
[LogUtils-前置]【div】方法开始执行,用的参数列表【[1, 0]】
【环绕异常通知】【div】方法出现异常,异常信息:java.lang.ArithmeticException: / by zero
【环绕后置通知】【div】方法结束
[LogUtils-后置]【div】方法最终结束了
[LogUtils-异常]【div】方法执行出现异常了,异常信息是【java.lang.RuntimeException: java.lang.ArithmeticException: / by zero】:;这个异常已经通知测试小组进行排查java.lang.RuntimeException: java.lang.ArithmeticException: / by zero
at com.atguigu.utils.LogUtils.myAround(LogUtils.java:143)
注:如果环绕通知不显示的抛出异常,那么普通通知是感知不到异常的,这个时候普通通知还是正常执行正常返回。
四、多个切面-普通通知
@Aspect
@Component
@Order(1)//使用Order改变切面顺序;数值越小优先级越高
public class LogUtils {
@Pointcut("execution(public int com.atguigu.impl.MyMathCalculator.*(..))")
public void hahaMyPoint(){};
//想在执行目标方法之前运行;写切入点表达式
//execution(访问权限符 返回值类型 方法签名)
@Before("hahaMyPoint()")
public static void logStart(JoinPoint joinPoint){
//获取到目标方法运行是使用的参数
Object[] args = joinPoint.getArgs();
//获取到方法签名
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println("[LogUtils-前置]【"+name+"】方法开始执行,用的参数列表【"+Arrays.asList(args)+"】");
}
//想在目标方法正常执行完成之后执行
@AfterReturning(value="hahaMyPoint()",returning="result")
public static void logReturn(JoinPoint joinPoint,Object result){
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println("[LogUtils-返回]【"+name+"】方法正常执行完成,计算结果是:"+result);
}
//想在目标方法出现异常的时候执行
@AfterThrowing(value="hahaMyPoint()",throwing="exception")
public static void logException(JoinPoint joinPoint,Exception exception) {
System.out.println("[LogUtils-异常]【"+joinPoint.getSignature().getName()+"】方法执行出现异常了,异常信息是【"+exception+"】:;这个异常已经通知测试小组进行排查");
}
//想在目标方法结束的时候执行
@After("hahaMyPoint()")
private int logEnd(JoinPoint joinPoint) {
System.out.println("[LogUtils-后置]【"+joinPoint.getSignature().getName()+"】方法最终结束了");
return 0;
}
}
@Aspect
@Component
@Order(2)
public class ValidateApsect {
@Before("com.atguigu.utils.LogUtils.hahaMyPoint()")
public void logStart(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println("[VaApsect-前置]【"+name+"】方法开始执行,用的参数列表【"+Arrays.asList(args)+"】");
}
@AfterReturning(value="com.atguigu.utils.LogUtils.hahaMyPoint()",returning="result")
public void logReturn(JoinPoint joinPoint,Object result){
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println("[VaApsect-返回]【"+name+"】方法正常执行完成,计算结果是:"+result);
}
@AfterThrowing(value="com.atguigu.utils.LogUtils.hahaMyPoint()",throwing="exception")
public void logException(JoinPoint joinPoint,Exception exception) {
System.out.println("[VaApsect-异常]【"+joinPoint.getSignature().getName()+"】方法执行出现异常了,异常信息是【"+exception+"】:;这个异常已经通知测试小组进行排查");
}
@After("com.atguigu.utils.LogUtils.hahaMyPoint()")
private int logEnd(JoinPoint joinPoint) {
System.out.println("[VaApsect-后置]【"+joinPoint.getSignature().getName()+"】方法最终结束了");
return 0;
}
}
没有异常的输出:
[LogUtils-前置]【div】方法开始执行,用的参数列表【[1, 1]】
[VaApsect-前置]【div】方法开始执行,用的参数列表【[1, 1]】
方法内部执行
[VaApsect-后置]【div】方法最终结束了
[VaApsect-返回]【div】方法正常执行完成,计算结果是:1
[LogUtils-后置]【div】方法最终结束了
[LogUtils-返回]【div】方法正常执行完成,计算结果是:1
异常输出:
[LogUtils-前置]【div】方法开始执行,用的参数列表【[1, 0]】
[VaApsect-前置]【div】方法开始执行,用的参数列表【[1, 0]】
[VaApsect-后置]【div】方法最终结束了
[VaApsect-异常]【div】方法执行出现异常了,异常信息是【java.lang.ArithmeticException: / by zero】:;这个异常已经通知测试小组进行排查
[LogUtils-后置]【div】方法最终结束了
[LogUtils-异常]【div】方法执行出现异常了,异常信息是【java.lang.ArithmeticException: / by zero】:;这个异常已经通知测试小组进行排查java.lang.ArithmeticException: / by zero
at com.atguigu.impl.MyMathCalculator.div(MyMathCalculator.java:39)
注:通知的执行顺序就是先进后出的执行顺序。谁先执行是按照通知类的类名字典序排序,当然可以用 @Order() 指定,值越小越靠前。
[LogUtils-前置]【add】方法开始执行,用的参数列表【[1, 1]】
[VaApsect-前置]【add】方法开始执行,用的参数列表【[1, 1]】
方法内部执行
[VaApsect-后置]【add】方法最终结束了
[VaApsect-返回]【add】方法正常执行完成,计算结果是:2
[LogUtils-后置]【add】方法最终结束了
[LogUtils-返回]【add】方法正常执行完成,计算结果是:2
五、多切面-普通通知-环绕通知
现在我们给 LogUtils 加上环绕通知
@Aspect
@Component
@Order(1)//使用Order改变切面顺序;数值越小优先级越高
public class LogUtils {
@Pointcut("execution(public int com.atguigu.impl.MyMathCalculator.*(..))")
public void hahaMyPoint(){};
//想在执行目标方法之前运行;写切入点表达式
//execution(访问权限符 返回值类型 方法签名)
@Before("hahaMyPoint()")
public static void logStart(JoinPoint joinPoint){
//获取到目标方法运行是使用的参数
Object[] args = joinPoint.getArgs();
//获取到方法签名
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println("[LogUtils-前置]【"+name+"】方法开始执行,用的参数列表【"+Arrays.asList(args)+"】");
}
//想在目标方法正常执行完成之后执行
@AfterReturning(value="hahaMyPoint()",returning="result")
public static void logReturn(JoinPoint joinPoint,Object result){
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println("[LogUtils-返回]【"+name+"】方法正常执行完成,计算结果是:"+result);
}
//想在目标方法出现异常的时候执行
@AfterThrowing(value="hahaMyPoint()",throwing="exception")
public static void logException(JoinPoint joinPoint,Exception exception) {
System.out.println("[LogUtils-异常]【"+joinPoint.getSignature().getName()+"】方法执行出现异常了,异常信息是【"+exception+"】:;这个异常已经通知测试小组进行排查");
}
//想在目标方法结束的时候执行
@After("hahaMyPoint()")
private int logEnd(JoinPoint joinPoint) {
System.out.println("[LogUtils-后置]【"+joinPoint.getSignature().getName()+"】方法最终结束了");
return 0;
}
@Around("hahaMyPoint()")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable{
Object[] args = pjp.getArgs();
String name = pjp.getSignature().getName();
//args[0] = 100;
Object proceed = null;
try {
//@Before
System.out.println("【环绕前置通知】【"+name+"方法开始】");
//就是利用反射调用目标方法即可,就是method.invoke(obj,args)
proceed = pjp.proceed(args);
//@AfterReturing
System.out.println("【环绕返回通知】【"+name+"方法返回,返回值"+proceed+"】");
} catch (Exception e) {
//@AfterThrowing
System.out.println("【环绕异常通知】【"+name+"】方法出现异常,异常信息:"+e);
//为了让外界能知道这个异常,这个异常一定抛出去
throw new RuntimeException(e);
} finally{
//@After
System.out.println("【环绕后置通知】【"+name+"】方法结束");
}
//反射调用后的返回值也一定返回出去
return proceed;
}
}
没有异常的输出:
【环绕前置通知】【div方法开始】
[LogUtils-前置]【div】方法开始执行,用的参数列表【[1, 1]】
[VaApsect-前置]【div】方法开始执行,用的参数列表【[1, 1]】
方法内部执行
[VaApsect-后置]【div】方法最终结束了
[VaApsect-返回]【div】方法正常执行完成,计算结果是:1
【环绕返回通知】【div方法返回,返回值1】
【环绕后置通知】【div】方法结束
[LogUtils-后置]【div】方法最终结束了
[LogUtils-返回]【div】方法正常执行完成,计算结果是:1
异常输出:
【环绕前置通知】【div方法开始】
[LogUtils-前置]【div】方法开始执行,用的参数列表【[1, 0]】
[VaApsect-前置]【div】方法开始执行,用的参数列表【[1, 0]】
[VaApsect-后置]【div】方法最终结束了
[VaApsect-异常]【div】方法执行出现异常了,异常信息是【java.lang.ArithmeticException: / by zero】:;这个异常已经通知测试小组进行排查
【环绕异常通知】【div】方法出现异常,异常信息:java.lang.ArithmeticException: / by zero
【环绕后置通知】【div】方法结束
[LogUtils-后置]【div】方法最终结束了
[LogUtils-异常]【div】方法执行出现异常了,异常信息是【java.lang.RuntimeException: java.lang.ArithmeticException: / by zero】:;这个异常已经通知测试小组进行排查java.lang.RuntimeException: java.lang.ArithmeticException: / by zero
at com.atguigu.utils.LogUtils.myAround(LogUtils.java:86)
LogProxy{
环绕前置
method.invoke(){
VaProxy{
va前置
target
va后置
va返回
}
}
环绕返回
环绕后置
}
1、Spring-IOC-AOP(动态代理);多层代理
LogAspectpRroxy{
try{
@Before
method.invoke()//pjp.procced(args){
BAspectProxy{
@Before
method.invoke()//---目标方法
@AfterReturning
//xxxxxxxx
//修改了返回值
}
}
@AfterReturning
}catch(e){
@AfterThrowing
}finally{
@After
}
}
注:LogUtils 里面的环绕通知,跟ValidateApsect里面的通知没有关系,他只是影响当前切面
六、如何选择通知
由于环绕通知比较强大,他能直接控制目标方法的执行。如果我们是做日志记录等仅仅感知目标方法执行,我们使用普通通知即可。如果我需要写一一个动态代理,要影响目标方法,那么我就使用环绕通知。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<context:component-scan base-package="com.atguigu"/>
<!-- 开启基于注解的AOP功能;aop名称空间-->
<aop:aspectj-autoproxy/>
</beans>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-expression -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
<!--基本切面包-->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
<!--切面plus包-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.12</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.12</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>