
๊ฐ๋ฐ์๊ฐ ํ์ผ์ ์ฝ์ด์ ์ค์ ๊ฐ์ผ๋ก ์ฌ์ฉํ ์ ์๋๋ก ๊ฐ๋ฐ์ ํด์ผ๊ฒ ์ง๋ง, ์คํ๋ง ๋ถํธ๋ ์ด๋ฏธ ์ด๋ฐ ๋ถ๋ถ์ ๋ค ๊ตฌํํด๋์๋ค.
๊ฐ๋ฐ์๋ application.properties ๋ผ๋ ์ด๋ฆ์ ํ์ผ์ ์๋ฐ๋ฅผ ์คํํ๋ ์์น์ ๋ง๋ค์ด ๋๊ธฐ๋ง ํ๋ฉด ๋๋ค. ๊ทธ๋ฌ๋ฉด ์คํ๋ง์ด ํด๋น ํ์ผ์ ์ฝ์ด์ ์ฌ์ฉํ ์ ์๋ PropertySource ์ ๊ตฌํ์ฒด๋ฅผ ์ ๊ณตํ๋ค.
์คํ๋ง์์๋ ์ด๋ฌํ application.properties ํ์ผ์ ์ค์ ๋ฐ์ดํฐ(Config data)๋ผ ํ๋ค.
โญ ๋น์ฐํ ์ค์ ๋ฐ์ดํฐ๋ Environment๋ฅผ ํตํด์ ์ฝ์ด ๋ค์ผ ์ ์๋ค!

