SpringBoot 引數別名實現
背景與痛點
專案中經常出現一種情況:定了某個引數之後,前端又要求改引數名字,而你又不想因為這個名字而改變程式碼的優雅,於是就需要擁有一個引數對應兩個引數名的能力。
具體來說:業務擁有自定義協議,在java實體中定義的名字是全名,例如:name
getter、setter 分別為 getName()
和 setName(String name)
。
但是,在協議轉發的過程中需要使用簡寫來優化訊息體的大小{"nm":"Dale"}
,不知道何種原因,前端要求使用nm
對應原有的name
。
思路
使用註解
設定引數的別名,將別名與實體的引數名繫結。然後再利用ExtendedServletRequestDataBinder.addBindValues
重新將別名對應的值與實體的引數名對應。有些拗口,以背景中的例子為例:接收到的引數為nm
,值為Dale
。經過繫結之後,將nm
的值繫結到name
上。
廢話不多,直接上程式碼。
程式碼
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
重新注入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)));
}
}
最後利用WebMvcConfigurer.addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers)
注入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);
}
}
使用
在定義傳入引數的時候使用註解設定別名
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();
}
}
總結
以上是 SpringBoot
設定引數別名的方法程式碼實記,深度不高,找時間再深挖。