JSP(JavaServer Pages)는 Java 기반의 서버 사이드 웹 개발 기술이다. 서버에서 HTML을 동적으로 생성해 클라이언트에게 전달하는 데 사용한다.
JSP만 놓고 보면 HTML 페이지에 서버 데이터를 넣어 동적으로 출력하는 기술처럼 보이지만, 실제로는 Servlet Container 위에서 Servlet과 함께 동작한다.
기본적인 요청 흐름은 다음과 같다.
Client -> HTTP Request -> WAS / Servlet Container -> Servlet -> Business Logic -> Database -> Servlet -> JSP -> HTML Response -> Client
Servlet이 HTTP 요청을 처리하고 필요한 데이터를 준비하면 JSP가 그 데이터를 HTML로 표현한다. 따라서 JSP 서버 개발을 이해하려면 JSP 문법뿐만 아니라 Servlet, WAS, HTTP Request/Response, 세션과 쿠키, MVC 구조까지 함께 이해해야 한다.
JSP
JSP는 서버에서 실행되어 HTML을 생성하는 템플릿 기술이다.
예를 들어 서버에서 user 객체를 JSP에 전달했다면 다음과 같이 데이터를 출력할 수 있다.
<h1>${user.name}</h1>
서버에서 JSP가 처리되면 브라우저에는 최종적으로 HTML이 전달된다.
<h1>Joo</h1>
브라우저가 JSP 파일을 직접 실행하는 것은 아니다.
JSP -> Server Processing -> HTML -> Browser
JSP의 역할은 서버 데이터를 HTML에 표현하는 것이다.
JSP와 Servlet
JSP와 Servlet은 Java 웹 개발에서 같이 이해해야 한다.
Servlet은 HTTP 요청을 처리하는 Java 클래스다.
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(
HttpServletRequest request,
HttpServletResponse response
) throws IOException {
response.getWriter().println("Hello");
}
}
클라이언트가 /hello로 요청하면 Servlet Container가 해당 Servlet을 찾아 실행한다.
JSP도 결국 Servlet Container에서 실행된다.
JSP 파일은 내부적으로 Servlet 코드로 변환되고 컴파일된 뒤 실행된다.
hello.jsp -> JSP Translation -> Servlet Source -> Compile -> Servlet Class -> Execute
따라서 JSP는 별도의 독립적인 서버 실행 모델을 가지고 있는 것이 아니라 Servlet 기반의 실행 환경을 사용한다.
WAS와 Servlet Container
JSP와 Servlet은 일반적인 Java 애플리케이션처럼 main() 메서드에서 직접 실행하는 구조가 아니다.
Tomcat 같은 WAS가 애플리케이션을 실행하고 Servlet Container가 HTTP 요청과 Servlet의 생명주기를 관리한다.
Browser -> HTTP -> Tomcat -> Servlet Container -> Servlet -> JSP
Servlet Container는 요청 URL에 맞는 Servlet을 찾고 실행한다.
또한 HttpServletRequest, HttpServletResponse 같은 객체를 생성해 Servlet에 전달하고 Servlet의 생명주기도 관리한다.
개발자는 네트워크 연결을 직접 관리하기보다 요청을 어떻게 처리할 것인지에 집중할 수 있다.
Request와 Response
웹 서버 개발에서 기본이 되는 것은 HTTP Request와 Response다.
클라이언트가 서버에 데이터를 전달하면 Servlet에서는 HttpServletRequest를 통해 접근할 수 있다.
protected void doGet(
HttpServletRequest request,
HttpServletResponse response
) throws IOException {
String name = request.getParameter("name");
response.getWriter().println(name);
}
다음과 같은 요청이 들어왔다고 하자.
GET /hello?name=Joo
Servlet에서
request.getParameter("name");
을 호출하면 "Joo"를 가져올 수 있다.
Response는 서버에서 클라이언트로 전달하는 결과다.
response.setContentType("text/html");
response.getWriter().println("<h1>Hello</h1>");
실제 웹 애플리케이션에서는 Servlet이 직접 HTML을 만들어 반환하기보다 필요한 데이터를 JSP에 전달하는 방식으로 구성하는 경우가 많다.
JSP에 데이터 전달
Servlet에서 데이터를 준비하고 JSP로 넘길 수 있다.
request.setAttribute("name", "Joo");
RequestDispatcher dispatcher =
request.getRequestDispatcher("/WEB-INF/views/hello.jsp");
dispatcher.forward(request, response);
JSP에서는 전달받은 값을 사용한다.
<h1>Hello, ${name}</h1>
흐름은 다음과 같다.
HTTP Request -> Servlet -> 데이터 조회 / 비즈니스 로직 -> request.setAttribute() -> JSP -> HTML 생성 -> HTTP Response
이 구조가 JSP를 사용하는 기본적인 MVC 형태로 이어진다.
MVC 구조
JSP를 실제 서버 개발에 사용할 때는 보통 역할을 나눠서 구성한다.
Controller -> 요청 처리 -> Service -> 비즈니스 로직 -> Repository / DAO -> Database 접근 -> Database
그리고 다시:
Controller -> View에 데이터 전달 -> JSP -> HTML 생성
Servlet이 Controller 역할을 맡고 JSP가 View 역할을 맡는다.
비즈니스 로직까지 JSP에 작성하면 코드가 빠르게 복잡해진다.
예를 들어 다음과 같이 JSP에서 DB 조회까지 수행하는 방식은 피하는 것이 좋다.
<%
// DB 연결
// SQL 실행
// 결과 처리
%>
JSP는 화면을 표현하는 역할에 집중시키고, 데이터 조회나 비즈니스 규칙은 별도의 계층에서 처리하는 편이 관리하기 쉽다.
EL과 JSTL
JSP에서는 Java 코드를 직접 작성하는 Scriptlet 방식도 사용할 수 있다.
<%
String name = (String) request.getAttribute("name");
%>
<h1><%= name %></h1>
하지만 이런 방식은 View 코드에 Java 로직이 섞이기 때문에 유지보수가 어렵다.
그래서 JSP에서는 EL(Expression Language)을 사용하는 방식이 일반적이다.
<h1>${name}</h1>
객체의 프로퍼티도 접근할 수 있다.
<p>${user.name}</p>
<p>${user.email}</p>
반복이나 조건 처리에는 JSTL 같은 태그 라이브러리를 사용할 수 있다.
<c:forEach var="user" items="${users}">
<p>${user.name}</p>
</c:forEach>
JSP를 View 기술로 사용할 때는 Java 코드를 직접 작성하기보다 EL과 JSTL을 사용하는 방향이 적절하다.
Session과 Cookie
웹 서버에서는 사용자의 상태를 유지해야 하는 경우가 많다.
HTTP는 기본적으로 각각의 요청을 독립적으로 처리하기 때문에 로그인 상태 같은 정보를 계속 유지하려면 별도의 메커니즘이 필요하다.
Cookie는 클라이언트에 데이터를 저장하고 요청할 때 서버로 전달하는 방식이다.
Session은 서버에 사용자 상태를 저장하고 클라이언트에는 해당 세션을 식별하기 위한 값만 전달하는 방식이다.
Servlet에서는 다음처럼 Session을 사용할 수 있다.
HttpSession session = request.getSession();
session.setAttribute("user", user);
이후 다른 요청에서 같은 Session에 저장된 데이터를 가져올 수 있다.
HttpSession session = request.getSession();
User user = (User) session.getAttribute("user");
일반적인 로그인 구조에서는 다음과 같은 흐름을 생각할 수 있다.
Login Request -> Servlet -> 사용자 인증 -> Session 생성 -> Session ID를 Cookie로 전달 -> Client
이후 클라이언트가 Session ID가 포함된 Cookie를 보내면 서버가 해당 Session을 찾아 로그인 상태를 확인한다.
JSP와 데이터베이스
JSP가 데이터베이스에 직접 접근하는 구조로 만들 필요는 없다.
일반적인 구조에서는 다음과 같이 계층을 분리한다.
JSP -> Controller -> Service -> Repository / DAO -> JDBC -> Database
예를 들어 게시글 목록을 요청하면 Controller가 Service를 호출하고 Service가 Repository를 통해 DB에서 데이터를 가져온다.
그 결과를 다시 JSP에 전달한다.
GET /posts -> Controller -> PostService -> PostRepository -> Database -> Post List -> JSP -> HTML
JSP는 Post List를 받아 화면에 출력하는 역할만 담당한다.
JSP의 위치
JSP를 현대적인 Java 서버 구조에 넣어 보면 역할이 명확해진다.
Client -> HTTP -> Controller -> Service -> Repository -> Database
그리고 다시:
Database -> Repository -> Service -> Controller -> JSP -> HTML -> Client
Controller는 요청을 받고 적절한 로직을 호출한다.
Service는 애플리케이션의 비즈니스 로직을 처리한다.
Repository 또는 DAO는 데이터베이스 접근을 담당한다.
JSP는 최종적으로 사용자에게 보여줄 HTML을 생성한다.
이렇게 각 계층의 책임을 분리하면 코드가 어디에 있어야 하는지가 명확해진다.
JSP에서 Spring MVC로
JSP를 공부하다 보면 자연스럽게 Spring MVC와 연결된다.
Spring MVC에서도 기본적인 구조는 크게 다르지 않다.
HTTP Request -> DispatcherServlet -> Controller -> Service -> Repository -> Database
처리 결과를 View에 전달하고 JSP를 View로 사용할 수도 있다.
Controller -> Model -> View Resolver -> JSP -> HTML
Spring MVC에서는 DispatcherServlet이 Front Controller 역할을 수행하기 때문에 개발자가 모든 요청마다 직접 Servlet을 구현할 필요가 줄어든다.
JSP 자체가 사라지는 것이 아니라, JSP를 사용하는 방식과 주변 구조가 프레임워크에 의해 정리되는 것이다.
정리
JSP 서버 개발에서 알아야 할 내용을 하나의 흐름으로 묶으면 다음과 같다.
HTTP Request -> WAS / Servlet Container -> Servlet / Controller -> Service -> Repository / DAO -> Database -> Service -> Controller -> JSP -> HTML -> HTTP Response
JSP는 이 구조에서 View를 담당하는 서버 사이드 템플릿 기술에 가깝다.
Servlet은 HTTP 요청을 처리하고, Service는 비즈니스 로직을 담당하고, Repository는 데이터베이스에 접근한다. JSP는 Controller가 전달한 데이터를 이용해 최종 HTML을 만든다.
그래서 JSP 문법만 공부하는 것보다 Servlet -> HTTP -> MVC -> Session/Cookie -> Database -> JSP의 관계를 같이 이해하는 것이 훨씬 중요하다.
JSP 자체는 오래된 기술이지만, 이 구조를 이해하면 Spring MVC 같은 Java 서버 프레임워크가 내부적으로 어떤 문제를 해결하고 있는지도 자연스럽게 연결된다.