Spring结合自定义注解实现 AOP 切面功能

news2025/1/10 23:44:39

Spring结合自定义注解实现 AOP 切面功能

  • Spring AOP 注解概述
  • @Aspect 快速入门
  • execution 切点表达式 拦截指定类的方法
  • @Pointcut("@annotation(xx)") 拦截拥有指定注解的方法
  • 环绕通知 实现开关目标方法
  • 案例1:自定义注解+切面实现统一日志处理
    • 1.自定义日志注解
    • 2声明日志切面组件
    • 3.控制层运行结果
    • 4.运行结果
  • 案例2:自定义注解与切面类
    • 1、创建自定义注解
    • 2、创建一个类,定义方法后使用自定义注解
    • 3、定义切面类进行,扫描自定义注解,并对切入点进行处理

Spring AOP 注解概述

1、Spring 的 AOP 功能除了在配置文件中配置一大堆的配置,比如切入点、表达式、通知等等以外,使用注解的方式更为方便快捷,特别是 Spring boot 出现以后,基本不再使用原先的 beans.xml 等配置文件了,而都推荐注解编程。

注解功能
@Aspect切面声明,标注在类、接口(包括注解类型)或枚举上。
@Pointcut切入点声明,即切入到哪些目标类的目标方法。既可以用 execution 切点表达式, 也可以是 annotation 指定拦截拥有指定注解的方法.value 属性指定切入点表达式,默认为 “”,用于被通知注解引用,这样通知注解只需要关联此切入点声明即可,无需再重复写切入点表达式
@Before前置通知, 在目标方法(切入点)执行之前执行。value 属性绑定通知的切入点表达式,可以关联切入点声明,也可以直接设置切入点表达式注意:如果在此回调方法中抛出异常,则目标方法不会再执行,会继续执行后置通知 -> 异常通知。
@After后置通知, 在目标方法(切入点)执行之后执行
@AfterReturning返回通知, 在目标方法(切入点)返回结果之后执行.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 动态代理。

5、所以如果目标类不是 Spring 组件,则无法拦截,如果是 类名.方法名 方式调用,也无法拦截。
在这里插入图片描述

@Aspect 快速入门

1、@Aspect 常见用于记录日志、异常集中处理、权限验证、Web 参数校验、事务处理等等
2、要想把一个类变成切面类,只需3步:

  • 在类上使用 @Aspect 注解使之成为切面类
  • 切面类需要交由 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 功能,在目标方法执行前后或者异常时都能捕获然后执行。

execution 切点表达式 拦截指定类的方法

1、@Pointcut 切入点声明注解,以及所有的通知注解都可以通过 value 属性或者 pointcut 属性指定切入点表达式。
2、切入点表达式通过 execution 函数匹配连接点,语法:execution([方法修饰符] 返回类型 包名.类名.方法名(参数类型) [异常类型])

  • 访问修饰符可以省略;
  • 返回值类型、包名、类名、方法名可以使用星号*代表任意;
  • 包名与类名之间一个点.代表当前包下的类,两个点…表示当前包及其子包下的类;
  • 参数列表可以使用两个点…表示任意个数,任意类型的参数列表;
    3、切入点表达式的写法比较灵活,比如:* 号表示任意一个,… 表示任意多个,还可以使用 &&、||、! 进行逻辑运算,不过实际开发中通常用不到那么多花里胡哨的,掌握以下几种就基本够用了。
    4、特别注意:当明确指定了切入的类时,类必须存在,否则启动报错,此时可以在类名前后加上*号表示模糊包含。
    切入点表达式常用举例
标题内容
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 方法
@Pointcut(“(execution(* grp.basic3…Controller.(…)) && !execution( grp.basic3.BaseExceptionController*.*(…)))”)匹配 grp.basic3包及其子包下面名称包含 ‘Controller’ 类中的全部方法,但是排除掉其中的以 BaseExceptionController 开头的类。
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” 结尾的类中的所有方法
* com.wmx…Controller.*(…))com.wmx 包及其子包下面类名包含’Controller’的任意类中的任意方法
* com.wmx..controller..*(…))第一二层包名为 com.wmx ,第三层包名任意,第4层包名为 controller 下面的任意类中的任意方法

