JSP 엔진은 JSP 파일로부터 서블릿 클래스를 생성할 때 HttpJspPage 인터페이스를 구현한 클래스를 만든다.
HttpJspPage를 구현한다는 것은 결국 Servlet 인터페이스도 구현한다는 것이기 때문에 해당 클래스는 서블릿이 될 수 밖에 없다. 이 인터페이스에 선언된 메서드를 살펴보면, jspInit()는 JSP 객체(JSP로부터 만들어진 서블릿 객체)가 생성될 때 호출된다. 자동 생성된 서블릿 소스 코드를 보면 init()가 호출될 때 jspInit()를 호출하도록 코딩되어 있다. 만약 JSP 페이지에서 init()를 오버라이딩할 일이 있다면 init()대신 jspInit()를 오버라이딩한다. jspDestroy()도 마찬가지로 JSP 객체가 언로드(Unload)될 때 호출된다. 자동 생성된 소스 코드를 보면 destroy()가 호출될 때 jspDestroy()를 호출하도록 코딩되어 있다. 만약 JSP 페이지에서 destroy()를 오버라이딩 할 일이 있다면 destroy() 대신 jspDestroy()를 오버라이딩하면 된다. HttpJspPage에 선언된 _jspService()는 JSP 페이지가 해야 할 작업이 들어있는 메서드인데 JSP 파일에 작성된 대부분의 내용이 들어있다. 서블릿 컨테이너가 service()를 호출하면 service() 내부에서는 _jspService()를 호출함으로써 JSP 페이지에 작성했던 코드들이 실행되는 것이다.
JSP 파일로부터 생성된 자바 서블릿 코드를 살펴본다.
C:\javaide\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\work\Catalina\localhost\web05\org\apache\jsp
위 경로의 Hello.java를 살펴본다.
/*
* Generated by the Jasper component of Apache Tomcat
* Version: Apache Tomcat/8.5.73
* Generated at: 2022-02-01 03:35:22 UTC
* Note: The last modified time of this file was set to
* the last modified time of the source file after
* generation to assist with modification tracking.
*/
package org.apache.jsp;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
//클래스명 : Hello_jsp (톰캣 서버에서는 이런 형식으로 클래스 이름을 짓고 있지만, 모든 JSP 엔진이 이런식으로 클래스 이름을 지어야 하는 것은 아니다. 이름 짓는 방식은 서블릿 컨테이너마다 다르다.)
//HttpJspBase : 톰캣 서버에서 제공받는 클래스로 HttpServlet 클래스를 상속받았고, HttpJspPage 인터페이스를 구현하였다. 즉 서블릿이다.
public final class Hello_jsp extends org.apache.jasper.runtime.HttpJspBase
implements org.apache.jasper.runtime.JspSourceDependent, org.apache.jasper.runtime.JspSourceImports {
private static final javax.servlet.jsp.JspFactory _jspxFactory =
javax.servlet.jsp.JspFactory.getDefaultFactory();
private static java.util.Map<java.lang.String,java.lang.Long> _jspx_dependants;
private static final java.util.Set<java.lang.String> _jspx_imports_packages;
private static final java.util.Set<java.lang.String> _jspx_imports_classes;
static {
_jspx_imports_packages = new java.util.HashSet<>();
_jspx_imports_packages.add("javax.servlet");
_jspx_imports_packages.add("javax.servlet.http");
_jspx_imports_packages.add("javax.servlet.jsp");
_jspx_imports_classes = null;
}
private volatile javax.el.ExpressionFactory _el_expressionfactory;
private volatile org.apache.tomcat.InstanceManager _jsp_instancemanager;
public java.util.Map<java.lang.String,java.lang.Long> getDependants() {
return _jspx_dependants;
}
public java.util.Set<java.lang.String> getPackageImports() {
return _jspx_imports_packages;
}
public java.util.Set<java.lang.String> getClassImports() {
return _jspx_imports_classes;
}
public javax.el.ExpressionFactory _jsp_getExpressionFactory() {
if (_el_expressionfactory == null) {
synchronized (this) {
if (_el_expressionfactory == null) {
_el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory();
}
}
}
return _el_expressionfactory;
}
public org.apache.tomcat.InstanceManager _jsp_getInstanceManager() {
if (_jsp_instancemanager == null) {
synchronized (this) {
if (_jsp_instancemanager == null) {
_jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(getServletConfig());
}
}
}
return _jsp_instancemanager;
}
public void _jspInit() {
}
public void _jspDestroy() {
}
/**
* _jspService()
* @param request,response
* @return
* @throws java.io.IOException,javax.servlet.ServletException
*/
public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
throws java.io.IOException, javax.servlet.ServletException {
final java.lang.String _jspx_method = request.getMethod();
if (!"GET".equals(_jspx_method) && !"POST".equals(_jspx_method) && !"HEAD".equals(_jspx_method) && !javax.servlet.DispatcherType.ERROR.equals(request.getDispatcherType())) {
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "JSP들은 오직 GET, POST 또는 HEAD 메소드만을 허용합니다. Jasper는 OPTIONS 메소드 또한 허용합니다.");
return;
}
//로컬변수 - 반드시 이 이름으로 존재해야 한다. 해당 객체의 주소가 할당될 것이다.
//JSP 내장 객체(Implicit Objects) : 메서드가 호출될 때 반드시 준비되는 객체들. 별도의 선언 없이 JSP 페이지에서 마음껏 사용 가능.
final javax.servlet.jsp.PageContext pageContext;
javax.servlet.http.HttpSession session = null;
final javax.servlet.ServletContext application;
final javax.servlet.ServletConfig config;
javax.servlet.jsp.JspWriter out = null;
final java.lang.Object page = this;
javax.servlet.jsp.JspWriter _jspx_out = null;
javax.servlet.jsp.PageContext _jspx_page_context = null;
try {
response.setContentType("text/html; charset=UTF-8");
pageContext = _jspxFactory.getPageContext(this, request, response,
null, true, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
_jspx_out = out;
//Hello.jsp에서 작성한 HTML 문장
out.write("\r\n");
out.write("<!DOCTYPE html >\r\n");
out.write("<html>\r\n");
out.write("<head>\r\n");
out.write("<meta charset=\"UTF-8\">\r\n");
out.write("<title>Hello</title>\r\n");
out.write("</head>\r\n");
out.write("<body>\r\n");
out.write("<p>안녕하세요</p>\r\n");
out.write("</body>\r\n");
out.write("</html>");
} catch (java.lang.Throwable t) {
if (!(t instanceof javax.servlet.jsp.SkipPageException)){
out = _jspx_out;
if (out != null && out.getBufferSize() != 0)
try {
if (response.isCommitted()) {
out.flush();
} else {
out.clearBuffer();
}
} catch (java.io.IOException e) {}
if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
else throw new ServletException(t);
}
} finally {
_jspxFactory.releasePageContext(_jspx_page_context);
}
}
}
Hello_jsp 클래스의 슈퍼 클래스인 HttpJspBase 클래스에 대해 살펴본다. 톰캣 서버의 JSP 엔진은 서블릿 소스를 생성할 때 슈퍼 클래스로서 HttpJspBase를 사용한다. 당연히 이 클래스도 톰캣 서버에 포함된 클래스이다. 다른 서버는 그들만의 클래스를 사용할 것이다.
public abstract class HttpJspBase extends HttpServlet implements HttpJspPage {
protected HttpJspBase() {}
@Override
public final void init(ServletConfig config) throws ServletException {
super.init(config);
jspInit();
_jspInit();
}
@Override
public String getServletInfo() {
return MESSAGES.jspInfo();
}
@Override
public final void destroy() {
jspDestroy();
_jspDestroy();
}
@Override
public final void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
_jspService(request, response);
}
@Override
public void jspInit() {}
public void _jspInit() {}
@Override
public void jspDestroy() {}
protected void _jspDestroy() {}
@Override
public abstract void _jspService(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
HttpJspBase는 HttpServlet을 상속받기 때문에 당연히 서블릿 클래스가 된다. init()가 호출되면 init()에서는 jspInit()를 호출하고, destroy()에서는 jspDestroy()를 호출한다. service()도 마찬가지로 _jspService()를 호출한다.
- JSP 프리컴파일
실무에서 웹 애플리케이션을 서버에 배치할 때 모든 JSP 파일에 대해 자바 서블릿 클래스를 미리 생성하기도 한다. 그 이유는 JSP 실행 요청이 들어왔을 때, 곧바로 서블릿을 호출할 수 있기 때문이다. 이렇게 미리 자바 서블릿을 만들면 JSP를 실행할 때마다 JSP 파일이 변경되었는지, JSP 파일에 대해 서블릿 파일이 있는지 매번 검사할 필요가 없고, JSP 파일에 대해 자바 서블릿 소스를 생성하고 컴파일하는 과정이 없어서 실행 속도를 높일 수 있다.
이 방식의 문제는 JSP를 편집하면 서버를 다시 시작해야 변경된 JSP에 대한 서블릿 코드가 다시 생성된다. 시스템을 도입한 후 안정화 단계까지는 오류교정이나 기능변경이 수시로 발생하므로, 시스템 도입 초기 단계에서는 가능한 이런 설정을 피하고 어느정도 안정화 된 이후에 JSP 프리컴파일(Precompile)을 고려한다.
참고도서 : https://freelec.co.kr/book/1674/
[열혈강의] 자바 웹 개발 워크북
[열혈강의] 자바 웹 개발 워크북
freelec.co.kr
'교재 실습 > 자바 웹 개발 워크북' 카테고리의 다른 글
38. JSP의 주요 구성 요소 (2) (0) | 2022.02.04 |
---|---|
37. JSP의 주요 구성 요소 (1) (0) | 2022.02.03 |
35. 뷰 컴포넌트와 JSP (1) (0) | 2022.02.01 |
34. MVC 이해하기 (0) | 2022.01.31 |
33. 필터 사용하기 (2) (0) | 2022.01.30 |
댓글