KH 94일차 - 세션 (**)
1. 세션 관리 ( Session Tracking )
- 세션의 정의는 ‘서버와 클라이언트 간의 지속적인 연결’을 의미한다.
- 연결을 통하여 클라이언트는 지속적으로 서버에 특정 동작을 요청할 수 있으며, 서버는 실행결과를 클라이언트에 응답할 수 있다.
- 세션관리는 일반적으로 HttpSession 클래스나 Cookie 클래스를 통하여 한다.
2. HttpSession 클래스를 이용한 세션처리
- 세션이란 사용자의 상태정보를 서버에서 관리하는 메커니즘을 의미한다.
- 세션의 정보는 클라이언트가 서버에 접속해서 종료할 때까지 유지된다.
- 서버의 부하가 클 수 있기에, 주로 time-out을 지정하여 일정시간 동안 요청이 없으면 서버는 세션의 정보를 유지하지 않고 제거해버린다.
- 클라이언트가 서블릿에 요청을 하면, 서블릿에서는 getSession( ) 메소드를 사용하여 session영역을 생성하고, 세션ID를 생성하여 session영역에 저장한다.
[ 1. web.xml파일에 세션 Time Out 지정 방법 - 전체적인 프로젝트에 적용 ]
<session-config>
<session-timeout>30</session-timeout>
</session-config>
[ 2. 세션 ID ] (*)
package org.zerock.myapp;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
@Log4j2
@NoArgsConstructor
@WebServlet("/SessionIdTest")
public class SessionIdTestServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
log.trace("service(req, res) invoked.");
// 웹 브라우저가 최초로 request message를 보내오면,
// Servlet Container는 이 웹 브라우저에 이름(= 세션 ID)을 지어서
// Response Message의 헤더에 담아서 전송한다.
// 웹 브라우저는 응답헤더에서 자기의 이름(= 세션 ID)를 추출해서 메모리에 저장
// 이후, 두번째 이상 요청 부터는 항상 자기의 이름을(= 세션 ID)을 함께 담아서 요청을 보낸다.
// =======================================================
// HttpSession 객체 => 추상적인 세션을 객체로 만든 것이다.
// request 객체로부터 획득이 가능하다.
// 1. Session 생성
HttpSession sess = req.getSession(); // 기존 세션이 있으면 돌려주고, 없으면 만들어달라
// HttpSession sess1 = req.getSession(true); // 기존 세션이 있으면 돌려주고, 없으면 만들어달라
// HttpSession sess2 = req.getSession(false); // 기존 세션이 있으면 돌려주고, 없으면 만들지 말라!! ( 잘 사용하지 X )
// =======================================================
// 2. Session Id 얻어내기
String sessionId = sess.getId();
log.info("\t + 1. sessionId :{}", sessionId);
// sessionId는 유니크한 전역적인 Id로 생성된다.
// =======================================================
// 3. Session Scope 파괴
sess.invalidate(); // 이 세션Id로 식별되는 Session Scope 공유데이터 영역 파괴
log.info("\t + Session Scope Destroyed Definitely.");
// 파괴되었기에, 새로고침을 하면 새로운 Session Id가 생성되게 된다.
// 파괴되지 않았다면, 새로고침을 해도 원래 기존의 Session Id가 나온다.
// =======================================================
// 4. Session Scope 파괴 후 다시 Session Id 생성
sess = req.getSession();
sessionId = sess.getId();
log.info("\t + 2. sessionId :{}", sessionId);
// 서로 다른 Session Id가 출력되고 있음을 알 수 있다.
// =======================================================
} // service
} // end class
[ 3. 세션을 이용한 장바구니 만들기 ] (***)
[ 3 - 1. 장바구니 생성 및 품목 넣기 ]
package org.zerock.myapp;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import lombok.Cleanup;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
@Log4j2
@NoArgsConstructor
@WebServlet("/CartSave") // 장바구니에 담기
public class CartSaveServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
log.trace("service(req, res) invoked.");
// 웹 브라우저가 최초로 request message를 보내오면,
// Servlet Container는 이 웹 브라우저에 이름(= 세션 ID)을 지어서
// Response Message의 헤더에 담아서 전송한다.
// 웹 브라우저는 응답헤더에서 자기의 이름(= 세션 ID)를 추출해서 메모리에 저장
// 이후, 두번째 이상 요청 부터는 항상 자기의 이름을(= 세션 ID)을 함께 담아서 요청을 보낸다.
// =======================================================
// HttpSession 객체 => 추상적인 세션을 객체로 만든 것이다.
// request 객체로부터 획득이 가능하다.
// =======================================================
// 1. 장바구니에 저장할 상품항목을 전송 파라미터로 획득
req.setCharacterEncoding("UTF-8");
String product = req.getParameter("product");
log.info("\t + product :{}",product);
// =======================================================
// 2. 장바구니 생성 및 수신된 상품을 장바구니에 추가
HttpSession sess = req.getSession(); // 기존 세션이 있으면 돌려주고, 없으면 만들어달라
// HttpSession sess1 = req.getSession(true); // 기존 세션이 있으면 돌려주고, 없으면 만들어달라
// HttpSession sess2 = req.getSession(false); // 기존 세션이 있으면 돌려주고, 없으면 만들지 말라!! ( 잘 사용하지 X )
// sess.getAttribute("장바구니 이름지정");
@SuppressWarnings("unchecked")
List<String> list = (List<String>) sess.getAttribute("basket");
// @SuppressWarnings는 너무 강하게 체크하지 말라는 이야기이다.
if( list != null ) { // 1. 장바구니가 있어 얻어냄
list.add(product); // 1-1. 장바구니가 있으니, 장바구니에 상품 추가
} else { // 2. 장바구니가 Session Scope에 없다.
list = new ArrayList<>(); // 2-1. 장바구니가 없으니 장바구니를 만들고
list.add(product); // 2-2. 상품을 추가한 후에
sess.setAttribute("basket", list); // 2-3. Session Scope에 올린다.
} // if - else
log.info("\t + list : {}", list);
// =======================================================
// 3. 응답문서 준비
res.setContentType("text/html; charset=utf8");
@Cleanup
PrintWriter out = res.getWriter();
out.println("<h1>장바구니에 담기 성공</h1>");
out.println("<a href='/CartSave'>장바구니 보기</a>");
out.flush();
// =======================================================
} // service
} // end class
[ 3 - 2. 장바구니 조회하기 ]
package org.zerock.myapp;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import java.util.Objects;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import lombok.Cleanup;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
@Log4j2
@NoArgsConstructor
@WebServlet("/CartBasket")
public class CartBasketServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@SuppressWarnings("unchecked")
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
log.trace("service(req, res) invoked.");
List<String> list = null;
try {
HttpSession sess = req.getSession(false);
log.info("\t + sess : {}", sess);
// 세션이 죽지 않았기에, 그대로 세션Id가 동일하게 나온다.
// 기존 세션이 없으면 장바구니도 없기에, false를 주어 출력하지 않게 하였다.
Objects.requireNonNull(sess);
// Null인지 체크
// =====================================
list = (List<String>) sess.getAttribute("basket");
// Session Scope에 올려져 있기에 sess에서 얻어야 한다.
Objects.requireNonNull(list);
// Null인지 체크
list.forEach(log::info);
// 널이 아니라면 하나씩 찍어라
// =====================================
} catch(Exception e) {
throw new ServletException(e);
} // try - catch
// =====================================
res.setContentType("text/html; charset=utf8");
@Cleanup
PrintWriter out = res.getWriter();
out.println("<h1>장바구니에 내용</h1>");
out.println("<ol>");
list.forEach(s -> {
out.println("\t <li>" + s + "</li>");
}); // for each
out.println("</ol>");
out.println("<a href='/CartDelete'>장바구니 비우기</a>");
out.flush();
} // service
} // end class
[ 3 - 3. 장바구니 비우기 ]
package org.zerock.myapp;
import java.io.IOException;
import java.util.Objects;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
@Log4j2
@NoArgsConstructor
@WebServlet("/CartDelete")
public class CartDeleteServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
log.trace("service(req, res) invoked.");
// 장바구니 비우기 : 현재 브라우저의 세션 아이디(이름)로 식별되는 Session Scope 공유영역 파괴
HttpSession sess = req.getSession(false);
// false로 준 이유는, 세션이 없다면 장바구니가 없다는 의미이기에 수행될 필요가 없기 때문이다.
try {
Objects.requireNonNull(sess);
// 세션이 유효하다면,
sess.invalidate();
// Session Id 무효화 + Session Scope 영역 파괴
// ===========================================
// 마지막 응답 페이지는 요청 포워딩을 통해 ( Request Forwarding)
// JSP에서 생성
RequestDispatcher dispatcher = req.getRequestDispatcher("/WEB-INF/views/cartdelete.jsp");
dispatcher.forward(req, res);
} catch(Exception e) {
throw new ServletException(e);
} // try - catch
}// service
} // end class
[ 3 - 4. 장바구니 비울 때의 화면 만들기 ]
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>cartdelete</title>
</head>
<body>
<h1>cartdelete.jsp</h1>
<hr>
<h2>장바구니가 비워졌습니다.</h2>
</body>
</html>
[ 4. jsp파일로 요청 포워딩하기 ] (***)
[ 4 - 1. DTO 객체 생성 ]
package org.zerock.myapp.domain;
import lombok.Data;
@Data
public class Person {
private String name;
private int age;
} // end class
[ 4 - 2. A Servlet 생성 -> B Servlet으로 요청 포워딩 ]
package org.zerock.myapp.servlet;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
@Log4j2
@NoArgsConstructor
@WebServlet("/A")
public class AServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
log.trace("service(req, res) invoked.");
log.info("A Servlet - 요청 잘 받았습니다.");
log.info("A Servlet - 저는 여기까지만 처리합니다.");
// B서블릿으로 위임시킨다.
req.getRequestDispatcher("/B").forward(req, res);
} // service
} // end class
[ 4 - 2. B Servlet 생성 -> C Servlet으로 요청 포워딩 ]
package org.zerock.myapp.servlet;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
@Log4j2
@NoArgsConstructor
@WebServlet("/B")
public class BServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
log.trace("service(req, res) invoked.");
log.info("B Servlet - 요청 잘 받았습니다.");
log.info("B Servlet - 저는 여기까지만 처리합니다.");
req.getRequestDispatcher("/C").forward(req, res);
} // service
} // end class
[ 4 - 3. C Servlet 생성 및 Person 객체 생성 및 Request Scope에 올리기 -> jsp 파일로 요청 포워딩 ]
package org.zerock.myapp.servlet;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.zerock.myapp.domain.Person;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
@Log4j2
@NoArgsConstructor
@WebServlet("/C")
public class CServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
log.trace("service(req, res) invoked.");
log.info("C Servlet - 요청 잘 받았습니다.");
log.info("C Servlet - 저는 여기까지만 처리합니다.");
// =====================================
// 비지니스 로직 수행결과 데이터인 Person 객체를 생성하여
// Request Scope 공유 데이터 영역에 올려 놓는다.
Person person = new Person();
person.setName("hehehehe");
person.setAge(23);
req.setAttribute("__MODEL__", person);
// =====================================
req.getRequestDispatcher("/WEB-INF/views/response.jsp").forward(req, res);
} // service
} // end class
[ 4 - 4. jsp 파일 생성 ] (***)
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>response</title>
</head>
<body>
<h1>response.jsp</h1>
<hr>
<h2>모델 데이터 출력</h2>
<ul>
<li>${__MODEL__.name}</li>
<li>${__MODEL__.age}</li>
</ul>
</body>
</html>