Skip to content

Commit

Permalink
添加 优雅的使用枚举参数
Browse files Browse the repository at this point in the history
  • Loading branch information
andanyoung committed Nov 1, 2023
1 parent 1e1967a commit d43abed
Show file tree
Hide file tree
Showing 13 changed files with 435 additions and 0 deletions.
59 changes: 59 additions & 0 deletions enum-spring-boot-starter/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.admin4j</groupId>
<artifactId>framework</artifactId>
<version>0.8.0</version>
</parent>

<groupId>com.admin4j.framework</groupId>
<artifactId>enum-spring-boot-starter</artifactId>
<packaging>jar</packaging>

<name>enum-spring-boot-starter</name>
<description>优雅的使用枚举参数</description>


<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-annotation</artifactId>
<version>3.5.3.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.13.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure-processor</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.admin4j.framework.autoconfigure;

import com.admin4j.framework.converter.EnumConverter;
import com.admin4j.framework.converter.impl.EnumValueConverter;
import com.admin4j.framework.converter.impl.JackosnEnumConverter;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonValue;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.Order;
import org.springframework.core.convert.ConversionService;

import java.util.List;

/**
* @author andanyang
* @since 2023/11/1 11:37
*/
public class EnumConverterAutoConfigure {

@Bean
@Order(1)
@ConditionalOnClass(name = "com.baomidou.mybatisplus.annotation.EnumValue")
public EnumValueConverter enumValueConverter() {
return new EnumValueConverter(EnumValue.class);
}

@Bean
@Order(2)
@ConditionalOnClass(name = "com.fasterxml.jackson.annotation.JsonValue")
public EnumValueConverter jsonEnumValueConverter() {
return new EnumValueConverter(JsonValue.class);
}

@Bean
@Order(3)
@ConditionalOnClass(name = "com.fasterxml.jackson.annotation.JsonCreator")
public JackosnEnumConverter jackosnEnumConverter(ObjectProvider<ConversionService> objectProvider) {
return new JackosnEnumConverter(objectProvider);
}

@Bean
@ConditionalOnBean(EnumConverter.class)
public EnumConverterWebConfigure enumConverterWebConfigure(List<EnumConverter> enumConverters) {
return new EnumConverterWebConfigure(enumConverters);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.admin4j.framework.autoconfigure;

import com.admin4j.framework.converter.EnumConverter;
import com.admin4j.framework.converter.factory.EnumConverterFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

/**
* @author andanyang
* @since 2023/11/1 13:26
*/
@RequiredArgsConstructor
public class EnumConverterWebConfigure implements WebMvcConfigurer {

private final List<EnumConverter> enumConverters;

@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverterFactory(new EnumConverterFactory(enumConverters));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.admin4j.framework.converter;

import org.springframework.lang.Nullable;

/**
* 枚举转化器
*
* @author andanyang
* @since 2023/11/1 9:57
*/
public interface EnumConverter {


@Nullable
<T extends Enum> T convert(String source, Class<T> enumType);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.admin4j.framework.converter.factory;


import com.admin4j.framework.converter.EnumConverter;
import lombok.RequiredArgsConstructor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* @author andanyang
* @since 2023/11/1 9:22
*/
@RequiredArgsConstructor
public class EnumConverterFactory implements ConverterFactory<String, Enum> {

@SuppressWarnings("rawtypes")
private static final Map<Class, Converter> CONVERTERS = new ConcurrentHashMap<>(64);
private final List<EnumConverter> enumConverters;

/**
* Get the converter to convert from S to target type T, where T is also an instance of R.
*
* @param targetType the target type to convert to
* @return a converter from S to T
*/
@Override
public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
return CONVERTERS.computeIfAbsent(targetType, (key) -> new StringToEnum<T>(targetType, enumConverters));
}

private static class StringToEnum<T extends Enum> implements Converter<String, T> {

private final Class<T> enumType;
private final List<EnumConverter> enumConverters;

StringToEnum(Class<T> enumType, List<EnumConverter> enumConverters) {
this.enumType = enumType;
this.enumConverters = enumConverters;
}

/**
* Convert the source object of type {@code S} to target type {@code T}.
*
* @param source the source object to convert, which must be an instance of {@code S} (never {@code null})
* @return the converted object, which must be an instance of {@code T} (potentially {@code null})
* @throws IllegalArgumentException if the source cannot be converted to the desired target type
*/
@Override
public T convert(String source) {
if (source.isEmpty()) {
// It's an empty enum identifier: reset the enum value to null.
return null;
}
for (EnumConverter enumConverter : enumConverters) {
T convert = enumConverter.convert(source, enumType);
if (convert != null) {
return convert;
}
}
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.admin4j.framework.converter.impl;


import com.admin4j.framework.converter.EnumConverter;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;

/**
* 使用 EnumValue注解 字段进行匹配
*
* @author andanyang
* @since 2023/11/1 10:07
*/
// @Service
public class EnumValueConverter<A extends Annotation> implements EnumConverter {

private final Class<A> targetAnnotation;

public EnumValueConverter(Class<A> targetAnnotation) {
this.targetAnnotation = targetAnnotation;
}

@Override
public <T extends Enum> T convert(String source, Class<T> enumType) {

// 被注解的值字段
Field valueField = null;
Field[] declaredFields = enumType.getDeclaredFields();
for (Field field : declaredFields) {
Annotation annotation = field.getAnnotation(targetAnnotation);
if (annotation != null) {
valueField = field;
}
}

if (valueField != null) {
T[] enumConstants = enumType.getEnumConstants();
// 查找枚举
for (T e : enumConstants) {
try {
valueField.setAccessible(true);
if (String.valueOf(valueField.get(e)).equals(source)) {
return e;
}
} catch (IllegalAccessException ex) {
throw new RuntimeException(ex);
}
}
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.admin4j.framework.converter.impl;


import com.admin4j.framework.converter.EnumConverter;
import com.fasterxml.jackson.annotation.JsonCreator;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.core.convert.ConversionService;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
* 使用 @JsonCreator 方法注解静态字段进行匹配
*
* @author andanyang
* @since 2023/11/1 10:07
*/

@RequiredArgsConstructor
public class JackosnEnumConverter implements EnumConverter {

private final ObjectProvider<ConversionService> conversionServiceObjectProvider;

@Override
public <T extends Enum> T convert(String source, Class<T> enumType) {

Method[] declaredMethods = enumType.getDeclaredMethods();

for (Method method : declaredMethods) {
if (method.isAnnotationPresent(JsonCreator.class)) {
try {
Object invoke;
Class<?> parameterType = method.getParameterTypes()[0];
Object cast = conversionServiceObjectProvider.getIfAvailable().convert(source, parameterType);
invoke = method.invoke(enumType, cast);

if (invoke == null) {
return null;
}
if (!enumType.isInstance(invoke)) {
throw new IllegalArgumentException("JsonCreator error: target " + enumType.getCanonicalName() + " but get " + invoke.getClass().getCanonicalName());
}

} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}

return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.admin4j.framework.autoconfigure.EnumConverterAutoConfigure
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.admin4j.framework;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
* Unit test for simple App.
*/
@SpringBootApplication
public class AppTest {
public static void main(String[] args) {
SpringApplication.run(AppTest.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.admin4j.framework;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

/**
* @author andanyang
* @since 2023/11/1 11:58
*/
@SpringBootTest(classes = AppTest.class)
@AutoConfigureMockMvc
public class EnumConverterTest {

@Autowired
private MockMvc mockMvc;

@ParameterizedTest
@ValueSource(strings = {"3", "2", "1"})
void genderIdCode(String userStatus) throws Exception {
final String result = mockMvc.perform(
MockMvcRequestBuilders.get("/UserStatus")
.param("userStatus", userStatus)
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print())
.andReturn()
.getResponse()
.getContentAsString();

Assertions.assertEquals(result, userStatus);
}
}
Loading

0 comments on commit d43abed

Please sign in to comment.