1、名词理解
- 切面(Aspect):
- 含有前置通知,后置通知,返回通知,异常抛出通知,环绕通知等方法的类;
- 通知(Advice):
- 切入点(PointCute):
- 通知需要在哪些方法上执行的表达式;(可以唯一匹配或模糊匹配);
- 连接点(JoinPoint):
- 目标(Target):
- 代理(Proxy):
- 生成的代理类(包含原业务类的 核心代码 和 通知里面的代码);
2、前置通知
2.1 jar
- <properties>
- <spring.version>4.3.18.RELEASE</spring.version>
- </properties>
- <dependencies>
-
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-beans</artifactId>
- <version>${spring.version}</version>
- </dependency>
-
-
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-core</artifactId>
- <version>${spring.version}</version>
- </dependency>
-
-
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-context</artifactId>
- <version>${spring.version}</version>
- </dependency>
-
-
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-expression</artifactId>
- <version>${spring.version}</version>
- </dependency>
-
-
-
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-aspects</artifactId>
- <version>${spring.version}</version>
- </dependency>
-
- </dependencies>
复制代码 2.2 切入点
通知需要在哪些方法上执行的表达式;(可以唯一匹配或模糊匹配);
2.2.1 唯一匹配
- execution(public int com.kgc.spring.aspectj.ArithmeticCalculator.add(int ,int ))
复制代码 execution(修饰符 返回值类型 方法全类名)
2.2.2 模糊匹配
- execution(* com.kgc.spring.aspectj.*.*(..)
复制代码 通用切入点表达式含义:
- 第一个*:代表任意的修饰符,任意的返回值类型;
- 第二个*:代表任意的类;
- 第三个*:代表任意的方法;
- . . :代表任意的类型和个数的形参;
2.2.3 可重用切入点表达式
其他地方直接应用此方法即可;- //重用切入点表达式
- @Pointcut( "execution(* com.kgc.spring.aspectj.*.*(..))")
- public void joinPointcut(){}
- //同一个类中引用
- @Before("joinPointcut()")
- @After("joinPointcut()")
- //其他类中引用(方法全类名)
- @Before("com.kgc.spring.aspectj.LogAspect.joinPointcut()")
复制代码 2.3 JoinPoint 和 ProceedingJoinPoint
2.3.1 JoinPoint 对象
JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象。
常用api:
方法名功能Signature getSignature();获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息Object[] getArgs();获取传入目标方法的参数对象Object getTarget();获取被代理的对象Object getThis();获取代理对象2.3.2 ProceedingJoinPoint对象
ProceedingJoinPoint对象是JoinPoint的子接口,该对象只用在@Around的切面方法中 添加了 两个方法.
方法名功能Object proceed() throws Throwable执行目标方法Object proceed(Object[] var1) throws Throwable传入的新的参数去执行目标方法2.4 @Before
2.4.1 接口
ArithmeticCalculator- public interface ArithmeticCalculator {
- //加
- int add(int m,int n);
- //减
- int sub(int m,int n);
- //乘
- int nul(int m,int n);
- //除
- int div(int m,int n);
- }
复制代码 2.4.2 实现类
ArithmeticCalculatorImpl- @Service("arithmeticCalculator")
- //起别名,方便单元测试,根据别名,从容器中获取
- public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
- @Override
- public int add(int m, int n) {
- return m + n;
- }
- @Override
- public int sub(int m, int n) {
- return m - n;
- }
- @Override
- public int nul(int m, int n) {
- return m*n;
- }
- @Override
- public int div(int m, int n) {
- System.out.println("====== 执行 div 方法 ======");
- return m/n;
- }
- }
复制代码 2.4.3 @Before 前置通知
在目标方法执行前,自动执行此方法(通过代理实现);- @Component //声明为一个普通的组件,放入spring的容器中,才可以生效
- @Aspect //声明当前类是 一个切面
- public class LogAspect {
- //重用切入点表达式
- @Pointcut( "execution(* com.kgc.spring.aspectj.*.*(..))")
- public void joinPointcut(){}
- //前置通知 @Before
- @Before("joinPointcut()")
- public void logBeforeMethod(JoinPoint joinPoint){
- //获取通知作用的目标方法名
- String methodName = joinPoint.getSignature().getName();
- //获取通知作用的目标方法入参,返回的是参数值数组
- Object[] methodParams = joinPoint.getArgs();
- System.out.println("------ LogAspect "+methodName+" 方法,入参:"+ Arrays.toString(methodParams) +" ------");
- }
-
- }
复制代码 2.5 配置文件
spring-aop.xml- <?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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
-
- <context:component-scan base-package="com.kgc.spring.aspectj"></context:component-scan>
-
-
- <aop:aspectj-autoproxy ></aop:aspectj-autoproxy>
-
- </beans>
复制代码 2.6测试
- public void testSpringAopAspectj(){
- //从容器中获取计算器的实例对象
- ArithmeticCalculator arithmeticCalculator = context.getBean("arithmeticCalculator", ArithmeticCalculator.class);
- System.out.println(arithmeticCalculator.getClass());
- //调用切面作用的目标方法,执行操作,
- int result = arithmeticCalculator.div(20, 10);
- System.out.println("****** 通过单元测试,计算结果:"+result +" ******");
- }
复制代码 测试结果- class com.sun.proxy.$Proxy15
-
- ------ LogAspect div 方法,入参:[20, 10] ------
-
- ====== 执行 div 方法 ======
-
- ****** 通过单元测试,计算结果:2 ******
复制代码 3、后置通知
3.1 @After
目标方法发执行之后,自动执行;
特点:
- 后置通知无法获取目标方法的返回值;
- 它的执行跟目标方法是否抛出异常无关,不影响此方法的执行;
- @After("joinPointcut()")
- public void logAfterMethod(JoinPoint joinPoint){
- //获取通知作用的目标方法名
- String methodName = joinPoint.getSignature().getName();
- //获取通知作用的目标方法入参,返回的是参数值数组
- Object[] methodParams = joinPoint.getArgs();
- System.out.println("------ LogAspect "+methodName+" 方法执行结束 ------");
- }
复制代码 3.2 测试
3.2.1 无异常
- @Test
- public void testSpringAopAspectj(){
- //从容器中获取计算器的实例对象
- ArithmeticCalculator arithmeticCalculator = context.getBean("arithmeticCalculator", ArithmeticCalculator.class);
- //调用切面作用的目标方法,执行操作,
- int result = arithmeticCalculator.div(20, 10);
- System.out.println("****** 通过单元测试,计算结果:"+result +" ******");
- }
复制代码 测试结果- ====== 执行 div 方法 ======
-
- ------ LogAspect div 方法执行结束 ------
-
- ****** 通过单元测试,计算结果:2 ******
复制代码 3.2.2 有异常
- @Test
- public void testSpringAopAspectj(){
- //从容器中获取计算器的实例对象
- ArithmeticCalculator arithmeticCalculator = context.getBean("arithmeticCalculator", ArithmeticCalculator.class);
- //调用切面作用的目标方法,执行操作,
- int result = arithmeticCalculator.div(20, 0);
- System.out.println("****** 通过单元测试,计算结果:"+result +" ******");
- }
复制代码 测试结果- ====== 执行 div 方法 ======
-
- ------ LogAspect div 方法执行结束 ------ //有异常也会执行后置通知
- java.lang.ArithmeticException: / by zero
复制代码 4、返回通知
4.1 @AfterReturning
- 目标方法返回结果后自动执行,可以获取目标方法的返回值;
- 但是要求@AfterReturning必须增加属性returning,指定一个参数名;
- 且此参数名必须跟通知方法的一个形参名一致,用于接收返回值;
- @AfterReturning(value = "joinPointcut()",returning = "result")
- public void afterReturningMethod(JoinPoint joinPoint,Object result){
- //获取通知作用的目标方法名
- String methodName = joinPoint.getSignature().getName();
- System.out.println("------ LogAspect "+methodName+" 方法,执行结果:"+ result +" ------");
- }
复制代码 4.2 测试
测试结果- ====== 执行 div 方法 ======
-
- ------ LogAspect div 方法,返回结果:2 ------
-
- ****** 通过单元测试,计算结果:2 ******
复制代码 5、异常抛出通知
5.1 @AfterThrowing
- 异常抛出通知 @AfterThrowing ,在目标方法抛出异常后,可以获取目标方法发生异常后抛出的异常信息;
- 但是要求 @AfterThrowing 必须增加属性 throwing,指定一个参数名;
- 且此参数名必须跟通知方法的一个形参名一致,用于接收异常;
- @AfterThrowing(value = "joinPointcut()",throwing = "ex")
- public void logAfterThrowingMethod(JoinPoint joinPoint,Exception ex){
- //获取通知作用的目标方法名
- String methodName = joinPoint.getSignature().getName();
- System.out.println("------ LogAspect "+methodName+" 方法,执行异常信息:"+ ex.getMessage() +" ------");
- }
复制代码 5.2 测试
- @Test
- public void testSpringAopAspectj(){
- //从容器中获取计算器的实例对象
- ArithmeticCalculator arithmeticCalculator = context.getBean("arithmeticCalculator", ArithmeticCalculator.class);
- System.out.println(arithmeticCalculator.getClass());
- //调用切面作用的目标方法,执行操作,
- int result = arithmeticCalculator.div(20, 0);
- System.out.println("****** 通过单元测试,计算结果:"+result +" ******");
- }
复制代码 测试结果- ====== 执行 div 方法 ======
- ------ LogAspect div 方法,执行异常信息:/ by zero ------
- java.lang.ArithmeticException: / by zero
复制代码 6、环绕通知
6.1 @Around
- 环绕通知 @Around,可以看作是上面四种通知的结合体,一般不建议跟单个的通知共用(防止冲突失效);
- 作用:可以让开发人员在环绕通知的处理方法中根据不同也业务逻辑,决定是否发起对目标方法的调用;
- @Around(value = "joinPointcut()")
- public Object logAroundMethod(ProceedingJoinPoint joinPoint){
- //获取通知作用的目标方法名
- String methodName = joinPoint.getSignature().getName();
- //定义获取目标方法的返回值变量
- Object result = null;
- try{
- //实现前置通知功能
- System.out.println("------ LogAspect "+methodName+" 方法 Around通知,入参:"+ Arrays.toString(joinPoint.getArgs()) +" ------");
- //手动调用原目标方法(业务中决定,是否对核心方法方法发起调用)
- result = joinPoint.proceed();
- }catch (Throwable tx){
- //实现异常抛出通知功能
- System.out.println("------ LogAspect "+methodName+" 方法 Around通知,执行异常信息:"+ tx.getMessage() +" ------");
- }finally {
- //实现后置通知功能
- System.out.println("------ LogAspect "+methodName+" 方法 Around通知,执行结束 ------");
- }
- //实现返回通知功能
- System.out.println("------ LogAspect "+methodName+" 方法 Around通知,执行结果:"+ result +" ------");
- return result;
- }
复制代码 6.2 测试
6.2.1 测试结果,无异常
- //调用切面作用的目标方法,执行操作,
- int result = arithmeticCalculator.div(20, 10);
复制代码- class com.sun.proxy.$Proxy13
-
- ------ LogAspect div 方法 Around通知,入参:[20, 10] ------
-
- ====== 执行 div 方法 ======
-
- ------ LogAspect div 方法 Around通知,执行结束 ------
-
- ------ LogAspect div 方法 Around通知,返回结果:2 ------
-
- ****** 通过单元测试,计算结果:2 ******
复制代码 6.2.2 测试结果,有异常
- //调用切面作用的目标方法,执行操作,
- int result = arithmeticCalculator.div(20, 0);
复制代码- class com.sun.proxy.$Proxy13
-
- ------ LogAspect div 方法 Around通知,入参:[20, 0] ------
-
- ====== 执行 div 方法 ======
-
- ------ LogAspect div 方法 Around通知,执行异常信息:/ by zero ------
-
- ------ LogAspect div 方法 Around通知,执行结束 ------
-
- ------ LogAspect div 方法 Around通知,返回结果:null ------
复制代码 6.2.3 测试结果 不调用 原方法
- //调用切面作用的目标方法,执行操作,
- int result = arithmeticCalculator.div(20, 0);//(业务中决定,是否对核心方法发起调用)//不调用核心方法//result = joinPoint.proceed();
复制代码- ------ LogAspect div 方法 Around通知,入参:[20, 10] ------
-
- ------ LogAspect div 方法 Around通知,执行结束 ------
-
- ------ LogAspect div 方法 Around通知,返回结果:null ------
复制代码 7、切入点优先级
当有多个前置通知时,我们想自定义前置通知顺序:使用@Order(int)
指定切面优先级,一般都是int型整数,值越小,优先级越高**(默认值 2^31 - 1 最低优先级);
7.1 多个前置通知
logBeforeMethod- @Before("joinPointcut()")
- public void logBeforeMethod(JoinPoint joinPoint){
- //获取通知作用的目标方法名
- String methodName = joinPoint.getSignature().getName();
- //获取通知作用的目标方法入参,返回的是参数值数组
- Object[] methodParams = joinPoint.getArgs();
- System.out.println("------ LogAspectBeforeMethod "+methodName+" 方法,入参:"+ Arrays.toString(methodParams) +" ------");
- }
复制代码 verifyBeforeMethod- @Component
- @Aspect
- public class VerifyParamAspect {
- @Before("com.kgc.spring.aspectj.LogAspect.joinPointcut()")
- public void verifyBeforeMethod( JoinPoint joinPoint){
- //获取通知作用的目标方法名
- String methodName = joinPoint.getSignature().getName();
- //获取通知作用的目标方法入参,返回的是参数值数组
- Object[] methodParams = joinPoint.getArgs();
- System.out.println("------ verifyBeforeMethod "+methodName+" 方法,入参:"+ Arrays.toString(methodParams) +" ------");
- }
- }
复制代码 7.2 测试(默认)
- @Test
- public void testVerifyParamAspect(){
- //从容器中获取计算器的实例对象
- ArithmeticCalculator arithmeticCalculator = context.getBean("arithmeticCalculator", ArithmeticCalculator.class);
- System.out.println(arithmeticCalculator.getClass());
- //调用切面作用的目标方法,执行操作,
- int result = arithmeticCalculator.div(20, 10);
- System.out.println("****** 通过单元测试,计算结果:"+result +" ******");
- }
复制代码 测试结果- ------ LogAspectBeforeMethod div 方法,入参:[20, 10] ------ //LogAspectBeforeMethod 先执行
-
- ------ verifyBeforeMethod div 方法,入参:[20, 10] ------
-
- ====== 执行 div 方法 ======
-
- ****** 通过单元测试,计算结果:2 ******
复制代码 7.3 测试(自定义优先级)
- @Component
- @Aspect
- @Order(1) //指定切面优先级,一般都是int型整数,值越小,优先级越高(默认值 2^31 - 1)
- public class VerifyParamAspect {
- @Before("com.kgc.spring.aspectj.LogAspect.joinPointcut()")
- public void verifyBeforeMethod( JoinPoint joinPoint){
- //获取通知作用的目标方法名
- String methodName = joinPoint.getSignature().getName();
- //获取通知作用的目标方法入参,返回的是参数值数组
- Object[] methodParams = joinPoint.getArgs();
- System.out.println("------ verifyBeforeMethod "+methodName+" 方法,入参:"+ Arrays.toString(methodParams) +" ------");
- }
- }
复制代码 测试结果- ------ verifyBeforeMethod div 方法,入参:[20, 10] ------ //优先级高的切面中的verifyBeforeMethod,先执行
-
- ------ LogAspectBeforeMethod div 方法,入参:[20, 10] ------
-
- ====== 执行 div 方法 ======
-
- ****** 通过单元测试,计算结果:2 ******
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |