Servlet 02: Listener와 Filter

업데이트:
4 분 소요

비트캠프 서초본원 엄진영 강사님의 수업을 듣고 정리했습니다.


Servlet 02: Listener와 Filter

1. Listener

서블릿 컨테이너가 관리하는 컴포넌트

  • 서블릿, 필터, 리스너

리스너 만들기

  • 블릿 컨테이너 또는 서블릿, 세션 등의 객체 상태가 변경되었을 때 보고 받는 옵저버
  • Observer 디자인 패턴이 적용된 것이다.
  • ServletContextListener
    • 서블릿 컨테이너를 시작하거나 종료할 때 보고 받고 싶다면 이 인터페이스를 구현하라.
  • ServletRequestListener
    • 요청이 들어오거나 종료될 때 보고 받고 싶다면 이 인터페이스를 구현하라.
  • HttpSessionListener
    • 세션이 생성되거나 종료될 때 보고 받고 싶다면 이 인터페이스를 구현하라.
  • XxxListener
    • 기타 다양한 인터페이스가 있다. 문서를 참고하라.

리스너 배포하기

  • 방법1) DD 파일(web.xml)에 리스너를 등록한다.
    • web.xml 파일에 등록하여 호출하면, 서버를 시작하기만 하면 Listener01.contextInitialized()가 호출된다.
    • 서버를 종료해야 Listener01.contextDestroyed()가 호출된다.
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
  version="4.0"
  metadata-complete="false">

<!-- 리스너 등록 -->
	<listener>
    <listener-class>com.eomcs.web.ex02.Listener01</listener-class>
	</listener>

</web-app>
  • 방법2) @WebListener 애노테이션으로 설정한다. 경로를 지정하지 않아도 된다.
    • 서블릿을 요청했을 때 Listener02.requestInitialized()가 호출된다.
    • 요청 처리를 완료하면 Listener02.requestDestroyed()가 호출된다.
@WebListener
public class Listener02 implements ServletContextListener {
  
  @Override
  public void requestInitialized(ServletRequestEvent sre) { }

  @Override
  public void requestDestroyed(ServletRequestEvent sre) { }
}
Listener02.requestInitialized()
클라이언트 IP: 0:0:0:0:0:0:0:1
요청 URL: /ex01/s04
Servlet04.service(ServletRequest,ServletResponse)
Listener02.requestDestroyed()

리스너의 용도

  • 서블릿 컨테이너나, 세션 등이 특별한 상태일 때 필요한 작업을 수행한다.
  • ServletContextListener
    • 웹 애플리케이션을 시작할 때 Spring IoC 컨테이너 준비하기
    • 웹 애플리케이션을 시작할 때 DB 커넥션 풀 준비하기
    • 웹 애플리케이션을 종료할 때 DB 커넥션 풀에 들어 있는 모든 연결을 해제하기
// web.xml 파일에 등록하여 호출
public class Listener01 implements ServletContextListener {
  @Override
  public void contextInitialized(ServletContextEvent sce) {
    // 웹 애플리케이션이 시작될 때 호출된다.
    System.out.println("Listener01.contextInitialized()");
  }
  
  @Override
  public void contextDestroyed(ServletContextEvent sce) {
    // 웹 애플리케이션이 종료될 때 호출된다.
    System.out.println("Listener01.contextDestroyed()");
  }
}
  • ServletRequestListener
    • 요청이 들어 올 때 로그 남기기
@WebListener
public class Listener02 implements ServletRequestListener {
  @Override
  public void requestInitialized(ServletRequestEvent sre) {
    // 요청이 들어 왔을 때 호출된다.
    System.out.println("Listener02.requestInitialized()");
    HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
		// sre.getServletRequest()의 리턴 타입은 ServletRequet(범용)이다.
    // HttpServletRequest(특정 기능)이라고 이야기(형변환)해준다.
		// 쉽게 생각해서 ServletRequest는 빵이고, HttpServletRequest는 단팥빵이다.
    // 왜? HttpServletReqeust는 웹 기술을 다룰 수 있기 때문에, 
		// HTTP 프로토콜로 통신한다면 HttpServletRequest를 사용하는 것이 좋다.
    System.out.println("클라이언트 IP: " + request.getRemoteAddr());
    System.out.println("요청 URL: " + request.getServletPath());
		// Servlet 객체는 범용 환경에서 사용할 수 있도록 기획 & 설계되었지만
		// HttpServlet은 Http 프로토콜에서 더 값을 넣기 쉽게 되어있다.
		// HttpServlet는 HTTP 프로토콜 정보를 가지고 있기 때문에
		// getRemoteAddr()이나 getServletPath()를 사용할 수 있다.
  }

  @Override
  public void requestDestroyed(ServletRequestEvent sre) {
    // 요청 처리를 완료할 때 호출된다.
    System.out.println("Listener02.requestDestroyed()");
  }
}

리스너 vs 필터

  • 리스너는 특정 요청을 구분할 수 없다. 요청이 들어오면 무조건 일을 한다.
  • 필터는 요청을 구분한다. 조건을 걸어서 필터를 실행하거나 실행하지 않도록 설계할 수 있다.

