Spring之@Aspect中通知的5种方式详解
作者:azhou的代码园 发布时间:2021-12-12 20:28:02
@Aspect中有5种通知
@Before:前置通知, 在方法执行之前执行
@Aroud:环绕通知, 围绕着方法执行
@After:后置通知, 在方法执行之后执行
@AfterReturning:返回通知, 在方法返回结果之后执行
@AfterThrowing:异常通知, 在方法抛出异常之后
这几种通知用起来都比较简单,都是通过注解的方式,将这些注解标注在@Aspect类的方法上,这些方法就会对目标方法进行拦截,下面我们一个个来看一下。
@Before:前置通知
介绍
定义一个前置通知
@Aspect
public class BeforeAspect {
@Before("execution(* com.javacode2018.aop.demo10.test1.Service1.*(..))")
public void before(JoinPoint joinPoint) {
System.out.println("我是前置通知!");
}
}
类上需要使用
@Aspect
标注任意方法上使用
@Before
标注,将这个方法作为前置通知,目标方法被调用之前,会自动回调这个方法被
@Before
标注的方法参数可以为空,或者为JoinPoint
类型,当为JoinPoint
类型时,必须为第一个参数被
@Before
标注的方法名称可以随意命名,符合java规范就可以,其他通知也类似
@Before
中value的值为切入点表达式,也可以采用引用的方式指定切入点,如:
package com.javacode2018.aop.demo10.test1;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class BeforeAspect {
@Pointcut("execution(* com.javacode2018.aop.demo10.test1.Service1.*(..))")
public void pc() {
}
@Before("com.javacode2018.aop.demo10.test1.BeforeAspect.pc()")
public void before(JoinPoint joinPoint) {
System.out.println("我是前置通知!");
}
}
此时,before方法上面的切入引用了pc方法上面的@Pointcut
的值
案例
来个普通的service
package com.javacode2018.aop.demo10.test1;
public class Service1 {
public String say(String name) {
return "你好:" + name;
}
public String work(String name) {
return "开始工作了:" + name;
}
}
给上面的类定义一个前置通知,Service1
中的所有方法执行执行,输出一段文字我是前置通知!
package com.javacode2018.aop.demo10.test1;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class BeforeAspect1 {
@Pointcut("execution(* com.javacode2018.aop.demo10.test1.Service1.*(..))")
public void pc() {
}
@Before("com.javacode2018.aop.demo10.test1.BeforeAspect1.pc()")
public void before(JoinPoint joinPoint) {
System.out.println("我是前置通知!");
}
}
测试代码
package com.javacode2018.aop.demo10;
import com.javacode2018.aop.demo10.test1.BeforeAspect1;
import com.javacode2018.aop.demo10.test1.Service1;
import org.junit.Test;
import org.springframework.aop.aspectj.annotation.AspectJProxyFactory;
public class AopTest10 {
@Test
public void test1() {
Service1 target = new Service1();
Class<BeforeAspect1> aspectClass = BeforeAspect1.class;
AspectJProxyFactory proxyFactory = new AspectJProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAspect(aspectClass);
Service1 proxy = proxyFactory.getProxy();
System.out.println(proxy.say("路人"));
System.out.println(proxy.work("路人"));
}
}
运行输出
我是前置通知!
你好:路人
我是前置通知!
开始工作了:路人
对应的通知类
@Before通知最后会被解析为下面这个通知类
org.springframework.aop.aspectj.AspectJMethodBeforeAdvice
通知中获取被调方法信息
通知中如果想获取被调用方法的信息,分2种情况
非环绕通知,可以将
org.aspectj.lang.JoinPoint
作为通知方法的第1个参数,通过这个参数获取被调用方法的信息如果是环绕通知,可以将
org.aspectj.lang.ProceedingJoinPoint
作为方法的第1个参数,通过这个参数获取被调用方法的信息
JoinPoint:连接点信息
org.aspectj.lang.JoinPoint
提供访问当前被通知方法的目标对象、代理对象、方法参数等数据:
package org.aspectj.lang;
import org.aspectj.lang.reflect.SourceLocation;
public interface JoinPoint {
String toString(); //连接点所在位置的相关信息
String toShortString(); //连接点所在位置的简短相关信息
String toLongString(); //连接点所在位置的全部相关信息
Object getThis(); //返回AOP代理对象
Object getTarget(); //返回目标对象
Object[] getArgs(); //返回被通知方法参数列表,也就是目前调用目标方法传入的参数
Signature getSignature(); //返回当前连接点签名,这个可以用来获取目标方法的详细信息,如方法Method对象等
SourceLocation getSourceLocation();//返回连接点方法所在类文件中的位置
String getKind(); //连接点类型
StaticPart getStaticPart(); //返回连接点静态部分
}
ProceedingJoinPoint:环绕通知连接点信息
用于环绕通知,内部主要关注2个方法,一个有参的,一个无参的,用来继续执行 * 链上的下一个通知。
package org.aspectj.lang;
import org.aspectj.runtime.internal.AroundClosure;
public interface ProceedingJoinPoint extends JoinPoint {
/**
* 继续执行下一个通知或者目标方法的调用
*/
public Object proceed() throws Throwable;
/**
* 继续执行下一个通知或者目标方法的调用
*/
public Object proceed(Object[] args) throws Throwable;
}
Signature:连接点签名信息
注意JoinPoint#getSignature()
这个方法,用来获取连接点的签名信息,这个比较重要
Signature getSignature();
通常情况,spring中的aop都是用来对方法进行拦截,所以通常情况下连接点都是一个具体的方法,Signature
有个子接口
org.aspectj.lang.reflect.MethodSignature
JoinPoint#getSignature()
都可以转换转换为MethodSignature
类型,然后可以通过这个接口提供的一些方法来获取被调用的方法的详细信息。
下面对上面的前置通知的案例改造一下,获取被调用方法的详细信息,新建一个Aspect类:BeforeAspect2
package com.javacode2018.aop.demo10.test2;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import java.lang.reflect.Method;
@Aspect
public class BeforeAspect2 {
@Pointcut("execution(* com.javacode2018.aop.demo10.test1.Service1.*(..))")
public void pc() {
}
@Before("com.javacode2018.aop.demo10.test2.BeforeAspect2.pc()")
public void before(JoinPoint joinPoint) {
//获取连接点签名
Signature signature = joinPoint.getSignature();
//将其转换为方法签名
MethodSignature methodSignature = (MethodSignature) signature;
//通过方法签名获取被调用的目标方法
Method method = methodSignature.getMethod();
//输出方法信息
System.out.println(method);
}
}
测试用例
@Test
public void test2() {
Service1 target = new Service1();
Class<BeforeAspect2> aspectClass = BeforeAspect2.class;
AspectJProxyFactory proxyFactory = new AspectJProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAspect(aspectClass);
Service1 proxy = proxyFactory.getProxy();
System.out.println(proxy.say("路人"));
System.out.println(proxy.work("路人"));
}
运行输出
public java.lang.String com.javacode2018.aop.demo10.test1.Service1.say(java.lang.String)
你好:路人
public java.lang.String com.javacode2018.aop.demo10.test1.Service1.work(java.lang.String)
开始工作了:路人
@Around:环绕通知
介绍
环绕通知会包裹目标目标方法的执行,可以在通知内部调用ProceedingJoinPoint.process
方法继续执行下一个 * 。
用起来和@Before类似,但是有2点不一样
若需要获取目标方法的信息,需要将ProceedingJoinPoint作为第一个参数
通常使用Object类型作为方法的返回值,返回值也可以为void
特点
环绕通知比较特殊,其他4种类型的通知都可以用环绕通知来实现。
案例
通过环绕通知来统计方法的耗时。
package com.javacode2018.aop.demo10.test3;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import java.lang.reflect.Method;
@Aspect
public class AroundAspect3 {
@Pointcut("execution(* com.javacode2018.aop.demo10.test1.Service1.*(..))")
public void pc() {
}
@Around("com.javacode2018.aop.demo10.test3.AroundAspect3.pc()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
//获取连接点签名
Signature signature = joinPoint.getSignature();
//将其转换为方法签名
MethodSignature methodSignature = (MethodSignature) signature;
//通过方法签名获取被调用的目标方法
Method method = methodSignature.getMethod();
long startTime = System.nanoTime();
//调用proceed方法,继续调用下一个通知
Object returnVal = joinPoint.proceed();
long endTime = System.nanoTime();
long costTime = endTime - startTime;
//输出方法信息
System.out.println(String.format("%s,耗时(纳秒):%s", method.toString(), costTime));
//返回方法的返回值
return returnVal;
}
}
测试用例
@Test
public void test3() {
Service1 target = new Service1();
Class<AroundAspect3> aspectClass = AroundAspect3.class;
AspectJProxyFactory proxyFactory = new AspectJProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAspect(aspectClass);
Service1 proxy = proxyFactory.getProxy();
System.out.println(proxy.say("路人"));
System.out.println(proxy.work("路人"));
}
运行输出
public java.lang.String com.javacode2018.aop.demo10.test1.Service1.say(java.lang.String),耗时(纳秒):19000500
你好:路人
public java.lang.String com.javacode2018.aop.demo10.test1.Service1.work(java.lang.String),耗时(纳秒):59600
开始工作了:路人
对应的通知类
@Around通知最后会被解析为下面这个通知类
org.springframework.aop.aspectj.AspectJAroundAdvice
@After:后置通知
介绍
后置通知,在方法执行之后执行,用法和前置通知类似。
特点
不管目标方法是否有异常,后置通知都会执行
这种通知无法获取方法返回值
可以使用
JoinPoint
作为方法的第一个参数,用来获取连接点的信息
案例
在Service1
中任意方法执行完毕之后,输出一行日志。
package com.javacode2018.aop.demo10.test4;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
@Aspect
public class AfterAspect4 {
@Pointcut("execution(* com.javacode2018.aop.demo10.test1.Service1.*(..))")
public void pc() {
}
@After("com.javacode2018.aop.demo10.test4.AfterAspect4.pc()")
public void after(JoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
System.out.println(String.format("%s,执行完毕!", methodSignature.getMethod()));
}
}
测试案例
@Test
public void test4() {
Service1 target = new Service1();
Class<AfterAspect4> aspectClass = AfterAspect4.class;
AspectJProxyFactory proxyFactory = new AspectJProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAspect(aspectClass);
Service1 proxy = proxyFactory.getProxy();
System.out.println(proxy.say("路人"));
System.out.println(proxy.work("路人"));
}
运行输出
public java.lang.String com.javacode2018.aop.demo10.test1.Service1.say(java.lang.String),执行完毕!
你好:路人
public java.lang.String com.javacode2018.aop.demo10.test1.Service1.work(java.lang.String),执行完毕!
开始工作了:路人
对应的通知类
@After通知最后会被解析为下面这个通知类
org.springframework.aop.aspectj.AspectJAfterAdvice
这个类中有invoke
方法,这个方法内部会调用被通知的方法,其内部采用try..finally
的方式实现的,所以不管目标方法是否有异常,通知一定会被执行。
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
try {
//继续执行下一个 *
return mi.proceed();
}
finally {
//内部通过反射调用被@After标注的方法
invokeAdviceMethod(getJoinPointMatch(), null, null);
}
}
@AfterReturning:返回通知
用法
返回通知,在方法返回结果之后执行。
特点
可以获取到方法的返回值
当目标方法返回异常的时候,这个通知不会被调用,这点和@After通知是有区别的
案例
后置通知中打印出方法及返回值信息。
package com.javacode2018.aop.demo10.test5;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
@Aspect
public class AfterReturningAspect5 {
@Pointcut("execution(* com.javacode2018.aop.demo10.test1.Service1.*(..))")
public void pc() {
}
@AfterReturning(value = "com.javacode2018.aop.demo10.test5.AfterReturningAspect5.pc()", returning = "retVal")
public void afterReturning(JoinPoint joinPoint, Object retVal) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
System.out.println(String.format("%s返回值:%s", methodSignature.getMethod(), retVal));
}
}
注意@AfterReturning
注解,用到了2个参数
value:用来指定切入点
returning:用来指定返回值对应方法的参数名称,返回值对应方法的第二个参数,名称为retVal
对应的通知类
@AfterReturning通知最后会被解析为下面这个通知类
org.springframework.aop.aspectj.AspectJAfterReturningAdvice
@AfterThrowing:异常通知
用法
在方法抛出异常之后会回调@AfterThrowing
标注的方法。
@AfterThrowing标注的方法可以指定异常的类型,当被调用的方法触发该异常及其子类型的异常之后,会触发异常方法的回调。也可以不指定异常类型,此时会匹配所有异常。
未指定异常类型
未指定异常类型,可以匹配所有异常类型,如下
@AfterThrowing(value = "切入点")
public void afterThrowing()
指定异常类型
通过@AfterThrowing
的throwing
指定参数异常参数名称,我们用方法的第二个参数用来接收异常,第二个参数名称为e,下面的代码,当目标方法发生IllegalArgumentException
异常及其子类型异常时,下面的方法会被回调。
@AfterThrowing(value = "com.javacode2018.aop.demo10.test6.AfterThrowingAspect6.pc()", throwing = "e")
public void afterThrowing(JoinPoint joinPoint, IllegalArgumentException e)
特点
不论异常是否被异常通知捕获,异常还会继续向外抛出。
案例
Service1中加了login方法,用户名不是路人甲java
时抛出异常。
package com.javacode2018.aop.demo10.test1;
public class Service1 {
public String say(String name) {
return "你好:" + name;
}
public String work(String name) {
return "开始工作了:" + name;
}
public boolean login(String name) {
if (!"路人甲java".equals(name)) {
throw new IllegalArgumentException("非法访问!");
}
return true;
}
}
来个异常通知
package com.javacode2018.aop.demo10.test6;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
@Aspect
public class AfterThrowingAspect6 {
@Pointcut("execution(* com.javacode2018.aop.demo10.test1.Service1.*(..))")
public void pc() {
}
@AfterThrowing(value = "com.javacode2018.aop.demo10.test6.AfterThrowingAspect6.pc()", throwing = "e")
public void afterThrowing(JoinPoint joinPoint, IllegalArgumentException e) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
System.out.println(String.format("%s发生异常,异常信息:%s", methodSignature.getMethod(), e.getMessage()));
}
}
测试用例
@Test
public void test6() {
Service1 target = new Service1();
Class<AfterThrowingAspect6> aspectClass = AfterThrowingAspect6.class;
AspectJProxyFactory proxyFactory = new AspectJProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAspect(aspectClass);
Service1 proxy = proxyFactory.getProxy();
proxy.login("路人");
}
运行输出
public boolean com.javacode2018.aop.demo10.test1.Service1.login(java.lang.String)发生异常,异常信息:非法访问!
java.lang.IllegalArgumentException: 非法访问!
at com.javacode2018.aop.demo10.test1.Service1.login(Service1.java:14)
at com.javacode2018.aop.demo10.test1.Service1$$FastClassBySpringCGLIB$$ea03ccbe.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:769)
对应的通知类
@AfterThrowing通知最后会被解析为下面这个通知类
org.springframework.aop.aspectj.AspectJAfterThrowingAdvice
来看一下这个类的invoke
方法,这个方法是关键
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
try {
//继续调用下一个 * 链
return mi.proceed();
}
catch (Throwable ex) {
//判断ex和需要不糊的异常是否匹配
if (shouldInvokeOnThrowing(ex)) {
//通过反射调用@AfterThrowing标注的方法
invokeAdviceMethod(getJoinPointMatch(), null, ex);
}
//继续向外抛出异常
throw ex;
}
}
几种通知对比
通知类型 | 执行时间点 | 可获取返回值 | 目标方法异常时是否会执行 |
---|---|---|---|
@Before | 方法执行之前 | 否 | 是 |
@Around | 环绕方法执行 | 是 | 自己控制 |
@After | 方法执行后 | 否 | 是 |
@AfterReturning | 方法执行后 | 是 | 否 |
@AfterThrowing | 方法发生异常后 | 否 | 是 |
来源:https://blog.csdn.net/weixin_46228112/article/details/124725186
猜你喜欢
- 本文实例讲解了统计文本中某个字符串出现的次数或字符串中指定元素出现的次数方法,分享给大家供大家参考,具体内容如下运行效果图:程序查找的上此文
- 序言for循环语句是java循环语句中最常用的循环语句,一般用在循环次数已知的情况下使用。for循环语句的语法格式如下:java语言 for
- 本文以实例形式详细讲述了Java的反射机制,是Java程序设计中重要的技巧。分享给大家供大家参考。具体分析如下:首先,Reflection是
- 笔者前段时间在做react-native开发,一直是有线连接安卓真机进行调试的。有线调试确实带来诸多麻烦,因为在调试过程中需要频繁和手机进行
- 前言使用基于TCP 协议的双向通信时,网络中的两个应用程序之间必须首先建立一个连接,这两个程序通过一个双向的通信连接实现数据的交换,这个连接
- 这篇文章主要介绍了设计模式在Spring框架中的应用汇总,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的
- C# 3.0为你提供了对象集合初始化器:/// <summary>/// 图书类/// </summary>publ
- Java-JDK * (AOP)使用及实现原理分析第一章:代理的介绍介绍:我们需要掌握的程度 * (理解) 基于反射机制掌握的程度:1.
- 一、基本概念C#只有两种数据类型:值类型和引用类型值类型在线程栈分配空间,引用类型在托管堆分配空间值类型转为引用类型称成为装箱,引用类型转为
- 一、引入maven依赖Spring Boot默认使用LogBack,但是我们没有看到显示依赖的jar包,其实是因为所在的jar包spring
- 背景公司的开发框架集成了附件本地存储,阿里云,华为云等,现项目有要求附件存储与应用部署环境不能是同一台服务器,也不能使用云存储,经过技术选型
- 简介在移动开发中,如果我们要实现一些图像处理相关的功能,难免要用到OpenCV。而OpenCV是用c++开发的。我们在Android中,需要
- 报错之一: Version 1.4.2_03 of the JVM not suitable for this product.Versio
- Java xml出现错误 javax.xml.transform.TransformerException: java.lang.NullP
- Android Studio 是谷歌基于IntelliJ IDEA开发的安卓开发工具,有点类似 EcliPSe ADT,Android St
- 被覆盖比较好理解,类似于多态的实现,访问时通过类方法表来访问,你实际是什么类型,访问的方法就是那个类型的方法而不会是你的父类的方法。被隐藏是
- 前言Genymotion 来自于 AndroVM 这个开源项目,基于 x86 和 VirtualBox,支持 OpenGL 加速,可以用于
- 这篇文章主要介绍了springmvc视图解析流程代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的
- collection标签的oftype属性能否为java.util.Map基于mybatis-3.4.5.jar版本,结论是可以的。<
- 基于SSM框架的仓库管理系统功能:系统操作权限管理。系统提供基本的登入登出功能,同时系统包含两个角色:系统超级管理员和普通管理员,超级管理员