gpt4 book ai didi

优雅地在Java应用中实现全局枚举处理的方法

转载 作者:qq735679552 更新时间:2022-09-28 22:32:09 27 4
gpt4 key购买 nike

CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.

这篇CFSDN的博客文章优雅地在Java应用中实现全局枚举处理的方法由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.

背景描述 。

为了表达某一个属性,具备一组可选的范围,我们一般会采用两种方式。枚举类和数据字典,两者具有各自的优点。枚举类写在java代码中,方便编写相应的判断逻辑,代码可读性高,枚举类中的属性是可提前预估和确定的。数据字典,一般保存在数据库,不便于编写判断和分支逻辑,因为数据如果有所变动,那么对应的代码逻辑很有可能失效,强依赖数据库数据的正确性,数据字典中对应的属性对业务影响并不大,日常开发中常用做分类,打标签使用,属性的多少无法估计.

目前基本上没有一个很好的全局处理枚举类的方案,所以我就自己综合各方面资料写了一个.

代码 。

架构还在不断完善中,代码不一定可以跑起来,不过关于枚举的配置已经完成,大家可以阅读并参考借鉴:pretty-demo 。

前言 。

大多数公司处理枚举的时候,会自定义一个枚举转换工具类,或者在枚举类中编写一个静态方法实现integer转换枚举的方式.

比如:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 静态方法方式
public enum genderenum {
 
   // 代码略
 
   public static genderenum get( int value) {
    for (genderenum item : genderenum.values()) {
    if (value == item.getvalue()) {
      return item;
     }
    }
    return null ;
   }
}
// 工具类方式
public class enumutil {
 
  public static <e extends enumerable> e of( @nonnull class <e> classtype, int value) {
   for (e enumconstant : classtype.getenumconstants()) {
    if (value == enumconstant.getvalue()) {
     return enumconstant;
    }
   }
   return null ;
  }
 
}
 
genderenum gender = enumutil.of(genderenum. class , 1 );

这种方式很麻烦,或者需要手动编写对应的静态方法,或者需要手动调用工具类进行转换.

解决方案 。

为了方便起见,我做了一个全局枚举值转换的方案,这个方案可以实现前端通过传递int到服务端,服务端自动转换成枚举类,进行相应的业务判断之后,再以数字的形式存到数据库;我们在查数据的时候,又能将数据库的数字转换成java枚举类,在处理完对应的业务逻辑之后,将枚举和枚举类对应的展示信息一起传递到前台,前台不需要维护这个枚举类和展示信息的对应关系,同时展示信息支持国际化处理,具体的方案如下:

1、基于约定大于配置的原则,制定统一的枚举类的编写规则。大概规则如下:

  • 每个枚举类有两个字段: int value(存数据库),string key(通过key找对应的i18n文本信息)。这块需要细细讨论下,枚举值通常存数据库有存int值,也有存string值,各有利弊。存int的好处就是体积小,如果枚举的值是包含规律的,比如-1是删除,0是预处理,1是处理,2是处理完成,那么我们所有非删除数据,我们可以不使用 status in ( 0,1,2)这种方式,而转换为 status >= 0 ; 存string的话,好处就是可读性高,直接能从数据库的值中明白对应的状态,劣势就是占的体积大点。当然这些都是相对的,存int的时候,我们可以完善好注释,也具备好的可读性。如果int换成string,占的体积多的那一点,其实也可以忽略不计的。
  • 枚举枚举类需要继承统一接口,提供相应的方法供通用处理枚举时候使用。

下面是枚举接口和一个枚举示例:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public interface enumerable<e extends enumerable> {
 
  /**
   * 获取在i18n文件中对应的 key
   * @return key
   */
  @nonnull
  string getkey();
 
  /**
   * 获取最终保存到数据库的值
   * @return 值
   */
  @nonnull
  int getvalue();
 
  /**
   * 获取 key 对应的文本信息
   * @return 文本信息
   */
  @nonnull
  default string gettext() {
   return i18nmessageutil.getmessage( this .getkey(), null );
  }
}
public enum genderenum implements enumerable {
 
  /** 男 */
  male( 1 , "male" ),
 
  /** 女 */
  female( 2 , "female" );
 
  private int value;
 
  private string key;
 
  genderenum( int value, string key) {
   this .value = value;
   this .key = key;
  }
 
  @override
  public string getkey() {
   return this .key;
  }
 
  @override
  public int getvalue() {
   return this .value;
  }
}

我们要做的就是,每个我们编写的枚举类,都需要按这样的方式进行编写,按照规范定义的枚举类方便下面统一编写.

2、我们分析下controller层面的数据进和出,从而处理好枚举类和int值的转换,在spring mvc中,框架帮我们做了数据类型的转换,所以我们以 spring mvc作为切入点。前台发送到服务端的请求,一般有参数在url中和body中两种方式为主,分别以get请求和post请求配合@requestbody为代表.

【入参】get方法为代表,请求的mediatype为"application/x-www-form-urlencoded",此时将 int 转换成枚举,我们注册一个新的converter,如果spring mvc判断到一个值要转换成我们定义的枚举类对象时,调用我们设定的这个转换器 。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@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对枚举类的序列化和反序列的支持。配置如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@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 和对应展示信息的关系。所以这块我们将值和展示信息一同返给前台,减轻前台的工作压力.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 注册枚举类序列化处理类
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();
  }
}

这样关于入参和出参的配置都完成了,我们可以保证,所有前台传递到后台的 int 都会自动转换成枚举类。如果返回的数据有枚举类,枚举类也会包含值和展示文本,方便简单.

3、存储层关于枚举类的转换。这里选的 ORM 框架为 Mybatis ,但是你如果翻看官网,官网的资料只提供了两个方案,就是通过枚举隐藏字段name和ordinal的转换,没有一个通用枚举的解决方案。但是通过翻看 github 中的 issue 和 release 记录,发现在 3.4.5版本中就提供了对应的自定义枚举处理配置,这块不需要我们做过多的配置,我们直接增加 mybatis-spring-boot-starter 的依赖,直接配置对应的Yaml 文件就实现了功能.

?
1
2
3
4
5
application.yml
--
mybatis:
  configuration:
   default - enum -type-handler: github.shiyajian.pretty.config.enums.enumtypehandler
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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);
   }
 
}

这样我们就完成了从前台页面到业务代码到数据库的存储,从数据库查询到业务代码再到页面的枚举类转换。整个项目中完全不需要再手动去处理枚举类了。我们的开发流程简单了很多.

结语 。

一个好的方案并不需要多么高大上的技术,比如各种反射,各种设计模式,只要设计合理,就是简单易用,类似中国古代的榫卯.

总结 。

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我的支持.

原文链接:https://www.cnblogs.com/shiyajian/p/10363554.html 。

最后此篇关于优雅地在Java应用中实现全局枚举处理的方法的文章就讲到这里了,如果你想了解更多关于优雅地在Java应用中实现全局枚举处理的方法的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com