은서파 2024. 8. 12. 15:47

이번 포스트에서는 JSP와 Servlet의 차이를 살펴보고 JSP의 구성요소를 학습해보자.

 

JSP

 

기존 Servlet의 문제점과 JSP

Servlet은 Java에서 동적 애플리케이션을 만들기 위해서 꼭 필요한 녀석인데 치명적인 단점이 존재한다.

더보기
private void handleRequest(HttpServletRequest request, HttpServletResponse response)
            throws IOException {
        String danStr = request.getParameter("dan");  // 1. 파라미터 확인 및 검증
        StringBuilder data = new StringBuilder();
        if (danStr == null || danStr.isEmpty()) {
            data.append("dan을 입력하세요.");
        } else {
            int dan = Integer.parseInt(danStr);  // 2. 비지니스 로직 처리
            for (int i = 1; i < 10; i++) {
                data.append(dan).append("*").append(i).append("=").append(i * dan).append("<br>\n");
            }
        }
        response.setContentType("text/html;charset=UTF-8"); // 3. 결과 출력
        PrintWriter writer = response.getWriter();
        writer.print("""
                <html>
                    <head>
                        <title>구구단출력</title>
                    </head>
                    <body>%s</body>
                </html>""".formatted(data));
    }

바로 출력을 위해 HTML 코드를 만드는 부분이다. 이 부분을 문자열로 한땀한땀 정성들여 만들다 보면 들여쓰기, 속성 설정 등 해야 할 일들이 너무 많아진다. 만약 CSS, JavaScript까지 작성해야 한다면 엄청나게 복잡해질 것이다.

 Servlet은 자바 코드를 이용해서 작업을 처리하기에는 아주 훌륭하지만 HTML, CSS, JavaScript 코드를 작성하기에는 너무 불편하다. 만약 퍼블리셔와 협업을 한다고 했을 때 퍼블리셔가 디버깅을 위해 Servlet을 만져야 한다고 생각하면 가슴이 먹먹하다.

이런 문제를 해결하기 위해 JSP(Java Server Page)라는 것을 사용할 수 있다. 

JSP

JSP는 태그 기반으로 서버 프로그램을 만들기 위한 기술이다. 일단 앞서 작성했던 GuguServlet을 JSP로 변경해보자. src/main/webapp/gugu.jsp을 다음과 같이 만들어보자.

<%@ page contentType="text/html;charset=UTF-8" %>
<%@ page import="java.util.*" %>
<%@ page import="java.io.*" %>

<html>
<head>
    <title>구구단 출력</title>
</head>
<body>
<%
    String danStr = request.getParameter("dan");  // 1. 파라미터 확인 및 검증
    StringBuilder data = new StringBuilder();
    if (danStr == null || danStr.isEmpty()) {
        data.append("dan을 입력하세요.");
    } else {
        int dan = Integer.parseInt(danStr);       // 2. 비지니스 로직 처리
        for (int i = 1; i < 10; i++) {
            data.append(dan).append(" * ").append(i).append(" = ").append(i * dan).append("<br>\n");
        }
    }
%>
<%=data %>                                        
</body>
</html>

한눈에 보더라도 HTML 태그 기반의 코드에 Java 코드가 약간은 어색하게 삽입된 형태인 것을 알 수 있다. 정확히 Servlet의 반대이다. 이처럼 Servlet의 경우 Java기반으로 HTML 작성에 약간의 번거로움이 있었고 JSP의 경우는 HTML 기반으로 Java 코드 작성에 번거로움이 존재한다.

 

JSP의 정체

JSP는 Servlet을 designer 및 publisher 직군이 좀 더 쉽게 접근할 수 있게 만든 기술이다. Servlet의 코드를 보면서 designer가 html을 디버깅하는 모습은 상상하기 힘들다. 그런데 사실 JSP Servlet을 작성하는 또다른 방식이고 나중에 Servlet으로 변환되서 동작한다.

그럼 jsp는 어떻게 Servlet으로 변환되는 것일까? Server 콘솔에 출력된 CATALINA_BASE 경로에 가보면(사실 꼭 가볼 필요는 없다.) Servlet으로 변환된 JSP를 확인할 수 있다.

[CATALINA_BASE]\work\Catalina\localhost\simpleweb\org\apache\jsp\gugu_jsp.java

 

필요한 부분만 코드를 발췌해서 살펴보자.

