AOP切面动态获取方法参数并回写标识

业务场景
为保证客户数据的准确性、安全和严谨性,现需要对请求参数包含电话号码的接口进行优化。如果请求中包含号码,必须验证该号码是否在号码屏蔽池,且号码在号码屏蔽池的接口不向前台响应任何数据。

业务分析
由于涉及号码查询的接口有很多,如果一个个进行优化则改动较大。结合实际情况我们采用Spring Aop切面技术进行处理,大大降低代码耦合度与研发周期,也能够提高代码可复用性和可维护性。

技术积累
AOP是Spring 提供的切面处理业务的一种方式,可以在代码运行中插入一段业务代码进行执行。其代表的方法有:
1、前通知:方法执行之前,method:增强执行的方法
2、后通知:方法执行之后,又称最终通知,无论如何都执行
3、返回后通知:成功返回后,有异常时不执行
4、异常通知:发生异常后,只有异常抛出时才执行,不能try…catch异常
5、环绕通知:在方法的执行前后进行一些增强,在方法的执行前后进行一些增强 =前通知+返回后通知

技术方案
1、首先我们需要在方法执行前进行号码验证业务处理并修改请求参数,所以我们选择环绕通知进行处理
2、由于我们需要对传入参数进行动态验证是否在屏蔽池,故我们需要增加动态获获取方法参数,这里就用到了Spring表达式语言Spel来处理自定义注解中的参数表达式。(Spring 表达式语言 Spring Expression Language是一个支持运行时查询和操作对象图的表达式语言。 语法类似于 EL 表达式,但提供了显式方法调用和基本字符串模板函数等额外特性 )

实战演练
1、首先创建一个验证号码的注解TelCheck,使其作用于方法并在运行时提供服务

@Documented
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TelCheck {
    /**
     * 号码字段名
     * @return
     */
    String telPhoneField() default "";
    /**
     * 返回索引
     * @return
     */
    int  resultIndex() default 1;
}

2、创建一个切面TelActionAop,并提供环绕增强
为保证演示效果,这里仅对号码不为空进行数据回写。
这里两个重点:
一个是以验证号码的注解作为切点

@Pointcut(value = "@annotation(com.ysjr.base.domain.annotation.TelCheck)")
public void annotationPointCut(){}

另一个是用spel表达式进行动态参数获取

 field = generateKey(joinPoint, field);

具体切面类为:

/**
 * 号码处理切面
 * @author senfel
 * @version 1.0
 * @date 2023/2/7 9:30
 */
@Aspect
@Component
@Slf4j
public class TelActionAop {

    @Pointcut(value = "@annotation(com.ysjr.base.domain.annotation.TelCheck)")
    public void annotationPointCut(){}

    /**
     * 环绕增强
     * @param joinPoint
     * @author senfel
     * @date 2023/2/7 14:43
     * @return java.lang.Object
     */
    @Around("annotationPointCut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //获取注解
        TelCheck annotation = signature.getMethod().getAnnotation(TelCheck.class);
        //获取注解方法参数
        Object[] args = joinPoint.getArgs();
        //获取注解中设置的数据
        String field = annotation.telPhoneField();
        int resultIndex = annotation.resultIndex();
        //解析动态参数
        field = generateKey(joinPoint, field);
        args[resultIndex] = true;
        if (StringUtils.isNotBlank(field)) {
            //具体业务逻辑处理
            //这里仅判断如果传入参数值不为空则返回异常标识
            args[resultIndex] = false;
        }
        return joinPoint.proceed(args);
    }


    /**
     * 解析动态参数
     * @param joinPoint
     * @param spELString
     * @author senfel
     * @date 2023/2/7 10:40
     * @return java.lang.String
     */
    private String generateKey(JoinPoint joinPoint,String spELString) {
        if(StringUtils.isBlank(spELString)){
            return null;
        }
        // 通过joinPoint获取被注解方法
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        //创建解析器
        SpelExpressionParser parser = new SpelExpressionParser();
        //获取表达式
        Expression expression = parser.parseExpression(spELString);
        //设置解析上下文(有哪些占位符,以及每种占位符的值)
        EvaluationContext context = new StandardEvaluationContext();
        //获取参数值
        Object[] args = joinPoint.getArgs();
        //获取运行时参数的名称
        DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
        String[] parameterNames = discoverer.getParameterNames(method);
        for (int i = 0; i < parameterNames.length; i++) {
            context.setVariable(parameterNames[i],args[i]);
        }
        //解析,获取替换后的结果
        String result = Objects.isNull(expression.getValue(context)) ? "" : expression.getValue(context).toString();
        System.out.println(result);
        return result;
    }

}

