看了同事写的代码,我竟然开始默默的模仿了。。。

事情是这样的,目前我正在参与 XXXX 项目的搭建,需要与第三方对接接口。在对方的接口中存在几个异步通知,为了接口的安全性,需要对接口的参数进行验签处理。

为了方便大家对异步通知返回参数的处理,Z 同事提出要将该验签功能进行统一封装,到时候大家只需要关注自己的业务逻辑即可。

Z 同事选择的是”自定义参数解析器”的解决方案,接下来我们通过代码来了解一下。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER})
public @interface RsaVerify {

    /**
     * 是否启用验签功能,默认验签
     */
    boolean verifySign() default true;
}
@AllArgsConstructor
@Component
//实现 HandlerMethodArgumentResolver 接口
public class RsaVerifyArgumentResolver implements HandlerMethodArgumentResolver {

    private final SecurityService securityService;

    /**
     * 此方法用来判断本次请求的接口是否需要解析参数,
     *  如果需要返回 true,然后调用下面的 resolveArgument 方法,
     *  如果不需要返回 false
     */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(RsaVerify.class);
    }

    /**
     * 真正的解析方法,将请求中的参数值解析为某种对象
     * parameter 要解析的方法参数
     * mavContainer 当前请求的 ModelAndViewContainer(为请求提供对模型的访问)
     * webRequest 当前请求
     * WebDataBinderFactory 用于创建 WebDataBinder 的工厂
     */
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        RsaVerify parameterAnnotation = parameter.getParameterAnnotation(RsaVerify.class);
        if (!parameterAnnotation.verifySign()) {
            return mavContainer.getModel();
        }

        //对参数进行处理并验签的逻辑
        ......

        //返回处理后的实体类参数
        return ObjectMapperFactory
                .getDateTimeObjectMapper("yyyyMMddHHmmss")
                .readValue(StringUtil.queryParamsToJson(sb.toString()), parameter.getParameterType());
    }

}
@Configuration
@AllArgsConstructor
public class PayTenantWebConfig implements WebMvcConfigurer {

    private final RsaVerifyArgumentResolver rsaVerifyArgumentResolver;

    /**
     * 将自定义的方法参数解析器加入到配置类中
     */
    @Override
    public void addArgumentResolvers(List resolvers) {
        resolvers.add(rsaVerifyArgumentResolver);
    }
}

使用方法非常简单,只需要在参数上引入注解就可以了

@RestController
@Slf4j
@RequestMapping("/xxx")
public class XxxCallbackController {

    /**
     * @param params
     * @return
     */
    @PostMapping("/callback")
    public String callback(@RsaVerify CallbackReq params) {
        log.info("receive callback req={}", params);
        //业务逻辑处理
        .....

        return "success";
    }
}

问题一

看到这,细心的朋友应该会有所疑问:既然这边用到了自定义的注解,为什么不用 切面来实现,而是使用自定义的参数解析器呢?Very Good!这也是阿Q提出的疑问,同事说是因为 jackson 的反序列化动作优先级远 高于切面的优先级,所以还没进入切面就已经报反序列化失败的错误了。

问题二

为什么在 controller 中注解 @RequestBody 不见了?

要回答这个问题,我们就得了解下 HandlerMethodArgumentResolverComposite这个类了,以下简称 CompositeSpringMVC 在启动时会将所有的参数解析器放到 Composite 中, Composite 是所有参数的一个集合。当对参数进行解析时就会从该参数解析器集合中选择一个支持对 parameter 解析的参数解析器,然后使用该解析器进行参数解析。

又因为 @RequestBody所以使用的参数解析器 RequestResponseBodyMethodProcessor优先级高于我们自定义的参数解析器,所以如果共用会被前者拦截解析,所以为了正常使用,我们需要将 @RequestBody 注解去掉。

/**
 * Find a registered {@link HandlerMethodArgumentResolver} that supports
 * the given method parameter.

 */
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
    if (result == null) {
        for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
            if (resolver.supportsParameter(parameter)) {
                result = resolver;
                this.argumentResolverCache.put(parameter, result);
                break;
            }
        }
    }
    return result;
}

