8

在 Spring 中通过配置类注入配置文件的值

 3 years ago
source link: https://www.boris1993.com/projects/java/coding-tips/spring-injecting-property-value-with-configuration-bean.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

我们在开发过程中,为了保证项目的灵活性,经常会选择将一些值放在配置文件中,并在代码中将它注入并使用。将值注入代码最常见的一种方法,则是使用 @Value() 注解搭配 SpEL 直接注入我们需要的属性。但是鲁迅先生有云:从来如此,便对吗?这里,我想介绍一个我个人认为更好的实践:通过配置类来注入属性的值。

旧的做法有什么问题

假设我们现在有这样一个 application.yml,其中 credentials 部分是我自定义的一个属性:

1
2
3
4
5
server:
port: 9999

credentials:
token: A_VERY_SECRET_TOKEN

然后,我们会在用到它的地方,直接通过 @Value 注解把它注入进来,就像这样:

1
2
3
4
5
6
@Component
public class SomeService {

@Value("${credientials.token}")
private String token;
}

好像没什么问题对吧,直接用表达式把值拿进来,然后该怎么用就怎么用。但是我不知道你们有没有注意过,这种做法其实既不利于后期重构,也不利于为代码生成好的文档。

比如说,这个值在多个类中都有被引用,但某一天,我们觉得这个名字不够直观,我们想改成 contactServiceAppToken,那么我们就只能先改掉属性的名字,然后在代码里面全文替换,把 credentials.token 批量替换成 credentials.contactServiceAppToken。我不知道你们是怎么想的,我每次做这种文本批量替换都很慌,生怕一个没看见而改掉了不应该改的东西。

而对于生成文档,我们都知道,在 Java 代码上面我们可以使用 JavaDoc 来编写文档,阐明这个类的作用等等。而对于 YAML 文件,则没有类似的东西,我们只能在属性上面写普通的注释。可是,大篇幅的注释又有可能会影响 YAML 文件的可读性,更不用说有谁会在看代码的时候专程去看 YAML 文件?

所以,我会建议团队使用配置类,也就是本文下面要讲的这个东西,来管理和注入这些自定义的属性。

首先,我们需要创建一个配置类,来给这些属性找一个家。

1
2
3
4
5
6
7
8
9
// 这个注解是重点,说明我们要把配置文件的 credentials 部分映射到这里
@ConfigurationProperties("credentials")
public class Credentials {
// 属性名与变量名保持一致即可,Spring会自动处理两者的绑定关系
// 同时,Spring会自动完成不同命名方式的转换,比如 kebab-case 变成 camelCase
private String token;

//getters and setters
}

接下来,在要使用这些属性的地方,把这个配置类注入,然后直接 get 属性的值,就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public class SomeService {

private final Credentials credentials;

public SomeService(Credentials credentials) {
this.credentials = credentials;
}

public void someMethod() {
final String token = credentials.getToken();

// ......
}
}

与直接取值的方法比较起来,使用配置类有这么几个优点:

  • 如果在重构的时候要改变属性名,那么我们只需要修改配置文件里面的属性名,和配置类里面的属性名。当然要记得使用 IDE 里面的重构功能改名,这样 IDE 会自动分析这个属性的引用,并自动改正过来。
  • 使用配置类还可以方便我们生成文档。如果直接在配置文件里面写文档,一方面是不一定易读,另一方面,也不是所有人都会想到在配置文件里面还有文档。而使用配置类的话,我们只需要在类上面加上 JavaDoc 就好了。
  • 而且,我们还不需要担心打错字,导致 @Value 注入失败而使得应用起不来。虽然这不是什么大问题,改正就行了,但毕竟还是麻烦。

多层属性怎么办

上面只是演示了只有一级子属性的情况,如果下面包含了多层属性,那配置类应该怎么写呢?

假设现在配置文件变成了这样:

1
2
3
4
5
6
7
8
9
10
11
12
credentials:
token:
contact: TOKEN_FOR_CONTACT_API
user: TOKEN_FOR_USER_API
oauth:
client-id: CLIENT_ID
client-secret: CLIENT_SECRET

endpoint:
contact:
v1: URL_FOR_CONTACT_API_VERSION_1
v2: URL_FOR_CONTACT_API_VERSION_2

对于 credentials 部分,因为里面子属性的名字大致是确定的,我们用一个内部类就可以搞定(其实写在单独的类里面也可以,只是我不喜欢那么做)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@ConfigurationProperties("credentials")
public class Credentials {
private Token token;
private Oauth oauth;

//getters and setters

public static class Token {
private String contact;
private String user;

//getters and setters
}

public static class Oauth {
private String clientId;
private String clientSecret;

//getters and setters
}
}

取值的时候呢,逐层取到就好了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public class SomeService {

private final Credentials credentials;

public SomeService(Credentials credentials) {
this.credentials = credentials;
}

public void someMethod() {
final String contactApiToken = credentials.getToken().getContact();

// ......
}
}

但是对于 endpoint 部分,因为里面的值是某个 API 各个版本的 URL,考虑到 API 还有可能会有新版本,每加一个版本都要再改配置类有点麻烦,所以我们可以直接用一个 Map 来存放。

1
2
3
4
5
6
@ConfigurationProperties("endpoint")
public class Endpoint {
private Map<String, String> contact;

//getters and setters
}

在取值的时候,就还是一样的套路,注入这个配置类,然后从 Map 中取值就行了。Map 的 key 就是属性名,比如 v1,值就是属性的值。当然这样做的话,就要处理一下取到 null 的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Component
public class SomeService {

private final Endpoint endpoint;

public SomeService(Endpoint endpoint) {
this.endpoint = endpoint;
}

public void someMethod() {
final String contactV1Url = endpoint.getContact().get("v1");

if (contactV1Url == null) {
// handle it here
}

// ......
}
}

给配置文件加上自动提示

其实,Configuration properties 配置类除了可以方便我们管理属性之外,他还可以搭配 spring-boot-configuration-processor 来实现配置文件的自动提示,当然这也需要 IDE 的支持。

pom.xml 中加入如下依赖:

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

然后在编写完配置类之后,执行一下 build 操作,或者 mvn compile,来让它帮我们生成一个 additional-spring-configuration-metadata.json 文件。有了这个文件之后,IDE 就会参照它在配置文件里面给我们提供自动提示。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK