JSP/Servlet - 상태관리, 포워딩, 재스퍼!

상태관리

리다이렉트의 처리순서는 다음과 같다.

  1. 클라이언트가 url에 해당하는 html페이지 요청
  2. 서버는 url에 해당하는 서블릿 혹은 jsp객체를 반환
  3. 만약 jsp페이지에 response.sendRedirect(url2) 메서드가 있다면 클라이언트에게 url2로 다시 접속하라고 알림
  4. 클라이언트가 url2에 해당하는 html페이지 요청
  5. 서버는 url2에 해당하는 서블릿 혹은 jsp객체를 반환

image03

위와 같은 처리 순서에서 클라이언트가 get방식, post방식으로 넘긴 파라미터를 서버에서 계속 유지하고 싶다면 상태관리가 필요하다.

<%
	String name = "admin";
	int age = 20;
	String params = String.format("name=%s&age=%d",name,age);
%>
...
<body>
	<a href="ex01_02.jsp?<%= params %>">ex01_02.jsp</a>
</body>

위와같이 nameage이라는 파라미터를 ex01_02.jsp로 넘긴다.

ex01_02.jsp에선 response.sendRedirect를 통해 바로 ex01_03.jsp로 다시 접속하라고 클라이언트에게 전달한다.

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%
	String location = String.format("ex01_03.jsp", name, age);
	response.sendRedirect(location);
%>

결국 클라이언트는 ex01_03.jsp url을 다시 요청하는 과정에서 파라미터 유지가 되지 않는다.

따라서 유지를 해주고 싶다면 클라이언트에게 전송하는 url뒤에 파라미터 정보를 붙여 보내야한다.

<%
	String name = request.getParameter("name");
	String age = request.getParameter("age");
	String location = String.format("ex01_03.jsp?name=%s&age=%s", name, age);
	System.out.println(location);
	response.sendRedirect(location);
%>

sendRedirect를 사용하려면 위와같이 유지해야 하지만 포워딩을 사용하면 서버에서 바로 처리하기 때문에 url뒤에 지저분하게 파리미터를 붙일 필요 없다.

포워딩

response.sendRedirect를 사용하면 서버가 클라이언트에게 재 연결을 위한 urld을 다시 보내게 되고 클라이언트가 서버에게 다시 요청해야 한다.

반면 포워딩을 사용하면 서버에서 바로 연결을 해주기 때문에 클라이언트가 서버에게 다시 요청할 일이 없다.

image04

String path = "ex01_03.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(path);
dispatcher.forward(request, response);

foward를 위해 RequestDispatcher객체가 필요하고 이 객체는 request.getRequestDispatcher("이동시킬 url")을 통해 받을 수 있다.

dispatcher.forward()메서드의 매개변수로 requestresponse객체를 넘기기 때문에 path변수 뒤에 파라미터를 붙일 필요가 없다.

즉 클라이언트가 <a href="ex01_02.jsp?<%= params %>">ex01_02.jsp</a>를 통해 ex01_02.jsp파일을 요청하면
서버의 ex01_02.jsp는 실제로 ex01_03.jsp으로 포워드를 하고 있고 ex01_03.jsp을 출력결과를 클라이언트에게 반환한다.

또한 클라이언트의 브라우저 주소창엔 http://localhost/jspPro/days03/ex02_info.jsp 이런식으로 뜨겠지만 실제 출력된 것은 서버의 ex01_03.jsp파일 결과물이다.

그리고 MVC패턴으로 설계하기 위해 Model, View, Controller가 DTO객체를 서로 전송해야 하는데, 이 DTO객체를 url뒤에?를 붙여 전달할 순 없으니 request객체에 데이터를 포함시켜 통째로 넘겨야 한다.

request객체를 전송하기 위해선 dispatcher.forward()메서드를 사용한 포워딩이 필수이다!

request Scope(요청 스코프)

클라이언트가 서버에게 html페이지 요청과 동시에 넘긴 파라미터들을 request객체로 가져올 수 있었다.

서버에선 이 데이터를 가지고 포워딩을 통해 각 서블릿 객체에서 작업을 해야 하는데
이 작업기간 동안 사용자가 넘긴 파라미터(request객체)가 유지되어야 한다.

