接口开发,入参是使用的蟒蛇式(如:psn_name),而后端接收使用的是驼峰式(如psnName)。并且入参很多,使用spring 的 @RequestParam 只适用少量入参,需要自动进行参数转换。
参考
如何在绑定Spring MVC命令对象时自定义参数名称?
spring mvc给参数起别名
SpringMVC请求参数别名设置
SpringMVC 通过post接收form参数或者json参数
Spring boot 参数别名处理
springboot中使用servlet
实现代码
接口
/**
* 如果是实体类,并且用到了注解ReqParamAs,请实现本类
*
* @author z.y
* @version v1.0
* @date 2022/6
*/
public interface ReqParamBean { }
注解
import java.lang.annotation.*;
/**请求的字段别名设置,通过自定义spring属性编辑器解决
* @author z.y
* @version v1.0
* @date 2022/6
*/
@Target (ElementType.FIELD)
@Retention (RetentionPolicy.RUNTIME)
@Documented
public @interface ReqParamAs {
String value();
}
自定义spring属性编辑器
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.web.servlet.mvc.method.annotation.ExtendedServletRequestDataBinder;
import javax.servlet.ServletRequest;
import java.util.Map;
/**
* 请求的字段别名-数据绑定处理,通过自定义spring属性编辑器解决
* @author z.y
* @version v1.0
* @date 2022/6
*/
public class ReqParamAsDataBinder extends ExtendedServletRequestDataBinder {
private static final Logger logger = LoggerFactory.getLogger(ReqParamAsDataBinder.class);
private final Map<String, String> cacheMap;
public ReqParamAsDataBinder(Object target,String objectName,Map<String, String> asMap){
super(target,objectName);
this.cacheMap = asMap;
}
/**
* 复写addBindValues方法
* @param mpv 这里面存的就是请求参数的key-value对
* @param req 请求本身, 这里没有用到
*/
@Override
protected void addBindValues(@NotNull MutablePropertyValues mpv, @NotNull ServletRequest req) {
super.addBindValues(mpv, req);
Object obj;
PropertyValue pv;
logger.info("字段别名-缓存,{}",cacheMap);
for (Map.Entry<String, String> entry : cacheMap.entrySet()) {
String alias = entry.getKey(),fieldName = entry.getValue();
if(mpv.contains(alias)){
pv = mpv.getPropertyValue(alias);
if( null == pv ){ continue; }
obj = pv.getValue();
logger.debug("字段别名-数据绑定处理,{}<>{},{}",alias,fieldName,obj);
// 给原始字段赋值
mpv.add(fieldName, obj);
}
}
}
}
参数解析器
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor;
import javax.servlet.ServletRequest;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
/**
* 请求的字段别名-参数解析器,通过自定义spring属性编辑器解决
*
* @author z.y
* @version v1.0
* @date 2022/6
*/
public class ReqParamAsProcessor extends ServletModelAttributeMethodProcessor {
private static final Map<Class<?>,Map<String, String>> PARAM_CACHE_MAP = new ConcurrentHashMap<>();
private final ApplicationContext context;
public ReqParamAsProcessor(ApplicationContext applicationContext){
super(true);
this.context = applicationContext;
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 接口方法不含 注解 RequestParam、入参不是 简单参数、并且参数对象内的字段包含自定义注解 RequestParam
return !parameter.hasParameterAnnotation(RequestParam.class)
&& !parameter.hasParameterAnnotation(RequestBody.class)
&& !BeanUtils.isSimpleProperty(parameter.getParameterType())
&& Arrays.stream(parameter.getParameterType().getDeclaredFields())
.anyMatch(field -> field.getAnnotation(ReqParamAs.class) != null);
}
@Override
protected void bindRequestParameters(@NotNull WebDataBinder binder, @NotNull NativeWebRequest request) {
Map<String, String> asMap = cacheMap(Objects.requireNonNull(binder.getTarget()).getClass());
ReqParamAsDataBinder dataBinder = new ReqParamAsDataBinder(binder.getTarget(),binder.getObjectName(),asMap);
RequestMappingHandlerAdapter adapter = context.getBean(RequestMappingHandlerAdapter.class);
Objects.requireNonNull(adapter.getWebBindingInitializer()).initBinder(dataBinder);
dataBinder.bind(Objects.requireNonNull(request.getNativeRequest(ServletRequest.class)));
super.bindRequestParameters(binder, request);
}
private Map<String, String> cacheMap(Class<?> target){
if(PARAM_CACHE_MAP.containsKey(target)){
return PARAM_CACHE_MAP.get(target);
}
Map<String, String> map = analyzeClass(target,"","");
PARAM_CACHE_MAP.put(target,map);
return map;
}
private Map<String, String> analyzeClass(Class<?> target,String prtAs,String prtField){
Field[] fields = target.getDeclaredFields();
Map<String, String> map = new HashMap<>(fields.length);
ReqParamAs rpa;
boolean boo;
String as;
for (Field field : fields) {
boo = (null == (rpa = field.getAnnotation(ReqParamAs.class)) || rpa.value().isEmpty());
as = boo ? field.getName() : rpa.value();
if( isExtendBean(field.getType().getInterfaces()) ){
//如果字段类实现了自定义接口,就认为是自定义是对象类,继续解析字段
// 通过自定义注解在类上面使用,在这里判断也可以
map.putAll(analyzeClass(field.getType(),prtAs + as + '.',prtField + field.getName() + '.'));
}else if( !boo || !"".equals(prtAs)){
// 只绑定需要的映射
map.put(prtAs + as,prtField + field.getName());
}
}
return map;
}
private boolean isExtendBean(Class<?>[] interfaces){
for (Class<?> face : interfaces) {
if(face == ReqParamBean.class){ return true; }
}
return false;
}
}
注入spring
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import java.util.ArrayList;
import java.util.List;
/**
* 请求的字段别名-参数解析器 添加到第一个参数解析器中的bean配置,通过自定义spring属性编辑器解决
*
* @author z.y
* @version v1.0
* @date 2022/6
*/
@Configuration
public class ReqParamAsConfig {
private ApplicationContext applicationContext;
@Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Bean
public ReqParamAsProcessor reqParamAsProcessor(){
return new ReqParamAsProcessor(applicationContext);
}
@Bean
public BeanPostProcessor beanPostProcessor(){
return new BeanPostProcessor() {
@Override
public Object postProcessBeforeInitialization(@NotNull Object bean, @NotNull String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(@NotNull Object bean, @NotNull String beanName) throws BeansException {
if(bean instanceof RequestMappingHandlerAdapter){
RequestMappingHandlerAdapter adapter = (RequestMappingHandlerAdapter)bean;
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
resolvers.add(reqParamAsProcessor());
if(adapter.getArgumentResolvers() != null){
resolvers.addAll(adapter.getArgumentResolvers());
}
adapter.setArgumentResolvers(resolvers);
}
return bean;
}
};
}
}
测试代码
实体类-使用别名注解
import ...annotation.ReqParamAs;
import ...annotation.ReqParamBean;
/**
* 测试类
* @author z.y
* @version v1.0
* @date 2022/6
*/
public class TestAsBean {
private String name;
private Integer age;
@ReqParamAs ("psn_name")
private String psnName;
private Integer psnAge;
@ReqParamAs (value = "my_dog")
private Dog dog;
public static class Dog implements ReqParamBean {
private String dogName;
@ReqParamAs ("dog_age")
private Integer dogAge;
public String getDogName() { return dogName; }
public void setDogName(String dogName) { this.dogName = dogName; }
public Integer getDogAge() { return dogAge; }
public void setDogAge(Integer dogAge) { this.dogAge = dogAge; }
@Override
public String toString() {
return "{dogName='" + dogName + "', dogAge=" + dogAge + '}';
}
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
public String getPsnName() { return psnName; }
public void setPsnName(String psnName) { this.psnName = psnName; }
public Integer getPsnAge() { return psnAge; }
public void setPsnAge(Integer psnAge) { this.psnAge = psnAge; }
public Dog getDog() { return dog; }
public void setDog(Dog dog) { this.dog = dog; }
@Override
public String toString() {
return "{name='" + name + "', age=" + age + ", psnName='" + psnName + "', psnAge=" + psnAge + ", dog=" + dog + '}';
}
}
测试接口
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author z.y
* @version v1.0
* @date 2022/6
*/
@RestController
@RequestMapping ("test/as")
public class TestAsController {
private static final Logger logger = LoggerFactory.getLogger(TestAsController.class);
@PostMapping (value = "root")
public TestAsBean root(TestAsBean bean){
logger.info("测试字段别名,{}",bean);
bean.setName("你说啥");
return bean;
}
}
测试记录
未使用别名
http://localhost:8092/test/as/root
age=1&dog.dogAge=2&dog.dogName=%E7%AD%89%E5%BE%85&name=%E5%95%8A%E5%95%8A&psnAge=4&psnName=%E5%A4%9A%E5%A4%A7
2022-06-15 18:22:39.211 - [] - [http-nio-8092-exec-5] INFO ...controller.TestAsController.root(20) - 测试字段别名,{name='啊啊', age=1, psnName='多大', psnAge=4, dog={dogName='等待', dogAge=2}}
使用别名
http://localhost:8092/test/as/root
age=1&my_dog.dog_age=2&dog.dogName=%E7%AD%89%E5%BE%85&name=%E5%95%8A%E5%95%8A&psnAge=4&psn_name=%E5%A4%9A%E5%A4%A7
2022-06-17 14:29:39.782 - INFO .annotation.ReqParamAsDataBinder.addBindValues(37) - 字段别名-缓存,{my_dog.dog_age=dog.dogAge, psn_name=psnName, my_dog.dogName=dog.dogName}
2022-06-17 14:29:39.783 - DEBUG .annotation.ReqParamAsDataBinder.addBindValues(44) - 字段别名-数据绑定处理,my_dog.dog_age<>dog.dogAge,2
2022-06-17 14:29:39.785 - DEBUG .annotation.ReqParamAsDataBinder.addBindValues(44) - 字段别名-数据绑定处理,psn_name<>psnName,多大
2022-06-17 14:29:40.057 - INFO .controller.TestAsController.root(20) - 测试字段别名,{name='啊啊', age=1, psnName='多大', psnAge=4, dog={dogName='等待', dogAge=2}}
问题-已解决
当前级联别名,暂未处理,记录debug截图,有时间调整代码逻辑实现。