上边 Z 同事的方案已经可以解决该问题了,但是该方案还有两个不足之处:

  • 需要每一个回调都去创建自己的 controller 层,没有一个对外的统一入口;
  • 需要在方法上添加自定义注解,侵入性比较强;

因此经过我们的商议,决定摒弃该方案,但是该方案的思想值得我们学习。接下来让我们分析一下新的解决方案:

业务接口类包含两个方法:具体业务处理的类型;业务的具体处理方法。

public interface INotifyService {
    /**
     * 处理类型
     */
    public String handleType();
    /**
     * 处理具体业务
     */
    Integer handle(String notifyBody);

}
@AllArgsConstructor
@RestController
@RequestMapping(value = "/notify")
public class NotifyController {
    private IService service;

    @PostMapping(value = "/receive")
    public String receive(@RequestBody String body) {
        //处理通知
        Integer status = service.handle(body);
        return "success";
    }
}

在 Iservice 中做两个步骤:

  • 在 spring 启动之后,收集所有的类型为 INotifyService的类并放入 map中;
  • 将参数进行处理转化,并验签处理;
private ApplicationContext applicationContext;
private Map notifyServiceMap;

/**
 * 启动加载
 */
@PostConstruct
public void init(){
    Map map = applicationContext.getBeansOfType(INotifyService.class);
    Collection services = map.values();
    if(CollectionUtils.isEmpty(services)){
        return;
    }
    notifyServiceMap = services.stream().collect(Collectors.toMap(INotifyService::handleType, x -> x));
}

@Override
public Map getNotifyServiceMap() {
    return notifyServiceMap;
}