@Pointcut(“@annotation(xx)”) 拦截拥有指定注解的方法

    /**
     * @Pointcut :切入点声明,即切入到哪些目标方法。
     * execution:可以用于指定具体类中的具体方法
     * annotation:匹配拥有指定注解的方法; 只匹配实现类中有注解的方法,不会匹配接口中的注解方法; 如果注解是在类上,而不是方法上,并不会匹配类中的全部方法.
     * 用于被下面的通知注解引用,这样通知注解只需要关联此切入点声明即可,无需再重复写切入点表达式
     * @annotation 中的路径表示拦截特定注解
     */
    @Pointcut("@annotation(com.wmx.annotation.RedisLock)")
    public void redisLockPC() {
    }

环绕通知 实现开关目标方法

1、比如某个方法只有管理员才有权限执行,而普通用户是没有权限
2、比如不符合条件的时候,需要终止(跳过)目标方法的执行
3、比如一个组件(Component)专门用于做校验,里面的方法是否校验可以配置在数据库中,当配置为启用时,则继续校验,否则不校验。

    /**
     * 环绕通知
     * 1、@Around 的 value 属性:绑定通知的切入点表达式。可以关联切入点声明,也可以直接设置切入点表达式
     * 2、Object ProceedingJoinPoint.proceed(Object[] args) 方法:继续下一个通知或目标方法调用,返回处理结果,如果目标方法发生异常,则 proceed() 会抛异常.
     * 3、假如目标方法是控制层接口,则本方法的异常捕获与否都不会影响业务层方法的事务回滚
     * 4、假如目标方法是控制层接口,本方法 try-catch 了异常后没有继续往外抛,则全局异常处理 @RestControllerAdvice 中不会再触发
     */
    @Around(value = "aspectPointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Signature signature = joinPoint.getSignature();
        Object target = joinPoint.getTarget();
        System.out.println("环绕通知=" + signature);
        System.out.println("环绕通知=" + target);
 
        // 是否继续校验
        boolean validation = true;
        if (validation) {
            // 校验通过后执行目标方法
            // 继续下一个切面通知或目标方法调用,返回处理结果,如果目标方法发生异常,则 proceed 会抛异常.
            // 如果在调用目标方法或者下一个切面通知前抛出异常,则不会再继续往后走
            return joinPoint.proceed(joinPoint.getArgs());
        } else {
            // 校验未通过时,不继续往后走,直接返回。
            // 可以返回提示信息,但是必须保证返回的参数类型与目标方法的返回值类型一致,否则类型转换异常。
            // 也可以直接抛异常。
            return null;
        }
    }

案例1:自定义注解+切面实现统一日志处理

1.自定义日志注解

/**
 * 自定义操作日志注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OptLog {
    /**
     * 业务
     * @return
     */
    String business();
 
    /**
     * 操作类型,增删改查
     * @return
     */
    OptType optType();
}

2声明日志切面组件

import com.alibaba.fastjson.JSONObject;
import com.example.demo.annotation.OptLog;
import com.example.demo.annotation.OptType;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
 
@Aspect
@Component
public class OptLogAspect {
 
    private static final Logger LOG = LoggerFactory.getLogger(OptLogAspect.class);
 
    /**
     * 声明切入点,凡是使用该注解都经过拦截
     */
    @Pointcut("@annotation(com.example.demo.annotation.OptLog)")
    public void OptLog() {
 
    }
 
    @Before("OptLog()")
    public void doOptLogBefore(JoinPoint proceedingJoinPoint) {
        LOG.info("前置通知, 在方法执行之前执行...");
    }
 
    @After("OptLog()")
    public void doOptLogAfter(JoinPoint proceedingJoinPoint) {
        LOG.info("后置通知, 在方法执行之后执行...");
    }
 
    @AfterReturning("OptLog()")
    public void doOptLogAfterReturning(JoinPoint proceedingJoinPoint) {
        LOG.info("返回通知, 在方法返回结果之后执行...");
    }
 
    @AfterThrowing("OptLog()")
    public void doOptLogAfterThrowing(JoinPoint proceedingJoinPoint) {
        LOG.info("异常通知, 在方法抛出异常之后执行...");
    }
 
    /**
     * 设置环绕通知,围绕着方法执行
     *
     * @param proceedingJoinPoint
     * @return
     */
    @Around("OptLog()")
    public Object optLogAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Method method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
        if (method == null) {
            return null;
        }
        // 获取方法名称
        String methodName = proceedingJoinPoint.getSignature().getName();
        LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
        // 请求参数名称
        String[] parameterNames = discoverer.getParameterNames(method);
        // 请求参数值
        Object[] paramValues = proceedingJoinPoint.getArgs();
 
