存储和访问 Java 应用程序数据的最佳方法

Best approach storing and accessing Java application data

我正在进行一个大规模的重构项目,该代码有一个 5000 行的主代码 class,它被注入到所有内容中,存储所有内容并具有所有通用代码。

我不是分析和设计方面的专家,但我已经尽我所能将事物分离出来,并且通过重构依赖于主要 class 使用我创建的新 classes。

有些类型的数据会在应用程序启动时初始化,并在应用程序的整个生命周期中被几乎所有内容访问。例如,有一个 Config class 包含数百个参数。

我采用的方法是创建几个单例,其中两个最重要的是 GUIData 和 ClientData。 GUIData 包含对应用程序主机的引用,clientdata 维护对配置和其他类似 classes 的引用。

这使我可以从代码中的任何位置调用 ClientData.getInstance().getConfig().getParam("param"),但我认为这不是最佳方法。

我考虑了单独的静态 classes 而不是这些包含 classes 实例的数据单例,但某些 classes 确实需要构造函数。

我在谷歌上断断续续地搜索了一个星期,试图找到一种更好的方法来做到这一点,但不知何故,我总是在讨论数据库缓存的线程上结束

抱歉,问题有点含糊,您要存储程序其他部分使用的配置或缓存对象吗?

由于您有 100 多个参数,请先将配置拆分为可管理的块

1) 将您的配置参数拆分为具有 1:1 对应于简单属性文件的逻辑块 - 这需要一些时间

2) 这些 属性 文件必须外部化,以便您可以随时更改它们,确保通过环境变量将基本位置传递给程序

3) 编写一个实用程序 class(单例)包装 Apache commons configuration 来保存您的配置。 (从基本位置读取 *.properties 并将属性合并到一个配置对象中)这必须在启动任何线程之前完成。

4) 使用 config.getXXXX() 方法在您的代码中引用配置参数

Apache 公共配置还可以在文件系统上的属性文件更改时重新加载配置。

完成后,使用像 Spring 或 Guice 这样的 DI 容器来缓存配置的对象。

如果您需要的只是字符串 属性 值,您甚至不需要 class - 已经为您准备了一个全球设施:System.getProperties()

您需要做的就是首先在启动时加载 属性 值:

System.setProperty("myKey", "myValue"); // see below how load properties from a file

然后在您的代码中的任意位置阅读它:

String myValue = System.getProperty("myKey");

String myValue = System.getProperty("myKey", "my desired default");

如果您的容器不支持 属性 开箱即用加载,则从如下所示的外部文件加载属性:

key1=value
key2=some other value
etc...

您可以使用此代码:

Files.lines(Paths.get("path/to/file"))
  .filter(line -> !line.startsWith("#") || !line.contains("=")) // ignore comment/blank
  .map(line -> line.split("=", 2)) // split into key/value
  .forEach(split -> System.setProperty(split[0], split[1])); // load as property

您可以使用 Java Properties class 实用程序,基本上它是一个 HashTable 参考:https://docs.oracle.com/javase/7/docs/api/java/util/Properties.html

您创建一个文件 fileName.properties 并将您的数据存储在键值对中,例如:

username=your name
port=8080

然后将其加载到 Properties 对象中并获取如下数据:

Properties prop = new Properties();
load the file...
String userName = prop.getProperty("username")
String port = prop.getProperty("port")// you can parse it to int if needed

我的建议是为每种类型的配置创建一个 属性 文件,例如:

  1. clientData.properties
  2. appConfig.properties

你可以按照这个简单的教程 http://www.mkyong.com/java/java-properties-file-examples/

不可变(配置)实例提供 "thread-safe application-wide data access"。 Typesafe 的 config(正如 Brian Kent 在评论中所建议的那样)正是这样做的。 请注意,这不涉及静态 类 或单例。静态 类 和单例现在可以满足您的目的, 但它们将来可能会很麻烦。它们当然可以派上用场,但请尽量限制它们的使用。

必须在读取和解析配置数据后进行初始化。它通常在应用程序启动时完成,在其他处理线程启动之前。初始化必须尽可能多地验证配置数据,以便快速失败并在配置数据不好时终止程序。

将大量配置数据捆绑在一起可以创建 "hidden lines of communication"。例如。您更新了一个值,但应用程序失败了,因为它还需要更新其他值。将所有配置数据放在一个文件中并从那里加载它是非常好的,但是您的应用程序(具有数百个配置选项)应该将配置数据分成多个集合,以供应用程序的不同部分使用。这改善了隔离,有助于单元测试,并使得将来可以更改应用程序而不会遇到太多令人讨厌的意外。

有两种方式使用一组配置数据:

  1. 从对象内部调用单例 Settings.getInstance().getConfigForThisModule()
  2. 通过构造函数或通过 setConfig(ConfigForThisModule config).
  3. 为每个使用配置数据的对象提供配置数据

第一种方法依赖于不调用 Settings.getInstance().getConfigForACompletelyUnrelatedModule() 的约定,这可能是一个弱点。第二种方法更符合 "dependency injection" 并且可能更适合未来。 您可以在重构时混合使用这两种方法,只需确保保持一致(例如,仅对应用程序所有部分中使用的配置数据使用单例方法)。

为了进一步改进使用配置数据的设计,请牢记以下(可能的)未来功能需求:更新配置文件时,重新加载配置数据并在应用程序中使用。大多数日志记录框架设法在不影响多线程应用程序性能的情况下支持此功能要求。除其他外,它需要您的应用程序满足以下条件:

  • 如果新的配置数据不好,程序不会终止,而是记录一个错误,旧的配置数据继续使用。您的初始化过程将需要处理 "load at fresh start" 和 "reload" 两种情况。最主要的是,您的初始化过程需要可重复使用,并且不应影响应用程序的其他 (运行) 部分(再次隔离)。
  • 长寿命对象可能不会保留配置数据的本地副本或对 ConfigForThisModule 实例的引用,而是 Settings.getInstance()...(或其他一些可以 return更新的实例)应该定期调用。
  • 用新配置替换旧配置可能不会导致错误。从技术上讲,替换配置就像用 return 编辑 Settings.getInstance()... 的新配置实例更新 AtomicReference 一样简单。但这也是测试配置数据集隔离的地方:在一个模块中使用旧集,在另一个模块中同时使用新集应该没有问题。

配置数据可以看作是一种"global state"。考虑到这一点,在以下两个问题中讨论了关于做什么和避免什么的进一步设计要点(部分公然复制到这个答案):