안녕하세요! 지난 글에서는 스프링의 의존성 주입(DI)에 대해 살펴보았는데요, 오늘은 그 외에 스프링의 또 다른 핵심 개념들을 탐구해보려 합니다. 특히 빈(Bean) 라이프사이클 관리, 컴포넌트 스캔, 그리고 관점 지향 프로그래밍(AOP)에 대해 제가 배우고 경험한 내용을 공유하고자 합니다.
🌱 Bean의 생애주기: 탄생부터 소멸까지
처음 스프링을 배울 때, "이 빈은 어디서 태어나서 언제 죽는 거지?"라는 의문이 들었습니다. 스프링 컨테이너는 빈의 전체 생명주기를 관리하는데, 이는 생각보다 더 섬세하게 제어할 수 있습니다.
@Component
public class DatabaseConnector {
@PostConstruct
public void initialize() {
System.out.println("데이터베이스 연결 초기화...");
// DB 연결 풀 생성, 설정 로드 등
}
public void executeQuery(String sql) {
// SQL 실행 로직
}
@PreDestroy
public void cleanup() {
System.out.println("데이터베이스 연결 정리...");
// 열린 연결 닫기, 리소스 해제 등
}
}
이 코드는 빈의 생명주기 콜백을 보여줍니다. @PostConstruct
로 표시된 메소드는 빈이 생성되고 의존성이 주입된 직후에 호출됩니다. 반면, @PreDestroy
는 빈이 소멸되기 직전에 호출됩니다.
실제 프로젝트에서의 활용
데이터베이스 프로젝트에서 이 기능을 활용했을 때의 경험이 생생합니다. 초기에는 연결 풀 초기화 코드를 서비스 클래스 생성자에 넣었는데, 이로 인해 테스트가 느려지고 복잡해졌습니다. @PostConstruct
를 사용하면서 초기화 로직을 명확히 분리할 수 있었고, 테스트 시에는 이 메소드를 별도로 호출하지 않아도 되었습니다.
특히 @PreDestroy
는 자원 누수 방지에 큰 도움이 되었습니다. 애플리케이션이 정상적으로 종료될 때 열린 연결을 모두 정리하므로 안심할 수 있었죠.
📦 컴포넌트 스캔: 마법 같은 빈 등록
스프링을 처음 배울 때 "어떻게 클래스에 @Component
만 붙이면 스프링이 알아서 찾아주는 걸까?"라는 의문이 들었습니다. 이것이 바로 컴포넌트 스캔의 마법입니다.
@Configuration
@ComponentScan(basePackages = {"com.myapp.services", "com.myapp.repositories"})
public class AppConfig {
// 설정 코드
}
이 설정으로 스프링은 지정된 패키지와 그 하위 패키지를 스캔하여 @Component
, @Service
, @Repository
, @Controller
등의 어노테이션이 붙은 클래스를 자동으로 빈으로 등록합니다.
고민과 해결책
대규모 프로젝트에서 컴포넌트 스캔은 양날의 검이었습니다. 빈 설정 코드가 크게 줄어드는 장점이 있었지만, 패키지가 많아지면서 어떤 클래스가 빈으로 등록되는지 파악하기 어려웠습니다.
이를 해결하기 위해 도입한 전략:
- 명확한 패키지 구조: 기능별로 패키지를 세분화하여 컴포넌트 스캔 범위를 명확히 했습니다.
- 명시적인 빈 이름: 중요한 빈에는
@Component("userService")
처럼 명시적 이름을 부여했습니다. - 문서화: 핵심 컴포넌트는 별도 문서로 관리하여 팀원 모두가 빈 구조를 이해할 수 있게 했습니다.
✂️ AOP: 코드의 반복을 고급스럽게 제거하기
관점 지향 프로그래밍(AOP)은 처음에는 이해하기 어려웠지만, 지금은 스프링에서 가장 애용하는 기능 중 하나입니다.
@Aspect
@Component
public class PerformanceAspect {
@Around("execution(* com.myapp.services.*.*(..))")
public Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long executionTime = System.currentTimeMillis() - start;
System.out.println(joinPoint.getSignature() + " 실행 시간: " + executionTime + "ms");
return result;
}
}
이 코드는 서비스 계층의 모든 메소드 실행 시간을 측정하는 애스펙트입니다. 핵심 비즈니스 로직을 수정하지 않고도 성능 모니터링을 추가할 수 있죠.
🔧 스프링 활용법
1. 프로파일을 활용한 환경별 설정
개발, 테스트, 운영 환경에 따라 다른 설정을 적용해야 하는 상황에서 스프링 프로파일은 정말 유용했습니다.
@Configuration
@Profile("dev")
public class DevDataSourceConfig {
@Bean
public DataSource dataSource() {
// 개발용 인메모리 데이터베이스 설정
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
}
@Configuration
@Profile("prod")
public class ProdDataSourceConfig {
@Bean
public DataSource dataSource() {
// 운영용 데이터베이스 설정
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("org.postgresql.Driver");
dataSource.setUrl("jdbc:postgresql://production-server:5432/myapp");
// 추가 설정...
return dataSource;
}
}
2. 조건부 빈 등록
때로는 특정 조건에 따라 빈을 등록해야 할 때가 있습니다. @Conditional
어노테이션이 이런 상황에서 큰 도움이 되었습니다.
@Bean
@ConditionalOnProperty(name = "cache.enabled", havingValue = "true")
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager();
}
이 코드는 cache.enabled
속성이 true
로 설정된 경우에만 캐시 매니저 빈을 등록합니다.
💭 개인적인 배움과 고민
스프링을 배우면서 가장 큰 깨달음은 "강력한 도구는 책임감 있게 사용해야 한다"는 것이었습니다. 스프링은 정말 많은 문제를 해결해주지만, 모든 기능을 무분별하게 사용하면 오히려 복잡성만 증가합니다.
고민했던 지점들:
"얼마나 자동화할 것인가?": 컴포넌트 스캔과 자동 설정은 편리하지만, 때로는 명시적 설정이 더 명확할 수 있습니다. 특히 대규모 팀에서는 더욱 그렇습니다.
"AOP를 언제 사용할 것인가?": 모든 횡단 관심사에 AOP를 적용하는 것이 항상 최선은 아닙니다. 때로는 간단한 유틸리티 메소드가 더 이해하기 쉬울 수 있습니다.
"스프링의 편리함과 투명성 사이의 균형": 스프링의 '마법 같은' 기능은 편리하지만, 새로운 팀원이나 나중에 코드를 유지보수할 때 이해하기 어려울 수 있습니다.
현재의 접근 방식:
명시적인 것을 선호합니다: 핵심 비즈니스 로직에서는 가능한 한 스프링의 '마법'을 줄이고 코드 흐름을 명확히 합니다.
기술적 세부사항만 AOP로 분리합니다: 로깅, 보안, 트랜잭션 같은 기술적 관심사는 AOP로 분리하지만, 비즈니스 로직 자체는 명확히 보이게 합니다.
문서화와 팀 회의에 투자합니다: 사용한 스프링 기능에 대해 문서화하고, 팀원들과 정기적으로 지식을 공유합니다.
마무리
스프링은 정말 강력한 프레임워크지만, 그 힘을 효과적으로 사용하는 것은 여정이자 예술입니다. 처음에는 모든 기능을 사용하고 싶은 유혹이 있었지만, 경험이 쌓이면서 '적절한' 사용이 '최대한' 사용보다 중요하다는 것을 깨달았습니다.
스프링을 사용할 때 제가 항상 되묻는 질문은 "이 기능이 지금 정말 필요한가?"입니다. 단순함을 유지하면서 필요할 때만 스프링의 고급 기능을 활용하는 것이 장기적으로 더 나은 코드베이스를 만드는 비결이라고 생각합니다.
'백엔드' 카테고리의 다른 글
Spring Boot 게시판 프로젝트에 JPA & Spring Security 통합 가이드 (0) | 2025.03.20 |
---|---|
웹 게시판 구현을 통해 배운 Spring 실전 개발 여정 (0) | 2025.03.17 |
스프링 프레임워크의 의존성 주입(DI) 마스터하기 (0) | 2025.03.16 |
Java 제네릭 메소드 완벽 가이드: 코드의 재사용성과 안전성을 한번에! (0) | 2025.03.13 |
Java 컬렉션 프레임워크 정리 (0) | 2025.03.06 |