관리 메뉴

거니의 velog

(26) 서블릿의 필터와 리스너 기능 3 본문

Java_Servlet

(26) 서블릿의 필터와 리스너 기능 3

Unlimited00 2023. 8. 31. 08:38

4. 여러 가지 서블릿 관련 Listener API

* 자바 GUI에서는 마우스 클릭과 같은 이벤트 발생 시 여러 가지 이벤트 핸들러를 이용해 화면의 기능을 구현한다. 이처럼 서블릿에서도 서블릿에서 발생하는 이벤트에 대해 적절한 처리를 해주는 여러 가지 리스너를 제공한다.

<서블릿 관련 여러 가지 리스너들>

서블릿 관련 Listener 추상 메서드 기능
ServletContextAttributeListener attributeAdded()
attributeRemoved()
attributeReplaced()
Context 객체에 속성 추가/제거/수정 이벤트 발생 시 처리한다.
HttpSessionListener sessionCreated()
sessionDestroyed()
세션 객체의 생성/소멸 이벤트 발생 시 처리한다.
ServletRequestListener requestInitialized()
requestDestroyed()
클라이언트의 요청 이벤트 발생 시 처리한다.
ServletRequestAttributeListener attributedAdded()
attributedRemoved()
attributeReplaced()
요청 객체에 속성 추가/제거/수정 이벤트 발생 시 처리한다.
HttpSessionBindingListener valueBound()
valueUnbound()
세션에 바인딩/언바인딩된 객체를 알려주는 이벤트 발생 시 처리한다.
HttpSessionAttributeListener attributedAdded()
attributedRemoved()
attributeReplaced()
세션에 속성 추가/제거/수정 이벤트 발생 시 처리한다.
ServletContextListener contextInitialized()
contextDestroyed()
컨텍스트 객체의 생성/소멸 이벤트 발생 시 처리한다.
HttpSessionActivationListener sessionDidActivate()
sessionWillPassivate()
세션의 활성화/비활성화 이벤트 발생시 처리한다.

(1) HttpSessionBindingListener 이용해 로그인 접속자수 표시

* HttpSessionBindingListener를 이용해 현재 웹 페이지에 로그인한 접속자수를 알아보는 기능을 구현해 보자.

1. 다음과 같이 실습 파일을 새로 준비한다.

2. ID와 비밀번호를 입력하여 전송하는 로그인창을 작성한다.

<!DOCTYPE html>
<html lang="ko">
    <head>
        <meta charset="UTF-8">
        <title>로그인창</title>
    </head>

    <body>
        <form name="frmLogin" method="post" action="login" enctype="utf-8">
            아이디 :<input type="text" name="user_id"><br>
            비밀번호:<input type="password" name="user_pw"><br>
            <input type="submit" value="로그인">
            <input type="reset" value="다시입력">
        </form>
    </body>
</html>

3. LoginTest 클래스를 다음과 같이 작성한다. LoginImpl loginImpl(user_id, user_pw)를 실행하여 전송된 ID와 비밀번호를 저장한다. 또 session, setAttribute("loginUser", loginUser)으로 세션에 바인딩 시 미리 HttpSessionBindingListener를 구현한 loginImpl의 valueBound() 메서드를 호출한다.

package sec04.ex01;

import java.io.IOException;
import java.io.PrintWriter;

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;


@WebServlet("/login")
public class LoginTest extends HttpServlet {
	
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding("utf-8");
		response.setContentType("text/html;charset=utf-8");
		PrintWriter out = response.getWriter();
		HttpSession session = request.getSession();

		String user_id = request.getParameter("user_id");
		String user_pw = request.getParameter("user_pw");

		LoginImpl loginUser = new LoginImpl(user_id, user_pw); // 이벤트 핸들러를 생성한 후 세션에 저장한다.
		if (session.isNew()) {
			session.setAttribute("loginUser", loginUser); // 세션에 바인딩 시 LoginImpl의 valueBound()메서드를 호출한다.
		}

		out.println("<head>");
		out.println("<script  type='text/javascript'>");
		out.println("setTimeout('history.go(0);', 5000)"); // 자바스크립트의 setTimeout() 함수를 이용해 5초마다 서블릿에 재요청하여 현재 접속자수를 표시한다.
		out.println("</script>");
		out.println("</head>");
		out.println("<html><body>");
		out.println("아이디는 " + loginUser.user_id + "<br>");
		out.println("총 접속자수는" + LoginImpl.total_user + "<br>"); // 접속자수를 브라우저로 출력한다.
		out.println("</body></html>");

	}

}

