头图

SpringBoot parameter alias implementation

first published on Dale's blog

Background and pain points

There is often a situation in the project: after a certain parameter is set, the front end asks to change the parameter name, and you don't want to change the elegance of the code because of this name, so you need to have the ability to have one parameter corresponding to two parameter names.
Specifically: the business has a custom protocol, and the name defined in the java entity is the full name, for example: name getter and setter are getName() and setName(String name) respectively.
However, in the process of protocol forwarding, you need to use abbreviations to optimize the size of the message body {"nm":"Dale"} . For unknown reasons, the front end requires the use of nm corresponding to the original name .

Ideas

Use the annotation to set the alias of the parameter, and bind the alias to the parameter name of the entity. Then use ExtendedServletRequestDataBinder.addBindValues re-correlate the value corresponding to the alias with the parameter name of the entity. Some mouthfuls, take the example in the background as an example: the received parameter is nm , and the value is Dale . After binding, nm the value of name to 0616edd7fc76fe.

Not much nonsense, just go to the code.

Code

ValueFrom

/**
 * 请求参数别名注解
 *
 * @author Dale
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ValueFrom {

    /**
     * 参数别名列表
     */
    String[] value();
}

AliasDataBinder

/**
 * 别名数据绑定
 *
 * @author Dale
 */
public class AliasDataBinder extends ExtendedServletRequestDataBinder {

    public AliasDataBinder(Object target, String objectName) {
        super(target, objectName);
    }

    @Override
    protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {
        super.addBindValues(mpvs, request);
        Class<?> targetClass = Objects.requireNonNull(getTarget()).getClass();
        Class<?> targetFatherClass = targetClass.getSuperclass();
        // 利用反射获取类的字段
        Field[] fields = targetClass.getDeclaredFields();
        Field[] superFields = targetFatherClass.getDeclaredFields();

        for (Field field : fields) {
            ValueFrom valueFromAnnotation = field.getAnnotation(ValueFrom.class);
            if (mpvs.contains(field.getName()) || valueFromAnnotation == null) {
                continue;
            }
            for (String alias : valueFromAnnotation.value()) {
                if (mpvs.contains(alias)) {
                    mpvs.add(field.getName(), Objects.requireNonNull(mpvs.getPropertyValue(alias)).getValue());
                    break;
                }
            }
        }
        // 将参数绑定到父类上
        for (Field field : superFields) {
            ValueFrom valueFromAnnotation = field.getAnnotation(ValueFrom.class);
            if (mpvs.contains(field.getName()) || valueFromAnnotation == null) {
                continue;
            }
            for (String alias : valueFromAnnotation.value()) {
                if (mpvs.contains(alias)) {
                    mpvs.add(field.getName(), Objects.requireNonNull(mpvs.getPropertyValue(alias)).getValue());
                    break;
                }
            }
        }
    }
}

AliasModelAttributeMethodProcessor

Re-inject DataBinder

/**
 * 参数别名绑定processor
 *
 * @author Dale
 */
public class AliasModelAttributeMethodProcessor extends ServletModelAttributeMethodProcessor {

    private ApplicationContext applicationContext;

    public AliasModelAttributeMethodProcessor(boolean annotationNotRequired) {
        super(annotationNotRequired);
    }

    public void setApplicationContext(ApplicationContext applicationContext){
        this.applicationContext = applicationContext;
    }

    @Override
    protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
        // 重新注入 databinder
        AliasDataBinder aliasDataBinder = new AliasDataBinder(binder.getTarget(), binder.getObjectName());
        RequestMappingHandlerAdapter requestMappingHandlerAdapter = applicationContext.getBean(RequestMappingHandlerAdapter.class);
        Objects.requireNonNull(requestMappingHandlerAdapter.getWebBindingInitializer()).initBinder(aliasDataBinder);
        aliasDataBinder.bind(Objects.requireNonNull(request.getNativeRequest(ServletRequest.class)));
    }
}

Finally, use WebMvcConfigurer.addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) inject AliasModelAttributeMethodProcessor

WebMvcConfiguration

/**
 * web mvc 配置
 *
 * @author Dale
 */
@Component
public class WebMvcConfiguration implements WebMvcConfigurer {

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        AliasModelAttributeMethodProcessor aliasModelAttributeMethodProcessor = new AliasModelAttributeMethodProcessor(true);
        aliasModelAttributeMethodProcessor.setApplicationContext(applicationContext);
        // 注入 AliasModelAttributeMethodProcessor
        resolvers.add(aliasModelAttributeMethodProcessor);
        WebMvcConfigurer.super.addArgumentResolvers(resolvers);
    }
}

use

Use annotations to set aliases when defining incoming parameters

RequestParam

/**
 * 发送消息必要参数
 *
 * @author baoxulong
 */
public class RequestParam {
    /**
     * name
     */
    @NotNull(message = "name is required!")
    @ValueFrom(value = "nm")
    private String name;
    
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    
}

NameController

/**
 * name 控制器
 *
 * @author Dale
 */
@RestController
@RequestMapping("name")
public class NameController {

    @PostMapping("set")
    public JsonResult set(@Valid RequestParam requestParam) {
        // todo::
        return JsonResult.success();
    }
    
}

Summarize

The above is a SpringBoot set the parameter alias, the depth is not high, find time to dig deeper.


Dale
103 声望3 粉丝

谁敢横刀立马!