작업기간동안 요청객체를 유지하는 것을 request Scope(범위)라 한다.

request가 파라미터만 유지하는것이 아니라 파라미터로 인해 생긴 데이터 혹은 DB에서 가져온 dto등의 데이터도 보관하고 있어야 하는데
request.setAttribute()메서드를 사용해 requset객체에 데이터를 포함시킬 수 있다.

예를 들어 클라이언트가 서버에게 10번 부서의 사원을 모두 출력해달라고 다음과 같이 select태그에서 사원번호를 선택해 서버로 넘기다 할 때

<form action="" method="get">
<select name="deptno" id="deptno">
		<option value="10">ACCOUNTING</option>
		<option value="20">RESEARCH</option>
		<option value="30">SALES</option>
		<option value="40">OPERATIONS</option>
</select>
</form>
...
...
<script>
var flag = true;
$("select").on("click", function() {
	if(flag)
	{
		flag = false;
		return;
	}
	var deptno = $(this).val();
	$("form")
	.attr("action", "ex04_emp")
	.submit();
	flag = true;
});
</script>

선택한 부서번호를 파라미터로 넘기며 ex04_emp url을 요청하는데
이는 web.xml에 정의된 mapping에 따라 Ex04_emp서블릿 객체를 호출한다.

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	String deptno = request.getParameter("deptno");
	String sql = "select empno, ename, job, hiredate, deptno from emp";
	if( deptno != null )    
		sql +=" where deptno = " + deptno;
	Connection con = null;
	Statement stmtEmp;
	ResultSet rsEmp;
	ArrayList<EmpDTO> elist = null;
	try {
		con = DBConn.getConnection();
		stmtEmp = con.createStatement();
		rsEmp = stmtEmp.executeQuery(sql);
		if( rsEmp.next()){
			elist = new ArrayList<>();
			do{
				EmpDTO dto = new EmpDTO();
				dto.setEmpno( rsEmp.getInt("empno") );
				dto.setDeptno( rsEmp.getInt("deptno") );
				dto.setEname( rsEmp.getString("ename") );
				dto.setHiredate(rsEmp.getDate("hiredate"));
				dto.setJob( rsEmp.getString("job"));
				elist.add(dto);
			}while(rsEmp.next());
		} 
		rsEmp.close();
		stmtEmp.close();
		DBConn.close();
	} catch (Exception e) { 
		e.printStackTrace();
	} 
	String path = "ex04_ok.jsp";
	RequestDispatcher dispatcher = request.getRequestDispatcher(path);
	request.setAttribute("list", elist);
	dispatcher.forward(request, response);
}

Ex04_emp서블릿 객체는 DB와 연결해서 부서리스트를 request.setAttribute("list", elist); 메서드를 통해 ex04_ok.jsp로 전달한다.

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<% 
	ArrayList<EmpDTO> elist = (ArrayList<EmpDTO>)request.getAttribute("list");
%>
<!DOCTYPE html>
<html>
...
...
<body>
<%
	if(elist == null)
	{
		out.print("사원이 존재하지 않습니다.");
	}
	else
	{
		Iterator<EmpDTO> ir = elist.iterator();
		while(ir.hasNext()){
		EmpDTO dto = ir.next();
%>
		<h2><%= dto.getEname() %>(<%= dto.getEmpno() %>)</h2>
		<p class="title"><%= dto.getDeptno() %>-<%= dto.getJob() %></p>
		<p><%= dto.getHiredate() %></p>
		}
	
	}
%>
</body>
</html>

코드를 보면 request.getAttribute("list");을 통해 Ex04_emp서블릿 객체가 보낸 list를 받는다.



jsp - 재스퍼

지금까지 jsp파일 안에서 html코딩과 java코딩을 같이했는데 상당히 어색하다.

서블릿 객체는 다음과 같이 PrintWriter객체를 만들고 출력버퍼에 html문자열을 집어넣고 클라이언트에게 전달하였는데.

PrintWriter out = resp.getWriter();
out.print("<!DOCTYPE html>");

