The string passed from the front end is automatically converted to the corresponding enumeration method
background
- In the project, we usually use enumeration to receive some parameters with fixed values. For example, in this project, we need to pass a language parameter. This parameter has three values in the system: Chinese, English and traditional Chinese. The corresponding Integer value is stored in the database
(don't ask me why I want the whole lowercase. The front-end specification is lowercase, and the later enumeration specification is uppercase. spring's own enumeration converter is not used.)
@Getter @RequiredArgsConstructor public enum LanguageType { /** * chinese */ CN("zh-cn",0), /** * english */ EN("en-us",1), /** * Traditional Chinese */ TC("zh-tw",2) ; private final String type; private final Integer index; }
We want to find the corresponding enumeration through the string passing through the front end.
My previous plan was like this
public static LanguageType get(String type) { for (LanguageType value : LanguageType.values()) { if (value.getType().equals(type)) { return value; } } return null; }
The string passed from the front end needs to be manually converted to the corresponding enumeration using findByType, which is really a little troublesome.
We know that spring has its own enumeration converter. Can we write a converter by referring to spring's method?
Implement custom enumeration converter
Converter is a special function introduced in spring 3. In fact, it is a converter that can convert one type to another. You can convert the parameters passed in from the front end to the types that can be used by the back end, such as date formatting, string de whitespace, etc.
package org.springframework.core.convert.converter; public interface Converter<S, T> { T convert(S var1); }
ConverterFactory is the factory class that creates the Converter.
We implement the ConverterFactory and Converter to implement custom enumeration conversion,
code implementation
@SuppressWarnings("all") public class EnumConverterFactory implements ConverterFactory<String, Enum<?>> { private final ConcurrentMap<Class<? extends Enum<?>>, EnumConverterHolder> holderMapper = new ConcurrentHashMap<>(); @Override public <T extends Enum<?>> Converter<String, T> getConverter(Class<T> targetType) { EnumConverterHolder holder = holderMapper.computeIfAbsent(targetType,EnumConverterHolder::createHolder); return (Converter<String, T>) holder.converter; } @AllArgsConstructor static class EnumConverterHolder { @Nullable final EnumMvcConverter<?> converter; static EnumConverterHolder createHolder(Class<?> targetType) { //Get all methods of EnumConvertMethod annotation List<Method> methodList = MethodUtils.getMethodsListWithAnnotation(targetType, EnumConvertMethod.class, false, true); if (CollectionUtil.isEmpty(methodList)) { return new EnumConverterHolder(null); } Assert.isTrue(methodList.size() == 1, "@EnumConvertMethod Only one factory method can be marked(Static method)upper"); Method method = methodList.get(0); Assert.isTrue(Modifier.isStatic(method.getModifiers()), "@EnumConvertMethod Methods can only be marked at the factory(Static method)upper"); return new EnumConverterHolder(new EnumMvcConverter<>(method)); } } static class EnumMvcConverter<T extends Enum<T>> implements Converter<String, T> { private final Method method; EnumMvcConverter(Method method) { this.method = method; this.method.setAccessible(true); } @Override public T convert(String source) { if (source.isEmpty()) { // reset the enum value to null. return null; } try { return (T) method.invoke(null, Integer.valueOf(source)); } catch (Exception e) { throw new IllegalArgumentException(e); } } } }
Here, we use @ EnumConvertMethod to identify the converted methods, and then get these methods in the Holder class for reflection execution.
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface EnumConvertMethod { }
load configuration
@Configuration public class InterceptorConfig implements WebMvcConfigurer { @Bean public EnumConverterFactory enumConverterFactory() { return new EnumConverterFactory(); } @Override public void addFormatters(FormatterRegistry registry) { registry.addConverterFactory(enumConverterFactory()); } }
use
Add @ EnumConvertMethod to this method
@EnumConvertMethod public static LanguageType get(String type) { for (LanguageType value : LanguageType.values()) { if (value.getType().equals(type)) { return value; } } return null; }
Processing of request body parameters
The enumeration converter of springMvc only supports the parameter conversion of get request amount. If it is a POST request, it cannot be supported.
In addition, if we look it up from the database and return it to the front end, we need to convert it to the corresponding string, which is still not convenient.
When we pass it to the front end, we pass the parameters to the front end through json serialization. We can use Jackson to provide us with two annotations to solve this problem.
- @JsonValue: when serializing, only the value marked by @ JsonValue annotation is serialized
- @JsonCreator: when deserializing, call the constructor or factory method of @ JsonCreator annotation to create an object
Final code
@EnumConvertMethod @JsonCreator(mode = JsonCreator.Mode.DELEGATING) @Nullable public static LanguageType get(String type) { for (LanguageType value : LanguageType.values()) { if (value.getType().equals(type)) { return value; } } return null; }