Spring 프레임워크 핵심 메커니즘 완벽 분석

빈 생명주기와 확장 포인트

Spring 컨테이너는 빈을 생성하고 관리하는 과정에서 여러 단계의 생명주기를 거칩니다. 각 단계마다 개발자가 커스터마이징할 수 있는 인터페이스와 애노테이션이 제공됩니다.

생명주기 단계별 흐름

  1. 인스턴스 생성: 생성자나 정적 팩토리 메서드를 통해 객체 생성
  2. 의존성 주입: 프로퍼티 설정 및 의존 객체 주입
  3. 의존성 인식 콜백: BeanNameAware, BeanFactoryAware, ApplicationContextAware 순으로 콜백 실행
  4. 초기화 전 후처리: BeanPostProcessor의 postProcessBeforeInitialization 실행
  5. 초기화 콜백: InitializingBean.afterPropertiesSet() 또는 @PostConstruct 메서드 실행
  6. 커스텀 초기화: @Bean(initMethod)로 지정한 메서드 실행
  7. 초기화 후 후처리: BeanPostProcessor의 postProcessAfterInitialization 실행
  8. 소멸 콜백: DisposableBean.destroy() 또는 @PreDestroy 메서드 실행
  9. 커스텀 소멸: @Bean(destroyMethod)로 지정한 메서드 실행

실제 활용 예시

@Component
public class CustomComponent implements InitializingBean, DisposableBean {
    
    @PostConstruct
    public void setup() {
        System.out.println("초기화 애노테이션 방식");
    }
    
    @Override
    public void afterPropertiesSet() {
        System.out.println("인터페이스 방식 초기화");
    }
    
    @PreDestroy
    public void cleanup() {
        System.out.println("소멸 애노테이션 방식");
    }
    
    @Override
    public void destroy() {
        System.out.println("인터페이스 방식 소멸");
    }
}

순환 의존성 해결: 3단계 캐시 아키텍처

Spring은 생성 중인 빈을 임시 보관하는 3개의 캐시를 활용하여 순환 의존성 문제를 해결합니다.

캐시 구조

캐시명용도키-값 타입
singletonObjects완전히 초기화된 싱글톤 빈String → Object
earlySingletonObjects프록시 적용된 미완성 빈String → Object
singletonFactories빈 생성 팩토리 (ObjectFactory)String → ObjectFactory<?>

순환 의존 해결 메커니즘

A → B → A 형태의 순환 의존이 있을 때:

  1. A 생성 시작, singletonFactories에 A의 ObjectFactory 등록
  2. A의 의존성 B 주입 시도
  3. B 생성 시작, singletonFactories에 B의 ObjectFactory 등록
  4. B의 의존성 A 주입 시도
  5. singletonFactories에서 A의 팩토리 발견 → 프록시/원본 객체 조기 반환
  6. B 완성, singletonObjects에 등록
  7. A에 B 주입 완료, A 초기화 완료

핵심 소스 코드 분석

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 1단계: 완성된 빈 확인
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 2단계: 미완성 프록시 빈 확인
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
            // 3단계: 팩토리에서 조기 생성
            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
            if (singletonFactory != null) {
                singletonObject = singletonFactory.getObject();
                this.earlySingletonObjects.put(beanName, singletonObject);
                this.singletonFactories.remove(beanName);
            }
        }
    }
    return singletonObject;
}

AOP 구현과 프록시 생성

@Aspect 기반 선언적 방식

@Aspect
@Component
public class PerformanceMonitor {
    
    @Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
    public void transactionalOperation() {}
    
    @Around("transactionalOperation()")
    public Object measureExecutionTime(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.nanoTime();
        try {
            return pjp.proceed();
        } finally {
            long elapsed = System.nanoTime() - start;
            System.out.printf("%s 실행 시간: %dms%n", 
                pjp.getSignature(), elapsed / 1_000_000);
        }
    }
}

프록시 생성 전략

Spring AOP는 타겟 클래스의 특성에 따라 두 가지 프록시 방식을 선택합니다:

  • JDK 동적 프록시: 인터페이스 기반 - java.lang.reflect.Proxy 활용
  • CGLIB 프록시: 클래스 기반 - 타겟 클래스를 상속하여 서브클래스 생성
