JSP를 실행하면 실제로는 JSP로부터 생성된 서블릿이 실행된다.
① 클라이언트가 JSP를 실행을 요청하면 서블릿 컨테이너는 JSP 파일에 대응하는 자바 서블릿을 찾아서 실행한다.
② 대응하는 서블릿이 없거나 JSP 파일이 변경됐으면 JSP 엔진을 통해 서블릿 자바 소스를 생성한다.
③ 자바 컴파일러가 서블릿 자바 소스를 클래스 파일로 컴파일한다. (JSP 파일이 변경될때마다 반복)
④ JSP로부터 생성된 서블릿은 서블릿 구동 방식에 의해 service() 메소드가 호출되고 서블릿이 생성한 HTML 화면을 웹 브라우저로 보낸다.
Spring boot에서 embedded tomcat으로 jsp 구동시 compiled 된 jsp 파일 위치
Spring boot로 윈도우즈에서 embedded tomcat으로 서버 구동 시 컴파일된 jsp 위치를 찾기가 어렵다. 확인해본 결과 아래 위치에 tomcat 배포시 생성됨을 확인할 수 있었다.
C:/Users/[사용자계정/AppData/Local/Temp/tomcat.9090.10723202586356060977/work/Tomcat/localhost/ROOT/org/apache/jsp/WEB_002dINF/jsp/login/loginForm_jsp.java, loginForm_jsp.class
컴파일된 _jsp.java 파일 구조
loginForm_jsp.java를 열어보면 다음과 같다.
생성된 loginForm_jsp 클래스는 HttpJspBase를 상속받는다. HttpJspBase는 톰캣에 포함된 클래스이며 톰캣의 JSP 엔진은 서블릿 소스를 생성할 때 이 클래스를 사용한다. (서블릿 컨테이너마다 다르다.)
/*
* Generated by the Jasper component of Apache Tomcat
* Version: Apache Tomcat/9.0.54
* Generated at: 2022-03-04 08:12:35 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.WEB_002dINF.jsp.login;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
public final class loginForm_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;
org/apache/tomcat/embed/tomcat-embed-jasper/9.0.54/tomcat-embed-jasper-9.0.54-sources.jar!/org/apache/jasper/runtime/HttpJspBase.java:30
HttpJspBase는 javax.servlet.jsp.HttpJspPage 인터페이스를 구현한다.
HttpJspPage는 Servlet -> JspPage -> HttpJspPage 순으로 상속되기 때문에 HttpJspPage를 구현하는 클래스는 서블릿 클래스이다. 그러므로 HttpJspBase를 상속받는 HelloWorld_jsp 역시 서블릿이 된다.
HttpJspBase 코드를 살펴보면, init()은 jspInit()을, destroy()는 jspDestroy()를, service는 _jspService를 호출한다.
만약 JSP에서 init()이나 destroy()를 override할 일이 있다면 해당 메소드 대신 jspInit()과 jspDestroy()를 override한다.
/**
* This is the super class of all JSP-generated servlets.
*
* @author Anil K. Vijendran
*/
public abstract class HttpJspBase extends HttpServlet implements HttpJspPage {
private static final long serialVersionUID = 1L;
protected HttpJspBase() {
}
@Override
public final void init(ServletConfig config)
throws ServletException
{
super.init(config);
jspInit();
_jspInit();
}
@Override
public String getServletInfo() {
return Localizer.getMessage("jsp.engine.info", Constants.SPEC_VERSION);
}
@Override
public final void destroy() {
jspDestroy();
_jspDestroy();
}
/**
* Entry point into service.
*/
@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;
}
컴파일된 JSP java파일에는 아래 _jsp_Service 메소드가 생성되어 있고 servelet 실행 시 아래 코드가 불리면서 html 렌더링이 수행된다.
public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
throws java.io.IOException, javax.servlet.ServletException {
if (!javax.servlet.DispatcherType.ERROR.equals(request.getDispatcherType())) {
final java.lang.String _jspx_method = request.getMethod();
if ("OPTIONS".equals(_jspx_method)) {
response.setHeader("Allow","GET, HEAD, POST, OPTIONS");
return;
}
if (!"GET".equals(_jspx_method) && !"POST".equals(_jspx_method) && !"HEAD".equals(_jspx_method)) {
response.setHeader("Allow","GET, HEAD, POST, OPTIONS");
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "JSPs only permit GET, POST or HEAD. Jasper also permits OPTIONS");
return;
}
}
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;
out.write('\r');
out.write('\n');
out.write("\r\n");
out.write("\r\n");
out.write("\r\n");
out.write("\r\n");
out.write("\r\n");
out.write("\r\n");
out.write("\r\n");
out.write("\r\n");
out.write("\r\n");
...중략
out.write(" </div>\r\n");
out.write(" </div>\r\n");
out.write("</body>\r\n");
out.write("\r\n");
out.write("<form id=\"pageMoveForm\" name=\"pageMoveForm\" action=\"\" method=\"get\">\r\n");
out.write("</form>\r\n");
out.write("</html>\r\n");
} 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);
}
}
JSP 내장 객체(JSP Implicit Objects)
request, response, pageContext, session, application, config, out, page
_jspService()의 매개변수는 HttpServlet의 doGet(), doPost()와 동일한 HttpServletRequest와 HttpServletResponse 객체이다. 단 매개변수의 이름은 반드시 request, response여야 한다.
_jspService()의 로컬 변수 중 pageContext, session, application, config, out, page는 반드시 이 이름으로 존재해야 한다.
이렇게 _jspServce()에 선언된 request, response, pageContext, session, application, config, out, page 객체들은 이 메소드가 호출될 때 반드시 준비되는 객체들이기 때문에 'JSP 내장 객체(Implicit Objects)'라 한다.
이 객체들은 별도의 선언 없이 JSP에서 사용할 수 있다.
컴파일된 JSP java 구동 Call Stack
localhost:9090으로 loginForm.jsp를 호출해보면 다음과 같은 Call Stack으로 jsp가 구동됨을 알 수 있다.
getPageContext:53, JspFactoryImpl (org.apache.jasper.runtime)
_jspService:1, loginForm_jsp (org.apache.jsp.WEB_002dINF.jsp.login)
service:70, HttpJspBase (org.apache.jasper.runtime)
service:764, HttpServlet (javax.servlet.http)
service:466, JspServletWrapper (org.apache.jasper.servlet)
serviceJspFile:379, JspServlet (org.apache.jasper.servlet)
service:327, JspServlet (org.apache.jasper.servlet)
service:764, HttpServlet (javax.servlet.http)
internalDoFilter:227, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:53, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:113, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:113, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:113, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:113, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
invoke:711, ApplicationDispatcher (org.apache.catalina.core)
processRequest:459, ApplicationDispatcher (org.apache.catalina.core)
doForward:385, ApplicationDispatcher (org.apache.catalina.core)
forward:313, ApplicationDispatcher (org.apache.catalina.core)
forward:171, HeaderWriterFilter$HeaderWriterRequestDispatcher (org.springframework.security.web.header)
renderMergedOutputModel:171, InternalResourceView (org.springframework.web.servlet.view)
render:316, AbstractView (org.springframework.web.servlet.view)
render:1400, DispatcherServlet (org.springframework.web.servlet)
processDispatchResult:1145, DispatcherServlet (org.springframework.web.servlet)
doDispatch:1084, DispatcherServlet (org.springframework.web.servlet)
doService:963, DispatcherServlet (org.springframework.web.servlet)
processRequest:1006, FrameworkServlet (org.springframework.web.servlet)
doGet:898, FrameworkServlet (org.springframework.web.servlet)
service:655, HttpServlet (javax.servlet.http)
service:883, FrameworkServlet (org.springframework.web.servlet)
service:764, HttpServlet (javax.servlet.http)
internalDoFilter:227, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:53, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:113, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:36, XssEscapeServletFilter (com.navercorp.lucy.security.xss.servletfilter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:327, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
invoke:115, FilterSecurityInterceptor (org.springframework.security.web.access.intercept)
doFilter:81, FilterSecurityInterceptor (org.springframework.security.web.access.intercept)
doFilter:336, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doFilter:121, ExceptionTranslationFilter (org.springframework.security.web.access)
doFilter:115, ExceptionTranslationFilter (org.springframework.security.web.access)
doFilter:336, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doFilter:126, SessionManagementFilter (org.springframework.security.web.session)
doFilter:81, SessionManagementFilter (org.springframework.security.web.session)
doFilter:336, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doFilter:105, AnonymousAuthenticationFilter (org.springframework.security.web.authentication)
doFilter:336, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doFilter:149, SecurityContextHolderAwareRequestFilter (org.springframework.security.web.servletapi)
doFilter:336, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doFilter:63, RequestCacheAwareFilter (org.springframework.security.web.savedrequest)
doFilter:336, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doFilterInternal:48, TokenAuthenticationFilter (com.sdp.common.security)
doFilter:119, OncePerRequestFilter (org.springframework.web.filter)
doFilter:336, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doFilter:103, LogoutFilter (org.springframework.security.web.authentication.logout)
doFilter:89, LogoutFilter (org.springframework.security.web.authentication.logout)
doFilter:336, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doHeadersAfter:90, HeaderWriterFilter (org.springframework.security.web.header)
doFilterInternal:75, HeaderWriterFilter (org.springframework.security.web.header)
doFilter:119, OncePerRequestFilter (org.springframework.web.filter)
doFilter:336, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doFilter:110, SecurityContextPersistenceFilter (org.springframework.security.web.context)
doFilter:80, SecurityContextPersistenceFilter (org.springframework.security.web.context)
doFilter:336, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doFilterInternal:55, WebAsyncManagerIntegrationFilter (org.springframework.security.web.context.request.async)
doFilter:119, OncePerRequestFilter (org.springframework.web.filter)
doFilter:336, FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doFilterInternal:211, FilterChainProxy (org.springframework.security.web)
doFilter:183, FilterChainProxy (org.springframework.security.web)
invokeDelegate:358, DelegatingFilterProxy (org.springframework.web.filter)
doFilter:271, DelegatingFilterProxy (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:100, RequestContextFilter (org.springframework.web.filter)
doFilter:119, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:93, FormContentFilter (org.springframework.web.filter)
doFilter:119, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:201, CharacterEncodingFilter (org.springframework.web.filter)
doFilter:119, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
invoke:197, StandardWrapperValve (org.apache.catalina.core)
invoke:97, StandardContextValve (org.apache.catalina.core)
invoke:540, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:135, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
service:357, CoyoteAdapter (org.apache.catalina.connector)
service:382, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:895, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1722, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:834, Thread (java.lang)
JSP 페이지 생성 시 Session 생성을 없애는 방법
JSP에서 Session 생성을 없애려면 JSP 소스에 아래 코드를 삽입한다.
<%@ page session="false" %>
위 설정이 JSP 컴파일시에 어떻게 session 생성을 없애는지 확인해본 결과는 다음과 같다.
org/apache/tomcat/embed/tomcat-embed-jasper/9.0.54/tomcat-embed-jasper-9.0.54-sources.jar!/org/apache/jasper/compiler/Validator.java:120
@Override
public void visit(Node.PageDirective n) throws JasperException {
JspUtil.checkAttributes("Page directive", n, pageDirectiveAttrs,
err);
// JSP.2.10.1
Attributes attrs = n.getAttributes();
for (int i = 0; attrs != null && i < attrs.getLength(); i++) {
String attr = attrs.getQName(i);
String value = attrs.getValue(i);
if ("language".equals(attr)) {
if (pageInfo.getLanguage(false) == null) {
pageInfo.setLanguage(value, n, err, true);
} else if (!pageInfo.getLanguage(false).equals(value)) {
err.jspError(n, "jsp.error.page.conflict.language",
pageInfo.getLanguage(false), value);
}
} else if ("extends".equals(attr)) {
if (pageInfo.getExtends(false) == null) {
pageInfo.setExtends(value);
} else if (!pageInfo.getExtends(false).equals(value)) {
err.jspError(n, "jsp.error.page.conflict.extends",
pageInfo.getExtends(false), value);
}
} else if ("contentType".equals(attr)) {
if (pageInfo.getContentType() == null) {
pageInfo.setContentType(value);
} else if (!pageInfo.getContentType().equals(value)) {
err.jspError(n, "jsp.error.page.conflict.contenttype",
pageInfo.getContentType(), value);
}
} else if ("session".equals(attr)) {
if (pageInfo.getSession() == null) {
pageInfo.setSession(value, n, err);
} else if (!pageInfo.getSession().equals(value)) {
err.jspError(n, "jsp.error.page.conflict.session",
pageInfo.getSession(), value);
}
}
//JSP에서 session 생성을 안하려면
//<%@ page session="false" %>를 JSP에 입력한다. 그러면
//pageInfo.setSession을 false로 설정하고 JSP compile시에
//pageContext.getSession 를 삽입하지 않는다.
... 생략
'Backend Development > Spring boot' 카테고리의 다른 글
[Spring boot] Spring Security 분석 - 메소드 Security (0) | 2022.03.11 |
---|---|
[Spring boot] refresh token 갱신 시 DB 저장값과 Cookie 값 안맞을 경우 (0) | 2022.03.11 |
[Spring boot] @ConfigurationProperties 사용하기 (0) | 2022.03.08 |
[Spring boot] Spring Security Authentication 개념 (0) | 2022.03.04 |
[Spring boot] AOP (관점 지향 프로그래밍) 이해 및 활용 (0) | 2022.02.08 |