Java | after reading the code written by my sophomore, I began to imitate silently...

Keywords: Python Java SQL

background

The thing is, at present, I am participating in the construction of XXXX project and need to interface with a third party. There are several asynchronous notifications in the other party's interface. In order to ensure the security of the interface, it is necessary to verify the parameters of the interface.

In order to facilitate the processing of asynchronous notification return parameters, colleague Z proposed to uniformly encapsulate the signature verification function. At that time, you only need to pay attention to your own business logic.

Z colleague's solution

Colleague Z chose the solution of "custom parameter parser". Let's learn about it through the code.

Custom annotation

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER})
public @interface RsaVerify {
    
    /**
     * Whether to enable the signature verification function. The default is signature verification
     */
    boolean verifySign() default true;
}

Custom method parameter parser

@AllArgsConstructor
@Component
//realization   HandlerMethodArgumentResolver   Interface
public class RsaVerifyArgumentResolver implements HandlerMethodArgumentResolver {

    private final SecurityService securityService;

    /**
     * This method is used to judge whether the requested interface needs to resolve parameters,
     * Return if necessary   true, and then call the following   resolveArgument   method,
     *  If you do not need to return   false
     */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(RsaVerify.class);
    }

    /**
     * The real parsing method parses the parameter value in the request into an object
     * parameter Method parameters to resolve
     * mavContainer Currently requested   ModelAndViewContainer (provides access to the model for requests)
     * webRequest Current request
     * WebDataBinderFactory Used to create   WebDataBinder   Factory
     */
    @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();
        }
        
        //Logic for processing and signing parameters
        ......
        
        //Returns the processed entity class parameters
        return ObjectMapperFactory
                .getDateTimeObjectMapper("yyyyMMddHHmmss")
                .readValue(StringUtil.queryParamsToJson(sb.toString()), parameter.getParameterType());
    }
   
}

Create configuration class

@Configuration
@AllArgsConstructor
public class PayTenantWebConfig implements WebMvcConfigurer {

    private final RsaVerifyArgumentResolver rsaVerifyArgumentResolver;
    
    /**
     * Add the custom method parameter parser to the configuration class
     */
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(rsaVerifyArgumentResolver);
    }
}

use

The usage method is very simple. You only need to introduce annotations on the parameters

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

    /**
     * @param params
     * @return
     */
    @PostMapping("/callback")
    public String callback(@RsaVerify CallbackReq params) {
        log.info("receive callback req={}", params);
  //Business logic processing
  .....
  
        return "success";
    }
}

problem

Question one

Seeing this, careful friends should have some questions: since custom annotations are used here, why not use the aspect to implement, but use the custom parameter parser? Very Good! This is also the question raised by ah Q. my colleague said it was because   jackson   The priority of the deserialization action of is much higher than that of the aspect, so the error of deserialization failure has been reported before entering the aspect.

Question two

Why   controller   Chinese annotation  @ RequestBody   be missing?

To answer this question, we have to understand the class HandlerMethodArgumentResolverComposite, hereinafter referred to as composite. SpringMVC   At startup, all parameter parsers will be put into   Composite   In, composite   Is a collection of all parameters. When a parameter is parsed, a support pair is selected from the parameter parser collection   parameter   The parsed parameter parser, and then use the parser for parameter parsing.

Because the parameter parser RequestResponseBodyMethodProcessor used by @ RequestBody takes precedence over our custom parameter parser, if shared, the former will intercept the parsing. Therefore, in order to use it normally, we need to use @ RequestBody   Remove the annotation.

/**
 * 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. solutions for colleagues

The solution of colleague Z above can solve this problem, but it still has two shortcomings:

  • Each callback needs to create its own   controller   On the first floor, there is no unified entrance to the outside;

  • User defined annotations need to be added to the method, which is highly invasive;

Therefore, after our discussion, we decided to abandon the scheme, but the idea of the scheme is worth learning. Next, let's analyze the new solution:

Define business interface class

The business interface class contains two methods: the type of specific business processing; Specific processing methods of business.

public interface INotifyService {
 /**
  * Processing type
  */
 public String handleType();
 /**
  * Handle specific business
  */
 Integer handle(String notifyBody);

}

Asynchronous notification unified portal

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

    @PostMapping(value = "/receive")
    public String receive(@RequestBody String body) {
        //Processing notification
        Integer status = service.handle(body);
        return "success";
    }
}

Do two steps in Iservice:

  • After spring starts, collect all types as   INotifyService class and put it into the map;

  • Convert the parameters and check the signature;

private ApplicationContext applicationContext;
private Map<String,INotifyService> notifyServiceMap;

/**
 * Start loading
 */
@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 logic
    ......
        
 //Get the specific business implementation class
 INotifyService notifyService=notifyServiceMap.get(notifyType);
 Integer status=null;
 if(Objects.nonNull(notifyService)) {
  //Execute specific business
  try {
   status=notifyService.handle(JSON.toJSONString(requestParameter));
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
 //Subsequent logic processing
    ......
        
 return status;
}

Specific business implementation

@Service
public class NotifySignServiceImpl implements INotifyService {

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

    @Override
    @Transactional
    public Integer handle(String notifyBody) {
        //Specific business processing
        ......
    }
}

Summary

  • This scheme provides a unified asynchronous notification entry, which separates the public parameter processing and signature verification logic from the business logic.

  • Using the characteristics of java dynamically loading classes, the implementation classes are collected by types.

  • Using the polymorphic characteristics of java, different implementation classes are used to deal with different business logic.

Seeing this, I believe you have a certain understanding of these two implementation schemes. You can try to apply them in future projects and experience them!

Posted by kat_jumper_33 on Thu, 25 Nov 2021 17:38:12 -0800