// CGLIB 기반 프록시 예시
public class Service$$SpringCGLIB$$0 extends DemoService {
    private MethodInterceptor interceptor;
    
    @Override
    public void execute() {
        return (MethodInterceptor) interceptor.intercept(
            this, 
            executeMethod, 
            CGLIB$emptyArgs, 
            CGLIB$execute$Proxy
        );
    }
}

트랜잭션 관리 아키텍처

선언적 트랜잭션 설정

@Service
public class OrderProcessor {
    
    @Transactional(
        propagation = Propagation.REQUIRED,
        isolation = Isolation.READ_COMMITTED,
        timeout = 30,
        rollbackFor = {SQLException.class, BusinessException.class},
        noRollbackFor = {NoRollbackException.class}
    )
    public void processOrder(Long orderId) {
        // 트랜잭션 로직
    }
}

전파 속성 상세

전파 유형동작 방식사용 시나리오
REQUIRED기존 트랜잭션 참여 또는 신규 생성기본값, 대부분의 비즈니스 로직
REQUIRES_NEW항상 새 트랜잭션, 기존 트랜잭션 일시 중지독립적 커밋이 필요한 로직
NESTED중첩 트랜잭션 생성 (세이브포인트)부분 롤백이 필요한 경우
MANDATORY반드시 기존 트랜잭션 필요트랜잭션 컨텍스트 강제 검증

DataSourceTransactionManager 동작 흐름

  1. doBegin(): Connection 획득 및 auto-commit 비활성화
  2. TransactionSynchronizationManager에 Connection 바인딩
  3. 비즈니스 로직 실행
  4. 예외 발생 시 doRollback(), 정상 시 doCommit()
  5. doCleanupAfterCompletion(): Connection 반납 및 동기화 정리

고급 애노테이션 활용

환경별 빈 구성

@Configuration
public class DataSourceConfig {
    
    @Bean
    @Profile("local")
    public DataSource embeddedDataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .build();
    }
    
    @Bean
    @Profile("production")
    public DataSource pooledDataSource(
        @Value("${db.url}") String url,
        @Value("${db.user}") String username,
        @Value("${db.pass}") String password) {
        
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl(url);
        config.setUsername(username);
        config.setPassword(password);
        return new HikariDataSource(config);
    }
}

지연 초기화와 의존성 제어

@Configuration
public class LazyConfiguration {
    
    @Bean
    @Lazy  // 첫 사용 시점에 초기화
    public HeavyResource heavyResource() {
        return new HeavyResource();
    }
    
    @Bean
    @DependsOn("databaseInitializer")  // 명시적 생성 순서 지정
    public CacheManager cacheManager() {
        return new CacheManager();
    }
}

다중 빈 선택 전략

public interface NotificationSender {
    void send(String message, String recipient);
}

@Component
@Primary  // 기본 구현체 지정
public class EmailNotificationSender implements NotificationSender { }

@Component
@Qualifier("sms")  // 한정자 지정
public class SmsNotificationSender implements NotificationSender { }

@Service
public class AlertService {
    
    @Autowired
    private NotificationSender defaultSender;  // EmailNotificationSender 주입
    
    @Autowired
    @Qualifier("sms")
    private NotificationSender smsSender;  // 명시적 선택
}

예외 처리 중앙화

@RestControllerAdvice(basePackages = "com.example.api")
public class GlobalExceptionHandler {
    
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidationErrors(
            MethodArgumentNotValidException ex) {
        
        List<String> errors = ex.getBindingResult()
            .getFieldErrors()
            .stream()
            .map(e -> e.getField() + ": " + e.getDefaultMessage())
            .collect(Collectors.toList());
        
        return ResponseEntity.badRequest()
            .body(new ErrorResponse("VALIDATION_FAILED", errors));
    }
    
    @ExceptionHandler(DataAccessException.class)
    public ResponseEntity<ErrorResponse> handleDatabaseErrors(
            DataAccessException ex) {
        
        return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
            .body(new ErrorResponse("DATABASE_ERROR", "일시적 오류 발생"));
    }
}

태그: Spring Framework IoC Container AOP Transaction Management CGLIB Proxy

10월 6일 22:56에 게시됨