주요글: 도커 시작하기
반응형
서블릿 2.3에 새롭게 추가된 세션과 콘텍스트 관련 이벤트에 대해서 살펴본다.

서블릿 2.3의 콘텍스트 라이프 사이클

필자는 서블릿이 처음 나왔을 때 부터 서블릿을 사용하여 웹 프로그래램을 개발해왔는데, 늘 느끼는 짜증나는 부분이 있었다. 그것은 바로 서블릿 콘테이너의 라이프 사이클과 관련된 처리를 정확하게 할 수 없다는 점이었다. 서블릿 콘테이너를 가동하거나 멈출 때 발생하는 이벤트가 없기 때문에 자원의 초기화 시점이나 반납 시점이 명확하지 못한 경우가 많았으며, 특히 자원의 반납 부분을 명확하게 만들기 위해서 이런 저런 꽁수(?)를 사용해야만 했었다.

하지만, 서블릿 2.3에 들어서면서 더 이상 이러한 문제때문에 머리에 열을 줄 필요가 없게 되었다. 왜냐면, 서블릿 2.3에 새롭게 추가된 이벤트 때문이다. 서블릿 2.3에는 콘텍스트의 라이프 사이클과 관련된 이벤트인 ServletContextEvent가 추가되었으며, 또한 세션의 라이프 사이클과 관련된 HttpSessionEvent가 추가되었다. 이제, 개발자들은 ServletContextEvent를 통해서 전체 웹 어플리케이션의 초기화 시점과 종료 시점을 선형적으로 처리할 수 있게 되었으며, HttpSessionEvent를 통해서 특정 사용자의 세션 관리를 보다 명확하게 할 수 있게 되었다.

'콘텍스트(context)'는 하나의 웹 어플리케이션을 나타낸다. 보통, 콘텍스트는 [Serlvet엔진]/webapps 디렉토리에 위치시키는 경우가 많다. 톰캣의 경우 [톰캣루트]/webapps 디렉토리에 WAR 파일을 위치시키거나 디렉토리를 생성하면 자동으로 콘텍스트에 추가되며, server.xml 설정 파일의 <web-app> 태그를 사용하여 임의의 디렉토리를 콘텍스트로 지정할 수도 있다. 콘텍스트는 JSP/서블릿/HTML/이미지 파일/커스텀 태그 등으로 구성된다.

서블릿 2.3은 콘텍스트의 라이프 사이클을 2단계로 정의하고 있다. 첫번째 단계는 콘텍스트 초기화(또는 생성) 단계이며, 두번째 단계는 콘텍스트의 종료(또는 삭제) 단계이다. 일반적으로 서블릿 엔진이 처음 시작할 때 각각의 콘텍스트가 초기화되며, 서블릿 엔진을 종료할 때 콘텍스트도 함께 종료된다. 그림 1은 콘텍스트의 초기화 시점과 종료 시점을 보여주고 있다.

그림1 - 콘텍스트의 라이프 사이클

콘텍스트의 초기화는 사용자들이 웹브라우저를 사용하여 웹어플리케이션을 실행하는 시점보다 이전에 발생하기 때문에, 웹 어플리케이션에서 사용하는 데이터베이스 커넥션이나 객체의 풀 또는 각종 설정 정보등을 초기화하기에 가장 알맞은 시점이다. 또한, 콘텍스트의 종료는 서블릿 엔진을 종료하기 이전에 수행되기 때문에 어플리케이션에서 사용하고 있는 자원을 반납하기에 가장 알맞은 시점이 된다.

ServletContextEvent와 ServletContextListener

서블릿 콘테이너는 콘텍스트의 초기화되거나 종료되는 것을 알리기 위해서 이벤트-리스너 방식을 사용한다. 콘텍스트의 초기화나 종료 시점에서 서블릿 콘테이너는 ServletContextEvent를 발생시키며, 이 이벤트는 콘텍스트의 정보를 지정하는 web.xml을 통해서 지정된 ServletContextListener에 전달된다.

먼저 javax.servlet.ServletContextListener 인터페이스에 대해서 살펴보자. ServletContextListener 인터페이스는 AWT의 다른 이벤트 리스너와 마찬가지로 이벤트가 발생하는 시점에 호출되는 메소드들을 선언하고 있으며, 그 목록은 다음과 같다.

  • public void contextInitialized(ServletContextEvent sce)
       콘텍스트가 초기화될 때 호출된다.

  • public void contextDestroyed(ServletContextEvent sce)
       콘텍스트가 종료될 때 호출된다.

