Spring @Aspect、@Before、@After 注解实现 AOP 切面功能

Spring @Aspect、@Before、@After 注解实现 AOP 切面功能SpringAOP 注解概述 1 Spring 的 AOP 功能除了在配置文件中配置一大堆的配置 比如切入点 表达式 通知等等以外 使用注解的方式更为方便快捷 特别是 Springboot 出现以后 基本不再使用原先的 beans xml 等配置文件了 而都推荐注解编程 Aspect 切面声明 标注在类 接口 包括注解类型 或枚举上 Pointcut 切入点声明 即切入到哪些目标类的目标方法 value 属性指定切入点表达式 默认为 用于被通知注解引用

目录

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 依赖中:

Spring @Aspect、@Before、@After 注解实现 AOP 切面功能

 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 动态代理。

Spring @Aspect、@Before、@After 注解实现 AOP 切面功能

@Aspect 快速入门

1、@Aspect 常见用于记录日志、异常集中处理、权限验证、Web 参数校验、事务处理等等

2、要想把一个类变成切面类,只需3步:

1)在类上使用 @Aspect 注解使之成为切面类

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); } } } }

Spring @Aspect、@Before、@After 注解实现 AOP 切面功能

 如上所示在不修改原来业务层代码的基础上,就可以使用 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” 结尾的类中的所有方法
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/231277.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 二、八、十、十六进制转换(图解篇)「建议收藏」

    二、八、十、十六进制转换(图解篇)「建议收藏」一.本文所涉及的内容(Contents)本文所涉及的内容(Contents)背景(Contexts)进制转换算法(Convert)(二、八、十六进制)→(十进制)二进制→十进制八进制→十

    2022年8月6日
    5
  • 网站加载速度优化的14个技巧

    网站加载速度优化的14个技巧

    2021年10月14日
    60
  • matplotlib的安装教程以及简单调用

    matplotlib的安装教程以及简单调用1.matplotlib的下载我们的常规下载方式就是在命令行中输入:`pipinstallmatplotlib`,这样你就可以从官方进行下载,但是这样的下载速度是十分的慢的,我们在最详细的AnacondaInstallers的安装【numpy,jupyter】(图+文)(https://chen-ac.blog.csdn.net/article/details/122374025?spm=1001.2014.3001.5502)这一博客中曾写到,可以在`pipinstallmatplo

    2022年6月15日
    79
  • 什么是Web Service(SOAP)?

    什么是Web Service(SOAP)?SOAP请求(SimpleObjectAccessProtocol,简单对象访问协议)是HTTPPOST的一个专用版本,遵循一种特殊的XML消息格式,Content-type设置为:text/xml,任何数据都可以XML化。SOAP:简单对象访问协议。SOAP是一种轻量的,简单的,基于XML的协议,它被设计成在web上交换结构化的和固化的信息。SOAP可以和现存的许多因特网协议和格式结合…

    2022年7月24日
    12
  • Java学习之注解篇

    Java学习之注解篇0x00前言续上篇文章,这篇文章就来写一下注解的相关内容。0x01注解概述Java注解(Annotation)又称Java标注,是JDK5.0约会的一种注释机制。和J

    2021年12月12日
    40
  • 74款android开机动画,修改Android系统开机动画

    74款android开机动画,修改Android系统开机动画Android系统开机动画包括两部分:开机显示的ANDROID文字;ANDROID发光动画。这篇文章说的开机动画是第一种,下面开始正文!1.制作当前屏幕像素的图片(模拟器默认为320*480)使用PS制作一张320*480的图片,保存时选“保存为Web所用格式”,然后在弹开的窗口上,“预设”项选择“PNG-24”,保存为android_logo.png注:好像只支持png-24,其他格式生…

    2022年5月14日
    50

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号