        OptLog optLog = method.getAnnotation(OptLog.class);
        this.handle(optLog.optType(), optLog.business(), methodName, parameterNames, paramValues);
        return proceedingJoinPoint.proceed();
    }
 
    /**
     * 日志处理
     *
     * @param optType
     * @param business
     * @param methodName
     * @param parameterNames
     * @param paramValues
     */
    public void handle(OptType optType, String business, String methodName, 
                       String[] parameterNames, Object[] paramValues) {
        JSONObject jsonObject = new JSONObject();
        if (parameterNames != null && parameterNames.length > 0) {
            for (int i = 0; i < parameterNames.length; i++) {
                jsonObject.put(parameterNames[i], paramValues[i]);
            }
        }
        LOG.info("optType:" + optType + ",business:" + business + ", methodName:" + methodName + ", params:" + jsonObject);
    }
 
}

3.控制层运行结果

@RestController
@RequestMapping("/user/")
public class UserController {
 
    @OptLog(optType = OptType.CREATE,business = "用户信息")
    @RequestMapping("create")
    public String createUser(String userName,int age,String address) {
        System.out.println("方法执行中...");
        return "success";
    }
}

4.运行结果

15:32:49.494 [http-nio-8080-exec-2] INFO  c.e.d.a.OptLogAspect - [handle,91] - optType:CREATE,business:用户信息, methodName:createUser, params:{"address":"广州市","userName":"阿杰","age":18}
15:32:49.494 [http-nio-8080-exec-2] INFO  c.e.d.a.OptLogAspect - [doOptLogBefore,32] - 前置通知, 在方法执行之前执行...
方法执行中...
15:32:49.495 [http-nio-8080-exec-2] INFO  c.e.d.a.OptLogAspect - [doOptLogAfterReturning,42] - 返回通知, 在方法返回结果之后执行...
15:32:49.495 [http-nio-8080-exec-2] INFO  c.e.d.a.OptLogAspect - [doOptLogAfter,37] - 后置通知, 在方法执行之后执行...

案例2:自定义注解与切面类

1、创建自定义注解

import java.lang.annotation.*;
 
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestAnnotation {
    String name() default "默认值";   // 允许注解有参数
    String age() default "15";   // 允许多个参数
}

2、创建一个类,定义方法后使用自定义注解

import com.yh.annotation.OperateLogAnnotation;
import com.yh.annotation.TestAnnotation;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/test")
public class TestAOPController {
 
    @RequestMapping("/show3")
    @ResponseBody
    @TestAnnotation(name = "我把值传进去", age = "24")   // 加上自定义注解
    public String getById() { 
        return "hello";
    }
 
}

3、定义切面类进行,扫描自定义注解,并对切入点进行处理

import com.yh.annotation.TestAnnotation;
import com.yh.annotation.TestAnnotation;
//import javassist.bytecode.SignatureAttribute;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
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 org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
 
import java.lang.reflect.Method;
 
@Aspect // FOR AOP
@Order(-99) // 控制多个Aspect的执行顺序,越小越先执行, 当然也可以不写这注解, 对于写和不写@order的两个切面, 有@order的优先于无@order的执行; 都有@order时, 越小越执先执行
@Component
public class TestAspect {
 
    // 可以参考若依的自定义注解。自定义注解一般使用@annotation
    // @Before可以有两种写法, @annotation(形参test),
    @Before("@annotation(test)")// 拦截被TestAnnotation注解的方法;如果你需要拦截指定package指定规则名称的方法,可以使用表达式execution(...)
    public void beforeTest(JoinPoint point, TestAnnotation test) throws Throwable {
        System.out.println("beforeTest:" + test.name());   // 直接获取注解参数
        //test.name()和test.age()
    }
 
    @After("@annotation(test)")
    public void afterTest(JoinPoint point, TestAnnotation test) {
        System.out.println("afterTest:" + test.name());  // 直接获取注解参数
    }
 
