Spring@Aspect、@Before、@After注解实现AOP切⾯功能⽬录
Spring AOP 注解概述
1、Spring 的 AOP 功能除了在配置⽂件中配置⼀⼤堆的配置,⽐如切⼊点、表达式、通知等等以外,使⽤注解的⽅式更为⽅便快捷,特别是 Spring boot 出现以后,基本不再使⽤原先的 l 等配置⽂件了,⽽都推荐注解编程。
@Aspect切⾯声明,标注在类、接⼝(包括注解类型)或枚举上。
@Pointcut 切⼊点声明,即切⼊到哪些⽬标类的⽬标⽅法。
value 属性指定切⼊点表达式,默认为 "",⽤于被通知注解引⽤,这样通知注解只需要关联此切⼊点声明即可,⽆需再重复写切⼊点表达式
@Before 前置通知, 在⽬标⽅法(切⼊点)执⾏之前执⾏。
value 属性绑定通知的切⼊点表达式,可以关联切⼊点声明,也可以直接设置切⼊点表达式
注意:如果在此回调⽅法中抛出异常,则⽬标⽅法不会再执⾏,会继续执⾏后置通知 -> 异常通知。
@After后置通知, 在⽬标⽅法(切⼊点)执⾏之后执⾏
@AfterReturning 返回通知, 在⽬标⽅法(切⼊点)返回结果之后执⾏,在 @After 的后⾯执⾏pointcut 属性绑定通知的切⼊点表达式,优先级⾼于 value,默认为 ""
@AfterThrowing 异常通知, 在⽅法抛出异常之后执⾏, 意味着跳过返回通知
pointcut 属性绑定通知的切⼊点表达式,优先级⾼于 value,默认为 ""
注意:如果⽬标⽅法⾃⼰ try-catch 了异常,⽽没有继续往外抛,则不会进⼊此回调函数
@Around 环绕通知:⽬标⽅法执⾏前后分别执⾏⼀些代码,类似,可以控制⽬标⽅法是否继续执⾏。通常⽤于统计⽅法耗时,参数校验等等操作。
环绕通知早于前置通知,晚于返回通知。
2、上⾯这些 AOP 注解都是位于如下所⽰的 依赖中:
3、对于习惯了 Spring 全家桶编程的⼈来说,并不是需要直接引⼊ aspectjweaver 依赖,因为 spring-boot-starter-aop 组件默认已经引⽤了 aspectjweaver 来实现 AOP 功能。换句话说 Spring 的 AOP 功能就是依赖的 aspectjweaver !
<!-- mvnrepository/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>
@Aspect 快速⼊门
1、@Aspect 常见⽤于记录⽇志、异常集中处理、权限验证、Web 参数校验、事务处理等等
2、要想把⼀个类变成切⾯类,只需3步:
1)在类上使⽤ @Aspect 注解使之成为切⾯类
2)切⾯类需要交由 Sprign 容器管理,所以类上还需要有 @Service、@Repository、@Controller、@Component 等注解2)在切⾯类中⾃定义⽅法接收通知
3、AOP 的含义就不再累述了,下⾯直接上⽰例:
import org.apachemons.lang3.time.StopWatch;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.flect.SourceLocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* 切⾯注解 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 = Logger(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.ller.DeptController.*(..)) " +
"|| execution(* com.ller.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 = Args();
Signature signature = Signature();
Object target = Target();
Object aThis = This();
JoinPoint.StaticPart staticPart = StaticPart();
SourceLocation sourceLocation = SourceLocation();
String longString = LongString();
String shortString = ShortString();
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={}", Kind());
}
/**
* 返回通知:⽬标⽅法返回后执⾏以下代码
* 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=", ShortString(), 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) {
<("【异常通知】" + methodName + "⽅法算术异常(ArithmeticException):" + ex.getMessage());
} else {
<("【异常通知】" + methodName + "⽅法异常:" + ex.getMessage());
}
}
/**
* 环绕通知
* 1、@Around 的 value 属性:绑定通知的切⼊点表达式。可以关联切⼊点声明,也可以直接设置切⼊点表达式
* 2、Object ProceedingJoinPoint.proceed(Object[] args) ⽅法:继续下⼀个通知或⽬标⽅法调⽤,返回处理结果,如果⽬标⽅法发⽣异常,则 proceed 会抛异常. * 3、假如⽬标⽅法是控制层接⼝,则本⽅法的异常捕获与否都不会影响⽬标⽅法的事务回滚
* 4、假如⽬标⽅法是控制层接⼝,本⽅法 try-catch 了异常后没有继续往外抛,则全局异常处理 @RestControllerAdvice 中不会再触发 *springboot aop
* @param joinPoint
* @return
* @throws Throwable
*/
@Around(value = "aspectPointcut()")
public Object handleControllerMethod(ProceedingJoinPoint joinPoint) throws Throwable {
this.checkRequestParam(joinPoint);
StopWatch stopWatch = ateStarted();
LOG.debug("【环绕通知】执⾏接⼝开始,⽅法={},参数={} ", Signature(), Arrays.Args()).toString()); //继续下⼀个通知或⽬标⽅法调⽤,返回处理结果,如果⽬标⽅法发⽣异常,则 proceed 会抛异常.
//如果在调⽤⽬标⽅法或者下⼀个切⾯通知前抛出异常,则不会再继续往后⾛.
Object proceed = joinPoint.Args());
stopWatch.stop();
long watchTime = Time();
LOG.debug("【环绕通知】执⾏接⼝结束,⽅法={}, 返回值={},耗时={} (毫秒)", Signature(), proceed, watchTime);
return proceed;
}
/**
* 参数校验,防⽌ SQL 注⼊
*
* @param joinPoint
*/
private void checkRequestParam(ProceedingJoinPoint joinPoint) {
Object[] args = Args();
if (args == null || args.length <= 0) {
return;
}
String params = Args()).toUpperCase();
String[] keywords = {"DELETE ", "UPDATE ", "SELECT ", "INSERT ", "SET ", "SUBSTR(", "COUNT(", "DROP ",
"TRUNCATE ", "INTO ", "DECLARE ", "EXEC ", "EXECUTE ", " AND ", " OR ", "--"};
for (String keyword : keywords) {
if (ains(keyword)) {
LOG.warn("参数存在SQL注⼊风险,其中包含⾮法字符 {}.", keyword);
throw new RuntimeException("参数存在SQL注⼊风险:params=" + params);
}
}
}
}
如上所⽰在不修改原来业务层代码的基础上,就可以使⽤ AOP 功能,在⽬标⽅法执⾏前后或者异常时
都能捕获然后执⾏。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" 结尾的类中的所有⽅法
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论