看了同事写的代码,我竟然开始默默的模仿了。。。
背景
事情是这样的,目前我正在参与 XXXX 项目的搭建,需要与第三方对接接口。在对方的接口中存在几个异步通知,为了接口的安全性,需要对接口的参数进行验签处理。
为了方便大家对异步通知返回参数的处理,Z 同事提出要将该验签功能进行统一封装,到时候大家只需要关注自己的业务逻辑即可。
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<HandlerMethodArgumentResolver> 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
这个类了,以下简称Composite
。 SpringMVC
在启动时会将所有的参数解析器放到 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; }
C同事的解决方案
上边 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<String,INotifyService> notifyServiceMap; /** * 启动加载 */ @PostConstruct public void init(){ Map<String,INotifyService> map = applicationContext.getBeansOfType(INotifyService.class); Collection<INotifyService> services = map.values(); if(CollectionUtils.isEmpty(services)){ return; } notifyServiceMap = services.stream().collect(Collectors.toMap(INotifyService::handleType, x -> x)); } @Override public Map<String, INotifyService> 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可以加入技术交流群参与讨论呦!

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
私有化场景下大规模云原生应用的交付实践
本文根据作者在 CSDN 云原生 Meetup 深圳站的演讲内容整理,分享云原生趋势下网易数帆在私有化场景下大规模应用的交付实践,包括在实践过程中遇到的问题,如何实现标准化、高效率且高质量的交付方案,以及取得效果。 背景介绍 软件私有化交付部署是建立在企业自有基础设施的基础之上的,是为一个企业客户单独使用而构建的硬件/软件运行环境;因而能够提供对数据安全、合规审计和服务质量的有效控制。 软件的私有化是由市场供需关系决定的。也分为甲方和乙方,甲乙双方各取所需从而使面向企业的私有化市场正常运转,例如以下双方的一些诉求: 甲方(资金提供方)诉求 政策性的行业合规和安全化诉求。 企业网络完全隔离限制,不能和互联网建立通讯,无法使用公有云产品。例如金融企业内网。 企业运营的数据具有敏感性,不适合直接运行在公共互联网。例如政府企业沟通交流的工具使用企业微信、企业钉钉等数据要求绝对保密。 头部互联网企业的云技术能力强,投入力度大,产品整体相对较好,期望使用领先的云技术能力。例如期望在内部使用阿里云、腾讯云、华为云以及数帆数帆轻舟或大数据等产品。 传统企业希望进行数字化转型,但是自研体系能力欠缺,从头...
- 下一篇
网络协议之:还在用HTTP代理?弱爆了!快试试SOCKS5
简介 存在即是合理,SOCKS5的出现是为了解决SOCKS4中不支持身份认证的大问题而出现的,毕竟大家对网络中的安全越来越重视了。没有认证的网络就好像是生活在摄像头下的人生,毫无隐私可言,实在是太可怕了。 今天给大家深入讲解一下SOCKS5和它的应用。 为什么要使用SOCKS SOCKS是一种代理服务协议,为什么会要有代理服务协议呢? 因为在现代网络中,很多情况下,由于网络或者防火墙的原因,我们很难直接去访问对方的网络,所以需要一种代理机制来充当本地网络和大型网络之间的网关。 代理服务器通过拦截发送方和接收方之间的连接来工作。 所有传入的数据都通过一个端口进入,并通过另一个端口转发到目标网络中。 当然流量转发是代理服务器的最基本的功能,代理服务器还可以通过隐藏客户端或者服务器端的IP地址,从而实现网络访问的安全性。另外因为代理服务器充当了目标服务器的代理,所以可以将代理服务器作为目标服务器的缓存服务器,从而提高网络访问效率。 另外代理服务器还可以通过对数据进行拦截,从而进行一些特殊的操作,比如对数据进行加密,从而保证数据传输的安全性。另外代理服务器还可以对客户端的访问进行控制,比如可以...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8编译安装MySQL8.0.19
- Hadoop3单机部署,实现最简伪集群
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- SpringBoot2全家桶,快速入门学习开发网站教程
- MySQL8.0.19开启GTID主从同步CentOS8
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果