예를 들어, 어플리케이션이 사용하는 자원을 초기화하거나 다시 시스템에 반환해주는 콘텍스트 리스너를 구현하고자 한다면 다음과 같이 구현하면 된다. (ConnectionManager 클래스는 DB 커넥션 풀을 관리해준다고 가정한다.)

    import javax.servlet.ServletContextListener;
    import javax.servlet.ServletContext;
    import javax.servlet.ServletContextEvent;
    
    ...
    
    public class ResouceProcessingListener implements ServletContextListener {
       
       //  필요한 자원의 초기화를 수행한다.
       public void contextInitialized(ServletContextEvent sce) {
          ConnectionManager.init();
          ...
       }
       
       // 이미 사용한 자원을 시스템에 반납한다.
       public void contextDestroyed(ServletContextEvent sce) {
          ConnectionManager.close();
          ...
       }
    }

ServletContextEvent 클래스는 getServletContext() 메소드를 제공하는 데, 이 메소드는 콘텍스트를 나타내는 javax.servlet.ServletContext 인스턴스를 리턴해준다.

AWT 프로그래밍에서 이벤트 리스너를 작성하면 이벤트를 발생시키는 이벤트 소스(source)에 리스너를 등록해주는 것처럼, 콘텍스트 이벤트 리스너도 이벤트 소스를 발생시키는 서블릿 콘테이너에 등록해주어야 한다. AWT의 경우와 다른 점이 있다면 리스너의 등록이 프로그래밍을 통해서 이루어지는 것이 아니라 web.xml 파일을 통해서 이루어진다는 점이다.

서블릿 2.3 규약에는 콘텍스트 이벤트 리스너를 등록할 때 사용되는 새로운 태그인 <listener>를 추가되었으며, 이 태그를 사용하여 작성한 리스너를 등록해주면 된다. <listener> 클래스는 다음과 같은 구조를 취한다.

    <listener>
        <listener-class>완전한클래스이름</listener-class>
    </listener>

<listener> 태그는 하나의 리스너를 나타내며, <listener>에 중첩되어 있는 <listener-class> 태그를 사용하여 리스너 클래스를 지정하게 된다. 예를 들어, com.javacan.listener.ResourceInitializeListener를 콘텍스트 리스너로 등록하고 싶다면 다음과 같이 하면 된다.

    <listener>
        <listener-class>
            com.javacan.listener.ResourceInitializeListener
        </listener-class>
    </listener>

<listener> 태그가 <servlet> 태그 이전에 위치하는 지 아니면 다른 태그 전에 위치하는 지 궁금할 것이니 http://java.sun.com/dtd/web-app_2_3.dtd에 정의되어 있는 web.xml 파일의 DTD 내용 중 일부를 살펴보자.

    <!ELEMENT web-app (icon?, display-name?, description?, distributable?,
    context-param*, filter*, filter-mapping*, listener*, servlet*,
    servlet-mapping*, session-config?, mime-mapping*, welcome-file-list?,
    error-page*, taglib*, resource-env-ref*, resource-ref*, security-constraint*,
    login-config?, security-role*, env-entry*, ejb-ref*,  ejb-local-ref*)>

위에서 볼 수 있듯이 web.xml 파일에서 <listener> 태그는 다른 태그들과 마찬가지로 <web-app> 태그에 중첩되어 정의되는데, <filter-mapping> 태그와 <servlet> 태그 사이에 위치하면 된다.

ServletContextAttributeEvent와 ServletContextAttributeListener

서블릿 2.3과 JSP 1.2에는 속성(attribute)을 정의할 수 있는 네 개의 객체가 존재하는데, 그것은 바로 페이지를 나타내는 객체와, Request, HttpSession 그리고 웹어플리케이션을 나타내는 ServletContext이다. ServletContext는 속성을 사용할 수 있도록 해 주는 setAttribute(String name, Object attribute), getAttribute(String name), removeAttribute(String name) 등의 메소드를 제공하는데 서블릿 2.3부터는 콘텍스트의 속성과 관련하여 새로운 이벤트인 ServletContextAttributeEvent가 추가되었다.

