Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
Tags
- 고가용성
- ApacheBench
- 마이크로서비스 운영
- 이벤트 스트리밍
- 메시지 브로커
- kubernetes
- 분산 모니터링
- 서비스 메시
- 분산 시스템
- docker
- 마이크로서비스
- 컨테이너오케스트레이션
- devops
- 클라우드
- CI/CD
- 서비스 설계
- 클러스터
- 보안
- 인메모리데이터베이스
- Kafka 클러스터
- infrastructureascode
- 마이크로서비스 통신
- rabbitmq
- 프로덕션 운영
- 세션저장소
- RabbitMQ Exchange
- 모니터링
- 메시징 패턴
- 모노리스 분해
- Python
Archives
- Today
- Total
hobokai 님의 블로그
Spring Boot 완전 가이드 Part 2: 설정과 자동구성 본문
시리즈 소개: 실무에서 바로 써먹는 Spring Boot 완전 가이드
Part 1: Spring Boot 기초 - 시작하기
Part 2: 설정과 자동구성 ← 현재
Part 3: 웹 개발과 REST API
Part 4: 데이터 액세스와 배치
Part 5: 운영과 모니터링
🎯 Auto Configuration 이해하기
Spring Boot의 마법: Auto Configuration
Part 1에서 보신 것처럼, Spring Boot에서는 단 한 줄의 설정도 없이 웹 애플리케이션이 동작했습니다. 이것이 어떻게 가능할까요?
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
이 간단한 코드 뒤에는 복잡한 자동 설정 메커니즘이 숨어있습니다.
Auto Configuration 동작 원리
1. @SpringBootApplication 분해
@SpringBootApplication
// 실제로는 이 3개의 조합입니다:
@SpringBootConfiguration // = @Configuration
@EnableAutoConfiguration // ← 자동 설정의 핵심!
@ComponentScan // 컴포넌트 스캔
public class DemoApplication { ... }
2. @EnableAutoConfiguration 동작 과정
// Spring Boot가 내부적으로 하는 일들
@EnableAutoConfiguration
public class DemoApplication {
// 1. 클래스패스에서 META-INF/spring.factories 파일 스캔
// 2. AutoConfiguration 클래스들 발견
// 3. @Conditional 조건 확인
// 4. 조건 만족 시 자동으로 빈 등록
}
실제 Auto Configuration 예시
WebMvcAutoConfiguration 살펴보기
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class })
public class WebMvcAutoConfiguration {
@Configuration
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
@Bean
@ConditionalOnMissingBean
public InternalResourceViewResolver defaultViewResolver() {
// 뷰 리졸버 자동 설정
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.mvc", name = "pathmatch.matching-strategy")
public PathMatcher mvcPathMatcher() {
// 경로 매칭 전략 설정
}
}
}
동작 조건 분석:
- @ConditionalOnWebApplication: 웹 애플리케이션일 때만
- @ConditionalOnClass: 해당 클래스가 클래스패스에 있을 때만
- @ConditionalOnMissingBean: 사용자가 정의하지 않았을 때만
Auto Configuration 우선순위
// 사용자 설정 > Auto Configuration
@Configuration
public class MyWebConfig implements WebMvcConfigurer {
@Bean
public InternalResourceViewResolver myViewResolver() {
// 이 빈이 있으면 Auto Configuration의 defaultViewResolver는 생성되지 않음
return new InternalResourceViewResolver();
}
}
⚙️ application.properties/yml 완전 정복
Properties vs YAML 비교
application.properties
# 서버 설정
server.port=8080
server.servlet.context-path=/api
# 데이터베이스 설정
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=secret
# JPA 설정
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
# 로깅 설정
logging.level.com.example=DEBUG
logging.level.org.springframework.security=DEBUG
application.yml (권장)
server:
port: 8080
servlet:
context-path: /api
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: secret
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
format_sql: true
logging:
level:
com.example: DEBUG
org.springframework.security: DEBUG
환경별 설정 파일 분리
파일 구조
src/main/resources/
├── application.yml # 공통 설정
├── application-local.yml # 로컬 개발
├── application-dev.yml # 개발 서버
├── application-test.yml # 테스트 환경
└── application-prod.yml # 운영 환경
application.yml (공통)
# 모든 환경 공통 설정
spring:
application:
name: demo-app
jpa:
hibernate:
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
management:
endpoints:
web:
exposure:
include: health,info,metrics
---
# 활성 프로파일 설정
spring:
config:
activate:
on-profile: local
profiles:
include: local
application-local.yml
server:
port: 8080
spring:
datasource:
url: jdbc:h2:mem:testdb
username: sa
password:
driver-class-name: org.h2.Driver
h2:
console:
enabled: true
path: /h2-console
jpa:
hibernate:
ddl-auto: create-drop
show-sql: true
properties:
hibernate:
format_sql: true
logging:
level:
com.example: DEBUG
org.springframework.web: DEBUG
application-prod.yml
server:
port: 8080
compression:
enabled: true
mime-types: text/html,text/xml,text/plain,text/css,application/json,application/javascript
min-response-size: 1024
spring:
datasource:
url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:myapp}
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
hikari:
maximum-pool-size: 20
minimum-idle: 5
jpa:
hibernate:
ddl-auto: validate
show-sql: false
open-in-view: false
logging:
level:
com.example: INFO
org.springframework.security: WARN
file:
name: logs/application.log
logback:
rollingpolicy:
max-file-size: 100MB
max-history: 30
프로파일 활성화 방법들
# 1. JAR 실행 시
java -jar app.jar --spring.profiles.active=prod
# 2. JVM 시스템 프로퍼티
java -Dspring.profiles.active=prod -jar app.jar
# 3. 환경 변수
export SPRING_PROFILES_ACTIVE=prod
java -jar app.jar
# 4. application.yml에서 설정
spring:
profiles:
active: dev
# 5. IDE에서 설정 (IntelliJ)
# Run Configuration → Environment Variables → SPRING_PROFILES_ACTIVE=local
설정값 우선순위
Spring Boot는 다음 순서로 설정값을 적용합니다:
1. 커맨드 라인 아규먼트
2. 시스템 프로퍼티 (System.getProperty())
3. 환경 변수
4. application-{profile}.yml
5. application.yml
6. @PropertySource
7. 기본값
예시:
# 최우선 순위: 커맨드 라인
java -jar app.jar --server.port=9090 --spring.profiles.active=prod
# 두 번째 우선순위: 시스템 프로퍼티
java -Dserver.port=8090 -jar app.jar
# 환경 변수로도 설정 가능
export SERVER_PORT=7090
🏗️ 커스텀 Configuration 클래스
기본 Configuration 클래스
@Configuration
@EnableConfigurationProperties
public class AppConfig {
@Bean
@Primary
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(PropertyNamingStrategies.SNAKE_CASE);
mapper.registerModule(new JavaTimeModule());
return mapper;
}
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
// 타임아웃 설정
HttpComponentsClientHttpRequestFactory factory =
(HttpComponentsClientHttpRequestFactory) restTemplate.getRequestFactory();
factory.setConnectTimeout(5000);
factory.setReadTimeout(10000);
return restTemplate;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
환경별 Configuration
@Configuration
@Profile("prod") // 운영환경에서만 활성화
public class ProductionConfig {
@Bean
@Primary
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(System.getenv("DB_URL"));
config.setUsername(System.getenv("DB_USERNAME"));
config.setPassword(System.getenv("DB_PASSWORD"));
config.setMaximumPoolSize(20);
config.setMinimumIdle(5);
config.setConnectionTimeout(30000);
return new HikariDataSource(config);
}
}
@Configuration
@Profile("local") // 로컬 개발환경에서만
public class LocalConfig {
@Bean
@Primary
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
@Bean
public CommandLineRunner initData(UserRepository userRepository) {
return args -> {
// 테스트 데이터 초기화
userRepository.save(new User("admin", "admin@test.com"));
userRepository.save(new User("user", "user@test.com"));
};
}
}
🔀 조건부 빈 등록 (@Conditional)
주요 @Conditional 어노테이션들
@Configuration
public class ConditionalConfig {
// 1. 클래스 존재 여부 확인
@Bean
@ConditionalOnClass(RedisTemplate.class)
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(jedisConnectionFactory());
return template;
}
// 2. 프로퍼티 값 확인
@Bean
@ConditionalOnProperty(name = "app.cache.type", havingValue = "redis")
public CacheManager redisCacheManager() {
RedisCacheManager.Builder builder = RedisCacheManager
.RedisCacheManagerBuilder
.fromConnectionFactory(jedisConnectionFactory());
return builder.build();
}
// 3. 빈이 없을 때만 생성
@Bean
@ConditionalOnMissingBean(CacheManager.class)
public CacheManager defaultCacheManager() {
return new ConcurrentMapCacheManager();
}
// 4. 웹 환경일 때만
@Bean
@ConditionalOnWebApplication
public FilterRegistrationBean<CorsFilter> corsFilter() {
FilterRegistrationBean<CorsFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new CorsFilter());
registration.addUrlPatterns("/*");
return registration;
}
// 5. 특정 프로파일에서만
@Bean
@ConditionalOnProfile("!prod") // 운영 환경이 아닐 때만
public MockExternalApiService mockApiService() {
return new MockExternalApiService();
}
}
커스텀 Conditional 만들기
// 커스텀 Condition 클래스
public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment()
.getProperty("os.name", "")
.toLowerCase()
.contains("linux");
}
}
// 커스텀 어노테이션
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Conditional(LinuxCondition.class)
public @interface ConditionalOnLinux {
}
// 사용법
@Configuration
public class OsSpecificConfig {
@Bean
@ConditionalOnLinux
public LinuxSpecificService linuxService() {
return new LinuxSpecificService();
}
@Bean
@ConditionalOnProperty(name = "os.name", havingValue = "Windows")
public WindowsSpecificService windowsService() {
return new WindowsSpecificService();
}
}
📋 외부 설정 바인딩 (@ConfigurationProperties)
기본 사용법
Properties 정의
# application.yml
app:
name: Demo Application
version: 1.0.0
api:
timeout: 5000
retries: 3
base-url: https://api.example.com
features:
- user-management
- notification
- analytics
database:
max-connections: 20
idle-timeout: 300
Configuration Properties 클래스
@ConfigurationProperties(prefix = "app")
@Data
@Component
public class AppProperties {
private String name;
private String version;
private Api api = new Api();
private List<String> features = new ArrayList<>();
private Database database = new Database();
@Data
public static class Api {
private int timeout = 5000;
private int retries = 3;
private String baseUrl;
}
@Data
public static class Database {
private int maxConnections = 10;
private int idleTimeout = 300;
}
}
검증(Validation) 추가
@ConfigurationProperties(prefix = "app")
@Validated // 검증 활성화
@Data
@Component
public class ValidatedAppProperties {
@NotBlank(message = "애플리케이션 이름은 필수입니다")
private String name;
@Pattern(regexp = "^\\d+\\.\\d+\\.\\d+$", message = "버전 형식이 올바르지 않습니다")
private String version;
@Valid // 중첩 객체 검증
private Api api = new Api();
@Data
public static class Api {
@Min(value = 1000, message = "타임아웃은 최소 1000ms 이상이어야 합니다")
@Max(value = 30000, message = "타임아웃은 최대 30000ms 이하여야 합니다")
private int timeout = 5000;
@Min(value = 1, message = "재시도 횟수는 최소 1회 이상이어야 합니다")
@Max(value = 10, message = "재시도 횟수는 최대 10회 이하여야 합니다")
private int retries = 3;
@URL(message = "올바른 URL 형식이어야 합니다")
@NotBlank(message = "API Base URL은 필수입니다")
private String baseUrl;
}
}
Properties 사용하기
@Service
@RequiredArgsConstructor
public class ExternalApiService {
private final AppProperties appProperties;
private final RestTemplate restTemplate;
public ApiResponse callApi(String endpoint) {
String url = appProperties.getApi().getBaseUrl() + endpoint;
// 타임아웃 설정
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory();
factory.setConnectTimeout(appProperties.getApi().getTimeout());
factory.setReadTimeout(appProperties.getApi().getTimeout());
RestTemplate customRestTemplate = new RestTemplate(factory);
// 재시도 로직
int retries = appProperties.getApi().getRetries();
for (int i = 0; i < retries; i++) {
try {
return customRestTemplate.getForObject(url, ApiResponse.class);
} catch (Exception e) {
if (i == retries - 1) {
throw new ExternalApiException("API 호출 실패: " + e.getMessage(), e);
}
try {
Thread.sleep(1000 * (i + 1)); // 지수 백오프
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException(ie);
}
}
}
return null;
}
}
복잡한 설정 매핑
# application.yml
app:
security:
jwt:
secret: mySecretKey
expiration: 86400
refresh-expiration: 604800
cors:
allowed-origins:
- http://localhost:3000
- https://mydomain.com
allowed-methods:
- GET
- POST
- PUT
- DELETE
allowed-headers:
- "*"
rate-limit:
enabled: true
requests-per-minute: 100
burst-capacity: 20
@ConfigurationProperties(prefix = "app.security")
@Validated
@Data
@Component
public class SecurityProperties {
@Valid
private Jwt jwt = new Jwt();
@Valid
private Cors cors = new Cors();
@Valid
private RateLimit rateLimit = new RateLimit();
@Data
public static class Jwt {
@NotBlank
private String secret;
@Min(3600) // 최소 1시간
private long expiration = 86400;
@Min(86400) // 최소 1일
private long refreshExpiration = 604800;
}
@Data
public static class Cors {
@NotEmpty
private List<String> allowedOrigins = new ArrayList<>();
@NotEmpty
private List<String> allowedMethods = Arrays.asList("GET", "POST");
private List<String> allowedHeaders = Arrays.asList("*");
}
@Data
public static class RateLimit {
private boolean enabled = false;
@Min(1)
private int requestsPerMinute = 100;
@Min(1)
private int burstCapacity = 20;
}
}
🔧 Configuration 실전 팁
1. 환경 변수 활용
# application-prod.yml
spring:
datasource:
url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME}
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
server:
port: ${SERVER_PORT:8080}
logging:
level:
com.example: ${LOG_LEVEL:INFO}
# Docker 환경에서
docker run -e DB_HOST=prod-db-server \
-e DB_USERNAME=app_user \
-e DB_PASSWORD=secret123 \
-e LOG_LEVEL=DEBUG \
myapp:latest
2. 설정 암호화
# application.yml (Jasypt 사용)
spring:
datasource:
password: ENC(encrypted_password_here)
app:
api:
secret-key: ENC(encrypted_api_key_here)
// JasyptConfig.java
@Configuration
public class JasyptConfig {
@Bean("jasyptStringEncryptor")
public StringEncryptor stringEncryptor() {
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
SimpleStringPBEConfig config = new SimpleStringPBEConfig();
config.setPassword(System.getenv("JASYPT_PASSWORD"));
config.setAlgorithm("PBEWithMD5AndDES");
config.setKeyObtentionIterations("1000");
config.setPoolSize("1");
config.setProviderName("SunJCE");
config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
config.setStringOutputType("base64");
encryptor.setConfig(config);
return encryptor;
}
}
3. 설정 테스트
@SpringBootTest
@TestPropertySource(properties = {
"app.name=Test Application",
"app.api.timeout=1000",
"app.features[0]=test-feature"
})
class AppPropertiesTest {
@Autowired
private AppProperties appProperties;
@Test
void should_LoadConfigurationProperties() {
assertThat(appProperties.getName()).isEqualTo("Test Application");
assertThat(appProperties.getApi().getTimeout()).isEqualTo(1000);
assertThat(appProperties.getFeatures()).contains("test-feature");
}
}
@TestConfiguration
public class TestConfig {
@Bean
@Primary
public AppProperties testAppProperties() {
AppProperties properties = new AppProperties();
properties.setName("Test App");
return properties;
}
}
🎯 Part 2 정리
핵심 포인트 요약
- Auto Configuration:
- @ConditionalOnXxx 어노테이션으로 조건부 빈 등록
- 사용자 설정이 Auto Configuration보다 우선
- spring.factories 파일로 자동 설정 클래스 등록
- 설정 관리:
- YAML 형식 권장 (가독성, 계층 구조)
- 프로파일별 설정 파일 분리
- 설정값 우선순위 이해
- Configuration Properties:
- @ConfigurationProperties로 타입 안전한 설정
- @Validated로 설정값 검증
- 환경 변수와 연동
- 조건부 설정:
- @ConditionalOnXxx로 상황별 빈 등록
- 커스텀 Condition 구현 가능
- 프로파일별 다른 설정 적용
실습 체크리스트
- Auto Configuration 동작 원리 이해
- 환경별 설정 파일 분리 (local, dev, prod)
- @ConfigurationProperties 클래스 작성
- 설정값 검증 (@Validated) 적용
- 조건부 빈 등록 실습
- 환경 변수 활용한 설정 외부화
- 설정 암호화 적용
- 설정 테스트 작성
다음 편 예고
Part 3: 웹 개발과 REST API에서는:
- Spring MVC vs WebFlux 선택 가이드
- REST API 설계 Best Practices
- 예외 처리와 검증 전략
- Spring Security 보안 설정
- API 문서화와 테스트 자동화
실무에서 바로 사용할 수 있는 웹 API 개발 노하우를 공유합니다! 🌐
📚 참고 자료:
🏷️ 태그: #SpringBoot #Configuration #AutoConfiguration #Properties #실무가이드
'Backend' 카테고리의 다른 글
| Spring Boot 완전 가이드 Part 5: 운영과 모니터링 (1) | 2025.08.06 |
|---|---|
| Spring Boot 완전 가이드 Part 4: 데이터 액세스와 배치 (2) | 2025.08.06 |
| Spring Boot 완전 가이드 Part 3: 웹 개발과 REST API (3) | 2025.08.06 |
| Spring Boot 완전 가이드 Part 1: 기초편 - 시작하기 (2) | 2025.08.06 |
| Redis 완전 마스터 가이드: 캐싱부터 실시간 애플리케이션까지 고성능 데이터 솔루션 (3) | 2025.07.23 |