사실 jsp파일도 tomcat에 의해 위와같은 서블릿 객체로 변환된다.

스크립트릿 안에서 java문법이 틀리게 되면 다음과 같은 오류가 발생한다.

org.apache.jasper.JasperException: Unable to compile class for JSP:
org.apache.jasper.compiler

재스퍼(Jasper)는 톰켓의 JSP엔진이다. 제스퍼는 JSP파일을 파싱하여 서블릿(JavaEE) 코드로 컴파일 한다.
JSP 파일의 변경을 감지하여 리컴파일 작업도 수행한다.

재스퍼에 의해 .java파일로 변환된 jsp파일은 아래위치에 저장된다.(프로젝트 폴더에서 jsp파일명을 검색해보자)
\workspace위치\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\work\Catalina\localhost\프로젝트명\org\apache\jsp\

다음 jsp파일을 재스퍼가 어떻게 파싱해서 서블릿 객체로 만드는지 확인해보자.

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%! 
	int age = 20;
	public void dispInfo()
	{
		System.out.println("displayInfo() callled...");
	}
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<% 
String name = "홍길동";
%>
<h3>이름은 <%= name %>입니다.</h3>
<% 
	out.print("out.print() called...");
%>
</body>
</html>

재스퍼가 변환한 ex03_005ftest_jsp.java라는 파일이 위의 디렉터리 위치에 생성되고 안을 살펴 보면 좀 복잡하긴 해도 우리가 지금까지 써온 서블릿 객체와 비슷한 점이 많다.

public final class ex03_005ftest_jsp 
		extends org.apache.jasper.runtime.HttpJspBase
    implements org.apache.jasper.runtime.JspSourceDependent,
               org.apache.jasper.runtime.JspSourceImports {

	int age = 20;
	public void dispInfo()
	{
		System.out.println("displayInfo() callled...");
	}
	// 스크립트릿의 변수와 선언부의 변수선언의 차이는 다음과 같다.  
	...
	...
  public void _jspInit() {
  }

  public void _jspDestroy() {
  }

  public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
      throws java.io.IOException, javax.servlet.ServletException {
				...
				...
    }

    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\n");
      out.write("\r\n");
 
		String name = "홍길동";
		// _jspService 메서드의 지역변수로 잡힌다.  

      out.write('\r');
      out.write('\n');
      out.write("\r\n");
      out.write("<!DOCTYPE html>\r\n");
      out.write("<html>\r\n");
      out.write("<head>\r\n");
      out.write("</head>\r\n");
      out.write("<body>\r\n");
      out.write("<h3>이름은 ");
      out.print( name );
      out.write("입니다.</h3>\r\n");
 
	out.print("out.print() called...");

      out.write("\r\n");
      out.write("</body>\r\n");
      out.write("</html>");
		} 
		catch (java.lang.Throwable t) {
			...
			...
		} 
		finally {
			...
			...
    }
  }
}

jsp파일에서 작성한 주석까지 .java파일 안에 쓰여있다.
또한 jsp파일에서 <%! ... %>선언부에서 정의했던 변수age와 메서드dispInfo()는 서블릿 객체의 전역변수로 초기화 되었다.

int age = 20;
public void dispInfo()
{
	System.out.println("displayInfo() callled...");
}

반면 스크립트릿에서 정의한 <% String name = "홍길동"; %>_jspService메서드 안의 지역변수로 잡힌다.

_jspService메서드를 보면 서블릿 객체의 오버라딩된 service()메서드와 형식이 비슷하다.
public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)

get방식, post방식 요청 모두 _jspService메서드에 의해 처리되는 걸 유추할 수 있다….

<%= %>표현부에서 출력했던 것도 out.print()메서드로 바뀌어 있다.

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;
...
...
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;

그리고 jsp에서 사용하는 기본객체 application, config, session, out등이 정의되어 있는데
지금까지 저런 기본객체를 jsp파일에서 사용할 수 있던것도 위와 같이 재스퍼가 기본적으로 파싱하면서 자동 생성해주기 때문이다.

즉 jsp도 tomcat의 재스퍼에게 컴파일 당할 뿐이지 일종의 서블릿객체이다.

카테고리:

업데이트: