웹 애플리케이션에서 리스너는 특정 이벤트가 발생했을 때 자동으로 코드를 실행하게 하는 컴포넌트입니다. 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 자체를 직접 사용하는 경우는 드물기 때문에, 대부분의 애플리케이션에서는 문제가 되지 않습니다.
주의: IntrospectorCleanupListener는 web.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>