@Override
public Integer handle(String body) {
    //参数处理+验签逻辑
    ......

    //获取具体的业务实现类
    INotifyService notifyService=notifyServiceMap.get(notifyType);
    Integer status=null;
    if(Objects.nonNull(notifyService)) {
        //执行具体业务
        try {
            status=notifyService.handle(JSON.toJSONString(requestParameter));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    //后续逻辑处理
    ......

    return status;
}
@Service
public class NotifySignServiceImpl implements INotifyService {

    @Override
    public String handleType() {
        return "type_sign";
    }

    @Override
    @Transactional
    public Integer handle(String notifyBody) {
        //具体的业务处理
        ......

    }
}
  • 此方案提供统一的异步通知入口,把公共的参数处理和验签逻辑与业务逻辑剥离。
  • 利用 java 动态加载类的特性,将实现类通过类型进行收集。
  • 利用 java 多态的特性,通过不同的实现类来处理不同的业务逻辑。

看到这,相信大家已经对这两种实现方案有了一定地理解,大家可以试着在以后的项目中应用一下,体验一把!

以上就是今天的全部内容了,如果你有不同的意见或者更好的 idea,欢迎联系阿Q,添加阿Q可以加入技术交流群参与讨论呦!

Original: https://www.cnblogs.com/aqsaycode/p/15542944.html
Author: 阿Q说代码
Title: 看了同事写的代码,我竟然开始默默的模仿了。。。

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/685578/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • SpringBoot-Test测试

    @RunWith(SpringRunner.class) @SpringBootTest(classes = com.aa.Application.class, webEnviro…

    技术杂谈 2023年6月21日
    096
  • 谈谈Raft

    本文主要参考: 极客时间-etcd 实战课 GitChat-分布式锁的最佳实践之:基于 Etcd 的分布式锁 谈到分布式协调组件,我们第一个想到的应该是大名鼎鼎的Zookeeper…

    技术杂谈 2023年7月25日
    085
  • CentOS7新系统手动配置网络简要步骤

    一、配置网卡文件 1.修改网卡文件进入网卡配置文件目录 2.查看网卡文件 CentOS中网卡文件一般为 ifcfg-ens* 这样的文件,多块网卡会有多个类似文件 3.编辑网卡文件…

    技术杂谈 2023年7月24日
    083
  • Linux静默安装weblogic12(fmw_12.1.3.0.0_wls.jar)

    1、安装JDK环境 2、创建安装用户 3、配置JAVA环境变量 4、创建响应文件wls.rsp 响应文件中的项一定要写全,否则会报奇怪的错误。 5、创建Loc文件oraInst.l…

    技术杂谈 2023年7月11日
    071
  • WSL安装

    安装辅助工具: LxRunOffline.exe 安装WSL: LxRunOffline.exe install -n 2centos7 -d C:\qiumc\programs\…

    技术杂谈 2023年6月1日
    092
  • Hadoop集群搭建的详细过程

    Hadoop集群搭建 一、准备 三台虚拟机:master01,node1,node2 时间同步 1.date命令查…

    技术杂谈 2023年7月11日
    096
  • segment fault 段错误 (core dumped)的起因分析(转)

    内核使用内存描述符结构体表示进程的地址空间,该结构体包含了和进程地址空间有关的全部信息。内存描述符由mm_struct结构体表示,定义在文件 mm_users域记录正在使用该地址的…

    技术杂谈 2023年6月1日
    099
  • 2021.07.13 我们是这样崩的

    至暗时刻 2021年7月13日22:52,SRE收到大量服务和域名的接入层不可用报警,客服侧开始收到大量用户反馈B站无法使用,同时内部同学也反馈B站无法打开,甚至APP首页也无法打…

    技术杂谈 2023年5月30日
    092
  • 使用 Playwright 进行 E2E 测试

    1 Playwright 简介 Playwright 是由微软开源的一个Web测试和自动化的框架,它具有以下特性: 跨平台 跨浏览器 跨语言(TypeScript、JS、Pytho…

    技术杂谈 2023年5月31日
    0111
  • 1956和1985高程转换

    Original: https://www.cnblogs.com/gisoracle/p/16394986.htmlAuthor: gisoracleTitle: 1956和19…

    技术杂谈 2023年5月30日
    086
  • 数据库篇:mysql锁详解

    前言 sql事务的执行,如果需要锁定数据进行更新操作,则必定离不开锁 共享锁和排他锁 表锁 行锁 Record Lock 间隙锁 Gap Lock 行锁+间隙锁 Next-Key …

    技术杂谈 2023年7月25日
    076
  • 小知识:vi如何使用列编辑模式快速插入

    经常需要用到列编辑这种操作,现在很多超文本的编辑器都可以轻松实现。但有时需要在vi界面直接使用,但是vi的列编辑操作因不常使用总是忘记现查。这次干脆记录下加深印象。vi编辑某个文本…

    技术杂谈 2023年5月31日
    0132
  • 组合模式之对购物网站

    1、 实例概况 用组合模式对购物网站的商品进管理,绘制选择模式的结构图,并绘制该案例的类图,并编写代码演示结果,运行结果样例显示如右图所示。 2、所用模式结构视图 3、实例类图 4…

    技术杂谈 2023年7月23日
    0101
  • 国产化之x64平台安装银河麒麟操作系统

    背景 某个项目需要实现基础软件全部国产化,其中操作系统指定银河麒麟v4,CPU使用飞腾处理器。飞腾处理器是ARMv8架构的,在之前的文章中介绍了使用QEMU模拟ARMv8架构安装银…

    技术杂谈 2023年7月11日
    0136
  • 苞米面Paddle助手介绍

    自己用的百度飞桨 Paddle,PaddleX 项目模板和小工具。My Paddle PaddleX project templates. 适用系统 一些脚本使用 shell 编写…

    技术杂谈 2023年7月23日
    093
  • 自底向上:从可变对象、不可变对象到深浅拷贝再到数据结构

    一、不可变对象和可变对象** Python 在 heap 中分配的对象分成两类:可变对象和不可变对象。所谓可变对象是指,对象的内容是可变的,例如 list。而不可变的对象则相反,表…

    技术杂谈 2023年7月25日
    099
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球