2. Filter

필터 만들기

  • javax.servlet.Filter 인터페이스 규칙에 따라 작성한다.

필터 배포하기

  • 방법1) DD 파일(web.xml)에 설정한다.
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
  version="4.0"
  metadata-complete="false">

<!-- 필터 등록 -->
  <filter>
    <filter-name>Filter01</filter-name>
    <filter-class>com.eomcs.web.ex02.Filter01</filter-class>
  </filter>
  
<!-- 필터를 적용할 URL을 지정 -->
  <filter-mapping>
    <filter-name>Filter01</filter-name>
    <url-pattern>/ex02/s1</url-pattern>
  </filter-mapping>

</web-app>
  • 방법2) @WebFilter(“/ex02/*”) 애노테이션으로 설정하면 된다.
//@WebFilter("/ex02/*")
public class Filter02 implements Filter {

  ...

}

필터의 용도

  • 블릿을 실행하기 전후에 필요한 작업을 수행
  • 서블릿 실행 전
    • 웹브라우저가 보낸 암호화된 파라미터 값을 서블릿으로 전달하기 전에 암호 해제하기
    • 웹브라우저가 보낸 압축된 데이터를 서블릿으로 전달하기 전에 압축 해제하기
    • 서블릿의 실행을 요청할 권한이 있는지 검사하기
    • 로그인 사용자인지 검사하기
    • 로그 남기기
  • 서블릿 실행 후
    • 클라이언트로 보낼 데이터를 압축하기
    • 클라이언트로 보낼 데이터를 암호화시키기
public class Filter01 implements Filter {
  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
    // 필터 객체를 생성한 후 제일 처음으로 호출된다.
    // 필터가 사용할 자원을 이 메서드에서 준비한다.
    // => 웹 애플리케이션을 시작할 때 필터는 자동 생성된다.
    System.out.println("Filter01.init()");
  }

  @Override
  public void destroy() {
    // 웹 애플리케이션이 종료될 때 호출된다.
    // init()에서 준비한 자원을 해제한다.
    System.out.println("Filter01.destroy()");
  }

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {
    // 요청이 들어 올 때 마다 호출된다.
    // => 단 필터를 설정할 때 지정된 URL의 요청에만 호출된다.
    // => 서블릿이 실행되기 전에 필터가 먼저 실행된다.
    // => 서블릿을 실행한 후 다시 필터로 리턴한다.
    System.out.println("Filter01.doFilter() : 시작");

    // 다음 필터를 실행한다.
    // 만약 다음 필터가 없으면,
    // 요청한 서블릿의 service() 메서드를 호출한다.
    // service() 메서드 호출이 끝나면 리턴된다.
    chain.doFilter(request, response);

    // 체인에 연결된 필터나 서블릿이 모두 실행된 다음에
    // 다시 이 필터로 리턴될 것이다.
    System.out.println("Filter01.doFilter() : 종료");
  }
}
@WebFilter("/ex02/*")
public class Filter02 implements Filter {
  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
    // 필터 객체를 생성한 후 제일 처음으로 호출된다.
    // 필터가 사용할 자원을 이 메서드에서 준비한다.
    System.out.println("Filter02.init()");
  }

  @Override
  public void destroy() {
//
    // 웹 애플리케이션이 종료될 때 호출된다.
    // init()에서 준비한 자원을 해제한다.
    System.out.println("Filter02.destroy()");
  }

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {
    // 요청이 들어 올 때 마다 호출된다.
    // => 단 필터를 설정할 때 지정된 URL의 요청에만 호출된다.
    // => 서블릿이 실행되기 전에 필터가 먼저 실행된다.
    // => 서블릿을 실행한 후 다시 필터로 리턴한다.
    System.out.println("Filter02.doFilter() : 시작");

    // 다음 필터를 실행하거나 요청한 서블릿을 실행하려면 다음 코드를 반드시 실행해야 한다.
    chain.doFilter(request, response);

    // 체인에 연결된 필터나 서블릿이 모두 실행된 다음에 다시 이 필터로 리턴될 것이다.
    System.out.println("Filter02.doFilter() : 종료");
  }
}

필터 실행 순서

  • 필터의 실행 순서를 임의로 조정할 수 없다.
  • 필터를 정의할 때 순서에 의존하는 방식으로 프로그래밍 하지 말라.
  • 필터의 실행 순서에 상관없이 각 필터가 독립적으로 동작하도록 작성하라.

필터 실행 확인

Listener02.requestInitialized()
클라이언트 IP: 0:0:0:0:0:0:0:1
요청 URL: /ex02/a/s2
Filter02.doFilter() : 시작
/ex02/a/s2 서블릿 실행!
Filter02.doFilter() : 종료
Listener02.requestDestroyed()
Listener02.requestInitialized()
클라이언트 IP: 0:0:0:0:0:0:0:1
요청 URL: /ex02/s1
Filter01.doFilter() : 시작
Filter02.doFilter() : 시작
/ex02/s1 서블릿 실행!
Filter02.doFilter() : 종료
Filter01.doFilter() : 종료
Listener02.requestDestroyed()

태그:

카테고리:

업데이트: