第二章 配置

注意 本章讲述 ActFramework 的配置处理以及使用方式,关于具体配置项的说明,请参见 配置模板.

ActFramework 为应用程序开发人员提供了丰富的配置管理支持

1. 定义配置

ActFramework 读取 resources/resources/conf/ 下面的任何 .properties 文件来获得配置数据。这里是一个应用配置的例子:

  1. resources
  2. ├── conf
  3. ├── app.properties # 一般配置
  4. ├── db.properties # 数据源配置
  5. ├── social.properties # 社交网 (Oauth2) 配置
  6. ├── mail.properties # SMTP 帐号配置
  7. └── cron.properties # 作业调度配置
  8. ...

注意,配置文件的名字和个数没有限制,可以把所有的配置放到一个文件里面,虽然这样做会导致可读性降低

在配置文件中使用标准的 Java properties 方式来定义配置数据, 例如:

  1. jwt=true
  2. session.ttl=60*30
  3. cors=true
  4. cors.origin=*
  5. cors.headers=Content-Type, X-HTTP-Method-Override, X-Act-Session, X-Requested-With, Location
  6. cors.option.check=false
  7. cron.withdraw-job.db-load=0 30 13 * * ?

1.1 配置项名字中的秘密

1.1.1 值类型指示器

ActFramework 通过后缀来辨识配置项的值类型:

  • .bool, .boolean, .enabled 或者 .disabled 表示 boolean 类型配置项. 例如: secure.enabled
  • .impl 表示配置项为某种实现,可以为实例,也可以为类型名称. 例如: cache.impl
  • .dir, .home, .path 表示配置项为某种路径配置. 例如 ping.path - URL 路径; template.home - 文件系统/资源路径
  • .int, .ttl, .len, .count, .times, .size, .port 表示整型配置. 例如: cli.session.ttl, cli.session.max.int

这些类型指示器当中有些是纯粹的修饰, 包括:

  • .enabled 以及其他所有 boolean 类型的指示器,
  • .int
  • .impl

对于纯粹的修饰类型指示器, 开发人员在配置的时候可以忽略, 比如下面两套配置的作用是完全一样的:

  1. jwt=true
  2. session.secure=false
  3. cache=com.mycache.MyCacheServiceProvider
  4. req.throttle=5
  1. jwt.enabled=true
  2. session.secure.enabled=false
  3. cache.impl=com.mycache.MyCacheServiceProvider
  4. req.throttle.int=5

注意 修饰类型指示器仅对 ActFramework 内置配置有效, 不能在应用自己的配置中使用.

另一些则是实际配置项名字的一部分, 只是起到了拥有类型指示器的作用, 包括:

  • .dir
  • .home
  • .path
  • .ttl
  • .port
  • .len
  • .count
  • .size
  • .times

对于这种指示器则不可忽略, 例如下面的配置就是无效的:

  1. ping=/ping
  2. template=/templates
  3. job.pool=10

正确的配置为:

  1. ping.path=/ping
  2. template.home=/templates
  3. job.pool.size=10

1.1.2 启用与禁用

对于 .enabled 型的类型指示器, ActFramework 可以灵活处理 .enabled.disabled 之间的互换, 下面的配置方式效果都是一样的:

方式一:

  1. act.api_doc.enabled=true

方式二:

  1. act.api_doc.disabled=false

方式三:

  1. act.api_doc=true

1.2 基于环境(profile)的配置

通常来讲一个正式的项目都有多个环境的配置, 比如数据库的 URL 对与本地环境和产品环境的配置不太可能是一样的. ActFramework 提供了基于环境的配置支持. 下面假设项目会在三种不同的环境当中部署运行:

  • sit - 系统集成测试环境
  • uat - 用户测试环境
  • prod - 产品环境

针对以上三种环境, 项目的配置目录结构为:

  1. resources
  2. ├── conf
  3. ├── prod
  4. ├── app.properties - prod 应用特定配置
  5. └── db.properties - prod 数据库特定配置
  6. ├── sit
  7. ├── app.properties - sit 应用特定配置
  8. └── db.properties - sit 数据库特定配置
  9. ├── uat
  10. ├── app.properties - uat 应用特定配置
  11. └── db.properties - uat 数据库特定配置
  12. ├── app.properties - 公共应用配置文件
  13. ├── db.properties - 公共数据库配置文件
  14. └── cron.properties - 公共作业调度配置文件
  15. ...

对于上面的配置结构, ActFramework 首先加载公共配置文件, 然后依照当前的环境设定加载环境特定配置文件. 如果环境配置文件中有配置项和公共配置文件中的配置项冲突, 则使用环境配置文件的配置设定来覆盖公共配置文件中的设定.

1.2.1 应用运行环境设定

上面我们讲到 ActFramework 根据当前运行环境加载配置文件, 带出来一个问题, 如何设定运行环境. 答案是在启动应用的时候通过 JVM 参数设定应用的运行环境:

  1. java ... -Dprofile=uat ...

其中 -Dprofile=uat 设定应用的运行环境为 uat.