public final class gugu_jsp extends HttpJspBase...{  // 1. HttpJspBase extends HttpServlet

  public void _jspInit() {  }                        // 2. life cycle 메서드
  public void _jspDestroy() {  }
  public void _jspService(HttpServletRequest request, HttpServletResponse response)
      throws java.io.IOException, jakarta.servlet.ServletException {

    jakarta.servlet.http.HttpSession session = null; // 3. request, response등 내장 객체
    final jakarta.servlet.ServletContext application;
    jakarta.servlet.jsp.JspWriter out = null;

    try {
      response.setContentType("text/html;charset=UTF-8"); // 4. 응답 준비
      application = pageContext.getServletContext();
      session = pageContext.getSession();
      out = pageContext.getOut();

      out.write("\r\n");                             // 5. PrintWriter에 해당하는 out를 이용한 출력     
      out.write("<html>\r\n");
      out.write("<head>\r\n");
      out.write("    <title>구구단 출력</title>\r\n");
      out.write("</head>\r\n");
      out.write("<body>\r\n");

    String danStr = request.getParameter("dan");     // 6. <% .. %> 내부에 있던 자바 코드
    StringBuilder data = new StringBuilder();
    if (danStr == null || danStr.isEmpty()) {
        data.append("dan을 입력하세요.");
    } else {
        int dan = Integer.parseInt(danStr);
        for (int i = 1; i < 10; i++) {
            data.append(dan).append("*").append(i).append("=").append(i*dan).append("<br>\n");
        }
    }

      out.write('\r');
      out.write('\n');
      out.print(data );                              // 7. <%=data%> 
      out.write("\r\n");
      out.write("</body>\r\n");
      out.write("</html>\r\n");
    } catch (java.lang.Throwable t) {}
  }
}

자세히 살펴보면 JSP에서 태그 기반으로 작성했던 내용이 Servlet으로 변환되서 동작하고 있음을 알 수 있다. WAS는 'xxx.jsp'에 해당하는 요청을 받으면 이 jsp에 해당하는 Servlet이 로딩되있는지 살펴본다. 만약 처음 요청이어서 아직 로드된 Servlet이 없다면 jsp를 Servlet으로 변경 후 객체 생성 -> _jspInit() -> _jspService()까지 호출한다. 물론 다시 해당 jsp에 대한 요청이 들어오면 _jspService() 만 호출하게 된다.

WAS는 JSP를 Servlet으로 변환해서 사용한다.

그럼 JSP의 구성 요소로는 어떤 것들이 있을까?

JSP의 구성 요소

 

page directive

page directive는 JSP 페이지가 실행될 때 필요한 정보를 지정하는 역할을 하는데 <%@ page 속성='값' 속성='값'%>의 형태로 작성한다. 지정할 수 있는 속성들이 정말 많지만 대부분 기본 값들이 사용되며 반드시 지정해야 하는 것은 2가지 정도로 생각할 수 있다.

  • contentType: 클라이언트로 전송되는 content의 타입으로 거의 text/html;charset=utf-8을 사용한다. 
  • import: JSP 페이지 내에서 import 해서 사용할 클래스를 선언하는 곳으로 ',' 를 이용해서 여러개 선언할 수 있다.
<%@ page contentType="text/html;charset=UTF-8" language="java" 
         import="java.util.*, java.io.*" %>

또는 page directive를 여러번 사용할 수도 있다.

<%@ page contentType="text/html;charset=UTF-8"%>
<%@ page import="java.util.*" %>
<%@ page import="java.io.*" %>

 

built in object

built in object(내장객체)란 JSP 파일을 Servlet 파일로 변경하면서 _jspService 메서드의 파라미터 또는 로컬 변수로 선언되는 객체를 말한다. 뒤에서 설명하겠지만 JSP의 scriptlet이나 expression 태그들은 모두 _jspService의 로컬에서 동작한다. 따라서 같은 로컬 영역이므로 내장 객체를 사용하는데 전혀 문제가 없다.

내장 객체의 종류는 다음과 같다.

