Java Web 애플리케이션 리스너(Listener) 활용법

웹 애플리케이션에서 리스너는 특정 이벤트가 발생했을 때 자동으로 코드를 실행하게 하는 컴포넌트입니다. Application, Session, Request 객체가 생성되거나 소멸될 때, 혹은 속성이 추가, 수정, 삭제될 때 리스너가 작동합니다. 이러한 리스너는 웹 애플리케이션의 시작과 함께 초기화되며, 애플리케이션이 종료될 때 소멸됩니다. 주로 애플리케이션 구동 시 필요한 초기 설정 작업이나 고정 값, 객체 등을 설정하는 데 사용됩니다.

리스너의 종류

1. ServletContext 리스너

웹 애플리케이션 전체의 생명주기를 감지합니다.

  • ServletContextListener: 웹 애플리케이션의 컨텍스트(Application)가 초기화되거나 소멸될 때를 감지합니다.
    • contextInitialized(ServletContextEvent sce): 컨텍스트가 초기화될 때 호출됩니다.
    • contextDestroyed(ServletContextEvent sce): 컨텍스트가 소멸될 때 호출됩니다.
  • ServletContextAttributeListener: ServletContext에 속성이 추가, 삭제, 교체될 때를 감지합니다.
    • attributeAdded(ServletContextAttributeEvent scab): 속성이 추가될 때 호출됩니다.
    • attributeRemoved(ServletContextAttributeEvent scab): 속성이 삭제될 때 호출됩니다.
    • attributeReplaced(ServletContextAttributeEvent scab): 속성이 교체될 때 호출됩니다.

2. Session 리스너

HTTP 세션과 관련된 이벤트를 감지합니다.

  • HttpSessionListener: 세션이 생성되거나 소멸될 때를 감지합니다.
    • sessionCreated(HttpSessionEvent se): 세션이 생성될 때 호출됩니다.
    • sessionDestroyed(HttpSessionEvent se): 세션이 소멸될 때 호출됩니다.
  • HttpSessionAttributeListener: 세션에 속성이 추가, 삭제, 교체될 때를 감지합니다.
    • attributeAdded(HttpSessionBindingEvent se): 속성이 추가될 때 호출됩니다.
    • attributeRemoved(HttpSessionBindingEvent se): 속성이 삭제될 때 호출됩니다.
    • attributeReplaced(HttpSessionBindingEvent se): 속성이 교체될 때 호출됩니다.

세션 관리 및 Request 리스너

세션은 다양한 방법으로 만료 및 비활성화될 수 있습니다.

  • 세션 타임아웃: web.xml 설정으로 일정 시간 동안 활동이 없으면 세션이 자동으로 만료됩니다.
    <session-config>
        <session-timeout>120</session-timeout><!-- 120분 후 세션 만료 -->
    </session-config>
  • 수동 세션 비활성화: session.invalidate() 메소드를 호출하여 세션을 즉시 비활성화할 수 있습니다.

Request 관련 이벤트도 감지할 수 있습니다.

  • ServletRequestListener: 클라이언트의 Request가 생성되거나 소멸될 때를 감지합니다.
    • requestInitialized(ServletRequestEvent sre): Request가 초기화될 때 호출됩니다.
    • requestDestroyed(ServletRequestEvent sre): Request가 소멸될 때 호출됩니다.
  • ServletRequestAttributeListener: Request에 속성이 추가, 삭제, 교체될 때를 감지합니다.
    • attributeAdded(ServletRequestAttributeEvent srae): 속성이 추가될 때 호출됩니다.
    • attributeRemoved(ServletRequestAttributeEvent srae): 속성이 삭제될 때 호출됩니다.
    • attributeReplaced(ServletRequestAttributeEvent srae): 속성이 교체될 때 호출됩니다.

리스너 등록

web.xml 파일에 리스너 클래스를 등록합니다. 리스너는 필터(Filter)나 서블릿(Servlet)보다 먼저 초기화되고, 서블릿과 필터보다 늦게 소멸됩니다.

<listener>
    <listener-class>com.example.MyCustomListener</listener-class>
</listener>

활용 사례

1. 동시 접속자 수 통계 (HttpSessionListener 활용)

HttpSessionListener를 사용하여 현재 접속 중인 사용자 수를 추적하고 최대 동시 접속자 수를 기록할 수 있습니다.

import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import javax.servlet.ServletContext;
import java.text.SimpleDateFormat;
import java.util.Date;

public class OnlineUserListener implements HttpSessionListener {