如果采用 ActFramework 推荐的项目结构, 应用部署的时候会生成 run 脚本, 可以通过下面的方式来设定运行环境:

  1. ./run -p uat

或者

  1. ./run --profile uat

2. 使用配置

在 ActFramework 应用中可以通过多种方式来使用配置. 假设我们定义了如下配置:

  1. myconf.foo.bar=100

下面我们会介绍如何在应用中使用 myconf.foo.bar 的配置:

2.1 从 AppConfig 实例获取配置

ActFramework 使用 AppConfig 实例来管理所有的配置项, AppConfig 实例可以通过 Act.appConfig() 来获得:

  1. @UrlContext("/conf")
  2. public class ConfTest1 {
  3. @GetAction("pull")
  4. public int pull() {
  5. AppConfig conf = Act.appConfig();
  6. return $.convert(conf.get("myconf.foo.bar")).toInt();
  7. }
  8. }

小贴士 上面的例子中我们用到了 OSGL 工具库的类型转换将 conf.get("myconf.foo.bar") 的值从字串转换为整型. 更多关于 OSGL 类型转换的信息可以参考 TBD

2.2 注入配置 - 方式一

通过 ActFramework 的依赖注入框架, 应用程序可以直接将配置注入到字段中:

  1. @UrlContext("/conf")
  2. public class ConfTest2 {
  3. @Configuration("myconf.foo.bar")
  4. private int fooBar;
  5. @GetAction("inject")
  6. public int inject() {
  7. return this.fooBar;
  8. }
  9. }

对于请求响应方法, 也可以直接注入到方法参数列表:

  1. @UrlContext("/conf")
  2. public class ConfTest3 {
  3. @GetAction("inject_param")
  4. public int injectParam(@Configuration("myconf.foo.bar") int fooBar) {
  5. return fooBar;
  6. }
  7. }

2.3 注入配置 - 方式二

除了通过标准依赖注入框架对字段和方法参数注入, ActFramework 还支持使用 @AutoConfig 注解将配置注入到静态字段中:

  1. @UrlContext("/conf")
  2. @AutoConfig("myconf") // 注意: 这里 `myconf` 指定配置的前缀, 如果没有则默认为 `app`
  3. public class ConfTest4 {
  4. private static final Const<Integer> FOO_BAR = $.constant();
  5. @GetAction("auto_conf")
  6. public int autoConf() {
  7. return FOO_BAR.get();
  8. }
  9. }

关于 AutoConfig 的注入方式, 有几点值得注意:

  1. 待静态字段的类必须有 @AutoConfig 注解. @AutoConfig 注解可以指定配置前缀, 在上面的例子中, 配置前缀为 myconf, 如果不指定, 则配置前缀默认为 app. ActFramework 使用配置前缀在配置中寻找和静态字段名对应的配置项, 在我们的例子中, FOO_BAR 对应了 myconf.foo.bar.

  2. 待注入配置的字段必须是静态的, 无需 public, 也无需加载任何注解.

  3. 待注入字段的名字通过以下规则匹配配置项:

    • 所有的下划线被替换为 .: FOO_BAR -> FOO.BAR
    • 所有的大写转换为小写: FOO.BAR -> foo.bar
    • . 和前缀相连: foo.bar -> myconf.foo.bar
  4. 如果静态字段需要标注为 final, 则应该使用 org.osgl.util.Const 来包裹注入配置类型, 这样 ActFramework (通过反射) 将配置值注入到 Const 常量中, 而应用则通过 Const.get() 来获得配置值, 如同我们上面的例子. 这样的方式可以在保持 final 语义的前提下最大程度地简化开发

2.4 注入复杂类型

ActFramework 支持复杂类型的注入,包括 Map, List 和服务实现

2.4.1 注入 Map

假设有下面的配置:

  1. myconf.map.demo.one=1
  2. myconf.map.demo.two=2

应用可以这样来注入一个 Map 类型的配置变量:

  1. @GetAction("map")
  2. @ResponseContentType(H.MediaType.JSON)
  3. public Object barMap(@Configuration("myconf.map.demo") Map<String, Integer> barMap) {
  4. return barMap;
  5. }

注意 本文中所有方法参数的配置注入均可以用于字段, 以下不再赘述

发送请求到 /conf/map 可以得到下面的响应:

  1. {
  2. "one": 1,
  3. "two": 2,
  4. }

如果按照下面的方式来注入:

  1. @GetAction("map2")
  2. @ResponseContentType(H.MediaType.JSON)
  3. public Object barMap2(@Configuration("myconf.map") Map<String, Integer> fooMap) {
  4. return fooMap;
  5. }

发送请求到 /conf/map2 得到的响应变为:

  1. {
  2. "demo.one": 1,
  3. "demo.two": 2,
  4. }

2.4.2 注入 List

假设有下面的配置:

  1. myconf.list.demo=1,2,3

应用可以注入一个整型数组:

  1. @GetAction("list")
  2. @ResponseContentType(H.MediaType.JSON)
  3. public int[] listDemo(@Configuration("myconf.list.demo") int[] list) {
  4. return list;
  5. }

