JSP와 Servlet의 관계
JSP는 JSP 컨테이너에 의해 Servlet으로 변환되며, 이후 Servlet 컨테이너에서 초기화 되고 서블릿 객체로 살아가며 클라이언트로부터의 요청을 처리합니다.
때문에 JSP 스펙의 버전은 Servlet 스펙의 버전에 의존하며, JSP를 작성할 때에는 해당 JSP 페이지가 Servlet으로 어떻게 변환되어 작동할 것인지를 머릿속에 염두해야 합니다. 


JSP가 Servlet으로 변환되는 규칙
먼저 변환 규칙을 간단히 설명하자면 다음과 같이 다섯 가지가 있습니다.

첫번째. 웹어플리케이션에 배포된 JSP 페이지는 최초 클라언트 요청이 들어올때 서블릿으로 변환된다.
두번째. JSP 스크립트 요소 중 스크립트릿에 작성된 소스는 변환된 Servlet의 service() 메서드 안에 들어간다.
세번째. JSP 스크립트 요소 중 표현식은 변환된 Servlet의 service() 메서드 안에서 out.println() 으로 변환된다.
네번째. JSP 스크립트 요소 중 선언문에 작성된 소스는 변환된 Servlet의 멤버메서드로 변환된다.
다섯번째. JSP에 작성된 일반 HTML 태그들은 변환된 Servlet의 service() 메서드 안에서 out.write() 메서드로 변환된다.
여섯번째. page 디렉티브의 속성값들은 Servlet으로 변환시 참고할 정보로 활용된다.

상세 규칙 설명
먼저 JSP가 Servlet으로 변환되는 규칙을 이해하기 위해 샘플 JSP페이지 소스 및 Servle으로 변환된 소스 요약 그리고 변환된 서블릿이 클라이언트의 요청을 처리한 결과를 정리해 보았습니다.

스크립트 요소를 설명하기 위한 jsp페이지 예제 소스

 

jspTest.jsp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%!
     public void startFor(){
           System.out.println("반복문이 시작됩니다. 이것은 html이 아닌 콘솔 출력");
     }
%>
<html>
<head>
<title>Insert title here</title>
</head>
<body>
안녕 월드!
<%
     this.startFor(); //this는 생략 가능
     int num = 1;
     for( ; num <= 10; num++){
           out.println("반복문 " + num + "번째 실행<br>");
     }
     
%>
<h>num의 최종 값은 : </h> <%= num %>
</body>
</html>
cs

 

변환된 Servlet 소스 요약(실제로는 예외처리 등 불필요한 소스가 포함되어 있으므로 이것을 제거)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public final class jspTest_jsp extends org.apache.jasper.runtime.HttpJspBase
    implements org.apache.jasper.runtime.JspSourceDependent,
                 org.apache.jasper.runtime.JspSourceImports {
   //선언문은 서블릿 클래스의 멤버로 변환된다.
   public void startFor(){
      System.out.println("반복문이 시작됩니다. 이것은 html이 아닌 콘솔 출력");
   }
   public void _jspService(final HttpServletRequest request, final HttpServletResponse response)
         IOException, ServletException {
      ...코드 일부 생략...
      JspWriter out = pageContext.getOut(); //jsp 출력객체 생성. pageContext는 코드 생략 부분에서 생성
 
      response.setContentType("text/html; charset=UTF-8");
 
      out.write("\r\n");
      out.write("\r\n");
      out.write("\r\n");
      out.write("\r\n");
      out.write("<html>\r\n");
      out.write("<head>\r\n");
      out.write("<title>Insert title here</title>\r\n");
      out.write("</head>\r\n");
      out.write("<body>\r\n");
      out.write("안녕 월드!\r\n");
      out.write("\r\n");
      out.write("\r\n");
      this.startFor(); //this는 생략 가능
      int num = 1;
      for( ; num <= 10; num++){
         out.println("반복문 " + num + "번째 실행<br>");
      }
     
      out.write("\r\n");
      out.write("\r\n");
      out.write("<h>num의 최종 값은 : </h> ");
      out.print( num );
      out.write("\r\n");
      out.write("\r\n");
      out.write("</body>\r\n");
      out.write("\r\n");
      out.write("\r\n");
      out.write("</html>");
   }
}
 
cs

 


클라이언트로의 응답 결과
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<html>
<head>
<title>Insert title here</title>
</head>
<body>
안녕 월드!
 
 
반복문 1번째 실행<br>
반복문 2번째 실행<br>
반복문 3번째 실행<br>
반복문 4번째 실행<br>
반복문 5번째 실행<br>
반복문 6번째 실행<br>
반복문 7번째 실행<br>
반복문 8번째 실행<br>
반복문 9번째 실행<br>
반복문 10번째 실행<br>
 
 
<h>num의 최종 값은 : </h> 11
 
</body>
 
 
</html>
cs


 



첫번째 JSP가 서블릿으로 변환
먼저 JSP가 서블릿으로 변환되는 과정은 아래와 같습니다.
최초 클라이언트에서 xx.jsp에 대해 요청을 하고 최초 요청인 경우에는 Servlet이 존재하지 않으므로 해당 JSP를 JSP컨테이너에서 Servlet클래스 .java 파일로 변환 후 .java 파일을 컴파일하여 .class로 만듭니다.

위 샘플소스를 보면 변환된 jsp 서블릿은 톰캣의 경우 org.apache.jasper.runtime.HttpJspBase 를 상속하고 있는데 HttpJspBase는 HttpServlet을 상속하고 있는 클래스이므로 서블릿이라고 할 수 있습니다.(서블릿으로 변환되는 과정 및 상속 클래스는 WAS 제조사별로 다를 수 있습니다.)

이후 Servlet 컨테이너는 서블릿 클래스를 이용하여 서블릿 객체 생성 후 init()메서드를 통해 초기화하고 service() 메서드를 실행해 클라이언트의 요청을 처리합니다.
웹어플리케이션 경로에 배포된 jsp 파일들은 톰캣을 기준으로 설명하자면 tomcat설치폴더\work\Catalina\localhost\웹어플리케이션디렉터리\변경된서블릿.class 와 같이 톰캣 하위의 work 디렉터리 내에 변환되어 저장됩니다.



두번째 스크립트릿(Scriptlet) 변환
JSP 페이지에서 가장 많이 사용되는 스크립트 요소는 스크립트릿입니다.
스크립트릿 안에 작성된 로직은 변환된 서블릿 클래스의 _jspService() 메서드 안에 들어가게 되는데 _jspService() 메서드는 HttpServlet의 service() 메서드와 동일하다고 생각하면 됩니다.

 

1
2
3
4
5
6
7
8
<%
     this.startFor(); //this는 생략 가능
     int num = 1;
     for( ; num <= 10; num++){
           out.println("반복문 " + num + "번째 실행<br>");
     }
     
%>
cs

 

다음과 같이 변환

 

1
2
3
4
5
6
7
8
9
10
   public void _jspService(final HttpServletRequest request, final HttpServletResponse response){
      ...생략...
      this.startFor(); //this는 생략 가능
 
      int num = 1;
      for( ; num <= 10; num++){
         out.println("반복문 " + num + "번째 실행<br>");
      }
    ...생략...
   }
cs

 

 


세번째 표현식(Expression) 변환
어떤 값을 출력하고 싶은 경우 사용하는 표현식또한 스크립트릿과 마찬가지로 _jspService() 안에 포함됩니다.
1
<h>num의 최종 값은 : </h> <%= num %>            
cs

 

다음과 같이 변환
1
2
3
4
5
6
7
8
9
10
   public void _jspService(final HttpServletRequest request, final HttpServletResponse response)
         IOException, ServletException {
      ...생략...
 
 
      out.write("<h>num의 최종 값은 : </h> ");
      out.print( num );//표현식부분
    ...생략...
 
   }
cs

 


네번째 선언문(Declaration) 변환
선언문은 JSP가 변환된 서블릿 클래스의 멤버로 변환됩니다.
JSP 페이지에서 사용될 공통 로직을 메서드로 빼서 쓰거나 공통 변수를 멤버변수로 선언하고 싶은 경우 사용합니다.
1
2
3
4
5
<%!
     public void startFor(){
           System.out.println("반복문이 시작됩니다. 이것은 html이 아닌 콘솔 출력");
     }
%>
cs

 

다음과 같이 변환
1
2
3
4
5
6
7
8
9
public final class jspTest_jsp extends org.apache.jasper.runtime.HttpJspBase
    implements org.apache.jasper.runtime.JspSourceDependent,
                 org.apache.jasper.runtime.JspSourceImports {
 
 
   //선언문은 서블릿 클래스의 멤버로 변환된다.
   public void startFor(){
      System.out.println("반복문이 시작됩니다. 이것은 html이 아닌 콘솔 출력");
   }
cs

 



다섯번째 일반 HTML 태그 변환
일반 HTML은 표현식과 같이 _jspService() 안에 포함됩니다.
표현식에서와 사용되는 메서드가 다르긴 하지만 줄넘김 이외에는 같은 기능이므로 다를게 없습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<html> 
<head> 
<title>Insert title here</title> 
</head> 
<body> 
안녕 월드!
 
 
    ...생략...
 
 
 
<h>num의 최종 값은 : </h> 
</body> 
</html> 
cs

 

다음과 같이 변환
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
   public void _jspService(final HttpServletRequest request, final HttpServletResponse response)
         IOException, ServletException {
      ...코드 일부 생략...
      JspWriter out = pageContext.getOut(); //pageContext는 생략 코드에서 생성
    
      out.write("\r\n");
      out.write("\r\n");
      out.write("\r\n");
      out.write("\r\n");
      out.write("<html>\r\n");
      out.write("<head>\r\n");
      out.write("<title>Insert title here</title>\r\n");
      out.write("</head>\r\n");
      out.write("<body>\r\n");
      out.write("안녕 월드!\r\n");
      out.write("\r\n");
      out.write("\r\n");
    ...생략....
   }
cs

 



여섯번째 page디렉티브 변환
page디렉티브는 서블릿으로 변환시 JSP 컨테이너가 참고할 정보로 활용됩니다.
예를들어 pageEncoding속성은 JSP 변환시 인코딩 값으로 활용합니다.
contentType 속성은 다음과 같이 _jspService() 안에서 사용됩니다.
1
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
cs

 

다음과 같이 변환
1
2
3
4
5
  public void _jspService(final HttpServletRequest request, final HttpServletResponse response)
         IOException, ServletException {
    ...생략...
      response.setContentType("text/html; charset=UTF-8");
      ...생략...
cs

 


지금까지 JSP가 서블릿으로 변환되는 규칙에 대해 알아 보았습니다.
이러한 내부 원리를 아는것이 큰 시행착오를 줄일 수 있는 지름길이라 생각됩니다.
블로그 이미지

도로락

IT, 프로그래밍, 컴퓨터 활용 정보 등을 위한 블로그

댓글을 달아 주세요! 질문 환영합니다!