The parameters passed from the front end are automatically converted to enumeration -- spring convert conversion

Posted by rodin69 on Mon, 29 Nov 2021 00:12:30 +0100

The string passed from the front end is automatically converted to the corresponding enumeration method

background

  1. 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;
}

Topics: Java Spring Project