다른 글에서 소개했던 것처럼, 스프링 부트의 application.yaml
을 이용해 Properties를 관리하는 방법은 다양한 애너테이션을 이용한 여러가지 방법이 있다. 그 중에서 내가 생각하기에 가장 객체지향스러운 방법을 오늘 포스트에서 다뤄보려 한다.
@ConfigurationProperties의 단점
@ConfigurationProperties
는 YAML의 계층형을 따라서 자동으로 카멜 케이스의 필드와 매칭되는 설정 값을 찾아 매핑을 진행한다. 스프링 프레임워크가 아닌 스프링 부트에서 지원을 하는 애너테이션이니 주의가 필요하다. 문자열을 이용한 하드코딩이 아니기 때문에 @Value
보단 더 선호되는 애너테이션이라고 생각이 드는데, setter 메서드가 반드시 필요하다는 것이 아쉽다. 불변으로 생성할 수 없기 때문이다.
@ConfigurationProperties("jwt")
public class JwtConfig {
private String secretKey;
private String expirationMinutes;
@PostConstruct
private void print() {
System.out.println("Secret Key: " + secretKey);
System.out.println("Expiration Minutes: " + expirationMinutes);
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
public void setExpirationMinutes(String expirationMinutes) {
this.expirationMinutes = expirationMinutes;
}
}
콘솔:
Secret Key: secret-key
Expiration Minutes: 15
@ConstructorBinding을 이용해 불변으로 Properties 관리
위와 같은 단점 또한 @ConstructorBinding
을 이용해 해소될 수 있었다.
package org.springframework.boot.context.properties.bind;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation that can be used to indicate which constructor to use when binding
* configuration properties using constructor arguments rather than by calling setters. A
* single parameterized constructor implicitly indicates that constructor binding should
* be used unless the constructor is annotated with `@Autowired`.
*
* @author Phillip Webb
* @since 3.0.0
*/
@Target({ ElementType.CONSTRUCTOR, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConstructorBinding {
}
간단하게 살펴보자면, 설정을 주입할 때 setter를 통한 주입이 아닌 생성자로 주입을 시켜주는 애너테이션이다. 위에 언급했던 아쉬운 점을 정확하게 해소시켜 줄 애너테이션이다. 나와 같은 아쉬움을 느끼는 사람이 많았던건지, 스프링 부트 3버전 이후로 @ConstructorBinding
이 지원됐다.
@ConfigurationProperties("jwt")
public class JwtConfig {
private final String secretKey;
private final String expirationMinutes;
@ConstructorBinding
public JwtConfig(String secretKey, String expirationMinutes) {
this.secretKey = secretKey;
this.expirationMinutes = expirationMinutes;
}
@PostConstruct
private void print() {
System.out.println("Secret Key: " + secretKey);
System.out.println("Expiration Minutes: " + expirationMinutes);
}
}
콘솔:
Secret Key: secret-key
Expiration Minutes: 15
@ConstructorBinding
을 이용해 설정 클래스의 필드를 불변으로 관리할 수 있게 되었다.
@ConfigurationProperties
의 prefix를 넣어준 것을 제외하면 문자열을 이용한 하드코딩이 없고 깔끔하게 불변으로 필드를 관리하는 형식이 되었으니 제일 마음에 드는 방법이라고 생각한다.
Properties 계층형 구조
YAML을 이용하면서 Properties도 보기 쉽게 계층형으로 구조를 만들 수 있었다. 그렇다면 객체에 Properties를 주입할 때에도 구조 계층을 그대로 사용할 수 있지 않을까? 더욱 객체지향스러운 사용이 예상됐다!
file:
resources:
path: "src/main/resources/"
build-path: "build/resources/main"
domains:
customer:
file-name: customer.json
csv-file-name: blacklist.csv
voucher:
file-name: voucher.json
jwt:
secret-key: secret-key
expiration-minutes: 15
이렇게 YAML로 정의된 Properties 중 file 부분을 보면, 소스 코드를 실행 했을 때의 경로를 읽기 위한 path
와 경로 아래에 바우처 정보와 고객 정보를 저장할 파일 이름을 정해두었고, 각각 domains
아래 저장되는 종류 별로 지정해 두었다.
파일이 저장될 디렉토리 구조는 아래와 같다.
├── build
│ └── resources
│ ├── blacklist.csv
│ ├── customer.json
│ └── voucher.json
└── src
├── main
│ └── resources
│ ├── blacklist.csv
│ ├── customer.json
│ └── voucher.json
└── test
소스코드를 IDE 혹은 java로 실행할 땐 src 디렉토리 밑의 파일들을 읽어야하고, Gradle로 빌드된 프로젝트의 jar파일 실행이거나 빌드된 적이 있는 프로젝트를 실행시킬 때는 build 디렉토리 밑의 파일들을 읽게끔 하려고 한다. 이런 디렉토리 계층을 YAML에서 보기 쉽게 계층 구조를 유지했고, 소스코드의 설정 클래스 또한 계층을 유지해보겠다.
최상위 설정 클래스 파일 FileConfig
:
@ConfigurationProperties("file")
public class FileConfig {
private final Resources resources;
private final Domains domains;
@ConstructorBinding
public FileConfig(Resources resources, Domains domains) {
this.resources = resources;
this.domains = domains;
}
@PostConstruct
public void print() {
System.out.println("Resources:");
System.out.println("Path: " + resources.getPath());
System.out.println("Build Path: " + resources.getBuildPath());
System.out.println("Domains:");
System.out.println("Customer:");
System.out.println("File Name: " + domains.getCustomer().getFileName());
System.out.println("CSV File Name: " + domains.getCustomer().getCsvFileName());
System.out.println("Voucher:");
System.out.println("File Name: " + domains.getVoucher().getFileName());
}
}
Resources:
public class Resources {
private final String path;
private final String buildPath;
public Resources(String path, String buildPath) {
this.path = path;
this.buildPath = buildPath;
}
public String getPath() {
return path;
}
public String getBuildPath() {
return buildPath;
}
}
Domains:
public class Domains {
private final Customer customer;
private final Voucher voucher;
public Domains(Customer customer, Voucher voucher) {
this.customer = customer;
this.voucher = voucher;
}
public Customer getCustomer() {
return customer;
}
public Voucher getVoucher() {
return voucher;
}
}
Customer:
public class Customer {
private final String fileName;
private final String csvFileName;
public Customer(String fileName, String csvFileName) {
this.fileName = fileName;
this.csvFileName = csvFileName;
}
public String getFileName() {
return fileName;
}
public String getCsvFileName() {
return csvFileName;
}
}
Voucher:
public class Voucher {
private final String fileName;
public Voucher(String fileName) {
this.fileName = fileName;
}
public String getFileName() {
return fileName;
}
}
최상위 설정 클래스 파일인 FileConfig
에서는 @ConfigurationProperties
와 @ConstructorBinding
이 붙어야 하지만, 설정을 주입 받을 하위 클래스들은 필드와 주입받을 생성자, 그리고 getter로 이루어진 POJO로 나타낼 수 있었다.
콘솔:
Resources:
Path: build/resources/main/
Domains:
Customer:
File Name: customer.json
CSV File Name: blacklist.csv
Voucher:
File Name: voucher.json
의도한대로 잘 작동하는 것을 알 수 있다!
객체지향스러운 Properties 활용
계층 구조를 그대로 유지한 채로 설정 클래스들을 위처럼 구성할 수 있지만, 개선점이 몇가지 있다. 우선 자바 14부터 지원되었던 record를 이용해 간단한 POJO의 보일러 플레이팅 코드를 줄여봤다.
public record Resources(String path, String buildPath) {
}
public record Domains(Customer customer, Voucher voucher) {
}
public record Customer(String fileName, String csvFileName) {
}
public record Voucher(String fileName) {
}
너무 마음에 드는 기능이다..!
이제 최상위의 FileConfig
에서만 필요한 수정을 해보겠다. 우선 path
와 buildPath
로 나누었으니, 현재의 실행 파일 위치를 기반으로 소스 환경인지 빌드 환경인지 판단하는 코드를 추가했다.
@ConfigurationProperties("file")
public class FileConfig {
private static final String WORKING_DIR = System.getProperty("user.dir");
private final Resources resources;
private final Domains domains;
@ConstructorBinding
public FileConfig(Resources resources, Domains domains) {
this.resources = resources;
this.domains = domains;
}
@PostConstruct
public void print() {
String filePath = hasBuilt() ? resources.buildPath() : resources.path();
System.out.println("File Path: " + filePath);
}
private boolean hasBuilt() {
File project = new File(WORKING_DIR);
String[] build = project.list((file, name) -> name.equals("build"));
return Objects.requireNonNull(build).length > 0;
}
}
깔끔한 방법이 아닐 수도 있겠지만, System.getProperty("user.dir")
을 이용해서 현재 실행한 파일의 위치를 알 수 있고 만약 빌드가 된 적이 있는 프로젝트라면 build 디렉토리가 생성될테니 파일 경로를 build 디렉토리에서 읽게끔 했다.
콘솔:
IntelliJ
File Path: /resources/main/
jar
$ java -jar ./build/libs/properties-0.0.1-SNAPSHOT.jar
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.2.2)
2024-01-24T02:24:42.628+09:00 INFO 22728 --- [ main] c.e.properties.PropertiesApplication : Starting PropertiesApplication v0.0.1-SNAPSHOT using Java 17.0.5 with PID 22728 (C:\Users\USER\IdeaProjects\Lab\ij-sloppy-lab\properties\build\libs\properties-0.0.1-SNAPSHOT.jar started by USER in C:\Users\USER\IdeaProjects\Lab\ij-sloppy-lab\properties)
2024-01-24T02:24:42.631+09:00 INFO 22728 --- [ main] c.e.properties.PropertiesApplication : No active profile set, falling back to 1 default profile: "default"
File Path: /resources/main/
2024-01-24T02:24:43.166+09:00 INFO 22728 --- [ main] c.e.properties.PropertiesApplication : Started PropertiesApplication in 0.953 seconds (process running for 1.343)
IDE로 스프링 부트 애플리케이션을 실행해도 빌드를 하기 때문에 사실 src 디렉토리를 읽을 일이 없겠지만, 그래도 혹시 모른다는 마음에 하는 것이다.
실제 파일들의 경로를 읽기 위해선 FileConfig
의 Customer
와 Voucher
의 파일 이름을 buildPath
와 조합해야한다. 이제 객체지향적으로 외부에서 쉽게 가져다 쓸 수 있도록 해보았다.
@ConfigurationProperties("file")
public class FileConfig {
private static final String WORKING_DIR = System.getProperty("user.dir");
private final Resources resources;
private final Domains domains;
private final String filePath;
@ConstructorBinding
public FileConfig(Resources resources, Domains domains) {
this.resources = resources;
this.domains = domains;
filePath = getFilePath();
}
@PostConstruct
public void print() {
System.out.println("Customer File: " + getCustomerPath());
System.out.println("Blacklist File: " + getBlacklistPath());
System.out.println("Voucher File: " + getVoucherPath());
}
public String getCustomerPath() {
return filePath + domains.customer().fileName();
}
public String getBlacklistPath() {
return filePath + domains.customer().csvFileName();
}
public String getVoucherPath() {
return filePath + domains.voucher().fileName();
}
private String getFilePath() {
return hasBuilt() ? resources.buildPath() : resources.path();
}
private boolean hasBuilt() {
File project = new File(WORKING_DIR);
String[] build = project.list((file, name) -> name.equals("build"));
return Objects.requireNonNull(build).length > 0;
}
}
콘솔:
Customer File: /resources/main/customer.json
Blacklist File: /resources/main/blacklist.csv
Voucher File: /resources/main/voucher.json
더 개선점이 보이긴 하지만 이 정도면 만족스럽다. 이렇게 FileConfig
의 메서드를 정의해주면 파일 경로를 알아야 할 외부 클래스에선 간략하게 FileConfig
로부터 전달받으면 되기 때문에 캡슐화를 깨뜨리지 않고 사용할 수 있다.
@SpringBootApplication
public class PropertiesApplication {
public static void main(String[] args) {
ApplicationContext applicationContext = SpringApplication.run(PropertiesApplication.class, args);
FileConfig fileConfig = applicationContext.getBean(FileConfig.class);
System.out.println("Customer File: " + fileConfig.getCustomerPath());
System.out.println("Blacklist File: " + fileConfig.getBlacklistPath());
System.out.println("Voucher File: " + fileConfig.getVoucherPath());
}
}
콘솔:
Customer File: /resources/main/customer.json
Blacklist File: /resources/main/blacklist.csv
Voucher File: /resources/main/voucher.json
예시로 main
함수에서 빈을 가져와 파일 경로를 출력했다. FileConfig
빈을 주입받을 수 있는 상황이라면 이렇게 객체지향적인 사용이 가능하다!
'Spring > Core(Boot)' 카테고리의 다른 글
Spring Boot의 다양한 Properties 설정 방법 (.properties, .yaml) (1) | 2024.01.22 |
---|
다른 글에서 소개했던 것처럼, 스프링 부트의 application.yaml
을 이용해 Properties를 관리하는 방법은 다양한 애너테이션을 이용한 여러가지 방법이 있다. 그 중에서 내가 생각하기에 가장 객체지향스러운 방법을 오늘 포스트에서 다뤄보려 한다.
@ConfigurationProperties의 단점
@ConfigurationProperties
는 YAML의 계층형을 따라서 자동으로 카멜 케이스의 필드와 매칭되는 설정 값을 찾아 매핑을 진행한다. 스프링 프레임워크가 아닌 스프링 부트에서 지원을 하는 애너테이션이니 주의가 필요하다. 문자열을 이용한 하드코딩이 아니기 때문에 @Value
보단 더 선호되는 애너테이션이라고 생각이 드는데, setter 메서드가 반드시 필요하다는 것이 아쉽다. 불변으로 생성할 수 없기 때문이다.
@ConfigurationProperties("jwt")
public class JwtConfig {
private String secretKey;
private String expirationMinutes;
@PostConstruct
private void print() {
System.out.println("Secret Key: " + secretKey);
System.out.println("Expiration Minutes: " + expirationMinutes);
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
public void setExpirationMinutes(String expirationMinutes) {
this.expirationMinutes = expirationMinutes;
}
}
콘솔:
Secret Key: secret-key
Expiration Minutes: 15
@ConstructorBinding을 이용해 불변으로 Properties 관리
위와 같은 단점 또한 @ConstructorBinding
을 이용해 해소될 수 있었다.
package org.springframework.boot.context.properties.bind;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation that can be used to indicate which constructor to use when binding
* configuration properties using constructor arguments rather than by calling setters. A
* single parameterized constructor implicitly indicates that constructor binding should
* be used unless the constructor is annotated with `@Autowired`.
*
* @author Phillip Webb
* @since 3.0.0
*/
@Target({ ElementType.CONSTRUCTOR, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConstructorBinding {
}
간단하게 살펴보자면, 설정을 주입할 때 setter를 통한 주입이 아닌 생성자로 주입을 시켜주는 애너테이션이다. 위에 언급했던 아쉬운 점을 정확하게 해소시켜 줄 애너테이션이다. 나와 같은 아쉬움을 느끼는 사람이 많았던건지, 스프링 부트 3버전 이후로 @ConstructorBinding
이 지원됐다.
@ConfigurationProperties("jwt")
public class JwtConfig {
private final String secretKey;
private final String expirationMinutes;
@ConstructorBinding
public JwtConfig(String secretKey, String expirationMinutes) {
this.secretKey = secretKey;
this.expirationMinutes = expirationMinutes;
}
@PostConstruct
private void print() {
System.out.println("Secret Key: " + secretKey);
System.out.println("Expiration Minutes: " + expirationMinutes);
}
}
콘솔:
Secret Key: secret-key
Expiration Minutes: 15
@ConstructorBinding
을 이용해 설정 클래스의 필드를 불변으로 관리할 수 있게 되었다.
@ConfigurationProperties
의 prefix를 넣어준 것을 제외하면 문자열을 이용한 하드코딩이 없고 깔끔하게 불변으로 필드를 관리하는 형식이 되었으니 제일 마음에 드는 방법이라고 생각한다.
Properties 계층형 구조
YAML을 이용하면서 Properties도 보기 쉽게 계층형으로 구조를 만들 수 있었다. 그렇다면 객체에 Properties를 주입할 때에도 구조 계층을 그대로 사용할 수 있지 않을까? 더욱 객체지향스러운 사용이 예상됐다!
file:
resources:
path: "src/main/resources/"
build-path: "build/resources/main"
domains:
customer:
file-name: customer.json
csv-file-name: blacklist.csv
voucher:
file-name: voucher.json
jwt:
secret-key: secret-key
expiration-minutes: 15
이렇게 YAML로 정의된 Properties 중 file 부분을 보면, 소스 코드를 실행 했을 때의 경로를 읽기 위한 path
와 경로 아래에 바우처 정보와 고객 정보를 저장할 파일 이름을 정해두었고, 각각 domains
아래 저장되는 종류 별로 지정해 두었다.
파일이 저장될 디렉토리 구조는 아래와 같다.
├── build
│ └── resources
│ ├── blacklist.csv
│ ├── customer.json
│ └── voucher.json
└── src
├── main
│ └── resources
│ ├── blacklist.csv
│ ├── customer.json
│ └── voucher.json
└── test
소스코드를 IDE 혹은 java로 실행할 땐 src 디렉토리 밑의 파일들을 읽어야하고, Gradle로 빌드된 프로젝트의 jar파일 실행이거나 빌드된 적이 있는 프로젝트를 실행시킬 때는 build 디렉토리 밑의 파일들을 읽게끔 하려고 한다. 이런 디렉토리 계층을 YAML에서 보기 쉽게 계층 구조를 유지했고, 소스코드의 설정 클래스 또한 계층을 유지해보겠다.
최상위 설정 클래스 파일 FileConfig
:
@ConfigurationProperties("file")
public class FileConfig {
private final Resources resources;
private final Domains domains;
@ConstructorBinding
public FileConfig(Resources resources, Domains domains) {
this.resources = resources;
this.domains = domains;
}
@PostConstruct
public void print() {
System.out.println("Resources:");
System.out.println("Path: " + resources.getPath());
System.out.println("Build Path: " + resources.getBuildPath());
System.out.println("Domains:");
System.out.println("Customer:");
System.out.println("File Name: " + domains.getCustomer().getFileName());
System.out.println("CSV File Name: " + domains.getCustomer().getCsvFileName());
System.out.println("Voucher:");
System.out.println("File Name: " + domains.getVoucher().getFileName());
}
}
Resources:
public class Resources {
private final String path;
private final String buildPath;
public Resources(String path, String buildPath) {
this.path = path;
this.buildPath = buildPath;
}
public String getPath() {
return path;
}
public String getBuildPath() {
return buildPath;
}
}
Domains:
public class Domains {
private final Customer customer;
private final Voucher voucher;
public Domains(Customer customer, Voucher voucher) {
this.customer = customer;
this.voucher = voucher;
}
public Customer getCustomer() {
return customer;
}
public Voucher getVoucher() {
return voucher;
}
}
Customer:
public class Customer {
private final String fileName;
private final String csvFileName;
public Customer(String fileName, String csvFileName) {
this.fileName = fileName;
this.csvFileName = csvFileName;
}
public String getFileName() {
return fileName;
}
public String getCsvFileName() {
return csvFileName;
}
}
Voucher:
public class Voucher {
private final String fileName;
public Voucher(String fileName) {
this.fileName = fileName;
}
public String getFileName() {
return fileName;
}
}
최상위 설정 클래스 파일인 FileConfig
에서는 @ConfigurationProperties
와 @ConstructorBinding
이 붙어야 하지만, 설정을 주입 받을 하위 클래스들은 필드와 주입받을 생성자, 그리고 getter로 이루어진 POJO로 나타낼 수 있었다.
콘솔:
Resources:
Path: build/resources/main/
Domains:
Customer:
File Name: customer.json
CSV File Name: blacklist.csv
Voucher:
File Name: voucher.json
의도한대로 잘 작동하는 것을 알 수 있다!
객체지향스러운 Properties 활용
계층 구조를 그대로 유지한 채로 설정 클래스들을 위처럼 구성할 수 있지만, 개선점이 몇가지 있다. 우선 자바 14부터 지원되었던 record를 이용해 간단한 POJO의 보일러 플레이팅 코드를 줄여봤다.
public record Resources(String path, String buildPath) {
}
public record Domains(Customer customer, Voucher voucher) {
}
public record Customer(String fileName, String csvFileName) {
}
public record Voucher(String fileName) {
}
너무 마음에 드는 기능이다..!
이제 최상위의 FileConfig
에서만 필요한 수정을 해보겠다. 우선 path
와 buildPath
로 나누었으니, 현재의 실행 파일 위치를 기반으로 소스 환경인지 빌드 환경인지 판단하는 코드를 추가했다.
@ConfigurationProperties("file")
public class FileConfig {
private static final String WORKING_DIR = System.getProperty("user.dir");
private final Resources resources;
private final Domains domains;
@ConstructorBinding
public FileConfig(Resources resources, Domains domains) {
this.resources = resources;
this.domains = domains;
}
@PostConstruct
public void print() {
String filePath = hasBuilt() ? resources.buildPath() : resources.path();
System.out.println("File Path: " + filePath);
}
private boolean hasBuilt() {
File project = new File(WORKING_DIR);
String[] build = project.list((file, name) -> name.equals("build"));
return Objects.requireNonNull(build).length > 0;
}
}
깔끔한 방법이 아닐 수도 있겠지만, System.getProperty("user.dir")
을 이용해서 현재 실행한 파일의 위치를 알 수 있고 만약 빌드가 된 적이 있는 프로젝트라면 build 디렉토리가 생성될테니 파일 경로를 build 디렉토리에서 읽게끔 했다.
콘솔:
IntelliJ
File Path: /resources/main/
jar
$ java -jar ./build/libs/properties-0.0.1-SNAPSHOT.jar
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.2.2)
2024-01-24T02:24:42.628+09:00 INFO 22728 --- [ main] c.e.properties.PropertiesApplication : Starting PropertiesApplication v0.0.1-SNAPSHOT using Java 17.0.5 with PID 22728 (C:\Users\USER\IdeaProjects\Lab\ij-sloppy-lab\properties\build\libs\properties-0.0.1-SNAPSHOT.jar started by USER in C:\Users\USER\IdeaProjects\Lab\ij-sloppy-lab\properties)
2024-01-24T02:24:42.631+09:00 INFO 22728 --- [ main] c.e.properties.PropertiesApplication : No active profile set, falling back to 1 default profile: "default"
File Path: /resources/main/
2024-01-24T02:24:43.166+09:00 INFO 22728 --- [ main] c.e.properties.PropertiesApplication : Started PropertiesApplication in 0.953 seconds (process running for 1.343)
IDE로 스프링 부트 애플리케이션을 실행해도 빌드를 하기 때문에 사실 src 디렉토리를 읽을 일이 없겠지만, 그래도 혹시 모른다는 마음에 하는 것이다.
실제 파일들의 경로를 읽기 위해선 FileConfig
의 Customer
와 Voucher
의 파일 이름을 buildPath
와 조합해야한다. 이제 객체지향적으로 외부에서 쉽게 가져다 쓸 수 있도록 해보았다.
@ConfigurationProperties("file")
public class FileConfig {
private static final String WORKING_DIR = System.getProperty("user.dir");
private final Resources resources;
private final Domains domains;
private final String filePath;
@ConstructorBinding
public FileConfig(Resources resources, Domains domains) {
this.resources = resources;
this.domains = domains;
filePath = getFilePath();
}
@PostConstruct
public void print() {
System.out.println("Customer File: " + getCustomerPath());
System.out.println("Blacklist File: " + getBlacklistPath());
System.out.println("Voucher File: " + getVoucherPath());
}
public String getCustomerPath() {
return filePath + domains.customer().fileName();
}
public String getBlacklistPath() {
return filePath + domains.customer().csvFileName();
}
public String getVoucherPath() {
return filePath + domains.voucher().fileName();
}
private String getFilePath() {
return hasBuilt() ? resources.buildPath() : resources.path();
}
private boolean hasBuilt() {
File project = new File(WORKING_DIR);
String[] build = project.list((file, name) -> name.equals("build"));
return Objects.requireNonNull(build).length > 0;
}
}
콘솔:
Customer File: /resources/main/customer.json
Blacklist File: /resources/main/blacklist.csv
Voucher File: /resources/main/voucher.json
더 개선점이 보이긴 하지만 이 정도면 만족스럽다. 이렇게 FileConfig
의 메서드를 정의해주면 파일 경로를 알아야 할 외부 클래스에선 간략하게 FileConfig
로부터 전달받으면 되기 때문에 캡슐화를 깨뜨리지 않고 사용할 수 있다.
@SpringBootApplication
public class PropertiesApplication {
public static void main(String[] args) {
ApplicationContext applicationContext = SpringApplication.run(PropertiesApplication.class, args);
FileConfig fileConfig = applicationContext.getBean(FileConfig.class);
System.out.println("Customer File: " + fileConfig.getCustomerPath());
System.out.println("Blacklist File: " + fileConfig.getBlacklistPath());
System.out.println("Voucher File: " + fileConfig.getVoucherPath());
}
}
콘솔:
Customer File: /resources/main/customer.json
Blacklist File: /resources/main/blacklist.csv
Voucher File: /resources/main/voucher.json
예시로 main
함수에서 빈을 가져와 파일 경로를 출력했다. FileConfig
빈을 주입받을 수 있는 상황이라면 이렇게 객체지향적인 사용이 가능하다!
'Spring > Core(Boot)' 카테고리의 다른 글
Spring Boot의 다양한 Properties 설정 방법 (.properties, .yaml) (1) | 2024.01.22 |
---|