Java教程

SpringMVC 之 自定义参数解析

本文主要是介绍SpringMVC 之 自定义参数解析,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

方案一:格式化器

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 得知参数对应的类信息

 

根据个人的需求灵活选择方式进行配置

这篇关于SpringMVC 之 自定义参数解析的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!