我们使用类型时,返回前段一般需要加一些字段来标识这个类型的中文名。但是这个需要我们手动添加一个字段,并且在返回时给这个中文字段设置值,很麻烦那有没有更方便的做法呢。当然有的,下边我们来学习一下序列化动态增加字段。
环境:
JDK 1.8 , Spring boot 2.4.3, fastjson 1.2.76
首先我们需要定义一个枚举,来标识我们哪些字段需要扩展中文名称字段
/**
* 功能描述 : 注解序列化
*
* @author ziyear 2021-12-18 19:21
*/
@Inherited
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface EnumSerialize {
/**
* 字段对应枚举的class
*
* @return
*/
Class<?> value();
/**
* 获取中文名称的方法
*
* @return
*/
String method() default "getMsgByCode";
}
我们再去创建两个枚举吧
用户性别枚举
/**
* 功能描述 : 性别枚举
*
* @author ziyear 2021-12-18 19:21
*/
public enum SexEnum {
MAN("man", "男"),
WOMAN("woman", "女"),
;
private final String code;
private final String msg;
public String getCode() {
return code;
}
public String getMsg() {
return msg;
}
SexEnum(String code, String msg) {
this.code = code;
this.msg = msg;
}
/**
* 根据code获取中文描述的方法
*/
public static String getMsgByCode(String code) {
for (SexEnum sexEnum : values()) {
if (sexEnum.code.equals(code)) {
return sexEnum.msg;
}
}
return code;
}
}
用户级别枚举
/**
* 功能描述 : 会员级别
*
* @author ziyear 2021-12-18 19:22
*/
public enum UserLevelEnum {
DEFAULT("default", "普通会员"),
ADVANCED("advanced", "高级会员"),
SUPER("super", "至尊会员"),
;
private final String code;
private final String msg;
public String getCode() {
return code;
}
public String getMsg() {
return msg;
}
UserLevelEnum(String code, String msg) {
this.code = code;
this.msg = msg;
}
/**
* 根据code取名称
*/
public static String getMsg(String code) {
for (UserLevelEnum anEnum : values()) {
if (anEnum.code.equals(code)) {
return anEnum.msg;
}
}
return code;
}
}
然后 我们创建一个用户类 在性别和等级上分别加上注解,指定我们注解中获取中文描述的方法,因为我们设置了默认方法,如果方法名和默认方法一致的话就不需要指定了。
/**
* 功能描述 : 用户类
*
* @author ziyear 2021-12-18 19:22
*/
@Data
public class User {
private Long id;
private String name;
private String mobile;
@EnumSerialize(SexEnum.class)
private String sex;
@EnumSerialize(value = UserLevelEnum.class,method = "getMsg")
private String level;
}
下边我们搞一个枚举自定义序列化类,对字段上包含 EnumSerialize 的类进行序列化
/**
* 功能描述 : 枚举自定义序列化
*
* @author ziyear 2021-12-18 19:23
*/
@Slf4j
public class EnumsSerializer implements ObjectSerializer {
private static final String FIELD_KEY_EXT = "Name";
private static final String BEAN_START = "{";
private static final String BEAN_END = "}";
private static final String EXT_FIELD_FORMAT = ",\"{0}\": \"{1}\"";
@Override
public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException {
if (object == null) {
serializer.write(object);
return;
}
JavaBeanSerializer javaBeanSerializer = new JavaBeanSerializer(object.getClass()) {
@Override
public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException {
super.write(serializer, object, fieldName, fieldType, features, true);
}
};
SerializeWriter out = serializer.getWriter();
out.write(BEAN_START);
javaBeanSerializer.write(serializer, object, fieldName, fieldType, features);
try {
Set<String> fieldNames = javaBeanSerializer.getFieldNames(object);
for (String name : fieldNames) {
FieldSerializer fieldSerializer = javaBeanSerializer.getFieldSerializer(name);
Field field = fieldSerializer.fieldInfo.field;
if (field != null) {
final EnumSerialize annotation = AnnotationUtils.findAnnotation(field, EnumSerialize.class);
if (annotation != null) {
Class<?> value = annotation.value();
String methodName = annotation.method();
Method method;
try {
method = value.getMethod(methodName, String.class);
} catch (Exception e) {
log.error("方法不存在!{}#{}", value, methodName);
continue;
}
Object fieldValue = javaBeanSerializer.getFieldValue(object, name);
Object msg = method.invoke(null, Objects.toString(fieldValue));
writeExtField(out, name, msg);
}
}
}
} catch (Exception e) {
log.error("序列化出现错误!", e);
} finally {
out.write(BEAN_END);
}
}
private void writeExtField(SerializeWriter out, String key, Object value) {
String format = MessageFormat.format(EXT_FIELD_FORMAT, key + FIELD_KEY_EXT, value);
out.write(format);
}
}
自定义序列化搞好了,然后我们需要将我们的序列化配置到消息转换器中,这样在序列化输出的时候就能用上我们的自定义序列化了
/**
* 功能描述 : 配置类
*
* @author Ziyear 2021-12-18 19:32
*/
@Configuration
public class MvcConfig extends WebMvcConfigurationSupport {
@Override
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
FastJsonHttpMessageConverter fastJsonConverter = new FastJsonHttpMessageConverter();
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(SerializerFeature.QuoteFieldNames,
SerializerFeature.WriteEnumUsingToString,
SerializerFeature.WriteMapNullValue,
SerializerFeature.WriteDateUseDateFormat,
SerializerFeature.WriteNullStringAsEmpty);
JSON.DEFAULT_GENERATE_FEATURE |= SerializerFeature.DisableCircularReferenceDetect.getMask();
fastJsonConverter.setSupportedMediaTypes(Lists.newArrayList(MediaType.APPLICATION_JSON_UTF8,
new MediaType(MediaType.TEXT_HTML, StandardCharsets.UTF_8),
new MediaType(MediaType.APPLICATION_FORM_URLENCODED, StandardCharsets.UTF_8)));
// 序列化配置
SerializeConfig serializeConfig = new SerializeConfig() {
@Override
public ObjectSerializer getObjectWriter(Class<?> clazz) {
// 从类里面寻找注解,如果字段上包含枚举序列化注解的话,我们就走到
final EnumSerialize annotation = findClassFiledAnnotation(clazz, EnumSerialize.class);
if (annotation != null) {
return new EnumsSerializer();
}
return super.getObjectWriter(clazz);
}
};
fastJsonConfig.setSerializeConfig(serializeConfig);
fastJsonConverter.setFastJsonConfig(fastJsonConfig);
converters.add(fastJsonConverter);
super.configureMessageConverters(converters);
}
public static <A extends Annotation> A findClassFiledAnnotation(Class<?> clazz, Class<A> annotation) {
Field[] declaredFields = clazz.getDeclaredFields();
for (Field declaredField : declaredFields) {
A result = declaredField.getAnnotation(annotation);
if (result != null) {
return result;
}
}
return null;
}
}
配置完成
我们搞一个接口试试
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@RestController
static class TestRest {
@RequestMapping("/test")
public Object getUser() {
User user = new User();
user.setId(1L);
user.setName("张三");
user.setMobile("18000000000");
user.setSex(SexEnum.MAN.getCode());
user.setLevel(UserLevelEnum.DEFAULT.getCode());
return user;
}
}
}
启动项目,我们访问一下
GET http://localhost:8080/test
{
"id": 1,
"level": "default",
"mobile": "18000000000",
"name": "张三",
"sex": "man",
"levelName": "普通会员",
"sexName": "男"
}
一般我们返回给前端都会把user进行包装一下,那我们再试一下吧user进行包装一下看看效果吧
/**
* 功能描述 : 通用返回结果封装
*
* @author ziyear 2020-12-18 19:54
*/
@Data
public class ActionResult<T> implements Serializable {
public static final String SUCCESS_CODE = "000000";
public static final String ERROR_CODE = "999999";
/**
* 操作编码
*/
private String code;
/**
* 返回值
*/
private T data;
/**
* 操作是否成功
*/
private boolean isSuccess = true;
/**
* 操作消息
*/
private String message;
public ActionResult(String code, T data, String message) {
this.isSuccess = false;
this.code = code;
this.data = data;
this.message = message;
}
public static <T> ActionResult<T> getSuccessResult(T value) {
return new ActionResult<>(SUCCESS_CODE, value, "成功");
}
}
改一下返回值
@RestController
static class TestRest {
@RequestMapping("/test")
public Object getUser() {
User user = new User();
user.setId(1L);
user.setName("张三");
user.setMobile("18000000000");
user.setSex(SexEnum.MAN.getCode());
user.setLevel(UserLevelEnum.DEFAULT.getCode());
return ActionResult.getSuccessResult(user);
}
}
启动项目,我们再次访问一下
GET http://localhost:8080/test
{
"code": "000000",
"data": {
"id": 1,
"level": "default",
"mobile": "18000000000",
"name": "张三",
"sex": "man",
"levelName": "普通会员",
"sexName": "男"
},
"message": "成功",
"success": false
}
可以看到一样是生效的
ok到此,我们的序列化动态增加字段就讲解完了。