ServletContextAttributeEvent는 속성이 추가되거나, 삭제되거나 또는 속성의 값이 변경될 때 콘텍스트 인스턴스가 발생시키는 이벤트인데, ServletContextAttributeListener를 사용해서 이 이벤트를 처리할 수 있다. ServletContextAttributeListener 인터페이스는 다음과 같은 메소드를 선언하고 있다.

  • public void attributeAdded(ServletContextAttributeEvent scab)
       콘텍스트에 속성이 추가될 때 호출된다.
  • public void attributeRemoved(ServletContextAttributeEvent scab)
       콘텍스트에서 속성이 삭제될 때 호출된다.
  • public void attributeReplaced(ServletContextAttributeEvent scab)
       콘텍스트의 속성값이 변경될 때 호출된다.
ServletContextAttributeEvent 클래스는 이벤트 발생과 관련된 콘텍스트의 속성을 참조할 수 있도록 getName()과 getValue() 메소드를 제공하고 있다. getName() 메소드는 이벤트 발생과 관련된 속성의 이름을 리턴해주고, getValue()는 그 속성의 값을 리턴해준다. 또한, ServletContextAttributeEvent 클래스는 ServletContextEvent 클래스를 상속받고 있기 때문에 getServletContext() 메소드를 사용하여 서블릿 콘텍스트를 참조할수도 있다.

ServletContextAttributeListener 인터페이스를 구현한 클래스를 리스너로 등록하는 방법은 앞에서 살펴봤던 ServletContextListener를 등록하는 방법과 동일하다. 즉, web.xml 파일에 <listener> 태그를 추가하면 된다. ServletContextListener를 등록할 때나 ServletContextAttributeListener를 등록할 때나 똑같이 <listener> 태그와 <listener-class> 태그를 사용하는데, 이때 어떤 리스너를 등록하는지의 여부는 서블릿 콘테이너가 자동으로 처리해준다.

세션의 라이프 사이클

HttpSession은 서블릿 프로그래밍을 할 때 많이 사용되는 것 중의 하나이다. 기본적으로 사용자의 세션 트랙킹을 할 때 사용되며, 그 외에도 임시적으로 데이터를 저장해야 할 때에도 많이 사용된다. 서블릿 2.2의 경우 세션과 관련된 이벤트로 HttpSessionBindingEvent가 존재했었다. HttpSessionBindingEvent를 사용하여 세션과 관련된 다양한 처리를 할 수 있긴 하지만 HttpSessionBindingEvent는 다음과 같은 단점을 갖고 있다.

  • 세션의 라이프 사이클과 관련된 이벤트가 아니다.
  • HttpSessionBindingListener는 속성의 값으로 사용되는 객체가 구현하기 때문에, 하나의 속성에 대해서만 처리가 가능하다.
이 두 단점을 보완하기 위해 서블릿 2.3에는 새로운 이벤트인 HttpSessionEvent가 추가되었으며, 새로운 이벤트를 처리하기 위한 HttpSessionListener와 HttpSessionAttributeListener가 새롭게 추가되었다.

HttpSessionEvent는 세션이 새롭게 생성되거나 세션이 종료될 때 발생하는 이벤트로서, 이 이벤트는 HttpSessionListener를 통해서 처리된다. HttpSessionListener 인터페이스는 다음과 같은 두 개의 메소드를 정의하고 있다.

  • public void sessionCreated(HttpSessionEvent se)
       세션이 생성될 때 호출된다.
  • public void sessionDestroyed(HttpSessionEvent se)
       세션이 종료될 때 호출된다.
이 두 메소드를 통해서 세션의 시작과 종료 시점을 명확하게 알아낼 수 있으며, 따라서 보다 정확하게 세션을 관리할 수 있게 된다. 그리고 HttpSessionEvent 클래스는 이벤트 발생과 관련된 HttpSession 객체를 구할 수 있는 getSession() 메소드를 제공하고 있기 때문에, 필요에 따라 HttpSession 객체를 참조할 수도 있다.

