个人专题目录
SpringMVC自定义参数解析-解决 xxx.N.xxx 和xxx.N的参数传递
1. 项目中遇到的问题
spring mvc默认不支持xxx.N.xxx 和xxx.N的参数传递 {@code HaveNAttribute}作用是把 xxx.N.xxx转换成* xxx[N].xxx 把 xxx.N转换成 xxx[N] 转换成spring mvc能处理的格式。
解决方案:
通过自定义注解HaveNAttribute来决定是否开启自定义参数解析:
/**
* 解决 xxx.N.xxx 和xxx.N的参数传递
* 如果参数加了该注解则开启自定义的参数解析
*/
public @interface HaveNAttribute {
String value() default "";
}
对象参数绑定扩展
对象参数解析绑定会交给ServletModelAttributeMethodProcessor这个类,在初始化argumentResolvers的时候。
所以我们只要扩展ServletModelAttributeMethodProcessor这个类即可。自定义的处理器也是处理复杂对象,只是扩展了可以处理名称映射,扩展这个ServletModelAttributeMethodProcessor即可。然后重写父类的bindRequestParameters方法,这个方法就是绑定数据对象的时候调用的方法。核心代码如下:
public class HaveNAttributeHandlerMethodArgumentResolver extends ServletModelAttributeMethodProcessor {
private HaveNAttribute haveNAttribute;
public HaveNAttributeHandlerMethodArgumentResolver() {
this(false);
}
public HaveNAttributeHandlerMethodArgumentResolver(boolean annotationNotRequired) {
super(annotationNotRequired);
}
/**
* 该解析器是否支持parameter参数的解析
*/
@Override
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.hasParameterAnnotation(HaveNAttribute.class)) {
haveNAttribute = parameter.getParameterAnnotation(HaveNAttribute.class);
return true;
}
return false;
}
/**
* 将请求绑定至目标binder的target对象
*/
@Override
protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
Assert.state(servletRequest != null, "No ServletRequest");
HaveNAttributeDataBinder attrBinder = new HaveNAttributeDataBinder(binder.getTarget(), binder.getObjectName());
attrBinder.bind(servletRequest);
}
}
在bindRequestParameters这个方法中,新建了一个自定义的DataBinder-HaveNAttributeDataBinder,然后调了DataBinder的bind方法。DataBinder就是实际去把请求参数和对象绑定的类,自定义的DataBinder继承自ExtendedServletRequestDataBinder,子类复写的方法是addBindValues,有两个参数,一个是MutablePropertyValues类型的,这里面存的就是请求参数的key-value对,还有一个参数是request对象本身。
核心代码DataBinder扩展
@Slf4j
public class HaveNAttributeDataBinder extends ExtendedServletRequestDataBinder {
private final static String THREE_LEVEL = "^(\\w+)\\.(-?\\d+)\\.(\\w+)$";
private final static String TWO_LEVEL = "^(\\w+)\\.(-?\\d+)$";
private final static String FOUR_LEVEL = "^(\\w+)\\.(-?\\d+)\\.(\\w+)\\.(-?\\d+)$";
private final static String DIGITS = "^-?\\d+$";
private Map pvMap;
public HaveNAttributeDataBinder(Object target) {
super(target);
}
public HaveNAttributeDataBinder(Object target, String objectName) {
super(target, objectName);
}
@Override
protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {
super.addBindValues(mpvs, request);
// 处理自己的逻辑
PropertyValue[] pvArray = mpvs.getPropertyValues();
pvMap = new HashMap<>();
StringBuilder beforeBuffer = new StringBuilder();
for (PropertyValue pv : pvArray) {
String field = pv.getName();
if (field.matches(THREE_LEVEL) || field.matches(TWO_LEVEL) || field.matches(FOUR_LEVEL)) {
pvMap.put(pv, field);
}
beforeBuffer.append(field).append("=======before========").append(pv.getValue()).append("\n");
}
log.info("handle before PropertyValues {},size {}", beforeBuffer.toString(), pvArray.length);
// 替换map中的key *.N.* replace *[N].*
this.replaceMapKey(pvMap, null);
this.handlePropertyValue(pvMap, mpvs);
PropertyValue[] pvArrays = mpvs.getPropertyValues();
StringBuilder afterBuffer = new StringBuilder();
for (PropertyValue pv : pvArrays) {
afterBuffer.append(pv.getName()).append("=======after========").append(pv.getValue()).append("\n");
}
log.info("handle after PropertyValues {},size {}", afterBuffer.toString(), pvArrays.length);
}
/**
* 替换map中的key *.N.* replace *[N].*
*/
public void replaceMapKey(Map handleResult, Map> countMap) {
boolean isReturn = true;
if (countMap == null) {
countMap = new HashMap<>();
}
if (!handleResult.isEmpty()) {
for (Map.Entry entry : handleResult.entrySet()) {
String str = entry.getValue();
if (str.lastIndexOf(".") > -1) {
isReturn = false;
String handleStr = this.handleKey(str, countMap);
entry.setValue(handleStr);
}
}
if (!isReturn) {
replaceMapKey(handleResult, countMap);
}
}
}
private String handleKey(String str, Map> countMap) {
int index = str.lastIndexOf(".");
String prefixStr = StringUtils.capitalize(str.substring(0, index));
String suffixStr = str.substring(index + 1);
if (suffixStr.matches(DIGITS)) {
List list = countMap.computeIfAbsent(prefixStr, k -> new ArrayList<>());
if (!list.contains(suffixStr)) {
list.add(suffixStr);
}
prefixStr += "[" + list.indexOf(suffixStr) + "]";
return prefixStr;
} else {
suffixStr = StringUtils.capitalize(suffixStr);
return handleKey(prefixStr, countMap) + suffixStr;
}
}
public void handlePropertyValue(Map handleResult, MutablePropertyValues mpvs) {
if (handleResult != null && !handleResult.isEmpty()) {
for (Map.Entry entry : handleResult.entrySet()) {
PropertyValue pv = entry.getKey();
String str = entry.getValue();
String value = str.replace("]", "].");
int index = value.lastIndexOf(".");
if (index == value.length() - 1) {
value = value.substring(0, index);
}
entry.setValue(value);
mpvs.removePropertyValue(pv);
mpvs.addPropertyValue(value, Objects.requireNonNull(pv.getValue()));
}
}
}
public Map getPvMap() {
return pvMap;
}
public void setPvMap(Map pvMap) {
this.pvMap = pvMap;
}
}
注册自定义解析器
核心代码如下: 添加解析器以支持自定义控制器方法参数类型。该方法不会覆盖用于解析处理程序方法参数的内置支持。 要自定义内置的参数解析支持, 同样可以通过 RequestMappingHandlerAdapter
直接配置 RequestMappingHandlerAdapter
。
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List resolvers) {
//添加一个自定义的参数解析对象
resolvers.add(new HaveNAttributeHandlerMethodArgumentResolver());
}
}
功能测试
测试核心代码如下:
Controller
@RestController
@RequestMapping(value = "/", produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
@Slf4j
public class TestController {
@RequestMapping
public void testAction(HttpServletRequest request, @Validated @HaveNAttribute TestParam param) {
log.info("param={}", param);
}
}
Model
@Data
public class TestParam {
@NotNull
@Min(1)
private Integer age;
@NotBlank
@Size(min = 2, max = 14)
private String name;
private Map<@NotBlank String, @NotBlank String> relationship;
private List<@NotBlank String> instanceId;
private List<@NotNull @Valid Dog> dog;
private Map mapDag;
@Valid
private Map mapFilter;
@Valid
private List filter;
@Data
public static class Dog {
@NotBlank
private String name;
@NotNull
private Integer age;
}
@Data
public static class Filter {
@NotBlank
private String name;
@NotNull
private List value;
}
}
相关测试postman截图如下:读者可以自行测试
注意:Content-Type:application/x-www-form-urlencoded