工作是如许的,目前我正在参与 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; @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(RsaVerify.class); } @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@AllArgsConstructorpublic class PayTenantWebConfig implements WebMvcConfigurer { private final RsaVerifyArgumentResolver rsaVerifyArgumentResolver; @Override public void addArgumentResolvers(List resolvers) { resolvers.add(rsaVerifyArgumentResolver); }}利用利用办法十分简单,只需要在参数上引入注解就能够了
@RestController@Slf4j@RequestMapping("/xxx")public class XxxCallbackController { @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 注解去掉。
@Nullableprivate 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 notifyServiceMap;@PostConstructpublic 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));}@Overridepublic Map getNotifyServiceMap() { return notifyServiceMap;}@Overridepublic 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;}营业详细实现@Servicepublic class NotifySignServiceImpl implements INotifyService { @Override public String handleType() { return "type_sign"; } @Override @Transactional public Integer handle(String notifyBody) { //详细的营业处置 ...... }}小结此计划供给同一的异步通知入口,把公共的参数处置和验签逻辑与营业逻辑剥离。操纵 java 动态加载类的特征,将实现类通过类型停止搜集。操纵 java 多态的特征,通过差别的实现类来处置差别的营业逻辑。看到那,相信各人已经对那两种实现计划有了必然的理解,各人能够试着在以后的项目中应用一下,体验一把!
--- EOF ---保举↓↓↓





还没有评论,来说两句吧...