javax.servlet.http.HttpSessionAttributeListener는 세션에 속성이 추가되거나, 세션에서 속성이 삭제되거나 또는 속성 값이 변경될 때 발생하는 이벤트인 HttpSessionBindingEvent를 처리해주는 리스너이다. HttpSessionBindingListener가 하나의 속성에 대해서만 동작하는 반면에, HttpSessionAttributeListener는 세션에 있는 모든 속성에 대해서 동작한다. 또한, HttpSessionBindingListener의 경우 세션의 속성값으로 사용될 객체가 구현해야 동작하는 반면에, HttpSessionAttributeListener는 그러한 제한을 갖고 있지 않다. 따라서, 세션의 속성값으로 사용되는 객체와 이벤트 리스너를 분리할 수 있게 된다.

HttpSessionAttributeListener는 다음과 같은 메소드를 정의하고 있다.

  • public void attributeAdded(HttpSessionBindingEvent se)
       세션에 새로운 속성값이 추가될 때 호출된다.
  • public void attributeRemoved(HttpSessionBindingEvent se)
       세션에서 속성값이 삭제될 때 호출된다.
  • public void attributeReplaced(HttpSessionBindingEvent se)
       세션의 속성값이 변경될 때 호출된다.
HttpSessionAttributeListener 인터페이스에 선언된 메소드가 ServletContextAttributeListener 인터페이스에 선언된 메소드와 비슷한 것을 알 수 있는데, HttpSessionBindingEvent 클래스에 정의되어 있는 getName() 메소드와 getValue() 메소드를 사용하여 이벤트 발생과 관련된 속성을 참조할 수 있다. 또한, getSession() 메소드를 사용하여 속성을 갖고 있는 세션 객체를 참조할 수도 있다.

세션과 관련된 이벤트 리스너들도 web.xml 파일의 <listener> 태그를 사용하여 등록하면 된다.

어디에 사용할 것인가?

지금까지 살펴본 이벤트들을 어디에 사용해야 한다라는 특별한 규칙같은 것은 없다. 왜냐면, 이러한 이벤트들은 개발자가 웹 어플리케이션을 어떤 구조로 설계하느냐에 따라서 효용가치가 달라지기 때문이다. 하지만, 라이프 사이클과 관련된 이벤트들은 일반적으로 다음과 같은 곳에서 유용하게 사용할 수 있다.

  • 자원의 초기화 및 반납
    라이프 사이클은 반드시 시작과 끝을 갖기 마련이다. 어플리케이션의 시작 시점이나 세션의 생성 시점이 명확하기 때문에 어플리케이션이나 세션 트랙킹시에 사용되는 자원을 필요한 시점에 보다 명료하게 초기화할 수 있다. 또한, 어플리케이션의 종료 시점이나 세션의 소멸시에 이벤트가 발생하기 때문에 사용했던 자원을 확실하게 반납할 수 있게 된다.
  • 속성값의 변화 추적
    콘텍스트나 세션에서 사용되는 속성들을 디버깅을 목적으로 추적해야 하는 경우가 있는데, ServletContextAttributeListener와 HttpSessionAttributeListener를 사용하면 매우 간단하게 이를 처리할 수 있다. 특히 web.xml 파일만 변경하면 리스너를 등록하거나 등록하지 않을 수 있기 때문에, 디버깅이 필요할 때에만 코드를 변경하지 않고도 리스너를 등록할 수 있다는 점이 편리하다.
이 두가지 외에도 필요에 따라 여러분들이 알맞게 이벤트를 사용하면 될 것이다. 예를 들면, 각각의 단계마다 로그를 기록하여 어플리케이션의 진행순서를 기록하는 등의 작업을 할 수 있을 것이다. 또한, 세션 관련 이벤트의 경우는 현재 접속 사용자 목록을 관리하는 데 유용하게 사용할 수 있을 것이다.

결론

이 글에서는 서블릿 2.3에 새롭게 추가된 콘텍스트와 세션의 라이프 사이클과 관련된 이벤트에 대해서 살펴보았으며, 더불어 속성과 관련된 이벤트에 대해서도 살펴보았다. 이 이벤트들을 사용함으로써 우리는 자원 관리를 보다 명확한 시점에 처리할 수 있게 되었으며, 편리하게 속성값과 관련된 버그를 찾을 수 있게 되었다. 필터와 함께 라이프 사이클 이벤트들을 유용하게 사용한다면, 잘 구조화된, 더욱 객체지향적인 웹어플리케이션을 구축할 수 있게 될 것이다.

관련링크:

+ Recent posts