4. loginImpl 클래스를 다음과 같이 작성한다. HttpSessionBindingListener를 구현하여 세션에 바인딩 이벤트를 처리하는 이벤트 핸들러가 구현되어 있다. 세션에 바인딩 시 valueBound()가 호출되어 static 변수인 total_user의 값을 1 증가시킨다.

package sec04.ex01;

import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;

public class LoginImpl implements HttpSessionBindingListener { // HttpSessionBindingListener를 구현해 세션에 바인딩 시 이벤트를 처리한다.
	String user_id;
	String user_pw;
	static int total_user = 0; // 세션에 바인딩 시 1씩 증가시킨다.

	public LoginImpl() {}

	public LoginImpl(String user_id, String user_pw) {
		this.user_id = user_id;
		this.user_pw = user_pw;
	}

	@Override
	public void valueBound(HttpSessionBindingEvent arg0) {
		System.out.println("사용자 접속");
		++total_user;
	} // 세션에 저장 시 접속자수를 증가시킨다.

	@Override
	public void valueUnbound(HttpSessionBindingEvent arg0) {
		System.out.println("사용자 접속 해제");
		total_user--;
	} // 세션에서 소멸 시 접속자 수를 감소시킨다.
}

5. 서로 다른 종류의 브라우저에서 접속하여 실행 결과를 확인해 보자. 우선 크롬에서 로그인하면 접속자 ID와 접속자 수가 표시된다.

- http://localhost:8090/pro10/login2.html

6. 이번에는 익스플로러에서 로그인하면 다음과 같이 접속자 ID와 접속자수가 표시된다.

7. 5초 후 크롬에서는 접속자수가 갱신되어 표시된다.

* 참고로 HttpSessionBindingListener를 구현한 LoginImpl 클래스는 리스너를 따로 등록한 필요가 없다.


(2) HttpSessionListener 이용해 로그인 접속자 수 표시

* 이번에는 HttpSessionListener를 이용해 웹 페이지 로그인 시 접속자수와 모든 접속자 ID를 표시해 주는 기능을 구현해 보자.

1. 다음과 같이 실습 파일을 준비한다.

2. 첫 번째 서블릿인 LoginTest 클래스 파일을 다음과 같이 수정한다. setAttribute()를 이용해 loginUser를 세션에 바인딩하면 LoginImpl 클래스에 구현된 이벤트 핸들러를 이용해 접속자수를 1 증가시킨다. 그리고 user_list에 접속자 ID를 저장한 다음 ServletContext 객체에 바인딩한다.

package sec04.ex02;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletContext;
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;


@WebServlet("/login")
public class LoginTest extends HttpServlet {
	
	ServletContext context = null;
	List user_list = new ArrayList(); // 로그인한 접속자 ID를 저장하는 ArrayList이다.
	
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding("utf-8");
		response.setContentType("text/html;charset=utf-8");
		context = getServletContext();
		PrintWriter out = response.getWriter();
		HttpSession session = request.getSession();
		String user_id = request.getParameter("user_id");
		String user_pw = request.getParameter("user_pw");
		LoginImpl loginUser = new LoginImpl(user_id, user_pw); // LoginImpl 객체를 생성한 후 전송된 ID와 비밀번호를 저장한다.
		if (session.isNew()) {
			session.setAttribute("loginUser", loginUser);
			user_list.add(user_id);
			context.setAttribute("user_list", user_list);
		} // 최초 로그인 시 접속자 ID를 ArrayList에 차례로 저장한 후 다시 context 객체에 속성으로 저장한다.

		out.println("<html><body>");
		out.println("아이디는 " + loginUser.user_id + "<br>");
		out.println("총 접속자수는" + LoginImpl.total_user + "<br><br>"); // 세션에 바인딩 이벤트 처리 후 총 접속자수를 표시한다.
		out.println("접속 아이디:<br>");
		List list = (ArrayList) context.getAttribute("user_list");
		for (int i = 0; i < list.size(); i++) {
			out.println(list.get(i) + "<br>");
		} // context 객체의 ArrayList를 가져와 접속자 ID를 차례로 브라우저로 출력한다.
		out.println("<a href='logout?user_id=" + user_id + "'>로그아웃 </a>"); // 로그아웃 클릭 시 서블릿 logout으로 접속자 ID를 전송해 로그아웃 한다.
		out.println("</body></html>");
	}

}

