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

得意时要看淡,失意时要看开。不论得意失意,切莫大意;不论成功失败,切莫止步。志得意满时,需要的是淡然,给自己留一条退路;失意落魄时,需要的是泰然,给自己觅一条出路AOP切面动态获取方法参数并回写标识,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

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

业务分析
由于涉及号码查询的接口有很多,如果一个个进行优化则改动较大。结合实际情况我们采用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 表达式语言来动态解析注解方法参数,大大提升代码可读性与可维护性。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/154656.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!