변수명 변수 타입 용도
response jakarta.servlet.http.httpServletResponse 메서드 파라미터로 서버의 응답 정보 저장
request jakarta.servlet.http.HttpServletRequest 메서드 파라미터로 클라이언트 요청 정보 저장
session jakarta.servlet.http.HttpSession HTTP 세션에 대한 정보 저장
application jakarta.servlet.ServletContext 웹 애플리케이션에 대한 정보 저장
out jakarta.servlet.jsp.JspWriter 클라이언트로의 정보 출력 스트림
pageContext jakarta.servlet.jsp.PageContext 현재 JSP 페이지에 대한 정보 저장
config jakarta.servlet.ServletConfig JSP 페이지에 대한 설정 정보 저장
exception java.lang.Throwable error페이지에서 사용되는 예외 객체
page java.lang.Object JSP 페이지를 구현한 자바 클래스 인스턴스

이중 특히 request, session은 정말 자주 사용되는 객체이므로 잘 기억해두자. 또한 내장 객체들은 이미 선언이 완료되었기 때문에 동일한 이름으로 다른 로컬 변수를 선언할 수 없다.

script let

scriptlet <%와 %> 사이에 java 코드를 사용하기 위한 태그이다.  scriptlet에 작성한 코드는 _jspService 메서드의 내부 즉 local 영역에서 동작한다는 점은 매우 중요하다.

local 영역에서 동작하기 때문에 local에 선언된 파라미터(request, response)와 내장 객체를 자유로이 사용할 수 있다. 즉 JSP 상에서는 request가 선언되지 않았지만 이 코드가 _jspService(request, response)에서 동작하므로 request라는 것을 사용할 수 있는 것이다.

<%
    String danStr = request.getParameter("dan");  // 1. 파라미터 확인 및 검증
    StringBuilder data = new StringBuilder();
    if (danStr == null || danStr.isEmpty()) {
        data.append("dan을 입력하세요.");
    } else {
        int dan = Integer.parseInt(danStr);       // 2. 비지니스 로직 처리
        for (int i = 1; i < 10; i++) {
            data.append(dan).append("*").append(i).append("=").append(i*dan).append("<br>\n");
        }
    }
%>

 

expression

expression은 <%=와 %> 사이에 출력할 내용(변수, 수식, 리턴이 있는 메서드 등)을 작성하면 된다. 이 코드는 나중에 Servlet에서 _jspService() 내부에서 out.print(내용)의 형태로 변형된다. 여기서 out은 JspWriter 타입의 built in object이다.

<%=data %>
out.print(data );

 

declaration

declaration <%!와 %> 사이에 멤버 변수 및 메서드를 선언하는데 Servlet으로 변환되면 선언 위치와 상관 없이  Servlet의 멤버 변수 또는 멤버 메서드로 등록된다.

<%!
  private String sayHello(String name){
      return "Hello "+name;
  }
%>

 

페이지 모듈화

 

페이지 모듈화의 필요성

대부분의 사이트들은 일정한 틀을 가지고 페이지 내에 고정된 부분과 변하는 부분을 가진다. 이중 고정된 부분(<header>, <footer> 등)은 여러 페이지에 등장하며 동일한 내용이 사용된다. <header>에서 크게 달라지는 부분은 로그인 전/후의 화면 구성 정도일 것이다.

만약 이런 부분에서 내용에 대한 수정이 필요하다면 어떻게 될까?

이를 대비하여 페이지를 모듈화하고 재사용할 필요가 있다.

 

include directive

페이지를 모듈화 하기 위해 include directive를 사용할 수 있다. include directive는 file 속성을 통해 삽입할 페이지를 지정한다.

<%@ include file="source_file" %>

include directive를 사용하면 현재의 JSP에 대상 JSP 파일을 끼워 넣어 하나의 소스 파일을 생성해서 servlet으로 변경된다. 결국 source_file의 html 내용 뿐 아니라 member 및 local 변수까지 include한 곳에서 사용할 수 있게 된다. 대표적으로 절대경로를 구성하기 위해 컨텍스트 이름을 사용해야 하는데 이 경로를 문자기반으로 사용하면 변경시 문제가 된다. 이런경우 컨텍스트 이름을 변수화 해두면 재사용이 용이하고 변경에 유연하게 대처할 수 있게 된다.

<%-- header.jsp --%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    String root = request.getContextPath();
%>
<h1>Simple Web App</h1>
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
    <title>JSP - Hello World</title>
</head>
<body>
<%@include file="/include/header.jsp"%>
<h1><%= "Hello World!" %>
</h1>
<br/>
<a href="hello-servlet">Hello Servlet</a> <br>
<!--a href="/simpleweb/static/gugu.html">구구단요청</a><br-->
<a href="<%=root%>/static/gugu.html">구구단요청</a><br>
</body>
</html>