    @Override
    public void sessionCreated(HttpSessionEvent event) {
        ServletContext context = event.getSession().getServletContext();
        // 현재 온라인 사용자 수 증가
        Integer currentCount = (Integer) context.getAttribute("onlineUserCount");
        if (currentCount == null) {
            currentCount = 0;
        }
        currentCount++;
        context.setAttribute("onlineUserCount", currentCount);

        // 최대 온라인 사용자 수 및 시간 갱신
        Integer maxCount = (Integer) context.getAttribute("maxOnlineUserCount");
        if (maxCount == null || currentCount > maxCount) {
            context.setAttribute("maxOnlineUserCount", currentCount);
            SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            context.setAttribute("peakTime", formatter.format(new Date()));
        }
        System.out.println("Session Created: " + event.getSession().getId());
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent event) {
        ServletContext context = event.getSession().getServletContext();
        // 현재 온라인 사용자 수 감소
        Integer currentCount = (Integer) context.getAttribute("onlineUserCount");
        if (currentCount != null && currentCount > 0) {
            currentCount--;
            context.setAttribute("onlineUserCount", currentCount);
        }
        System.out.println("Session Destroyed: " + event.getSession().getId());
    }
}

2. Spring ApplicationContext 로딩 (ContextLoaderListener)

ContextLoaderListener는 Spring 웹 애플리케이션 컨텍스트를 자동으로 로드하는 데 사용됩니다. web.xml에 설정하면 웹 애플리케이션 시작 시 Spring 설정 파일을 읽어옵니다.

  • 기본 설정: /WEB-INF/applicationContext.xml 파일을 로드합니다.
  • 사용자 정의 설정: contextConfigLocation 파라미터를 통해 설정 파일 경로를 지정할 수 있습니다.
<!-- Spring 설정 파일 위치 지정 (여러 개일 경우 콤마로 구분) -->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:META-INF/spring/*.xml</param-value>
</context-param>

<!-- Spring ContextLoaderListener 등록 -->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

3. Log4j 설정 관리 (Log4jConfigListener)

Log4jConfigListener는 Log4j 로깅 설정을 동적으로 관리하고 웹 애플리케이션의 로그 파일 경로를 편리하게 지정할 수 있도록 합니다.

  • `webAppRootKey`: 로그 파일 경로 지정을 위한 웹 애플리케이션 루트 키를 설정합니다.
  • `log4jConfigLocation`: Log4j 설정 파일의 위치를 지정합니다.
  • `log4jRefreshInterval`: 설정 파일 변경 감지 간격을 밀리초 단위로 지정합니다.
<!-- 웹 애플리케이션 루트 경로 키 설정 -->
<context-param>
    <param-name>webAppRootKey</param-name>
    <param-value>myapp.root</param-value>
</context-param>

<!-- Log4j 설정 파일 위치 -->
<context-param>
    <param-name>log4jConfigLocation</param-name>
    <param-value>classpath:log4j.properties</param-value>
</context-param>

<!-- Log4j 설정 자동 갱신 간격 (60초) -->
<context-param>
    <param-name>log4jRefreshInterval</param-name>
    <param-value>60000</param-value>
</context-param>

<!-- Log4jConfigListener 등록 -->
<listener>
    <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>

4. JavaBean Introspector 캐시 정리 (IntrospectorCleanupListener)

IntrospectorCleanupListener는 웹 애플리케이션 종료 시 JDK의 JavaBean Introspector 캐시를 정리하여 클래스 로더 메모리 누수를 방지합니다. 이 리스너는 다른 리스너보다 먼저 등록하는 것이 좋습니다.

이 리스너는 특히 Spring과 같은 프레임워크를 사용하거나, 서드파티 라이브러리가 Introspector 캐시를 제대로 관리하지 않을 때 유용할 수 있습니다. Spring 자체는 Introspector 캐시 누수를 일으키지 않지만, 다른 라이브러리와 함께 사용할 때 발생할 수 있는 문제를 예방합니다.

이 리스너는 웹 애플리케이션이 종료될 때, 시스템 전체의 Introspector 캐시를 비웁니다. 이 과정에서 다른 애플리케이션의 캐시 정보도 함께 삭제될 수 있으므로 주의가 필요합니다. 하지만 JavaBean Introspector 자체를 직접 사용하는 경우는 드물기 때문에, 대부분의 애플리케이션에서는 문제가 되지 않습니다.

주의: IntrospectorCleanupListenerweb.xml에서 가장 먼저 등록되어야 합니다. 예를 들어, Spring의 ContextLoaderListener보다 먼저 등록되어야 웹 애플리케이션의 생명주기 동안 적절한 시점에 동작할 수 있습니다.

<!-- IntrospectorCleanupListener 등록 (가장 먼저 등록) -->
<listener>
    <listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
</listener>

<!-- 다른 리스너들 (예: ContextLoaderListener) -->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

태그: java Servlet listener ServletContextListener HttpSessionListener

7월 27일 15:46에 게시됨