每个枚举类有两个字段: int value(存数据库),string key(通过key找对应的i18n文本信息)。这块需要细细讨论下,枚举值通常存数据库有存int值,也有存string值,各有利弊。存int的好处就是体积小,如果枚举的值是包含规律的,比如-1是删除,0是预处理,1是处理,2是处理完成,那么我们所有非删除数据,我们可以不使用 status in ( 0,1,2)这种方式,而转换为 status >= 0 ; 存string的话,好处就是可读性高,直接能从数据库的值中明白对应的状态,劣势就是占的体积大点。当然这些都是相对的,存int的时候,我们可以完善好注释,也具备好的可读性。如果int换成string,占的体积多的那一点,其实也可以忽略不计的。
我们要做的就是,每个我们编写的枚举类,都需要按这样的方式进行编写,按照规范定义的枚举类方便下面统一编写。
2、我们分析下controller层面的数据进和出,从而处理好枚举类和int值的转换,在spring mvc中,框架帮我们做了数据类型的转换,所以我们以 spring mvc作为切入点。前台发送到服务端的请求,一般有参数在url中和body中两种方式为主,分别以get请求和post请求配合@requestbody为代表。
【入参】get方法为代表,请求的mediatype为"application/x-www-form-urlencoded",此时将 int 转换成枚举,我们注册一个新的converter,如果spring mvc判断到一个值要转换成我们定义的枚举类对象时,调用我们设定的这个转换器
@configuration
public class mvcconfiguration implements webmvcconfigurer, webbindinginitializer {
/**
* [get]请求中,将int值转换成枚举类
* @param registry
*/
@override
public void addformatters(formatterregistry registry) {
registry.addconverterfactory(new enumconverterfactory());
}
}
public class enumconverterfactory implements converterfactory<string, enumerable> {
private final map<class, converter> convertercache = new weakhashmap<>();
@override
@suppresswarnings({"rawtypes", "unchecked"})
public <t extends enumerable> converter<string, t> getconverter(@nonnull class<t> targettype) {
return convertercache.computeifabsent(targettype,
k -> convertercache.put(k, new enumconverter(k))
);
}
protected class enumconverter<t extends enumerable> implements converter<integer, t> {
private final class<t> enumtype;
public enumconverter(@nonnull class<t> enumtype) {
this.enumtype = enumtype;
}
@override
public t convert(@nonnull integer value) {
return enumutil.of(this.enumtype, value);
}
}
}
【入参】post为代表,将 int 转换成枚举。这块我们和前台达成一个约定( ajax中applicationtype),所有在body中的数据必须为json格式。同样后台@requestbody对应的参数的请求的mediatype为"application/json",spring mvc中对于json格式的数据,默认使用 jackson2httpmessageconverter。在jackson转换成实体时候,有@jsoncreator和@jsonvalue两个注解可以用,但是感觉还是有点麻烦。为了统一处理,我们需要修改jackson对枚举类的序列化和反序列的支持。配置如下:
@configuration
@slf4j
public class jacksonconfiguration {
/**
* jackson的转换器
* @return
*/
@bean
@primary
@suppresswarnings({"rawtypes", "unchecked"})
public mappingjackson2httpmessageconverter mappingjacksonhttpmessageconverter() {
final mappingjackson2httpmessageconverter converter = new mappingjackson2httpmessageconverter();
objectmapper objectmapper = converter.getobjectmapper();
// include.non_empty 属性为 空("") 或者为 null 都不序列化,则返回的json是没有这个字段的。这样对移动端会更省流量
objectmapper.setserializationinclusion(jsoninclude.include.non_empty);
// 反序列化时候,遇到多余的字段不失败,忽略
objectmapper.configure(deserializationfeature.fail_on_unknown_properties, false);
// 允许出现特殊字符和转义符
objectmapper.configure(jsonparser.feature.allow_unquoted_control_chars, true);
// 允许出现单引号
objectmapper.configure(jsonparser.feature.allow_single_quotes, true);
simplemodule customermodule = new simplemodule();
customermodule.adddeserializer(string.class, new stringtrimdeserializer(string.class));
customermodule.adddeserializer(enumerable.class, new enumdeserializer(enumerable.class));
customermodule.addserializer(enumerable.class, new enumserializer(enumerable.class));
objectmapper.registermodule(customermodule);
converter.setsupportedmediatypes(immutablelist.of(mediatype.text_html, mediatype.application_json));
return converter;
}
}
public class enumdeserializer<e extends enumerable> extends stddeserializer<e> {
private class<e> enumtype;
public enumdeserializer(@nonnull class<e> enumtype) {
super(enumtype);
this.enumtype = enumtype;
}
@override
public e deserialize(jsonparser jsonparser, deserializationcontext deserializationcontext) throws ioexception {
return enumutil.of(this.enumtype, jsonparser.getintvalue());
}
}
【出参】当我们查询出结果,要展示给前台的时候,我们会对结果集增加@responsebody注解,这时候会调用jackson的序列化方法,所以我们增加了枚举类的序列配置。如果我们只简单的将枚举转换成 int 给前台,那么前台需要维护这个枚举类的 int 和对应展示信息的关系。所以这块我们将值和展示信息一同返给前台,减轻前台的工作压力。
// 注册枚举类序列化处理类
customermodule.addserializer(enumerable.class, new enumserializer(enumerable.class));
public class enumserializer extends stdserializer<enumerable> {
public enumserializer(@nonnull class<enumerable> type) {
super(type);
}
@override
public void serialize(enumerable enumerable, jsongenerator jsongenerator, serializerprovider serializerprovider) throws ioexception {
jsongenerator.writestartobject();
jsongenerator.writenumberfield("value", enumerable.getvalue());
jsongenerator.writestringfield("text", enumerable.gettext());
jsongenerator.writeendobject();
}
}
public class enumtypehandler<e extends enumerable> extends basetypehandler<e> {
private class<e> enumtype;
public enumtypehandler() { /* instance */ }
public enumtypehandler(@nonnull class<e> enumtype) {
this.enumtype = enumtype;
}
@override
public void setnonnullparameter(preparedstatement preparedstatement, int i, e e, jdbctype jdbctype) throws sqlexception {
preparedstatement.setint(i, e.getvalue());
}
@override
public e getnullableresult(resultset rs, string columnname) throws sqlexception {
int value = rs.getint(columnname);
return rs.wasnull() ? null : enumutil.of(this.enumtype, value);
}
@override
public e getnullableresult(resultset rs, int columnindex) throws sqlexception {
int value = rs.getint(columnindex);
return rs.wasnull() ? null : enumutil.of(this.enumtype, value);
}
@override
public e getnullableresult(callablestatement cs, int columnindex) throws sqlexception {
int value = cs.getint(columnindex);
return cs.wasnull() ? null : enumutil.of(this.enumtype, value);
}
}