Servlet을 배우고 나면 자연스럽게 만나게 되는 기술이 있다. 바로 JSP(JavaServer Pages)다.
JSP(JavaServer Pages)는 HTML 안에 Java 코드를 삽입할 수 있게 해주는 서버 사이드 기술이다. Servlet과 반대로, Java 코드 안에 HTML을 넣는 것이 아니라 HTML 안에 Java 코드를 넣는 방식이다.
더 정확히 말하면, JSP는 웹 페이지를 동적으로 생성하기 위한 서버 사이드 스크립트 언어다. 확장자는 .jsp를 사용하며, 기본적으로는 HTML과 동일하지만 특별한 JSP 태그들을 사용하여 동적인 내용을 생성할 수 있다.
가장 중요한 점은 JSP는 결국 Servlet으로 변환되어 실행된다는 것이다. 즉, JSP는 Servlet을 좀 더 편리하게 작성할 수 있게 해주는 도구라고 볼 수 있다.
Servlet만으로 웹 페이지를 만들 때는 HTML 출력을 위해 수많은 out.println() 문을 작성해야 했다. 회원 목록을 보여주는 간단한 테이블 하나를 만들어도 HTML 태그 하나하나를 문자열로 출력해야 했고, HTML 구조가 복잡해질수록 코드는 더욱 지저분해졌다.
또한 HTML 디자이너와 Java 개발자 간의 협업도 어려웠다. HTML의 작은 수정 하나라도 Java 코드를 고치고 다시 컴파일해야 했기 때문이다.
JSP를 사용하면 기본적으로는 HTML 문서를 작성하되, 필요한 부분에만 Java 코드를 삽입할 수 있다. 이로 인해 개발 생산성이 크게 향상되었고, 디자이너와의 협업도 훨씬 쉬워졌다.
JSP의 가장 중요한 특징은 번역(Translation) 과정을 거친다는 것이다. 이 과정을 이해하면 JSP의 모든 것을 이해할 수 있다.
JSP 컨테이너는 .jsp 파일을 읽어서 HTML 부분과 Java 코드 부분을 구분한다. 퍼센트 기호로 둘러싸인 JSP 태그들을 찾아내고 이를 적절한 Java 코드로 변환할 준비를 한다.
JSP 파일을 Java Servlet 소스 코드(.java 파일)로 변환한다. HTML 부분은 out.write() 형태로 변환되고, JSP 태그 안의 Java 코드는 그대로 Java 메서드 안에 삽입된다. 표현식은 out.print() 형태로 변환된다.
생성된 Java 소스 코드를 컴파일하여 .class 파일을 만든다. 이 과정에서 문법 오류가 있다면 컴파일 에러가 발생한다.
컴파일된 클래스 파일을 메모리에 로딩하고, 일반적인 Servlet처럼 실행한다.
중요한 점: 이 번역 과정은 JSP 파일이 수정되지 않는 한 한 번만 수행된다. 따라서 첫 번째 요청은 번역 시간 때문에 조금 느릴 수 있지만, 이후 요청들은 빠르게 처리된다.
JSP에서 Java 코드를 삽입하는 방법은 크게 세 가지다.
퍼센트 기호와 열린 괄호, 닫힌 괄호로 둘러싸인 형태로 일반적인 Java 문장들을 작성한다. 변수 선언, 조건문, 반복문, 메서드 호출 등이 모두 가능하다.
스크립틀릿 안에서는 문자열 변수를 선언하고, 나이를 확인해서 성인인지 미성년자인지 판단하는 로직을 작성할 수 있다. 반복문을 사용해서 목록을 출력하는 것도 가능하다.
주의사항: 스크립틀릿 안의 코드는 변환된 Servlet의 service 메서드 안에 그대로 삽입된다. 따라서 메서드 정의는 할 수 없고, 지역 변수나 제어문만 사용 가능하다.
퍼센트 기호와 등호, 닫힌 퍼센트 기호로 둘러싸인 형태로 값을 출력할 때 사용한다. 변수명이나 메서드 호출 등 값을 반환하는 Java 표현식을 작성한다. 세미콜론을 붙이지 않는다는 것이 중요하다.
이름과 나이를 출력하거나, 현재 시간을 보여주거나, 간단한 계산 결과를 표시할 때 사용한다. 표현식은 내부적으로 out.print() 메서드로 변환되어 결과값이 브라우저로 출력된다.
퍼센트 기호와 느낌표, 닫힌 퍼센트 기호로 둘러싸인 형태로 멤버 변수나 메서드를 정의할 때 사용한다. 카운터 변수를 선언하거나 현재 시간을 반환하는 메서드를 정의할 수 있다.
주의사항: 선언문으로 선언된 변수는 모든 사용자가 공유하는 멤버 변수가 된다. 동시성 문제가 발생할 수 있으므로 신중하게 사용해야 한다. 일반적으로는 상수나 유틸리티 메서드 정의에만 사용하는 것이 좋다.
지시어는 JSP 페이지의 전체적인 설정을 담당한다. 퍼센트 기호와 골뱅이 기호로 시작하는 형태로 사용한다.
페이지의 속성을 설정하는 가장 중요한 지시어다. 대부분의 JSP 파일 맨 위에 위치한다.
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ page import="java.util.Date, java.util.List" %>
<%@ page errorPage="error.jsp" %>
주요 속성들:
contentType: 응답의 MIME 타입과 문자 인코딩을 설정한다. 한글을 사용한다면 반드시 UTF-8로 설정해야 한다. "text/html; charset=UTF-8" 형태로 작성한다.
language: 스크립트 언어의 유형을 지정한다. 기본값은 java이므로 보통 생략한다.
import: Java 클래스를 import한다. 여러 클래스는 쉼표로 구분하거나 여러 번 사용할 수 있다. java.util 패키지의 모든 클래스나 특정 모델 클래스들을 import할 때 사용한다.
errorPage: 페이지에서 예외가 발생했을 때 보여줄 에러 페이지를 지정한다. error.jsp 같은 파일을 지정할 수 있다.
pageEncoding: JSP 파일 자체의 인코딩을 설정한다. 보통 UTF-8로 설정한다.
다른 파일의 내용을 현재 JSP 페이지에 포함시킨다. 번역 시점에 포함되므로 정적 include라고도 한다. 헤더, 푸터 등 공통 부분을 모듈화할 때 유용하다.
<%@ include file="header.jsp" %>
<main>
<h1>메인 콘텐츠</h1>
<p>여기는 페이지별 고유 내용입니다.</p>
</main>
<%@ include file="footer.jsp" %>
header.jsp와 footer.jsp 같은 공통 파일을 만들어 여러 페이지에서 재사용할 수 있다. 이로 인해 코드 중복을 줄이고 유지보수성을 향상시킬 수 있다.
커스텀 태그 라이브러리를 사용할 때 선언한다. JSTL(JSP Standard Tag Library) 사용 시 반드시 필요하다. uri와 prefix 속성을 통해 태그 라이브러리를 등록한다.
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<c:forEach var="item" items="${list}">
<p>${item.name}</p>
</c:forEach>
<fmt:formatDate value="${now}" pattern="yyyy-MM-dd" />
JSP에서는 별도의 선언 없이 사용할 수 있는 내장 객체들이 제공된다. 이 객체들이 JSP 개발의 핵심이다.
클라이언트의 요청 정보를 담고 있는 HttpServletRequest 타입의 객체다. 가장 많이 사용되는 내장 객체 중 하나다.
getParameter() 메서드로 폼에서 전송된 데이터를 받을 수 있다. 사용자가 입력한 이름이나 이메일 등을 가져올 때 사용한다. getParameterValues() 메서드는 체크박스처럼 여러 값을 선택할 수 있는 항목의 값들을 배열로 받는다.
getHeader() 메서드로 HTTP 헤더 정보를 얻을 수 있다. User-Agent 헤더를 통해 사용자의 브라우저 정보를 알 수 있고, getRemoteAddr() 메서드로 클라이언트의 IP 주소를 얻을 수 있다.
실무 팁: getParameter() 메서드는 null을 반환할 수 있으므로 반드시 null 체크를 해야 한다.
클라이언트에게 보낼 응답을 구성하는 HttpServletResponse 타입의 객체다.
sendRedirect() 메서드로 다른 페이지로 리다이렉트할 수 있다. 로그인 성공 후 메인 페이지로 이동하거나, 권한이 없을 때 로그인 페이지로 이동시킬 때 사용한다.
addCookie() 메서드로 쿠키를 설정할 수 있다. 사용자의 로그인 정보나 선호 설정을 저장할 때 유용하다. setContentType() 메서드로 응답의 콘텐츠 타입을 설정할 수도 있다.
사용자별 세션 정보를 관리하는 HttpSession 타입의 객체다. 로그인 상태 유지의 핵심이다.
setAttribute() 메서드로 세션에 데이터를 저장하고, getAttribute() 메서드로 저장된 데이터를 가져온다. 로그인 시 사용자 ID와 이름을 세션에 저장해두면, 다른 페이지에서도 로그인 상태를 확인할 수 있다.
invalidate() 메서드로 세션을 무효화할 수 있다. 로그아웃 기능을 구현할 때 사용한다. setMaxInactiveInterval() 메서드로 세션 타임아웃을 설정할 수도 있다.
클라이언트에게 내용을 출력하는 JspWriter 타입의 객체다. 표현식을 사용하면 내부적으로 이 객체의 print() 메서드가 호출된다.
println() 메서드로 HTML 태그를 동적으로 생성할 수 있다. 조건에 따라 다른 내용을 출력해야 할 때 유용하다. print() 메서드는 줄바꿈 없이 출력하고, println() 메서드는 줄바꿈과 함께 출력한다.
웹 애플리케이션 전체에서 공유하는 ServletContext 타입의 객체다. 모든 사용자와 모든 페이지에서 공유되는 데이터를 관리한다.
setAttribute()와 getAttribute() 메서드로 애플리케이션 전역 데이터를 관리할 수 있다. 방문자 수 카운터나 애플리케이션 설정 정보 등을 저장할 때 사용한다.
getInitParameter() 메서드로 web.xml에 설정된 초기화 파라미터를 읽어올 수 있다. getRealPath() 메서드로 웹 애플리케이션의 실제 경로를 얻을 수도 있다.
PageContext 타입의 객체로, 다른 모든 내장 객체에 대한 접근을 제공한다. 또한 페이지 범위의 속성을 관리한다.
findAttribute() 메서드는 page, request, session, application 순서로 속성을 찾는다. 어느 범위에 속성이 있는지 모를 때 유용하다.
getRequest(), getResponse(), getSession() 등의 메서드로 다른 내장 객체들에 접근할 수 있다.
개발 편의성
HTML을 기반으로 하므로 웹 디자이너와의 협업이 쉽다. Servlet처럼 복잡한 출력 문을 작성할 필요가 없어 개발 시간을 크게 단축할 수 있다.
신속한 개발
단순한 동적 페이지는 매우 빠르게 개발할 수 있다. 프로토타이핑에 특히 유용하며, 화면 중심의 개발이 가능하다.
표준 기술
Java EE 표준 기술이므로 어떤 WAS에서든 동일하게 동작한다. 벤더 종속성 없이 개발할 수 있다.
재사용성
include 지시어나 액션 태그를 통해 코드를 모듈화하고 재사용할 수 있다. 공통 헤더나 푸터를 별도 파일로 관리할 수 있다.
유지보수의 어려움
비즈니스 로직과 프레젠테이션 로직이 하나의 파일에 섞여있어 복잡한 페이지는 관리가 어렵다. 코드의 가독성이 떨어지고 역할 분담이 모호해진다.
디버깅의 어려움
JSP 에러 시 변환된 Java 파일의 라인 번호가 표시되어 원인을 찾기 어렵다. 특히 복잡한 JSP에서는 디버깅이 매우 힘들다.
성능 이슈
처음 호출 시 번역 과정으로 인해 응답 시간이 길다. 또한 복잡한 JSP는 큰 Java 파일을 생성하여 메모리 사용량이 증가한다.
코드 복잡성
스크립틀릿을 많이 사용하면 HTML과 Java 코드가 뒤섞여 코드가 매우 복잡해진다. 특히 중첩된 제어문이나 복잡한 로직이 들어가면 가독성이 급격히 떨어진다.