或者一个整形 List

  1. @GetAction("list2")
  2. @ResponseContentType(H.MediaType.JSON)
  3. public List<Integer> listDemo2(@Configuration("myconf.list.demo") List<Integer> list) {
  4. return list;
  5. }

发送请求到 /conf/list/conf/list2 获得相同的响应:

  1. [
  2. 1,
  3. 2,
  4. 3
  5. ]

如果注入字串数组或者列表, 响应会改变:

  1. @GetAction("list3")
  2. @ResponseContentType(H.MediaType.JSON)
  3. public List<String> list3(@Configuration("myconf.list.demo") List<String> list) {
  4. return list;
  5. }

响应:

  1. [
  2. "1",
  3. "2",
  4. "3"
  5. ]

2.4.3 注入接口实现

假设我们定义了一下接口:

  1. public interface GreetingService {
  2. String greet();
  3. default String getName() {
  4. return greet() + " service";
  5. }
  6. }

以及若干接口的实现类:

  1. public class HelloService implements GreetingService {
  2. @Override
  3. public String greet() {
  4. return "Hello";
  5. }
  6. }

  1. public class NiHaoService implements GreetingService {
  2. @Override
  3. public String greet() {
  4. return "NiHao";
  5. }
  6. }

应用可以为不同的场景配置不同的接口实现:

  1. greet.default=demo.HelloService
  2. greet.west=demo.HelloService
  3. greet.east=demo.NiHaoService

下面演示如何在应用代码中使用接口实现配置:

  1. @UrlContext("/conf")
  2. public class ConfTest {
  3. @Configuration("greet.default")
  4. private GreetingService defaultService;
  5. @Configuration("greet.west")
  6. private GreetingService westService;
  7. @Configuration("greet.east")
  8. private GreetingService eastService;
  9. @GetAction("greet")
  10. public String greetDefault() {
  11. return defaultService.greet();
  12. }
  13. @GetAction("greet/west")
  14. public String greetWest() {
  15. return westService.greet();
  16. }
  17. @GetAction("greet/east")
  18. public String greetEast() {
  19. return eastService.greet();
  20. }
  21. }

发送请求到 /conf/greet/conf/greet/west 都获得 Hello 的响应. 而发送请求到 /conf/greet/east 则获得 NiHao 的响应.

应用也可以注入接口实现到一个 Map 中:

  1. @UrlContext("/conf")
  2. public class ConfTest {
  3. @Configuration("greet")
  4. private Map<String, GreetingService> greetingServiceMap;
  5. @GetAction("greet/all")
  6. public Object allGreetings() {
  7. return greetingServiceMap;
  8. }
  9. }

发送请求到 /conf/greet/all 获得下面的响应:

  1. {
  2. "default": {
  3. "name": "Hello service"
  4. },
  5. "scenario2": {
  6. "name": "NiHao service"
  7. },
  8. "scenario1": {
  9. "name": "Hello service"
  10. }
  11. }

注入接口实现到一个 List 的情况:

配置:

  1. greets=act.HelloService,demo.NiHaoService

Java 代码:

  1. @UrlContext("/conf")
  2. @ResponseContentType(H.MediaType.JSON)
  3. public class ConfTest {
  4. @Configuration("greets")
  5. private List<GreetingService> greetingServices;
  6. @GetAction("greet/list")
  7. public Object greetingList() {
  8. return greetingServices;
  9. }
  10. }

发送请求到 /conf/greet/list 会得到一下响应:

  1. [
  2. {
  3. "name": "Hello service"
  4. },
  5. {
  6. "name": "NiHao service"
  7. }
  8. ]

3. 加载三方配置文件

如果应用引入的第三方库需要特殊的配置, 往往需要提供配置文件或者 InputStream 给三方库, 这些配置有时候并不是 .properties 文件,而是采用其他格式, 比如 .json, .yaml 等等, 对于这种情况, ActFramework 为应用提供了 @act.inject.util.LoadConfig 注解:

假设应用需要一个 libx.json 的配置文件, 下面是获得这个配置文件的办法:

  1. public class ConfTest {
  2. @LoadConfig("libx.json")
  3. private String libxContent;
  4. @LoadConfig("libx.json")
  5. private File libxFile;
  6. @LoadConfig("libx.json")
  7. private URL libxUrl;
  8. @LoadConfig("libx.json")
  9. private InputStream libxInputStream;
  10. }

使用 @LoadConfig 和应用自行采用 Class.getResource 来加载的区别在于:

  1. @LoadConfig 更加简单, 而且可以加载配置文件到不同类型的字段,如上例所示.
  2. @LoadConfig 在不同的运行环境(profile)下可以加载环境目录下的配置文件

4. pom 文件的配置

TBD

总结

本篇详细讲述了如何在 ActFramework 应用中使用 ActFramework 提供的配置管理工具, 包括

  • 配置值类型指示器
  • 基于环境的配置
  • 使用 AppConfig 来获取配置
  • 注入配置到字段和请求处理方法参数
  • 注入配置到静态字段
  • 处理配置中的复杂类型
  • 加载三方配置文件
  • pom 文件配置

本文中的代码可以从下面的代码库中获得:

https://github.com/greenlaw110/act-doc-configuration