3、测试请求接口
3.1 后端接口增加验证号码注解

@TelCheck(telPhoneField = "#query.nameOrTel",resultIndex = 3)

3.2 方法内部增加回写验证标识参数 Boolean telCheckFlag

详细接口:

/**
 * 根据条件查询商机列表(分页)
 *
 * @param query 查询条件
 * @return ResultMsg
 * @ignore
 */
@TelCheck(telPhoneField = "#query.nameOrTel",resultIndex = 3)
@PostMapping("queryBusinessListPage")
public ResultMsg queryBusinessListPage(@Valid @RequestBody BusinessClueListQueryVo query,
                                       BindingResult bindingResult,
                                       HttpServletRequest request,Boolean telCheckFlag) {
    try {
        if(!telCheckFlag){
            //验证失败直接响应空的数据,不再进行后续业务逻辑
            ResultMsg resultMsg = ResultMsg.ok();
            resultMsg.put("total", 0);
            return resultMsg;
        }
        if (bindingResult.hasErrors()) {
            return ValidUtil.validError(bindingResult);
        }
        ResultMsg resultMsg = this.searchService.searchBusiness(query, false, false);
        return resultMsg;
    }catch (CustomTelException te){
        ResultMsg resultMsg = ResultMsg.ok();
        resultMsg.put("total", 0);
        return resultMsg;
    }catch (CustomException e) {
        return ResultMsg.systemError(e.getMessage());
    } catch (Exception e) {
        log.error("商机列表查询异常:", e);
        return ResultMsg.systemError("商机列表查询异常!");
    }
}

4、请求结果
4.1 页面发起请求并传入电话号码
在这里插入图片描述

4.2 后端切面环绕增强在方法执行之前就获取到参数传入号码,这里为了测试仅验证不为空则响应验证失败,并回写到号码验证标识参数中。
在这里插入图片描述

4.3 线程处理增强后进入接口方法,获取到验证失败的标识,直接响应前端不再执行后续逻辑。
在这里插入图片描述

4.4 页面直接是没有数据的状态
在这里插入图片描述

5、总结与重点
本案例直接运用AOP切面动态获取方法参数并回写标识,其中重点是AOP切面环绕增强逻辑处理与请求参数回写,以及内部通过Spring 表达式语言来动态解析注解方法参数,大大提升代码可读性与可维护性。

Spring AOP中,可以通过使用javassist和Spring框架中的LocalVariableTableParameterNameDiscoverer来获取方法参数名。使用LocalVariableTableParameterNameDiscoverer的原因是代码量较少,而使用javassist会出现一些不知道来源的参数名(如this、e等)。 为了使用Spring AOP获取方法参数,首先需要在项目的pom文件中添加以下依赖,这基于项目是Spring Boot项目: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> ``` 然后,可以自定义注解来标记需要获取参数方法。自定义注解可以通过在方法上添加注解来标识,然后在AOP切面中使用切点表达式来匹配这些带有注解的方法。 在获取方法参数时,需要注意参数类型的获取。通过`args[k].getClass().getName()`可以获取到类似Integer.class、Double.class的封装类型,而在调用`getMethod`方法时,需要传入自定义参数的类型。如果自定义了int类型的参数,就无法通过`getMethod`获取到,因为`Integer.class`不等于`int.class`。为了解决这个问题,可以使用一个Map进行转化。 综上所述,可以通过Spring AOP结合自定义注解和适当的依赖,来获取方法参数。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [在SpringAOP中如何获取方法参数值(实体类)以及参数名](https://blog.csdn.net/qq_21267357/article/details/126725513)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [Spring AOP自定义注解并获取注解的参数](https://blog.csdn.net/u013066244/article/details/118558258)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小沈同学呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值