Unified Asynchronous Notification Verification Using Custom Spring MVC Argument Resolvers
The article explains how to encapsulate asynchronous notification signature verification in Spring MVC by creating a custom annotation and argument resolver, discusses why aspects and @RequestBody are unsuitable, and presents an alternative unified notification handling design using a service interface and dynamic implementation loading.
Background
The project requires integrating third‑party asynchronous notifications and performing signature verification on the incoming parameters.
Z colleague's solution
Z chose a custom parameter resolver approach.
Custom annotation
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER})
public @interface RsaVerify {
/** Whether to enable signature verification, default true */
boolean verifySign() default true;
}Custom argument resolver
@AllArgsConstructor
@Component
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 annotation = parameter.getParameterAnnotation(RsaVerify.class);
if (!annotation.verifySign()) {
return mavContainer.getModel();
}
// signature verification logic ...
return ObjectMapperFactory.getDateTimeObjectMapper("yyyyMMddHHmmss")
.readValue(StringUtil.queryParamsToJson(sb.toString()), parameter.getParameterType());
}
}Configuration class
@Configuration
@AllArgsConstructor
public class PayTenantWebConfig implements WebMvcConfigurer {
private final RsaVerifyArgumentResolver rsaVerifyArgumentResolver;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(rsaVerifyArgumentResolver);
}
}Usage
Annotate a controller method parameter with @RsaVerify.
@RestController
@Slf4j
@RequestMapping("/xxx")
public class XxxCallbackController {
@PostMapping("/callback")
public String callback(@RsaVerify CallbackReq params) {
log.info("receive callback req={}", params);
// business logic
return "success";
}
}Issues
Issue 1 : Why not use an aspect? Because Jackson deserialization has higher priority than aspects, causing deserialization errors before the aspect runs.
Issue 2 : Why is @RequestBody missing? The default RequestResponseBodyMethodProcessor has higher priority than the custom resolver, so keeping @RequestBody would let the processor handle the parameter and bypass the custom logic.
C colleague's solution
The previous approach works but has two drawbacks: each callback needs its own controller and the custom annotation is intrusive.
Define business interface
public interface INotifyService {
/** processing type */
String handleType();
/** actual business handling */
Integer handle(String notifyBody);
}Unified asynchronous notification entry
@AllArgsConstructor
@RestController
@RequestMapping("/notify")
public class NotifyController {
private IService service;
@PostMapping("/receive")
public String receive(@RequestBody String body) {
Integer status = service.handle(body);
return "success";
}
}Service implementation
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) {
// parameter processing + signature verification
// ...
INotifyService notifyService = notifyServiceMap.get(notifyType);
Integer status = null;
if (Objects.nonNull(notifyService)) {
try {
status = notifyService.handle(JSON.toJSONString(requestParameter));
} catch (Exception e) {
e.printStackTrace();
}
}
// further logic
return status;
}Business implementation example
@Service
public class NotifySignServiceImpl implements INotifyService {
@Override
public String handleType() {
return "type_sign";
}
@Override
@Transactional
public Integer handle(String notifyBody) {
// specific business logic
// ...
return 1;
}
}Summary
This approach provides a unified entry for asynchronous notifications, separating common parameter handling and signature verification from business logic.
It leverages Java's dynamic class loading to collect implementations by type.
It uses polymorphism to dispatch different business processing.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Su San Talks Tech
Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