3. LogoutTest 클래스를 다음과 같이 작성한다. 여기서는 로그아웃 링크를 클릭하면 접속자 수를 1 감소시키고 user_list에서 로그아웃한 접속자 ID를 삭제한 후 다시 user_list를 ServletContext 객체에서 바인딩하도록 설정한다.

package sec04.ex02;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletContext;
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;


@WebServlet("/logout")
public class LogoutTest extends HttpServlet {
	
	ServletContext context;

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doHandle(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doHandle(request, response);
	}

	private void doHandle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding("utf-8");
		response.setContentType("text/html;charset=utf-8");
		context = getServletContext();
		PrintWriter out = response.getWriter();
		HttpSession session = request.getSession();
		String user_id = request.getParameter("user_id"); // user_list에서 삭제할 ID를 가져온다.

		session.invalidate(); // 로그아웃 시 세션을 소멸시킨다.

		List user_list = (ArrayList) context.getAttribute("user_list");
		user_list.remove(user_id);
		context.removeAttribute("user_list");
		context.setAttribute("user_list", user_list); // user_list에서 로그아웃한 접속자 ID를 삭제한 후 다시 user_list를 컨텍스트에 저장한다.
		out.println("<br>로그아웃 했습니다.");
	}

}

* LoginImpl 클래스는 HttpSessionListener를 구현해 세션 생성과 소멸 시 이벤트를 처리하는 핸들러이다. 중요한 것은 앞의 LoginImpl에서 구현한 HttpSessionBindingListener와는 다르게 HttpSessionListener는 반드시 리스너를 구현한 이벤트 핸들러를 애너테이션을 이용해서 등록해야 한다는 것이다. 직접 구현해 보자.

1. sec04.ex02 패키지를 선택하고 마우스 오른쪽 버튼을 클릭한 후 New > Listener를 선택한다.

2. Class name으로 LoginImpl을 입력하고 Next를 클릭한다.

3. HttpSessionListener에 체크하고 Next를 클릭한다.

4. Finish를 클릭한다.

5. @WebListener 애너테이션으로 리스너가 등록된 것을 확인할 수 있다.

6. 리스너를 등록한 이벤트 핸들러를 이용해서 세션을 생성할 때는 sessionCreated() 메서드로 이벤트를 처리하고, 세션을 삭제할 때는 sessionDestroyed() 메서드로 이벤트를 처리한다.

package sec04.ex02;

import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;


@WebListener // HttpSessionBindingListener를 제외한 Listener를 구현한 모든 이벤트 핸들러는 반드시 애너테이션을 이용해서 Listener로 등록해야 한다.
public class LoginImpl implements HttpSessionListener {
	
	String user_id;
	String user_pw;
	static int total_user = 0;

	public LoginImpl() {
	}

	public LoginImpl(String user_id, String user_pw) {
		this.user_id = user_id;
		this.user_pw = user_pw;
	}

	@Override
	public void sessionCreated(HttpSessionEvent arg0) { // 세션 생성 시 이벤트를 처리한다.
		System.out.println("세션 생성");
		++total_user; // 세션 생성 시 접속자수를 1 증가시킨다.
	}

	@Override
	public void sessionDestroyed(HttpSessionEvent arg0) { // 세션 소멸 시 이벤트를 처리한다.
		System.out.println("세션 소멸");
		--total_user; // 세션 소멸 시 접속자수를 1 감소시킨다.
	}

}

7. 실행하면 사용자마다 로그인/로그아웃 시 접속자수와 접속자 ID를 표시해 준다. 다음은 첫 번째 아이디로 로그인한 결과이다.

- http://localhost:8090/pro10/login2.html

8. 이번에는 인터넷 익스플로러로 두 번째 ID로 로그인하면 다음과 같이 현재 접속자수와 접속자 ID가 출력된다.

9. 다시 크롬에서 화면을 갱신하면 다음과 같이 현재 접속자수와 접속자 ID가 표시된다.

10. 익스플로러에서 로그아웃을 클릭한다.

11. 크롬에서 화면을 재요청하면 다음과 같이 현재 접속자수와 접속자 ID가 표시된다.

* 이상으로 서블릿에서 제공하는 리스너의 기능에 대해 알아봤다. 이처럼 다른 리스너에 대해서도 세부 기능을 익히고 나면 고급 기능도 쉽게 구현할 수 있다.