    // 可以控制方法运行, 同时修改入参和返回值
    @Around("@annotation(test)")   // test表示aroundTest方法中的test入参
    public Object aroundTest(ProceedingJoinPoint pjp, TestAnnotation test) throws Throwable {
        System.out.println("aroundTest:" + test.value());
        // 获取入参并修改
        Object[] args = pjp.getArgs();
        args[0] = "";
        // 传入修改后的参数, 并继续执行
        Object res = pjp.proceed(args);
        // 修改返回值
        return res.toString() + res.toString();
    }
 
 
/* 
   // 指定切面
   @Pointcut("@annotation(com.yh.annotation.TestAnnotation)")
    public void annotationPointCut() {
    }
   // @Before可以有两者写法, @annotation(函数名annotationPointCut)
   @Before("annotationPointCut()")
    public void before(JoinPoint joinPoint) {
        MethodSignature sign = (MethodSignature) joinPoint.getSignature();
        Method method = sign.getMethod();
        TestAnnotation annotation = method.getAnnotation(TestAnnotation.class);   // 获取指定注解实例
        System.out.println("打印:" + annotation.name() + " 前置日志1");   // 获取注解实例的参数
    }
    @After("annotationPointCut()")
    public void afterTTT(JoinPoint point) {
        MethodSignature sign = (MethodSignature) point.getSignature();
        Method method = sign.getMethod();
        TestAnnotation annotation = method.getAnnotation(TestAnnotation.class);  // 获取指定注解实例
        System.out.println("打印自带参数:" + annotation.age() + " 后置日志1");  // 获取注解实例的参数
    }
*/
 
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1050551.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python3中类的高级语法及实战

Python3中类的高级语法及实战 Python3(基础|高级)语法实战(|多线程|多进程|线程池|进程池技术)|多线程安全问题解决方案 一: 类定义语法 通过下面的实例,你将会了解到如下的内容体系&#xff1a; (1)类静态属性 (2)类动态属性 (3)类初始化过程: __init__(self)被执行&am…

java对象半初始化问题是怎么回事

文章目录 一、前言1. 什么是Java对象半初始化2. 对象半初始化问题引发的影响 二、对象半初始化问题详解1. Java对象创建过程 2. 对象半初始化问题产生的原因三、实例分析&#xff1a;对象半初始化问题的表现1. 单线程环境下的半初始化2. 多线程环境下的半初始化 四、解决方案及…

Java 基于 SpringBoot 的校园疫情防控系统

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 文章目录 1 简介2.主要技术3 需求分析4系统设计4.1功能结构4.2 数据库设计4.2.1 数据库E/R图4.2.2 数据库表…

预编译(1)

目录 预定义符号&#xff1a; 使用&#xff1a; 结果&#xff1a; 预编译前后对比&#xff1a; #define定义常量&#xff1a; 基本语法&#xff1a; 举例1&#xff1a; 结果&#xff1a; 预编译前后对比&#xff1a; 举例2&#xff1a; 预编译前后对比&#xff1a; 注…

嵌入式软硬件

在这里插入图片描述

APP渗透测试

APP反抓包突破 抓包失败分析 工具证书未配置 app不使用HTTP/S协议 反模拟器 1.使用真机进行抓包 2.用模拟器模拟真机 3.逆向删除反模拟器代码打包重新测试 反证书 SSL证书绑定分为单向校验和双向校验&#xff0c;单向校验就是客户端校验服务端的证书&#xff0c;双向…

回调函数的用途

一、函数指针 在讲回调函数之前&#xff0c;我们需要了解函数指针。 我们都知道&#xff0c;C语言的灵魂是指针&#xff0c;我们经常使用整型指针&#xff0c;字符串指针&#xff0c;结构体指针等 int *p1; char *p2; STRUCT *p3; //STRUCT为我们定义的结构体但是好像我们一…

【CTFHUB】SSRF绕过方法之靶场实践(二)

SSRF POST请求 提示信息&#xff1a; 这次是发一个HTTP POST请求.对了.ssrf是用php的curl实现的.并且会跟踪302跳转.加油吧骚年 首先测试了http的服务请求&#xff0c;出现对话框 输入数值后提示&#xff1a;只能接受来自127.0.0.1的请求 右键查看源码发现key值 通过file协…

k8s+kubeedge+sedna安装的全套流程

一&#xff0c;环境准备 把两台虚拟机的ip地址设置成静态的IP地址&#xff0c;否则ip地址会变 虚拟机配置静态IP&#xff08;NAT模式&#xff09;_nat子网的准入_阿祖&#xff0c;收手吧的博客-CSDN博客​​​​​​ 节点IP软件 云节点192.168.133.139kubernetescloudcore边…

与初至波相关的常见误解

摘要: 初至波是指检波器首次接收到的波. 对它的误解会使我们失去重要的信息. 1. 波从震源到检波器的传导过程 从震源产生波以后, 有些波通过地面直接传导到检波器, 这些称为直达波 (面波);有些在地层中传播,遇到两种地层的分界面时 产生波的反射,在原来地层中形成一种新波, …

【算法——双指针】LeetCode 15 三数之和

题目描述&#xff1a; 解题思路&#xff1a;双指针 首先&#xff0c;按升序对数组进行排序。然后&#xff0c;我们可以用如下步骤求解&#xff1a; 初始化一个空的结果集result&#xff0c;用于存储找到的和为0的三元组。 遍历整个数组&#xff0c;直到倒数第三个元素&#xff…

基于APP数据爬取的运行环境

前提 数据爬取本就是“道高一尺&#xff0c;魔高一丈”&#xff1b;越往后&#xff0c;爬取越接近于真实&#xff0c;真实包含了真实的运行环境&#xff08;不再是简单地伪造请求、User-Agent和Cookie等&#xff09;和真实的操作流程。本文对APP的运行环境做了简单梳理以供参考…

python爬虫基于管道持久化存储操作

文章目录 基于管道持久化存储操作scrapy的使用步骤1.先转到想创建工程的目录下&#xff1a;cd ...2.创建一个工程3.创建之后要转到工程目录下4.在spiders子目录中创建一个爬虫文件5.执行工程setting文件中的参数 基于管道持久化存储的步骤&#xff1a;持久化存储1&#xff1a;保…

【Linux学习】05-1Linux上安装部署各类软件

Linux&#xff08;B站黑马&#xff09;学习笔记 01Linux初识与安装 02Linux基础命令 03Linux用户和权限 04Linux实用操作 05-1Linux上安装部署各类软件 文章目录 Linux&#xff08;B站黑马&#xff09;学习笔记前言05-1Linux上安装部署各类软件JDK安装部署Tomcat安装部署maven…

Matlab绘图函数subplot、tiledlayout、plot和scatter

一、绘图函数subplot subplot(m,n,p)将当前图窗划分为 mn 网格&#xff0c;并在 p 指定的位置创建坐标区。MATLAB按行号对子图位置进行编号。第一个子图是第一行的第一列&#xff0c;第二个子图是第一行的第二列&#xff0c;依此类推。如果指定的位置已存在坐标区&#xff0c;…

ahk系列——ahk_v2实现win10任意界面ocr

前言&#xff1a; 不依赖外部api接口&#xff0c;界面简洁&#xff0c;翻译快速&#xff0c;操作简单&#xff0c; 有网络就能用 、还可以把ocr结果非中文翻译成中文、同样可以识别中英日韩等60多个国家语言并翻译成中文&#xff0c;十分的nice 1、所需环境 windows10及其以上…

【小沐学C++】C++ 基于Premake构建工程项目(Windows)

文章目录 1、简介2、下载和安装2.1 下载2.3 快速入门 3、使用3.1 支持的工程文件Project Files3.2 构建设置Build Settings3.3 链接Linking3.4 配置Configurations3.5 平台Platforms3.6 过滤Filters3.7 预设值Tokens 4、测试4.1 测试1&#xff1a;入门例子4.2 测试2&#xff1a…

STL upper_bound和lower_bound函数

声明&#xff1a; 首先包含头文件#include<algorithm> 这里的两个函数所运用的对象必须是非递减的序列&#xff08;也就是数组&#xff0c;数组必须是非递减的&#xff09;&#xff0c;只有这样才可以使用upper_bound和lower_bound这两个函数。 还有一点&#xff0c;就…

手摸手带你 在Windows系统中安装Istio

Istio简介 通过负载均衡、服务间的身份验证、监控等方法&#xff0c;Istio 可以轻松地创建一个已经部署了服务的网络&#xff0c;而服务的代码只需很少更改甚至无需更改。 通过在整个环境中部署一个特殊的 sidecar 代理为服务添加 Istio 的支持&#xff0c;而代理会拦截微服务…

【C++】string 之 find、rfind、replace、compare函数的学习

前言 上篇文章&#xff0c;我们学习了assign、at、append这三个函数 今天&#xff0c;我们来学习find、 函数 find函数 引入 我们都知道&#xff0c;find函数可以是string类中&#xff0c;用于查找字符或者字符串的函数 也可以是&#xff0c;<algorithm>头文件中&am…