Spring:从需求出发,读懂AOP如此easy -- 面向切面编程
闲言碎语:Spring中比较重要的两个点就是 IOC 和 AOP,前面有篇博客专门介绍了IOC的思想及使用,这篇文主要是通过实际的需求对 AOP 进行深入的研究,接地气的案例,我们一起来读懂 AOP。
AOP
AOP: Aspect Oriented Project --> 面向切面编程。
OOP: Object Oriented Project --> 面向对象编程。
1、AOP场景:
计算器添加日志记录:
Calculator 接口:
package com.xptx.inter;
public interface Calculator {
int add(int x, int y);
int sub(int x, int y);
int mul(int x, int y);
int div(int x, int y);
}
实现类:
package com.xptx.impl;
import com.xptx.inter.Calculator;
public class MyMathCalculator implements Calculator {
public int add(int x, int y) {
int result = x + y;
return result;
}
public int sub(int x, int y) {
int result = x - y;
return result;
}
public int mul(int x, int y) {
int result = x * y;
return result;
}
public int div(int x, int y) {
int result = x / y;
return result;
}
}
测试类:
import com.xptx.impl.MyMathCalculator;
import com.xptx.inter.Calculator;
public class Test {
public static void main(String[] args) {
Calculator calculator = new MyMathCalculator();
int result = calculator.add(3, 5);
System.out.println(result);
}
}
1.1 添加日志记录:
在每一个方法中进行手动添加,直接编写在方法内部:
package com.xptx.impl;
import com.xptx.inter.Calculator;
public class MyMathCalculator implements Calculator {
public int add(int x, int y) {
System.out.println("[add] 方法开始了,它使用的参数是"+x + "," + y);
int result = x + y;
System.out.println("[add] 方法执行完成:" + "结果是:" + result);
return result;
}
public int sub(int x, int y) {
System.out.println("[sub] 方法开始了,它使用的参数是"+x + "," + y);
int result = x - y;
System.out.println("[sub] 方法执行完成:" + "结果是:" + result);
return result;
}
public int mul(int x, int y) {
System.out.println("[mul] 方法开始了,它使用的参数是"+x + "," + y);
int result = x * y;
System.out.println("[mul] 方法执行完成:" + "结果是:" + result);
return result;
}
public int div(int x, int y) {
System.out.println("[div] 方法开始了,它使用的参数是"+x + "," + y);
int result = x / y;
System.out.println("[div] 方法执行完成:" + "结果是:" + result);
return result;
}
}
通过上面的代码我们可以看到,每个方法由于方法名不同,对应的日志记录也是不同的,如果有成千上万个方法,我们就需要手动添加成千上万的语句,既麻烦还灵活性弱,代码量十分大,那有没有什么更好的方法呢?答案是肯定的。
- 日志记录:系统的辅助功能。
- 业务逻辑:核心功能。
- 这种方法耦合性太强。
我们希望的是:
- 业务逻辑: 核心功能。
- 日志模块: 在核心功能运行期间,自己动态的加上。
1.2 动态代理实现日志记录:
我们希望的是:
- 业务逻辑: 核心功能。
- 日志模块: 在核心功能运行期间,自己动态的加上。
CalculatorProxy:
package com.xptx.proxy;
import com.xptx.inter.Calculator;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class CalculatorProxy {
public static Calculator getProxy(Calculator calculator) {
InvocationHandler handler = new InvocationHandler() {
/**
*
* @param proxy : 代理对象:给 jdk 使用,任何时候都不要动这个对象。
* @param method: 当前将要执行的目标对象的方法。
* @param args : 这个方法调用时外界传入的值。
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
// 添加日志功能
System.out.println("["+method.getName()+"]方法开始执行,用的参数列表:"+ Arrays.asList(args));
// 目标方法执行后的返回值
result = method.invoke(calculator, args);
System.out.println("["+method.getName()+"]方法执行完成,计算结果是:"+ result);
} catch (Exception e) {
System.out.println("["+method.getName()+"]方法出现异常,异常信息是:"+e.getMessage());
} finally {
System.out.println("["+method.getName()+"]最终结束了");
}
return result;
}
};
Object proxy = Proxy.newProxyInstance(calculator.getClass().getClassLoader(),calculator.getClass().getInterfaces(),handler);
return (Calculator) proxy;
}
}
测试:
import com.xptx.impl.MyMathCalculator;
import com.xptx.inter.Calculator;
import com.xptx.proxy.CalculatorProxy;
public class Test {
public static void main(String[] args) {
// 被代理对象
Calculator calculator = new MyMathCalculator();
// 代理对象
Calculator proxy = CalculatorProxy.getProxy(calculator);
proxy.div(3, 1);
proxy.add(5,6);
}
}
从上述代码和结果可以看到,通过动态代理的方式实现了该类日志功能的动态添加,并且实现了日志和业务逻辑代码的分离。
添加LogUtils实现:
LogUtils:
package com.xptx.utils;
import java.lang.reflect.Method;
import java.util.Arrays;
public class LogUtils {
public static void logStart(Method method,Object[] args) {
System.out.println("["+method.getName()+"]方法开始执行,用的参数列表:"+ Arrays.asList(args));
}
public static void logReturn(Method method,Object result){
System.out.println("["+method.getName()+"]方法执行完成,计算结果是:"+ result);
}
public static void logException(Method method,Exception e) {
System.out.println("["+method.getName()+"]方法出现异常,异常信息是:"+e.getCause());
}
public static void logEnd(Method method) {
System.out.println("["+method.getName()+"]最终结束了");
}
}
CalculatorProxy:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
// 直接调用 LogUtils 中的静态方法
LogUtils.logStart(method,args);
// 目标方法执行后的返回值
result = method.invoke(calculator, args);
LogUtils.logReturn(method,result);
} catch (Exception e) {
LogUtils.logException(method,e);
} finally {
LogUtils.logEnd(method);
}
return result;
}
可以使用动态代理将日志代码动态的在目标方法执行前后进行执行。
缺陷:
- 写起来太过麻烦。
- jdk 默认的动态代理,如果目标对象没有实现任何接口,是无法为它创建代理对象的,代理对象和被代理对象之间的唯一关联就是实现了相同的接口。
1.3 Spring 弥补了动态代理的缺陷:
Spring 中的 AOP 底层就是动态代理:
- 可以利用 Spring 一句代码都不写的去创建动态代理。
- 实现简单,而且没有强制要求目标对象必须实现相同的接口。
将某段代码(日志)动态的切入(不把代码写死到业务逻辑方法中)到指定方法(加减乘除)的指定位置(方法的开始、结束、异常。。)进行运行的编程方式(Spring 简化了面向切面编程)。
2、AOP 相关专业术语:
3、AOP 的简单配置:
3.1 使用步骤:
3.1.1 导包:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.7.RELEASE</version>
<scope>compile</scope>
</dependency>
/**
Spring 支持面向切面编程的包是:
基础版:
spring-aspects
加强版:
加强版的面向切面编程,即使目标对象没有实现任何接口也能创建动态代理对象。
com.springsource.net.sf.cglib
com.springsource.org.aspectj.weaver
com.springsource.org.aopalliance
*/
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--https://mvnrepository.com/artifact/net.sourceforge.cglib/
com.springsource.net.sf.cglib -->
<dependency>
<groupId>net.sourceforge.cglib</groupId>
<artifactId>com.springsource.net.sf.cglib</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>com.springsource.org.aspectj.weaver</artifactId>
<version>1.6.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aopalliance</groupId>
<artifactId>com.springsource.org.aopalliance</artifactId>
<version>1.0.0</version>
</dependency>
3.1.2 配置
- 将目标类和切面类加入到 ioc 容器中:
package com.xptx.impl;
import com.xptx.inter.Calculator;
import org.springframework.stereotype.Service;
/**
将该目标类注册到 IOC 容器中
*/
@Service
public class MyMathCalculator implements Calculator {
public int add(int x, int y) {
int result = x + y;
return result;
}
public int sub(int x, int y) {
int result = x - y;
return result;
}
public int mul(int x, int y) {
int result = x * y;
return result;
}
public int div(int x, int y) {
int result = x / y;
return result;
}
}
package com.xptx.utils;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
*
* 切面类: 如何将这个类中的所有方法(通知方法)动态的在目标方法的各个位置切入
*
*/
// 将切面类注册到 IOC 容器中
@Component
@Aspect
public class LogUtils {
public static void logStart(Method method, Object[] args) {
System.out.println("[" + method.getName() + "]方法开始执行,用的参数列表:" + Arrays.asList(args));
}
public static void logReturn(Method method, Object result) {
System.out.println("[" + method.getName() + "]方法执行完成,计算结果是:" + result);
}
public static void logException(Method method, Exception e) {
System.out.println("[" + method.getName() + "]方法出现异常,异常信息是:" + e.getCause());
}
public static void logEnd(Method method) {
System.out.println("[" + method.getName() + "]最终结束了");
}
}
<!-- 开启注解自动扫描某个包下的所有类,注册到 Spring 的 IOC 容器中 -->
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
">
<context:component-scan base-package="com.xptx"/>
</beans>
- 告诉 Spring 哪个是切面类:
package com.xptx.utils;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
*
* 切面类: 如何将这个类中的所有方法(通知方法)动态的在目标方法的各个位置切入
*
*/
// 将切面类注册到 IOC 容器中
@Component
// 告诉 Spring 哪个是切面类
@Aspect
public class LogUtils {
public static void logStart(Method method, Object[] args) {
System.out.println("[" + method.getName() + "]方法开始执行,用的参数列表:" + Arrays.asList(args));
}
public static void logReturn(Method method, Object result) {
System.out.println("[" + method.getName() + "]方法执行完成,计算结果是:" + result);
}
public static void logException(Method method, Exception e) {
System.out.println("[" + method.getName() + "]方法出现异常,异常信息是:" + e.getCause());
}
public static void logEnd(Method method) {
System.out.println("[" + method.getName() + "]最终结束了");
}
}
- 告诉Spring,切面类中的每一个方法,何时何地运行:
package com.xptx.utils;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
*
* 切面类: 如何将这个类中的所有方法(通知方法)动态的在目标方法的各个位置切入
*
*/
// 将切面类注册到 IOC 容器中
@Component
// 告诉Spring 这是个切面类
@Aspect
public class LogUtils {
/**
* 告诉 Spring 每个方法什么时候执行
*
* 通知注解
*
* @Before : 在目标方法之前运行 前置通知
* @After : 在目标方法之后执行 后置通知
* @AfterReturning : 在目标方法正常返回之后运行 返回通知
* @AfterThrowing : 在目标方法抛出异常之后执行 异常通知
* @Around : 环绕 环绕通知
*/
/**
* 想在目标方法之前运行
*/
@Before("execution(public * com.xptx.impl.MyMathCalculator.*(int,int))")
public static void logStart() {
System.out.println("[xxx]方法开始执行,用的参数列表");
}
/**
* 在目标方法正常返回之后运行
*/
@AfterReturning("execution(public * com.xptx.impl.MyMathCalculator.*(int,int))")
public static void logReturn() {
System.out.println("[xxx]方法执行完成,计算结果是:");
}
/**
* 在目标方法抛出异常之后执行
*/
@AfterThrowing("execution(public * com.xptx.impl.MyMathCalculator.*(int,int))")
public static void logException() {
System.out.println("[xxx]方法出现异常,异常信息是:");
}
/**
* 在目标方法之后执行
*/
@After("execution(public * com.xptx.impl.MyMathCalculator.*(int,int))")
public static void logEnd() {
System.out.println("[xxx]最终结束了");
}
}
-
开启基于注解的 AOP 功能:
xmlns:aop="http://www.springframework.org/schema/aop" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
3.1.3 测试:
import com.xptx.impl.MyMathCalculator;
import com.xptx.inter.Calculator;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Calculator bean = (Calculator) context.getBean("myMathCalculator");
bean.add(2,3);
}
}
3.2 AOP 中的细节问题:
3.2.1 ioc 容器中保存的是组件的代理对象:
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
/**
* 从 IOC 容器中拿到目标对象。
* 注意: 如果使用类型,一定要用它的接口类型
*/
Calculator bean = context.getBean(Calculator.class);
System.out.println(bean.getClass());
bean.add(2,3);
}
cglib 为没有接口的组件创建代理对象:
package com.xptx.impl;
import com.xptx.inter.Calculator;
import org.springframework.stereotype.Service;
/**
* 该类没有实现任何的接口
*/
@Service
public class MyMathCalculator {
public int add(int x, int y) {
int result = x + y;
return result;
}
public int sub(int x, int y) {
int result = x - y;
return result;
}
public int mul(int x, int y) {
int result = x * y;
return result;
}
public int div(int x, int y) {
int result = x / y;
return result;
}
}
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 没有接口就是本类对象
MyMathCalculator bean = context.getBean(MyMathCalculator.class);
bean.add(3,4);
System.out.println(bean.getClass());
}
3.2.2 切入点表达式:
切入表达式的写法:
固定格式: execution(访问权限符 返回值类型 方法全类名(参数表))
通配符:
*:
匹配一个或多个字符
// 匹配多个字符 public * com.xptx.impl.MyMath*.add(int,int)) // 匹配一个字符 public * com.xptx.impl.MyMath*r.add(int,int))
匹配任意一个参数:
public * com.xptx.impl.MyMathCalculator.*(int,*))
权限位置不能用 *: public(可选)
… :
匹配任意多个参数,匹配任意类型参数:
execution(public * com.xptx.impl.MyMathCalculator.*(..))"
匹配任意多层路径:
execution(public * com.xptx..MyMathCalculator.*(..))"
3.3.3 通知方法的执行顺序:
- 正常执行: @Before(前置通知) --> @After(后置通知) -->@AfterReturning(正常返回)
- 异常执行: @Before(前置通知) --> @After(后置通知) -->@AfterThrowing(异常返回)
3.3.4 JoinPoint 获取目标方法的信息:
如何在通知方法运行的时候,拿到目标方法的详细信息:
- 在通知方法的参数列表上写一个参数:
- JoinPoint joinPoint : 封装了当前目标方法的详细信息。
@Before("execution(public * com.xptx.impl.MyMathCalculator.*(..))")
public static void logStart(JoinPoint joinPoint) {
// 获取参数列表
Object[] args = joinPoint.getArgs();
// 得到方法的信息
String name = joinPoint.getSignature().getName();
System.out.println("["+name+"]方法开始执行,用的参数列表"+Arrays.asList(args));
}
3.3.5 得到异常信息和返回结果:
得到异常信息和返回结果:
告诉Spring 这个方法需要返回结果:
- returning = “result”
告诉Spring 哪个参数用来接收异常:
- throwing=“e” – 告诉Spring 哪个参数用来接收异常。
@AfterReturning(value = "execution(public * com.xptx.impl.MyMathCalculator.*(..))",returning = "result")
public static void logReturn(JoinPoint joinPoint,Object result) {
String name = joinPoint.getSignature().getName();
System.out.println("["+name+"]方法执行完成,计算结果是:"+result);
}
/**
* 在目标方法抛出异常之后执行
*/
@AfterThrowing(value = "execution(public * com.xptx.impl.MyMathCalculator.*(int,int))",throwing = "e")
public static void logException(JoinPoint joinPoint,Exception e) {
System.out.println("["+joinPoint.getSignature().getName()+"]方法出现异常,异常信息是:"+e);
}
3.3.6 Spring 对通知方法的约束:
方法的参数列表一定不能乱写,通知方法是 Spring 利用反射调用的,每次方法调用都得确定这个方法的参数表的值,参数表上的每一个参数,Spring 都得知道。
3.3.7 抽取可重用的切点表达式:
- 随便声明一个没有实现的 返回 void 的空方法。
- 给方法标注 @Pointcut 注解。
// 可重用的切入点表达式,这样用起来修改更加方便
@Pointcut("execution(public * com.xptx.impl.MyMathCalculator.*(int,int))")
public void myPoint(){};
/**
* 想在目标方法之前运行
*/
// 直接重用切入点表达式即可。
@Before("myPoint()")
public static void logStart(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
String name = joinPoint.getSignature().getName();
System.out.println("["+name+"]方法开始执行,用的参数列表"+Arrays.asList(args));
}
/**
* 在目标方法正常返回之后运行
*/
@AfterReturning(value = "myPoint()",returning = "result")
public static void logReturn(JoinPoint joinPoint,Object result) {
String name = joinPoint.getSignature().getName();
System.out.println("["+name+"]方法执行完成,计算结果是:"+result);
}
/**
* 在目标方法抛出异常之后执行
*/
@AfterThrowing(value = "myPoint()",throwing = "e")
public static void logException(JoinPoint joinPoint,Exception e) {
System.out.println("["+joinPoint.getSignature().getName()+"]方法出现异常,异常信息是:"+e);
}
/**
* 在目标方法之后执行
*/
@After("myPoint()")
public static void logEnd(JoinPoint joinPoint) {
System.out.println("["+joinPoint.getSignature().getName()+"]最终结束了");
}
3.3.8 环绕通知:
@Around 环绕通知: Spring 中最强大的通知。
try{
// 前置通知
method.invoke(obj,args);
// 返回通知
} catch(Exception e) {
// 异常通知
} finally{
// 后置通知
}
这四个通知合起来就是 环绕通知。
@Pointcut("execution(public * com.xptx.impl.MyMathCalculator.*(int,int))")
public void myPoint(){};
/**
* 环绕通知,从代码中可以看到环绕通知中就包括了其它 4 个通知。
*/
@Around("myPoint()")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
// 获取方法名
String name = pjp.getSignature().getName();
Object proceed = null;
try{
System.out.println("环绕前置通知:"+ name + "方法开始执行。");
// 就是利用 反射调用 目标方法即可,实际上就是 method.invoke(obj,args);
proceed = pjp.proceed(args);
System.out.println("环绕返回通知:"+ name + "方法返回执行完成,执行结果是:"+proceed);
} catch (Exception e) {
System.out.println("环绕异常通知.异常信息:"+ e.getCause());
} finally {
System.out.println("环绕后置通知:"+name +"执行结束");
}
return proceed;
}
环绕通知:优于普通通知执行。
- 环绕只是影响当前切面。
3.3.9 多切面运行顺序:
LogUtilsAspect
package com.xptx.utils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
*
* 切面类: 如何将这个类中的所有方法(通知方法)动态的在目标方法的各个位置切入
*
*/
// 将切面类注册到 IOC 容器中
@Component
// 告诉Spring 这是个切面类
@Aspect
public class LogUtils {
@Pointcut("execution(public * com.xptx.impl.MyMathCalculator.*(int,int))")
public void myPoint(){};
/**
* 想在目标方法之前运行
*/
@Before("myPoint()")
public static void logStart(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
String name = joinPoint.getSignature().getName();
System.out.println("LogUtils["+name+"]方法开始执行,用的参数列表"+Arrays.asList(args));
}
/**
* 在目标方法正常返回之后运行
*/
@AfterReturning(value = "myPoint()",returning = "result")
public static void logReturn(JoinPoint joinPoint,Object result) {
String name = joinPoint.getSignature().getName();
System.out.println("LogUtils["+name+"]方法执行完成,计算结果是:"+result);
}
/**
* 在目标方法抛出异常之后执行
*/
@AfterThrowing(value = "myPoint()",throwing = "e")
public static void logException(JoinPoint joinPoint,Exception e) {
System.out.println("LogUtils["+joinPoint.getSignature().getName()+"]方法出现异常,异常信息是:"+e);
}
/**
* 在目标方法之后执行
*/
@After("myPoint()")
public static void logEnd(JoinPoint joinPoint) {
System.out.println("LogUtils["+joinPoint.getSignature().getName()+"]最终结束了");
}
/**
* 环绕通知
*/
//@Around("myPoint()")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
// 获取方法名
String name = pjp.getSignature().getName();
Object proceed = null;
try{
System.out.println("环绕前置通知:"+ name + "方法开始执行。");
// 就是利用 反射调用 目标方法即可,实际上就是 method.invoke(obj,args);
proceed = pjp.proceed(args);
System.out.println("环绕返回通知:"+ name + "方法返回执行完成,执行结果是:"+proceed);
} catch (Exception e) {
System.out.println("环绕异常通知.异常信息:"+ e.getCause());
} finally {
System.out.println("环绕后置通知:"+name +"执行结束");
}
return proceed;
}
}
ValidateAspect
package com.xptx.utils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
@Aspect
public class ValidateAspect {
@Before("com.xptx.utils.LogUtils.myPoint()")
public static void logStart(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
String name = joinPoint.getSignature().getName();
System.out.println("Va["+name+"]方法开始执行,用的参数列表"+ Arrays.asList(args));
}
@AfterReturning(value = "com.xptx.utils.LogUtils.myPoint()",returning = "result")
public static void logReturn(JoinPoint joinPoint,Object result) {
String name = joinPoint.getSignature().getName();
System.out.println("Va["+name+"]方法执行完成,计算结果是:"+result);
}
@AfterThrowing(value = "com.xptx.utils.LogUtils.myPoint()",throwing = "e")
public static void logException(JoinPoint joinPoint,Exception e) {
System.out.println("Va["+joinPoint.getSignature().getName()+"]方法出现异常,异常信息是:"+e);
}
@After("com.xptx.utils.LogUtils.myPoint()")
public static void logEnd(JoinPoint joinPoint) {
System.out.println("Va["+joinPoint.getSignature().getName()+"]最终结束了");
}
}
切面类会按照类名的字典序进行排列,先进后出的顺序。
如何改变顺序:
4、AOP 的使用场景:
- AOP 加日志保存到数据库。
- AOP 做权限验证(前置通知)。
- AOP 做安全检查。
- AOP 做事务控制。
5、基于配置的AOP:
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
">
<context:component-scan base-package="com.xptx">
</context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!-- 基于配置的 AOP -->
<bean id="myMathCalculator" class="com.xptx.impl.MyMathCalculator" />
<bean id="validateAspect" class="com.xptx.utils.ValidateAspect"/>
<bean id="logUtils" class="com.xptx.utils.LogUtils"/>
<!-- 需要 AOP 命名空间 -->
<aop:config>
<aop:pointcut id="myPoint" expression="execution(public * com.xptx.impl.MyMathCalculator.*(int,int))"/>
<!-- 指定切面 order 是优先级 -->
<aop:aspect ref="logUtils" order="1">
<!-- 当前切面能用的 -->
<aop:pointcut id="myPoint" expression="execution(public * com.xptx.impl.MyMathCalculator.*(int,int))"/>
<aop:before method="logStart" pointcut-ref="myPoint"></aop:before>
<aop:after-returning method="logReturn" pointcut-ref="myPoint" returning="result"/>
<aop:after-throwing method="logException" pointcut-ref="myPoint" throwing="e"/>
<aop:after method="logEnd" pointcut-ref="myPoint"/>
<aop:around method="myAround" pointcut-ref="myPoint"/>
</aop:aspect>
<aop:aspect ref="validateAspect">
<aop:before method="logStart" pointcut-ref="myPoint"/>
<aop:after-returning method="logReturn" pointcut-ref="myPoint" returning="result"/>
<aop:after-throwing method="logException" pointcut-ref="myPoint" throwing="e"/>
<aop:after method="logEnd" pointcut-ref="myPoint"/>
</aop:aspect>
</aop:config>
</beans>
重要的用配置。