方案一:格式化器
Java 中,时间格式为 LocalXxx 时间,无法解析前端页面传入的字符串
WebMvcConfigurer 是 MVC 的核心配置类:
@Configuration public class IWebMvcConfigurer implements WebMvcConfigurer { /** * Get 请求,参数转换格式处理器 * @param registry */ @Override public void addFormatters(FormatterRegistry registry) { // Get请求处理时间字符串转换 DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar(); registrar.setTimeFormatter(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN)); registrar.setDateFormatter(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)); registrar.setDateTimeFormatter(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)); registrar.registerFormatters(registry); } }
若自定义 格式化器:
类:
public class Person { private String code; private String name; }
格式化器:
public class PersonFormatter implements Formatter<Person> { /** * 入参解析对象 * @param text 入参时的字符串值 * @param locale */ @Override public Person parse(String text, Locale locale) throws ParseException { // 自行实现逻辑将 字符串餐宿转换为对象 return null; } @Override public String print(Person person, Locale locale) { // 自行实现逻辑,将对象转为字符串 return null; } }
方案二:属性编辑器:
编写自定义参数转换对象的类 (实现 接口 PropertyEditor ,为了简化,我们继承 PropertyEditorSupport 减少了实现的方法)
public class PersonPropertyEditor extends PropertyEditorSupport { @Override public void setAsText(String text) throws IllegalArgumentException { // 根据需求编写逻辑 Person person = new Person(); person.setName(text); super.setValue(person); } @Override public String getAsText() { // 仅仅是示例代码,伪代码 Person person = (Person)getValue(); return person.getName(); } }
注册编辑器:
public class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar { @Override public void registerCustomEditors(PropertyEditorRegistry registry) { registry.registerCustomEditor(Person.class, new PersonPropertyEditor()); } }
方案三:自定义参数解析器模式(自定义注解方式)
@Documented @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface JsonRequestParam { @AliasFor("name") String value() default ""; @AliasFor("value") String name() default ""; boolean required() default true; }
注解参数解析类:
/** * 处理解析注解的参数 * * @author Alay * @date 2022-03-19 23:24 */ public class JsonArgumentResolver implements HandlerMethodArgumentResolver { /** * 适配到自定义注解的方式 * * @param methodParameter * @return */ @Override public boolean supportsParameter(MethodParameter methodParameter) { return methodParametermethodParameter.hasParameterAnnotation(JsonRequestParam.class); } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest webRequest, WebDataBinderFactory webDataBinderFactory) throws Exception { // 得到 JSONRequestParam 注解信息并将其转换成用来记录注解信息的 JSONRequestParamNamedValueInfo 对象 JsonRequestParam jsonRequestParam = parameter.getParameterAnnotation(JsonRequestParam.class); JSONRequestParamNamedValueInfo namedValueInfo = new JSONRequestParamNamedValueInfo(jsonRequestParam.name(), jsonRequestParam.required()); if (namedValueInfo.name.isEmpty()) { namedValueInfo.name = parameter.getParameterName(); if (namedValueInfo.name == null) { throw new IllegalArgumentException( "Name for argument type [" + parameter.getNestedParameterType().getName() + "] not available, and parameter name information not found in class file either."); } } HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class); //获得对应的 value 的 JSON 字符串 String jsonText = servletRequest.getParameter(namedValueInfo.name); //得到参数的 Class Class clazz = parameter.getParameterType(); //使用 Jackson 将 JSON 字符串转换成我们想要的对象类 ObjectMapper mapper = new ObjectMapper(); Object value = mapper.readValue(jsonText, clazz); return value; } private static class JSONRequestParamNamedValueInfo { private String name; private boolean required; public JSONRequestParamNamedValueInfo(String name, boolean required) { this.name = name; this.required = required; } } }
将解析器注册到 SpringMVC 核心配置类中:
@Configuration public class IWebMvcConfigurer implements WebMvcConfigurer { @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { resolvers.add(new JsonArgumentResolver()); } }
方案四: 自定义参数转换器模式:
转换器编写:
public class PersonConverter implements Converter<String, Person> { /** * @param source 客户端参数数据字符串 * @return */ @Override public Person convert(String source) { if (null == source) { return null; } String[] array = source.split("&"); // 我测试的Person 类只有两个属性 if (array.length >= 2) { // 自行实现参数转换类的逻辑 Person person = new Person(); person.setCode(array[0]); person.setName(array[1]); return person; } System.out.println("参数不合法 : " + source); return null; } }
转换器注册如 SpringMVC 核心配置类中:
@Configuration public class IWebMvcConfigurer implements WebMvcConfigurer { /** * 自定义参数转换器注册如 SpringMvc 核心配置类 * @param registry */ @Override public void addFormatters(FormatterRegistry registry) { // 注册转换器 registry.addConverter(new PersonConverter()); }
转化器进一步扩展:(以上转换器都是转换具体的类,下面扩展的是转换接口的所有实现类,如:所有枚举统一转换器定义)
枚举抽象接口(约束所有枚举必须具备 code 属性)
public interface IEnum<T extends Serializable> extends Serializable { IRuntimeException EXCEPTION = new ParamException(FailedResult.NOT_PERMITTED); /** * 获取枚举编码 * @return */ int getCode(); /** * 根据枚举名称获取枚举对象 */ @SneakyThrows static <E extends Enum<E> & IEnum> E sourceOf(Class<E> clazz, String name) { Method valuesMethod = clazz.getMethod("values"); E[] enumInstanceArr = (E[]) valuesMethod.invoke(null); for (E enumInstance : enumInstanceArr) { if (Objects.equals(enumInstance.name(), name)) { return enumInstance; } } throw ExceptionUtil.create(IRuntimeException.class, ErrorResult.PARAMETER_ERROR); } /** * 根据编码获取枚举对象 */ @SneakyThrows static <E extends Enum<E> & IEnum> E codeOf(Class<E> clazz, int code) { Method valuesMethod = clazz.getMethod("values"); E[] enumInstanceArr = (E[]) valuesMethod.invoke(null); for (E enumInstance : enumInstanceArr) { if (code == enumInstance.getCode()) { return enumInstance; } } throw ExceptionUtil.create(IRuntimeException.class, ErrorResult.PARAMETER_ERROR); } }
IEnum 下有大量的实现类枚举,如下是其中之一(为了便利客户端传参自由,自定义转换器兼容使用枚举的 code 或者 name )
public enum SexEnum implements IEnum<String> { /** * 男性 */ MALE(1, "男"), /** * 女性 */ FEMALE(2, "女"), /** * 未知 */ UNKNOWN(3, "未知"); /** * 枚举编码 */ @EnumValue private int code; /** * 枚举值 */ private String value; }
编写自定义的转换器工厂:
public class IEnumConverterFactory implements ConverterFactory<String, IEnum> { /** * 调用转换器 * @param targetType 具体实现类的类信息 */ @Override public <T extends IEnum> Converter<String, T> getConverter(Class<T> targetType) { return new Str2IEnumConverter(targetType); } }
枚举转换器(可以将转换器类 Str2IEnumConverter 以 内部类的形式 编写到 IEnumConverterFactory 类中 ):
public class Str2IEnumConverter<T extends Enum<T> & IEnum> implements Converter<String, T> { // 入参值对应属性的具体类对象信息 private Class<T> enumType; private Str2IEnumConverter(Class<T> enumType) { this.enumType = enumType; } /** * 将参数转换为枚举对象 * @param source 入参值 */ @Override public T convert(String source) { if (source != null && !source.isEmpty()) { boolean isNumber = NumberUtil.isNumber(source); if (isNumber) { // 枚举值传参为数值时 return IEnum.codeOf(enumType, Integer.valueOf(source)); } else { // 传参为枚举名称类型时 return IEnum.sourceOf(enumType, source); } } return null; } }
在SpringMVC 核心配置类中 注册转换器:
@Configuration public class IWebMvcConfigurer implements WebMvcConfigurer { @Override public void addFormatters(FormatterRegistry registry) { // 自定义枚举 Get 请求系列化处理 registry.addConverterFactory(new IEnumConverterFactory()); }
总结:
第一种 Formatter :从类名可看出来,主要用于格式化处理,比如时间,数字,等等,举例中使用了 Person 类,并不是很恰当,抛砖引玉罢了
第二种 PropertyEditor ,对属性的修改,适合对字段进行自定义解析处理,缺点:只能处理指定类型的属性字段处理
第三种: 自定义注解方式比较灵活,缺,但只支持指定类的转换
第四种转换器:这种方法比较灵活,如果只需要对指定类进行参数转换,则无需编写 转换器工厂类,若希望处理的是一个接口的所有实现类,则编写一个 装换器工厂,可以通过 targetType 得知参数对应的类信息
根据个人的需求灵活选择方式进行配置