개발 slecs

JSP 워밍업 의존성 주입 조건 분기 버그 수정

목차

JSP 워밍업이라는 개념부터 짚고 가자면, 서블릿 기반 웹 앱에서 JSP는 최초 요청 시 컴파일과 클래스 로딩 과정을 거친다. 그 비용이 첫 사용자 요청에 고스란히 전가되는 구조다. JspWarmupRunner는 그걸 서버 기동 시점에 미리 처리해서 첫 요청 지연을 줄이는 컴포넌트인데, 보통 ApplicationListenerCommandLineRunner 같은 훅에 물려 앱이 뜨자마자 주요 JSP를 선제로 로드한다.

문제는 이 워밍업 러너가 의존성을 주입받아 동작하는 방식에 있었다. 의존성 주입(DI)이 Spring 컨텍스트를 전제로 동작하는 만큼, 워밍업 러너처럼 기동 타이밍이 민감한 컴포넌트는 DI 방식을 잘못 쥐면 특정 환경 또는 초기화 순서에서 조용히 실패한다.

조건 분기 버그가 생기는 전형적인 패턴

이번 버그의 원인은 조건 분기 로직이었다. DI를 받아 쓰는 코드에서 이런 실수가 자주 나온다.

// 문제가 생기기 쉬운 패턴
if (someCondition) {
    // 이 경로에서는 injectedBean을 사용
    injectedBean.doSomething();
} else {
    // 이 경로에서는 injectedBean을 사용하지 않는다고 가정 — 실제론 null일 수도 있음
    fallback();
}

이 구조에서 someCondition이 특정 케이스에서만 참이 되거나, 반대로 특정 케이스에서만 거짓이 될 때 문제가 드러난다. 테스트 환경에서는 항상 한쪽 경로만 타다가, 배포 환경이나 특정 설정 조합에서 반대 분기로 빠지면서 주입이 안 된 상태의 빈을 건드리게 되는 식이다.

워밍업 러너처럼 ApplicationContext가 완전히 뜨기 전에 실행 타이밍이 당겨질 수 있는 컴포넌트는 이 위험이 배로 크다. @Autowired나 생성자 주입이 예상대로 완료되지 않은 상태에서 조건 분기가 엉뚱한 경로를 타면, 런타임 에러도 아니고 로직이 묵묵히 스킵되거나 잘못된 값을 들고 진행하는 경우도 있어서 원인 추적이 번거로워진다.

접근 순서: 재현 먼저, 수정은 그 다음

이번에 택한 순서는 재현 → 원인 파악 → 수정 → 재검증이었다. 이게 당연해 보이지만 실제로는 재현 없이 "원인이 이 코드겠지" 하고 바로 수정하는 경우가 꽤 있다. 그렇게 하면 고쳤다고 생각했는데 다른 진입 경로에서 여전히 발생하는 상황이 생긴다.

재현 시나리오를 먼저 만들면 두 가지가 좋아진다. 하나는 원인이 맞는지 확인할 기준이 생긴다는 것, 다른 하나는 수정 후 같은 시나리오로 검증하면 회귀 여부를 명확히 판단할 수 있다는 것. 이번에도 버그가 발생하는 조건을 먼저 정리한 뒤 수정했고, 수정 후 동일 시나리오를 다시 돌려서 재현되지 않는 걸 확인했다.

수정 후에는 정상 경로도 같이 체크했다. 버그 픽스가 의도치 않게 정상 흐름을 건드리는 경우가 있기 때문이다. 특히 조건 분기를 수정할 때는 한쪽 경로를 고치면서 다른 쪽의 동작을 바꾸는 실수가 나오기 쉽다.

주변 코드에 유사한 패턴이 남아 있는지도 같이 봤다. 같은 구조로 쓰인 코드가 여러 곳에 있으면, 이번에 잡은 버그가 비슷한 조건에서 다른 곳에서도 터질 수 있다. 이번엔 변경 파일이 1개였지만, 동일한 실수 패턴이 반복되지 않도록 명확한 조건 분기나 방어 코드를 추가하는 것도 함께 검토했다.

DI와 조건 분기를 같이 쓸 때 체크할 것들

의존성 주입이 개입된 컴포넌트에서 조건 분기를 쓸 때 챙길 부분을 정리하면 이렇다.

항목 확인 포인트
주입 시점 컴포넌트가 실행되는 시점에 빈이 이미 초기화되어 있는가
null 가능성 선택적 의존성(@Autowired(required = false))이라면 null 체크가 모든 분기에 있는가
분기 누락 새 조건이 추가될 때 기존 분기 가정이 깨지지 않는가
환경 차이 로컬/스테이징/프로덕션 설정 조합에서 모두 같은 경로를 타는가
회귀 테스트 정상 경로와 버그 경로 양쪽을 커버하는 케이스가 있는가

특히 워밍업 러너처럼 기동 초기에 실행되는 컴포넌트는 빈 초기화 순서(@DependsOn, @Order, SmartLifecycle 등)와 함께 검토하는 게 맞다. 애플리케이션이 완전히 준비된 뒤에 실행되길 원한다면 ApplicationReadyEvent를 쓰는 게 CommandLineRunner보다 안전한 경우가 많다.


변경 파일 1개짜리 커밋이지만, 조건 분기 하나가 워밍업 전체를 오동작시킬 수 있다는 점에서 영향 범위는 좁지 않았다. 특정 케이스에서만 발생하는 버그일수록 재현 조건을 명확히 정의하는 데 공을 들이는 게 맞는 것 같다. 재현 시나리오가 있어야 수정이 맞는지도 알고, 나중에 비슷한 버그가 들어왔을 때 비교 기준도 생긴다.

댓글 0

첫 댓글 달아줘.