Spring @Aspect、@Before、@After 注解实现 AOP 切面功能
目录
Spring AOP 注解概述
@Aspect 快速入门
execution 切点表达式
Spring AOP 注解概述
1、Spring 的 AOP 功能除了在配置文件中配置一大堆的配置,比如切入点、表达式、通知等等以外,使用注解的方式更为方便快捷,特别是 Spring boot 出现以后,基本不再使用原先的 beans.xml 等配置文件了,而都推荐注解编程。
@Aspect | 切面声明,标注在类、接口(包括注解类型)或枚举上。 |
@Pointcut |
切入点声明,即切入到哪些目标类的目标方法。 value 属性指定切入点表达式,默认为 "",用于被通知注解引用,这样通知注解只需要关联此切入点声明即可,无需再重复写切入点表达式 |
@Before |
前置通知, 在目标方法(切入点)执行之前执行。 value 属性绑定通知的切入点表达式,可以关联切入点声明,也可以直接设置切入点表达式 注意:如果在此回调方法中抛出异常,则目标方法不会再执行,会继续执行后置通知 -> 异常通知。 |
@After | 后置通知, 在目标方法(切入点)执行之后执行 |
@AfterReturning |
返回通知, 在目标方法(切入点)返回结果之后执行,在 @After 的后面执行 pointcut 属性绑定通知的切入点表达式,优先级高于 value,默认为 "" |
@AfterThrowing |
异常通知, 在方法抛出异常之后执行, 意味着跳过返回通知 pointcut 属性绑定通知的切入点表达式,优先级高于 value,默认为 "" 注意:如果目标方法自己 try-catch 了异常,而没有继续往外抛,则不会进入此回调函数 |
@Around |
环绕通知:目标方法执行前后分别执行一些代码,类似拦截器,可以控制目标方法是否继续执行。 通常用于统计方法耗时,参数校验等等操作。 环绕通知早于前置通知,晚于返回通知。 |
2、上面这些 AOP 注解都是位于如下所示的 aspectjweaver 依赖中:
3、对于习惯了 Spring 全家桶编程的人来说,并不是需要直接引入 aspectjweaver 依赖,因为 spring-boot-starter-aop 组件默认已经引用了 aspectjweaver 来实现 AOP 功能。换句话说 Spring 的 AOP 功能就是依赖的 aspectjweaver !
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId><version>2.1.4.RELEASE</version>
</dependency>
4、AOP 底层是通过 Spring 提供的的动态代理技术实现的,在运行期间动态生成代理对象,代理对象方法执行时进行增强功能的介入,再去调用目标对象的方法,从而完成功能的增强。主要使用 JDK 动态代理与 Cglib 动态代理。
@Aspect 快速入门
1、@Aspect 常见用于记录日志、异常集中处理、权限验证、Web 参数校验、事务处理等等
2、要想把一个类变成切面类,只需3步:
1)在类上使用 @Aspect 注解使之成为切面类 2)切面类需要交由 Sprign 容器管理,所以类上还需要有 @Service、@Repository、@Controller、@Component 等注解 |
3、AOP 的含义就不再累述了,下面直接上示例:
/*** 切面注解 Aspect 使用入门* 1、@Aspect:声明本类为切面类* 2、@Component:将本类交由 Spring 容器管理* 3、@Order:指定切入执行顺序,数值越小,切面执行顺序越靠前,默认为 Integer.MAX_VALUE** @author wangMaoXiong* @version 1.0* @date 2020/8/20 19:22*/
@Aspect
@Order(value = 999)
@Component
public class AspectHelloWorld {private static final Logger LOG = LoggerFactory.getLogger(AspectHelloWorld.class);/*** @Pointcut :切入点声明,即切入到哪些目标方法。value 属性指定切入点表达式,默认为 ""。* 用于被下面的通知注解引用,这样通知注解只需要关联此切入点声明即可,无需再重复写切入点表达式* <p>* 切入点表达式常用格式举例如下:* - * com.wmx.aspect.EmpService.*(..)):表示 com.wmx.aspect.EmpService 类中的任意方法* - * com.wmx.aspect.*.*(..)):表示 com.wmx.aspect 包(不含子包)下任意类中的任意方法* - * com.wmx.aspect..*.*(..)):表示 com.wmx.aspect 包及其子包下任意类中的任意方法* </p>* value 的 execution 可以有多个,使用 || 隔开.*/@Pointcut(value ="execution(* com.wmx.hb.controller.DeptController.*(..)) " +"|| execution(* com.wmx.hb.controller.EmpController.*(..))")private void aspectPointcut() {}/*** 前置通知:目标方法执行之前执行以下方法体的内容。* value:绑定通知的切入点表达式。可以关联切入点声明,也可以直接设置切入点表达式* <br/>* * @param joinPoint:提供对连接点处可用状态和有关它的静态信息的反射访问<br/> <p>* * * Object[] getArgs():返回此连接点处(目标方法)的参数,目标方法无参数时,返回空数组* * * Signature getSignature():返回连接点处的签名。* * * Object getTarget():返回目标对象* * * Object getThis():返回当前正在执行的对象* * * StaticPart getStaticPart():返回一个封装此连接点的静态部分的对象。* * * SourceLocation getSourceLocation():返回与连接点对应的源位置* * * String toLongString():返回连接点的扩展字符串表示形式。* * * String toShortString():返回连接点的缩写字符串表示形式。* * * String getKind():返回表示连接点类型的字符串* * * </p>*/@Before(value = "aspectPointcut()")public void aspectBefore(JoinPoint joinPoint) {Object[] args = joinPoint.getArgs();Signature signature = joinPoint.getSignature();Object target = joinPoint.getTarget();Object aThis = joinPoint.getThis();JoinPoint.StaticPart staticPart = joinPoint.getStaticPart();SourceLocation sourceLocation = joinPoint.getSourceLocation();String longString = joinPoint.toLongString();String shortString = joinPoint.toShortString();LOG.debug("【前置通知】" +"args={},signature={},target={},aThis={},staticPart={}," +"sourceLocation={},longString={},shortString={}", Arrays.asList(args), signature, target, aThis, staticPart, sourceLocation, longString, shortString);}/*** 后置通知:目标方法执行之后执行以下方法体的内容,不管目标方法是否发生异常。* value:绑定通知的切入点表达式。可以关联切入点声明,也可以直接设置切入点表达式*/@After(value = "aspectPointcut()")public void aspectAfter(JoinPoint joinPoint) {LOG.debug("【后置通知】kind={}", joinPoint.getKind());}/*** 返回通知:目标方法返回后执行以下代码* value 属性:绑定通知的切入点表达式。可以关联切入点声明,也可以直接设置切入点表达式* pointcut 属性:绑定通知的切入点表达式,优先级高于 value,默认为 ""* returning 属性:通知签名中要将返回值绑定到的参数的名称,默认为 ""** @param joinPoint :提供对连接点处可用状态和有关它的静态信息的反射访问* @param result :目标方法返回的值,参数名称与 returning 属性值一致。无返回值时,这里 result 会为 null.*/@AfterReturning(pointcut = "aspectPointcut()", returning = "result")public void aspectAfterReturning(JoinPoint joinPoint, Object result) {LOG.debug("【返回通知】,shortString={},result=", joinPoint.toShortString(), result);}/*** 异常通知:目标方法发生异常的时候执行以下代码,此时返回通知不会再触发* value 属性:绑定通知的切入点表达式。可以关联切入点声明,也可以直接设置切入点表达式* pointcut 属性:绑定通知的切入点表达式,优先级高于 value,默认为 ""* throwing 属性:与方法中的异常参数名称一致,** @param ex:捕获的异常对象,名称与 throwing 属性值一致*/@AfterThrowing(pointcut = "aspectPointcut()", throwing = "ex")public void aspectAfterThrowing(JoinPoint jp, Exception ex) {String methodName = jp.getSignature().getName();if (ex instanceof ArithmeticException) {LOG.error("【异常通知】" + methodName + "方法算术异常(ArithmeticException):" + ex.getMessage());} else {LOG.error("【异常通知】" + methodName + "方法异常:" + ex.getMessage());}}/*** 环绕通知* 1、@Around 的 value 属性:绑定通知的切入点表达式。可以关联切入点声明,也可以直接设置切入点表达式* 2、Object ProceedingJoinPoint.proceed(Object[] args) 方法:继续下一个通知或目标方法调用,返回处理结果,如果目标方法发生异常,则 proceed 会抛异常.* 3、假如目标方法是控制层接口,则本方法的异常捕获与否都不会影响目标方法的事务回滚* 4、假如目标方法是控制层接口,本方法 try-catch 了异常后没有继续往外抛,则全局异常处理 @RestControllerAdvice 中不会再触发** @param joinPoint* @return* @throws Throwable*/@Around(value = "aspectPointcut()")public Object handleControllerMethod(ProceedingJoinPoint joinPoint) throws Throwable {this.checkRequestParam(joinPoint);StopWatch stopWatch = StopWatch.createStarted();LOG.debug("【环绕通知】执行接口开始,方法={},参数={} ", joinPoint.getSignature(), Arrays.asList(joinPoint.getArgs()).toString());//继续下一个通知或目标方法调用,返回处理结果,如果目标方法发生异常,则 proceed 会抛异常.//如果在调用目标方法或者下一个切面通知前抛出异常,则不会再继续往后走.Object proceed = joinPoint.proceed(joinPoint.getArgs());stopWatch.stop();long watchTime = stopWatch.getTime();LOG.debug("【环绕通知】执行接口结束,方法={}, 返回值={},耗时={} (毫秒)", joinPoint.getSignature(), proceed, watchTime);return proceed;}/*** 参数校验,防止 SQL 注入** @param joinPoint*/private void checkRequestParam(ProceedingJoinPoint joinPoint) {Object[] args = joinPoint.getArgs();if (args == null || args.length <= 0) {return;}String params = Arrays.toString(joinPoint.getArgs()).toUpperCase();String[] keywords = {"DELETE ", "UPDATE ", "SELECT ", "INSERT ", "SET ", "SUBSTR(", "COUNT(", "DROP ","TRUNCATE ", "INTO ", "DECLARE ", "EXEC ", "EXECUTE ", " AND ", " OR ", "--"};for (String keyword : keywords) {if (params.contains(keyword)) {LOG.warn("参数存在SQL注入风险,其中包含非法字符 {}.", keyword);throw new RuntimeException("参数存在SQL注入风险:params=" + params);}}}
}
如上所示在不修改原来业务层代码的基础上,就可以使用 AOP 功能,在目标方法执行前后或者异常时都能捕获然后执行。
在线演示源码:src/main/java/com/wmx/hb/aop/AspectHelloWorld.java · 汪少棠/hb - Gitee.com
execution 切点表达式
1、@Pointcut 切入点声明注解,以及所有的通知注解都可以通过 value 属性或者 pointcut 属性指定切入点表达式。
2、切入点表达式通过 execution 函数匹配连接点,语法:execution([方法修饰符] 返回类型 包名.类名.方法名(参数类型) [异常类型])
- 访问修饰符可以省略;
- 返回值类型、包名、类名、方法名可以使用星号*代表任意;
- 包名与类名之间一个点.代表当前包下的类,两个点..表示当前包及其子包下的类;
- 参数列表可以使用两个点..表示任意个数,任意类型的参数列表;
3、切入点表达式的写法比较灵活,比如:* 号表示任意一个,.. 表示任意多个,还可以使用 &&、||、! 进行逻辑运算,不过实际开发中通常用不到那么多花里胡哨的,掌握以下几种就基本够用了。
execution(* com.wmx.aspect.EmpServiceImpl.findEmpById(Integer)) | 匹配 com.wmx.aspect.EmpService 类中的 findEmpById 方法,且带有一个 Integer 类型参数。 |
execution(* com.wmx.aspect.EmpServiceImpl.findEmpById(*)) | 匹配 com.wmx.aspect.EmpService 类中的 findEmpById 方法,且带有一个任意类型参数。 |
execution(* com.wmx.aspect.EmpServiceImpl.findEmpById(..)) | 匹配 com.wmx.aspect.EmpService 类中的 findEmpById 方法,参数不限。 |
execution(* grp.basic3.se.service.SEBasAgencyService3.editAgencyInfo(..)) || execution(* grp.basic3.se.service.SEBasAgencyService3.adjustAgencyInfo(..)) | 匹配 editAgencyInfo 方法或者 adjustAgencyInfo 方法 |
execution(* com.wmx.aspect.EmpService.*(..)) | 匹配 com.wmx.aspect.EmpService 类中的任意方法 |
execution(* com.wmx.aspect.*.*(..)) | 匹配 com.wmx.aspect 包(不含子包)下任意类中的任意方法 |
execution(* com.wmx.aspect..*.*(..)) | 匹配 com.wmx.aspect 包及其子包下任意类中的任意方法 |
execution(* grp.pm..*Controller.*(..)) | 匹配 grp.pm 包下任意子孙包中以 "Controller" 结尾的类中的所有方法 |
Spring @Aspect、@Before、@After 注解实现 AOP 切面功能相关推荐
- Spring boot 项目(五)——AOP切面
一.AOP简介 1.在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术. 2.AOP是 ...
- Multipart自定义资源限制文件大小限制设计——aop切面怎么才能切入Multipart的文件大小拦截?
Multipart自定义资源限制文件大小限制设计--aop切面怎么才能切入Multipart的文件大小拦截? author:陈镇坤27 创建时间:2022年1月23日 创作不易,转载请注明来源 摘要: ...
- Spring AOP 切面@Around注解的具体使用
@Around注解可以用来在调用一个具体方法前和调用后来完成一些具体的任务. 比如我们想在执行controller中方法前打印出请求参数,并在方法执行结束后来打印出响应值,这个时候,我们就可以借助于@ ...
- Spring —— 基于注解的Aop在同一类下产生嵌套时切面不生效问题产生原因及解决
一.背景介绍 由于程序中大量方法需要监控执行耗时,因此写了基于注解的Aop类来减少重复代码,主要作用是通过环绕通知在方法执行前后进行耗时计算,最后输出到日志/监控. 相关代码如下: // 注解 @Re ...
- java 切面 注解_Java自学之spring:使用注解进行面向切面编程(AOP)
学习目的:学会使用注解进行面向切面编程(AOP),实现在面向切面编程(AOP)中,使用XML配置完成的操作. Part 1 修改cn.vaefun.dao.UserServiceImpl.java,在 ...
- Spring AOP 切面(Aspect)应用详解
1. AOP 切面应用 下面是一个AOP切面的一个简单的应用实例 引入AOP依赖 <dependency><groupId>org.springframework.boot&l ...
- Spring基于注解的AOP配置
pom.xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns="htt ...
- Spring中AOP切面编程学习笔记
注解方式实现aop我们主要分为如下几个步骤: 1.在切面类(为切点服务的类)前用@Aspect注释修饰,声明为一个切面类. 2.用@Pointcut注释声明一个切点,目的是为了告诉切面,谁是它的服务对 ...
- Spring 框架基础(04):AOP切面编程概念,几种实现方式演示
本文源码:GitHub·点这里 || GitEE·点这里 一.AOP基础简介 1.切面编程简介 AOP全称:Aspect Oriented Programming,面向切面编程.通过预编译方式和运行期 ...
- 【Spring AOP】基于注解的 AOP 编程
Spring AOP 基于注解的 AOP 编程的开发 开发步骤 切入点复用 切换动态代理的创建方式(JDK.Cglib) AOP 开发中的一个坑(业务方法互相调用) AOP 知识总结 更多内容请查看笔 ...
最新文章
- 在Ubuntu 14.04 64bit上安装python-pyqt5软件包(python 2.7)
- 黑箱方法 支持向量机①
- 都2021年了,不会还有人连深度学习都不了解吧(一)- 激活函数篇
- TIMEOUT will also publish one order event
- 美团(Leaf)分布式ID算法
- linux yum libsasl2,CentOS8 yum 凡是安装 安装mysql +需要:libsasl2.so.2()(64bit)
- 关于 Docker ,你必须了解的核心都在这里
- 【Flutter】Dart的工厂构造方法单例对象初始化列表
- matlab画图常用符号,matlab画图特殊符号
- GEE学习笔记03(空间类型数据)
- 漫漫学习路——计算机专业学习经验记录
- 使用jQuery快速高效制作网页交互特效 第五章 上机练习四 制作广告图片轮播切换效果
- CSS设置表格行列,给bootstrap table设置行列单元格样式
- ABP应用开发(Step by Step)-下篇
- Java:集合类性能分析
- 如何批量将多个 PDF 文档快速合并成一个文档
- 服务器开机只显示logo,Ubuntu 20.04 开机画面显示 OEM 厂商 Logo
- 打字测试的手机软件,手机上有什么软件可以练习打字
- 2010计算机试题答案,2010全国计算机等级考试试题与答案公布
- 狂神mysql笔记 md_MyBatis狂神总结笔记
热门文章
- java连接DB2数据库
- 用C语言描述数据结构
- 向爸爸借了500,向妈妈借了500,买了双皮鞋用了970。剩下30元,还爸爸10块,还妈妈10块,自己剩下了10块,欠爸爸490,欠妈妈490,490+490=980。加上自己的10块=990。还有1
- 拓端tecdat|R语言估计获胜概率:模拟分析学生多项选择考试通过概率可视化
- 拓端tecdat|R语言ggmap空间可视化机动车交通事故地图
- LeetCode3 无重复字符的最长子串
- 广义表的存储结构算法c语言,广义表(一)
- 训练深度神经网络时验证损失可能低于训练损失的三个原因:
- 机器人操作系统ROS(一):发布接收消息
- keras训练一个简单的模型