在大型企业应用程序中,有时我们需要将数据对象与Map
相互转换。 通常,这是特殊序列化的中间步骤。 如果可以使用某种标准,则最好使用该标准,但是很多时候,一些首席架构师所设想的体系结构,严格的环境或某些类似的原因使得使用JOOQ,Hibernate,Jackson,JAX或其他某种东西成为可能。像那样。 在这种情况下,就像几年前我遇到的那样,我们必须将对象转换为字符串或二进制的某种专有格式,朝该方向的第一步是将对象转换为Map
。
最终,转换不仅仅是简单的
Map myMap = (Map)myObject;
因为这些对象几乎从来都不是自己的地图。 转换中真正需要的是具有一个Map
,其中每个条目都对应于“ MyObject”类中的一个字段。 条目中的关键字是字段的名称,值是可能转换为Map
本身的字段的实际值。
一种解决方案是使用反射并以反射方式读取对象的字段并从中创建地图。 另一种方法是在需要转换为Map
的类中创建toMap()
方法,该方法只需使用字段名称将每个字段简单地添加到返回的Map
中即可。 这比基于反射的解决方案要快一些,并且代码要简单得多。
几年前,当我在一个实际的应用程序中遇到这个问题时,我为每个数据对象编写原始但众多的toMap()
方法感到沮丧,以至于我创建了一个简单的基于反射的工具,该工具可以针对我们想要的任何类进行操作。 它解决了问题吗? 没有。
这是一个专业的环境,在这里不仅功能很重要,而且代码质量和我的程序员的判断所得出的我的代码质量都不匹配。 他们认为基于反射的解决方案很复杂,如果它成为代码库的一部分,那么后来加入普通开发人员将无法维护它。 好吧,我不得不承认它们是正确的。 在不同的情况下,我会说开发人员必须在Java所需的水平上学习Java的反射和编程。 但是,在这种情况下,我们不是在谈论某个特定的人,而是在将来某个人加入并加入团队,可能是在我们已经离开该项目的某个时候。 假定此人是普通开发人员,这似乎很合理,因为我们对该人一无所知。 从这个意义上讲,代码质量不好,因为它太复杂了。 开发人员团队的法定人数决定,维护许多手动制作的toMap()
方法比将来寻找高级和有经验的开发人员要便宜。
老实说,我有点不愿意接受他们的决定,但是即使我仅根据我在团队中的职位就可以推翻它,我还是接受了它。 即使我不同意,但我倾向于接受团队的决定,但前提是我可以接受这些决定。 如果一项决定是危险的,可怕的并且威胁着项目的未来,那么我们必须继续讨论细节,直到达成协议。
几年后,我开始创建Java :: Geci作为副项目,可以从http://github.com/verhas/javageci下载
Java :: Geci是在Java开发生命周期的测试阶段运行的代码生成工具。 Java :: Geci中的代码生成是一个“测试”。 它运行代码生成,并且如果所有生成的代码都保留下来,则测试成功。 如果代码库中的任何内容发生了更改,导致代码生成器生成的代码与以前不同,因此源代码发生了更改,则测试将失败。 如果测试失败,则必须修复该错误并运行构建,包括再次进行测试。 在这种情况下,测试将生成新的,现已固定的代码,因此您所要做的只是再次运行构建。
在开发框架时,我创建了一些简单的生成器来生成equals()
和hashCode()
,设置器和获取器,委托生成器,最后我无法抗拒,但创建了通用的toMap()
生成器。 该生成器生成的代码将对象转换为Map
,就像我们之前讨论的一样,还有我之前未提到的fromMap()
,但显然也需要。
Java :: Geci生成器是实现Generator
接口的类。 Mapper
生成器可以扩展抽象类AbstractJavaGenerator
。 这使生成器可以抛出任何异常,从而简化了生成器开发人员的工作,并且它已经在查找从当前已处理的源生成的Java类。 生成器可以通过参数klass
来访问实际的Class
对象,同时可以通过参数source
来访问源代码, source
代表源代码并提供创建要插入其中的Java代码的方法。
第三个global
参数类似于保存源代码注释@Geci
定义的配置参数的映射。
package javax0.geci.mapper;import ...public class Mapper extends AbstractJavaGenerator {...@Overridepublic void process(Source source, Class<?> klass, CompoundParams global)throws Exception {final var gid = global.get("id");var segment = source.open(gid);generateToMap(source, klass, global);generateFromMap(source, klass, global);final var factory = global.get("factory", "new {{class}}()");final var placeHolders = Map.of("mnemonic", mnemonic(),"generatedBy", generatedAnnotation.getCanonicalName(),"class", klass.getSimpleName(),"factory", factory,"Map", "java.util.Map","HashMap", "java.util.HashMap");final var rawContent = segment.getContent();try {segment.setContent(Format.format(rawContent, placeHolders));} catch (BadSyntax badSyntax) {throw new IOException(badSyntax);}}
生成器本身仅调用这两个方法generateToMap()
和generateFromMap()
,这两个方法generateToMap()
,其名称暗示将toMap()
和fromMap()
方法引入类。
两种方法都使用Segment
类提供的源生成支持,还使用Jamal提供的模板。 还需要注意的是,这些字段是通过调用反射工具方法getAllFieldsSorted()
来收集的,该方法返回该类具有确定顺序的所有字段,而不依赖于实际的JVM供应商或版本。
private void generateToMap(Source source, Class<?> klass, CompoundParams global) throws Exception {final var fields = GeciReflectionTools.getAllFieldsSorted(klass);final var gid = global.get("id");var segment = source.open(gid);segment.write_r(getResourceString("tomap.jam"));for (final var field : fields) {final var local = GeciReflectionTools.getParameters(field, mnemonic());final var params = new CompoundParams(local, global);final var filter = params.get("filter", DEFAULTS);if (Selector.compile(filter).match(field)) {final var name = field.getName();if (hasToMap(field.getType())) {segment.write("map.put(\"%s\", %s == null ? null : %s.toMap0(cache));", field2MapKey(name), name, name);} else {segment.write("map.put(\"%s\",%s);", field2MapKey(name), name);}}}segment.write("return map;")._l("}\n\n");}
该代码仅选择由filter
表达式表示的字段。
翻译自: https://www.javacodegeeks.com/2019/06/converting-objects-map-back.html