도입
JSP는 HTML 안에 Java 기반의 동적 처리를 섞어 서버에서 응답 페이지를 만들기 위한 기술입니다. 하지만 서버가 JSP 파일을 그대로 실행하는 것은 아닙니다.
JSP 컨테이너는 JSP 파일을 Servlet 형태의 Java 코드로 변환하고, 이를 컴파일한 뒤 Servlet 객체처럼 요청을 처리합니다. 즉 JSP의 실제 서버 사이드 실행 모델은 Servlet 생명주기와 매우 밀접하게 연결됩니다.
그래서 JSP 생명주기를 이해한다는 것은 단순히 jspInit(), _jspService(), jspDestroy() 세 메서드를 외우는 일이 아니라, JSP가 서버 안에서 언제 Servlet으로 바뀌고 언제 재사용되며 언제 제거되는지 이해하는 일에 가깝습니다.
필요성
JSP 코드 안에 작성한 내용은 모두 같은 시점에 실행되지 않습니다. 선언부에 작성한 메서드와 필드는 Servlet 클래스의 멤버가 되고, 스크립틀릿이나 표현식은 요청마다 실행되는 서비스 메서드 쪽으로 들어갑니다.
이 차이를 모르면 한 번만 초기화해야 할 자원을 요청마다 만들거나, 요청별로 달라야 하는 데이터를 인스턴스 필드에 저장해 동시성 문제를 만들 수 있습니다.
즉 JSP 생명주기는 성능 최적화보다 먼저, 서버 사이드 실행 위치와 상태 범위를 정확히 구분하기 위해 필요합니다.
- JSP가 실제로는 Servlet으로 변환되어 실행된다는 점을 이해할 수 있음
- 최초 요청 시 느릴 수 있는 이유를 번역·컴파일 과정으로 설명할 수 있음
jspInit(),_jspService(),jspDestroy()의 책임을 구분할 수 있음- 요청별 상태와 JSP 인스턴스 상태를 섞어 생기는 동시성 문제를 줄일 수 있음
정의
JSP 컨테이너는 JSP 파일을 읽어 Servlet 기반의 구현 클래스를 생성합니다. 이 과정은 보통 translation, compilation, class loading, instantiation, initialization, request processing, destruction 으로 설명됩니다.
HTTP 기반 JSP 페이지는 HttpJspPage 인터페이스 관점에서 볼 수 있고, 요청 처리는 _jspService(HttpServletRequest request, HttpServletResponse response) 메서드에 대응됩니다.
다만 _jspService() 는 JSP 컨테이너가 JSP 본문으로부터 자동 생성하는 메서드입니다. JSP 작성자는 이 메서드를 직접 정의하지 않아야 하며, 초기화와 종료가 필요할 때는 jspInit(), jspDestroy() 를 사용합니다.
"JSP의 본질은 서버가 JSP 파일을 그대로 실행하는 것이 아니라
Servlet 구현 클래스로 변환한 뒤 Servlet 생명주기 위에서 요청을 처리한다는 데 있습니다."
핵심 원리
_jspService() 를 통해 반복 처리된다JSP 요청이 들어오면 컨테이너는 해당 JSP가 이미 변환·컴파일되어 있는지 확인합니다. 아직 준비되지 않았거나 JSP가 변경되었다면 컨테이너는 JSP를 Java Servlet 소스로 변환하고 컴파일합니다.
컴파일된 클래스는 로딩되고 인스턴스화됩니다. 그 뒤 초기화 단계에서 jspInit() 이 호출되고, 실제 클라이언트 요청은 _jspService() 로 전달됩니다.
이후 같은 JSP에 대한 요청은 보통 이미 생성된 JSP Servlet 인스턴스를 재사용합니다. 서버가 종료되거나 애플리케이션이 언로드되거나 JSP 구현 클래스가 교체될 때는 jspDestroy() 가 호출될 수 있습니다.
JSP Server-Side Lifecycle
1) Client requests .jsp
2) JSP container checks translation/compilation status
3) JSP is translated into servlet source
4) Servlet source is compiled into class
5) Class is loaded and instantiated
6) jspInit() is invoked once
7) _jspService(request, response) handles each request
8) jspDestroy() is invoked before unload/destruction
생명주기 단계
| 단계 | 서버 내부 동작 | 실무 해석 |
|---|---|---|
| Translation | JSP 파일을 Servlet Java 소스로 변환 | JSP 문법 오류나 directive 오류가 드러나는 단계 |
| Compilation | 생성된 Java 소스를 class 파일로 컴파일 | Java 코드 오류, import 오류, 타입 오류가 드러나는 단계 |
| Class Loading | 컴파일된 JSP Servlet 클래스를 로딩 | 컨테이너 classloader 영역에서 처리됨 |
| Instantiation | JSP Servlet 인스턴스 생성 | 요청마다 새로 만드는 것이 아니라 보통 재사용됨 |
| Initialization | jspInit() 호출 |
페이지 단위 초기화가 필요한 경우 사용 |
| Request Processing | _jspService() 호출 |
각 HTTP 요청마다 실행되는 핵심 처리 단계 |
| Destruction | jspDestroy() 호출 |
자원 정리나 종료 처리에 사용 |
핵심 메서드
jspInit(), _jspService(), jspDestroy() 는 JSP 생명주기의 핵심 메서드이며, 작성자가 직접 다룰 수 있는 범위와 컨테이너가 생성하는 범위가 다르다| 메서드 | 호출 시점 | 작성자 정의 가능 여부 | 실무 해석 |
|---|---|---|---|
jspInit() |
JSP 페이지 초기화 시점 | 가능 | 페이지 단위 자원 초기화에 사용 |
_jspService() |
각 요청 처리 시점 | 직접 정의하면 안 됨 | JSP 본문이 변환되어 들어가는 요청 처리 메서드 |
jspDestroy() |
JSP 페이지 소멸 직전 | 가능 | 페이지 단위 자원 해제에 사용 |
핵심은 _jspService() 입니다. 이 메서드는 JSP 페이지의 body에 대응하고, 컨테이너가 자동으로 생성합니다. JSP 작성자는 요청마다 실행될 코드를 JSP 본문에 작성하는 것이지, _jspService() 를 직접 오버라이드하는 방식으로 작성하지 않습니다.
기본 구조
example.jsp
↓ translation
Generated Servlet Java Source
↓ compilation
Generated Servlet Class
↓ loading / instantiation
JSP Servlet Instance
↓ initialization
jspInit()
↓ request
_jspService(request, response)
↓ unload
jspDestroy()
이 구조 때문에 JSP의 첫 요청이 상대적으로 느릴 수 있습니다. 최초 요청 시 번역과 컴파일이 함께 일어날 수 있기 때문입니다. 운영 환경에서는 컨테이너 설정이나 사전 컴파일 전략으로 이 비용을 줄이기도 합니다.
기본 구현
jspInit(), jspDestroy() 를 정의해 표현할 수 있지만, 요청 처리 본문은 컨테이너가 _jspService() 로 변환한다<%!
public void jspInit() {
System.out.println("JSP initialized");
}
public void jspDestroy() {
System.out.println("JSP destroyed");
}
%>
<html>
<body>
<h1>Hello JSP</h1>
<p>Request URI: <%= request.getRequestURI() %></p>
</body>
</html>
// 개념적으로 보면 JSP는 이런 Servlet 구조로 변환된다.
// 실제 생성 코드는 컨테이너 구현체마다 다를 수 있다.
public final class example_jsp extends HttpServlet implements HttpJspPage {
public void jspInit() {
System.out.println("JSP initialized");
}
public void jspDestroy() {
System.out.println("JSP destroyed");
}
public void _jspService(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
JspWriter out = null;
// JSP의 템플릿 텍스트, 표현식, 스크립틀릿 등이
// 이 요청 처리 메서드 안으로 변환된다.
}
}
jspInit() 과 jspDestroy() 는 JSP 작성자가 선언할 수 있지만, _jspService() 는 컨테이너가 자동 생성합니다. 요청마다 실행되는 코드를 직접 _jspService() 로 만들려고 하지 말고, JSP 본문·태그·EL·JSTL 구조로 작성해야 합니다.패턴 1. Translation 과 Compilation
Translation 단계에서는 JSP 지시자, 선언부, 스크립틀릿, 표현식, 정적 HTML, JSP 태그 등이 Servlet Java 소스 코드로 변환됩니다.
Compilation 단계에서는 생성된 Java 소스가 컴파일됩니다. 이때 Java 문법 오류, import 문제, 타입 문제 등이 발생할 수 있습니다.
즉 JSP 오류는 크게 JSP 문법을 해석하는 translation-time error 와 Java 컴파일 과정에서 나타나는 compile-time error 로 나눠 볼 수 있습니다.
JSP Error 관점
- Translation error
- 잘못된 directive
- 잘못된 JSP 문법
- 태그 라이브러리 해석 문제
- Compilation error
- Java 문법 오류
- import 누락
- 타입 불일치
- 메서드/필드 참조 오류
패턴 2. 요청 처리는 _jspService 중심
_jspService() 안으로 들어가며, 이 메서드는 요청마다 호출된다JSP에서 HTML 템플릿, EL, 표현식, 스크립틀릿은 결국 요청 처리 코드로 변환됩니다. 따라서 요청마다 달라지는 값은 이 단계에서 계산됩니다.
예를 들어 request, response, session, application, out 같은 implicit object는 _jspService() 안에서 요청 처리에 사용되는 객체로 이해할 수 있습니다.
즉 JSP 화면에서 매번 달라지는 출력은 대부분 _jspService() 실행 결과입니다.
패턴 3. jspInit 과 jspDestroy
jspInit() 은 페이지 인스턴스 초기화, jspDestroy() 는 페이지 인스턴스 소멸 직전 정리에 대응하며, 요청마다 실행되는 메서드가 아니다jspInit() 은 JSP 페이지가 초기화될 때 호출됩니다. 따라서 요청마다 반복 생성할 필요가 없는 페이지 단위 리소스 초기화가 필요할 때 사용할 수 있습니다.
jspDestroy() 는 JSP 페이지가 소멸되기 직전에 호출됩니다. 파일 핸들, 커넥션, 캐시 등 페이지 인스턴스가 보유한 리소스를 정리할 때 사용할 수 있습니다.
다만 JSP에 직접 리소스 관리 코드를 많이 넣는 설계는 현대 웹 애플리케이션에서는 권장되는 방향이 아닙니다. 보통은 Servlet, Controller, Service, DI 컨테이너 같은 상위 구조에서 자원 수명을 관리하는 편이 더 안전합니다.
패턴 4. 컨테이너 구현체 관점
예를 들어 Tomcat은 Jasper JSP 엔진을 사용해 JSP를 처리합니다. 개발 환경에서는 JSP 변경 감지와 재컴파일이 중요하고, 운영 환경에서는 첫 요청 지연을 줄이기 위해 사전 컴파일이나 production 설정을 고려할 수 있습니다.
Tomcat Jasper는 JSP 변경 시 백그라운드 컴파일을 수행하고, 새 JSP 컴파일이 성공하면 기존 컴파일 결과를 대체할 수 있는 방식도 제공합니다. 이런 동작은 JSP 생명주기의 큰 흐름 위에서 구현체가 제공하는 최적화로 볼 수 있습니다.
즉 JSP 생명주기는 표준적으로 이해하되, 실제 운영 문제를 볼 때는 사용하는 컨테이너의 JSP 엔진 설정까지 함께 확인해야 합니다.
한계와 주의점
첫째, JSP 인스턴스는 요청마다 새로 만들어지는 객체가 아닙니다. 따라서 선언부에 둔 필드가 모든 요청에 대해 안전한 요청별 상태처럼 동작한다고 생각하면 위험합니다.
둘째, JSP는 요청 처리 시점에 여러 스레드가 같은 인스턴스를 사용할 수 있습니다. 따라서 인스턴스 필드에 요청별 데이터를 저장하면 race condition이 발생할 수 있습니다.
셋째, JSP에 Java 로직을 많이 넣으면 변환된 Servlet 코드가 복잡해지고, 컴파일 오류를 추적하기도 어려워집니다. 현대적인 구조에서는 JSP는 View 역할에 가깝게 두고, 비즈니스 로직은 Controller나 Service 계층으로 분리하는 편이 안전합니다.
- JSP는 요청마다 새 객체로 생성된다고 생각하면 안 됨
- 선언부 필드는 요청별 안전한 상태 저장소가 아님
_jspService()는 컨테이너가 자동 생성하므로 직접 정의하지 않아야 함- JSP 안에 복잡한 Java 로직을 넣으면 변환·컴파일·디버깅이 어려워짐
- 컨테이너별 JSP 재컴파일 설정과 운영 모드는 별도로 확인해야 함
자주 하는 실수
- JSP 파일이 서버에서 그대로 실행된다고 생각함
- JSP 요청마다 새로운 JSP 객체가 생성된다고 오해함
jspInit()이 요청마다 호출된다고 생각함_jspService()를 JSP 작성자가 직접 정의하려고 함- 선언부 필드에 사용자별 요청 상태를 저장함
- JSP 변경 후 재컴파일 여부를 컨테이너 설정과 분리해서 생각하지 않음
- JSP 안에 DB 연결, 비즈니스 로직, 대량 처리 로직을 직접 넣음
실무 루틴
- JSP는 최종적으로 Servlet으로 변환된다는 점을 전제로 본다.
- 요청마다 달라지는 데이터는
request,session, model attribute 같은 요청/세션 범위로 전달한다. - JSP 선언부 필드에 사용자별 상태를 저장하지 않는다.
- 초기화가 꼭 필요할 때만
jspInit()을 검토하고, 가능하면 상위 애플리케이션 구조에서 관리한다. - 종료 처리가 꼭 필요할 때만
jspDestroy()를 검토하고, 자원 수명은 컨테이너나 프레임워크에 맡기는 편을 우선 고려한다. - 운영 환경에서는 JSP 사전 컴파일, 재컴파일 설정, 생성 소스 위치를 컨테이너 문서 기준으로 확인한다.
디버깅
_jspService() 안에서 실행되는 표현식, EL, 태그, 스크립틀릿을 의심한다.점검 체크리스트
- translation error 인가
- compilation error 인가
- request processing error 인가
- 생성된 Servlet 소스를 확인했는가
- jspInit / _jspService / jspDestroy 호출 시점을 혼동하지 않았는가
- 선언부 필드에 요청별 상태를 저장하지 않았는가
- 컨테이너의 JSP 재컴파일 설정이 기대와 맞는가
요약
jspInit(), 요청 처리는 컨테이너 생성 _jspService(), 소멸은 jspDestroy() 를 통해 이루어진다는 데 있다- ✅ JSP는 서버에서 Servlet 구현 클래스로 변환되어 실행된다.
- ✅ JSP 생명주기는 translation, compilation, loading, instantiation, initialization, service, destruction 으로 볼 수 있다.
- ✅
jspInit()은 JSP 페이지 초기화 시점에 호출된다. - ✅
_jspService()는 JSP 본문에 대응하며 컨테이너가 자동 생성한다. - ✅ JSP 작성자는
_jspService()를 직접 정의하면 안 된다. - ✅
jspDestroy()는 JSP 페이지 소멸 직전에 호출된다. - ✅ JSP 인스턴스는 요청마다 새로 만들어지는 것이 아니므로 인스턴스 필드에 요청별 상태를 저장하면 위험하다.
- ✅ 실제 재컴파일, 캐싱, 생성 소스 위치는 Tomcat Jasper 같은 컨테이너 구현체 설정의 영향을 받는다.