์๋ฅผ ๋ค์ด, ๋ค์๊ณผ ๊ฐ์ด ์ต์ ์ ์ค์๋ ์๋ค.
-Dspring.profiles.active=dev
์ปค๋งจ๋๋ผ์ธ ์ต์
์ธ์
key=value ํ์์ผ๋ก ๊ตฌ๋ถ
--key=value ํ์
๋จ์ : ๋๋ฌด ๊ธธ๋ฉด ์ฌ์ฉํ๊ธฐ ๋ถํธํด์ ธ..
์๋ฅผ ๋ค์ด์ OS ํ๊ฒฝ ๋ณ์์ ๋๋ฉด System.getenv(key) ๋ฅผ ์ฌ์ฉํด์ผ ํ๊ณ , ์๋ฐ ์์คํ
์์ฑ์ ์ฌ์ฉํ๋ฉด System.getProperty(key) ๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค. ๋ง์ฝ OS์ ํ๊ฒฝ ๋ณ์๋ฅผ ๋์๋๋ฐ, ์ดํ์ ์ ์ฑ
์ด
๋ณ๊ฒฝ๋์ด์ ์๋ฐ ์์คํ
์์ฑ์ ํ๊ฒฝ ๋ณ์๋ฅผ ๋๊ธฐ๋ก ํ๋ค๊ณ ๊ฐ์ ํด๋ณด์. ๊ทธ๋ฌ๋ฉด ํด๋น ์ฝ๋๋ค์ ๋ชจ๋ ๋ณ๊ฒฝํด์ผ ํ๋ค.
์ธ๋ถ ์ค์ ๊ฐ์ด ์ด๋์ ์์นํ๋ ์๊ด์์ด ์ผ๊ด์ฑ ์๊ณ , ํธ๋ฆฌํ๊ฒ key=value ํ์์ ์ธ๋ถ ์ค์ ๊ฐ์ ์ฝ์ ์ ์์ผ๋ฉด ์ฌ์ฉํ๋ ๊ฐ๋ฐ์ ์ ์ฅ์์ ๋ ํธ๋ฆฌํ๊ณ ๋ ์ธ๋ถ ์ค์ ๊ฐ์ ์ค์ ํ๋ ๋ฐฉ๋ฒ๋ ๋ ์ ์ฐํด์ง ์ ์๋ค.
์๋ฅผ ๋ค์ด, ์ธ๋ถ ์ค์ ๊ฐ์ OS ํ๊ฒฝ๋ณ์๋ฅผ ์ฌ์ฉํ๋ค๊ฐ ์๋ฐ ์์คํ ์์ฑ์ผ๋ก ๋ณ๊ฒฝํ๋ ๊ฒฝ์ฐ์ ์์ค์ฝ๋๋ฅผ ๋ค์ ๋น๋ํ์ง ์๊ณ ๊ทธ๋๋ก ์ฌ์ฉํ ์ ์๋ค.
์คํ๋ง์ ์ด ๋ฌธ์ ๋ฅผ Environment ์ PropertySource ๋ผ๋ ์ถ์ํ๋ฅผ ํตํด์ ํด๊ฒฐํ๋ค.
์์
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class EnvironmentCheck {
private final Environment env;
public EnvironmentCheck(Environment env) {
this.env = env;
}
@PostConstruct
public void init() {
String url = env.getProperty("url");
String username = env.getProperty("username");
String password = env.getProperty("password");
log.info("env url={}", url);
log.info("env username={}", username);
log.info("env password={}", password);
}
}
์ปค๋งจ๋ ๋ผ์ธ ์ต์
์ธ์ ์คํ
--url=devdb --username=dev_user --password=dev_pw
์๋ฐ ์์คํ
์์ฑ ์คํ
-Durl=devdb -Dusername=dev_user -Dpassword=dev_pw
ExternalApplication.main ์ ์คํํ์.
์คํ ๊ฒฐ๊ณผ
env username=dev_user
env password=dev_pw
์ ๋ฆฌ
์ปค๋งจ๋ ๋ผ์ธ ์ต์ ์ธ์, ์๋ฐ ์์คํ ์์ฑ ๋ชจ๋ Environment ๋ฅผ ํตํด์ ๋์ผํ ๋ฐฉ๋ฒ์ผ๋ก ์ฝ์ ์ ์๋ ๊ฒ์ ํ์ธํ๋ค.
์คํ๋ง์ Environment ๋ฅผ ํตํด์ ์ธ๋ถ ์ค์ ์ ์ฝ๋ ๋ฐฉ๋ฒ์ ์ถ์ํํ๋ค. ๋๋ถ์ ์๋ฐ ์์คํ ์์ฑ์ ์ฌ์ฉํ๋ค๊ฐ ๋ง์ฝ ์ปค๋งจ๋ ๋ผ์ธ ์ต์ ์ธ์๋ฅผ ์ฌ์ฉํ๋๋ก ์ฝ๋ ๋ฐฉ๋ฒ์ด ๋ณ๊ฒฝ๋์ด๋, ๊ฐ๋ฐ ์์ค ์ฝ๋๋ ์ ํ ๋ณ๊ฒฝํ์ง ์์๋ ๋๋ค.
properties, yaml(yml) ํ์ผ์ ์ ์ฅ (์ค๋ฌด์์๋ ๊ณ์ธต๊ตฌ์กฐ๋ฅผ ๊ฐ์ง๊ณ ์์ด ์ฝ๊ธฐ ํธํ yml ํ์ผ์ ๋ ์ ํธ)
spring์ด ์ธ์ํ๋ ์ฐ์ ์์: java ์์คํ ํ๋กํผํฐ(-D ์ต์ ) > properties > yml
properties ๊ฐ์ด ์ค์ ํ์ผ๋ค์ ์ผ๋ฐฅ์ผ์ด์ค-๋ฅผ ๊ถ์ฅ
profile ์ค์
1) ํ์ผ๋ก ๋ถ๋ฆฌ ex. application-dev.yml, application-prod.yml
2) application.yml 1๊ฐ์ ํ์ผ์์ profile์ ๋ถ๋ฆฌ
--- dash 3 ๋ณดํต ์ด ๋ฐฉ๋ฒ์ผ๋ก ์ฐ์
1) application.yml , --- Profile ๋ถ๋ฆฌ
2) Profile ๋ถ๋ฆฌ ์ค์
# default ์ค์ , profile์ต์
์ ์ํด์ฃผ๋ฉด ์ด ์ค์ profile์ ์ ํ
spring:
profiles:
active: "profile"
spring:
config:
activate:
on-profile: "profile"
server:
port: 8080
spring:
config:
import: optional:file:.env[.properties] # .env ํ์ผ ๋ก๋
profiles:
active: local # ๊ธฐ๋ณธ ํ์ฑํ ํ๋กํ์ผ๋ก local ์ค์
jpa:
hibernate:
ddl-auto: update # ํ
์ด๋ธ ์์ฑ ๋ฐ ์
๋ฐ์ดํธ ์ ๋ต
properties:
hibernate:
format_sql: true # SQL ํฌ๋งทํ
highlight_sql: true # ํ์ด๋ผ์ดํธ SQL ์ถ๋ ฅ
use_sql_comments: true # ์ค์ JPQL SQL ์ฃผ์ ์ฌ์ฉ
---
spring:
config:
activate:
on-profile: local
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Seoul&characterEncoding=UTF-8
username: root
password: 1234
app:
aws:
ec2:
instance-front-url: http://localhost:3000
logging:
level:
org.hibernate.SQL: debug # Hibernate์ SQL์ ์ถ๋ ฅ
org.hibernate.orm.jdbc.bind: trace # Hibernate์ SQL ๋ฐ์ธ๋ฉ์ ์ถ๋ ฅ
org.springframework.transaction.interceptor: trace # Hibernate์ SQL ๋ฐ์ธ๋ฉ์ ์ถ๋ ฅ
---
spring:
config:
activate:
on-profile: prod
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://my-db:3306/test?serverTimezone=Asia/Seoul&characterEncoding=UTF-8
username: root
password: 1234
hikari:
max-lifetime: 600000 # ์ปค๋ฅ์
์ต๋ ์์กด ์๊ฐ
idle-timeout: 300000 # ์ปค๋ฅ์
์ต๋ ์ ํด ์๊ฐ
connection-timeout: 30000 # ์ปค๋ฅ์
ํ์์์
app:
aws:
ec2:
instance-front-url: http://43.203.42.37
logging:
level:
org.hibernate.SQL: error # Hibernate์ SQL์ ์ถ๋ ฅ
์ Environment ๊ฐ์ฒด๋ก ๊ฐ์ ธ์ค๋ ๋ฐฉ์์ ๋๋ค.
์์
@Slf4j
@Component
public class EnvironmentCheck {
private final Environment env;
public EnvironmentCheck(Environment env) {
this.env = env;
}
@PostConstruct
public void init() {
String url = env.getProperty("url");
String username = env.getProperty("username");
String password = env.getProperty("password");
log.info("env url={}", url);
log.info("env username={}", username);
log.info("env password={}", password);
}
}
์ปค๋งจ๋ ๋ผ์ธ ์ต์
์ธ์ ์คํ
--url=devdb --username=dev_user --password=dev_pw
์๋ฐ ์์คํ
์์ฑ ์คํ
-Durl=devdb -Dusername=dev_user -Dpassword=dev_pw
ExternalApplication.main ์ ์คํํ์.
์คํ ๊ฒฐ๊ณผ
env username=dev_user
env password=dev_pw
@Value ๋ฐฉ๋ฒapplication.yml
# Application Active Type(prod, dev)
spring:
profiles:
active: dev
@Value ์ด๋ ธํ ์ด์ ์ผ๋ก ๊ฐ์ ธ์ฌ ์ ์๋ค.
@Component
public class EnvironmentProps {
@Value("${spring.profiles.active}")
private String activeProfile;
public boolean isProdActiveProfile() {
return Objects.equals(activeProfile, "prod");
}
}
@Data
@Component
public class ElasticSearchProps {
@Value("${app.props.elastic-search.product.enterprise-search-baseUrl}")
private String productEngineUrl;
@Value("${app.props.elastic-search.product.private-key}")
private String productEnginePrivateKey;
}
@Slf4j
@Component
@RequiredArgsConstructor
public class ElasticSearchScheduler {
private final ElasticSearchService elasticSearchService;
private final EnvironmentProps environmentProps;
@Scheduled(fixedRate = 30000)
public void saveProductList() {
// ์ค์๋ฒ์ธ ๊ฒฝ์ฐ์๋ง ๋์
if (environmentProps.isProdActiveProfile()) {
elasticSearchService.indexProductList();
}
}
}
@ConfigurationProperties ๋ฐฉ๋ฒ(๊ฐ์ฅ ์ถ์ฒ)์คํ๋ง๋ถํธ๋ BeanPostProcessor(ํ๋กํผํฐ ๋นํ์ฒ๋ฆฌ๊ธฐ)๋ฅผ ํตํด ๋น ๊ฐ์ฒด๊ฐ ์์ฑ๋ ํ์ ํน์ ์ด๋
ธํ
์ด์
์ด ๋ถ์ ํด๋์ค์ Environment์ ํ๋กํผํฐ ๊ฐ์ ์๋์ผ๋ก ์ฑ์์ฃผ๋(๋ฐ์ธ๋ฉ) ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค. ์ด๋ฅผ ์ํ ์ด๋
ธํ
์ด์
@ConfigurationProperties ์
๋๋ค.
application.yml
...
# Properties
app:
props:
type: dev
url: https://XXXXXXXX.com
host: https://XXXXXXXX.com/
ai:
resnet50Url: http://api-XXXXXXXX/api/v1/classify
allowOrigins: http://localhost:3001
sms:
smsTellNumber: "010-XXXX-XXX"
client-key: "NCSQGSYTWQSHNXDR@@"
secret-key: "ZTHWIUBEM2IKME8W044EU2WTX0EMNZQP@@"
yaml ํ์ผ์ app.props.sms @ConfigurationProperties ์ด๋
ธํ
์ด์
์ ์ฌ์ฉํฉ๋๋ค.
โ๏ธ ์ด๋
ธํ
์ด์
์์ application.yml ํ์ผ ์์ app.props.sms ๊ฐ์ ๊ณตํต๋ ์ ๋์ฌ๋ฅผ ๋ฃ์ด, ํ๋กํผํฐ๋ค์ ๊ทธ๋ฃนํํ๊ณ ๊ด๋ฆฌํ๊ธฐ ์ฝ๊ฒ ๋ง๋ค์ด์ ์ฌ์ฉํฉ๋๋ค!
โ๏ธ ๊ทธ๋ค์ ํ๋กํผํฐ ์์ฑ์ Java ์ฝ๋๋ก Java๋ ์นด๋ฉ์ผ์ด์ค ํํ๋ก ํ๋ ์ ๋ณด๋ก ์ ๋ฆฌํด, ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค!
@Data
@Component // ๋น์ผ๋ก ๋ฑ๋กํ๊ธฐ ์ํจ
@ConfigurationProperties("app.props.sms")
public class SmsProps {
private String smsTellNumber;
private String clientKey;
private String secretKey;
}
application.yml
cors:
allowed-origins:
- https://dsgpost.link
- https://www.dsgpost.link
ConfigurationProperties ํ์ผ
@Data
@Component
@ConfigurationProperties("cors")
public class CorsAllowedOriginsProps {
private List<String> allowedOrigins;
}
ConfigurationProperties ์ ์ฉ
@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {
private final CorsAllowedOriginsProps props;
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins(props.getAllowedOrigins().toArray(new String[0])) // ํ๋ก ํธ ํ์ฉ ์ฃผ์ ์ค์
.allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")
.allowedHeaders("*")
.exposedHeaders("Access-Control-Allow-Origin", "Access-Control-Allow-Credentials")
.maxAge(3600) // 1์๊ฐ ๋์ preflight ์์ฒญ ๊ฒฐ๊ณผ๋ฅผ ์บ์
.allowCredentials(true);
}
}
ConfigurationProperties ์ฅ์
- ์ธ๋ถ ์ค์ ์ ๊ฐ์ฒด๋ก ํธ๋ฆฌํ๊ฒ ๋ณํํด์ ์ฌ์ฉํ ์ ์๋ค.
- ์ธ๋ถ ์ค์ ์ ๊ณ์ธต์ ๊ฐ์ฒด๋ก ํธ๋ฆฌํ๊ฒ ํํํ ์ ์๋ค.
- ์ธ๋ถ ์ค์ ์ ํ์ ์์ ํ๊ฒ ์ฌ์ฉํ ์ ์๋ค.
- ๊ฒ์ฆ๊ธฐ๋ฅผ ์ ์ฉํ ์ ์๋ค.
@Profile@Slf4j
@Configuration
public class PayConfig {
@Bean
@Profile("default")
public LocalPayClient localPayClient() {
log.info("LocalPayClient ๋น ๋ฑ๋ก");
return new LocalPayClient();
}
@Bean
@Profile("prod")
public ProdPayClient prodPayClient() {
log.info("ProdPayClient ๋น ๋ฑ๋ก");
return new ProdPayClient();
}
}
package org.springframework.context.annotation;
...
@Conditional(ProfileCondition.class)
public @interface Profile {
String[] value();
}
@Profile ์ ํน์ ์กฐ๊ฑด์ ๋ฐ๋ผ์ ํด๋น ๋น์ ๋ฑ๋กํ ์ง ๋ง์ง ์ ํํ๋ค. ์ด๋์ ๋ง์ด ๋ณธ ๊ฒ ๊ฐ์ง ์์๊ฐ? ๋ฐ๋ก @Conditional ์ด๋ค.
์ฝ๋๋ฅผ ๋ณด๋ฉด @Conditional(ProfileCondition.class) ๋ฅผ ํ์ธํ ์ ์๋ค.
์คํ๋ง์ @Conditional ๊ธฐ๋ฅ์ ํ์ฉํด์ ๊ฐ๋ฐ์๊ฐ ๋ ํธ๋ฆฌํ๊ฒ ์ฌ์ฉํ ์ ์๋ @Profile ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค.
@Profile ์ ์ฌ์ฉํ๋ฉด ๊ฐ ํ๊ฒฝ ๋ณ๋ก ์ธ๋ถ ์ค์ ๊ฐ์ ๋ถ๋ฆฌํ๋ ๊ฒ์ ๋์ด์, ๋ฑ๋ก๋๋ ์คํ๋ง ๋น๋ ๋ถ๋ฆฌํ ์ ์์ต๋๋ค.
-Dspring.profiles.active=prod
or
--spring.profiles.active=prod
์์)
java -Dspring.profiles.active=prod -jar *-SNAPSHOT.jar
java -jar *-SNAPSHOT.jar --spring.profiles.active=prod
์คํ๋ง๋ถํธ๋ ์๋๊ตฌ์ฑ + ์ธ๋ถ์ค์ ๊ตฌ์ฑ + ๊ทธ๋ฆฌ๊ณ ์ฌ์ฉ์(๊ฐ๋ฐ์)๊ฐ ์ง์ ๋น์ผ๋ก ๋ฑ๋ก๋ ๊ฐ์ฒด๋ค๋ก
Containerless ์ดํ๋ ์ผ์ด์ ๊ตฌ์กฐ์ ๊ฐ๋ฐ์ ์๋ฃ์ผ ํ๋ ๊ฒ์ ๋๋ค. - ํ ๋น
