발생일: 2009.01.06
문제:
진행 중인 미니 프로젝트에서 웹 어플리케이션이 로딩될 때 설정해야 할 것들이 있다.
여기서는 contextRealRoot 를 메모리에 미리 올려둘 목적으로 쓰려고 하는데,
그 외에도 리소스나, 권한 등 일반적으로도 초기화 때 설정할 사항들이 많다.
스트럿츠 프레임웍의 경우, 웹 어플리케이션의 초기 환경 설정을 위해 PlugIn 기능을 제공한다.
스트럿츠 프레임웍의 PlugIn 기능 보기
스트럿츠 프레임웍의 경우는 이런 식으로 사용한다.
스트럿츠 프레임웍은 웹 어플리케이션 환경 설정을 위해 Plug-In 기능을 제공하며,
컨텍스트 로드 시 해당 플러그인이 작동한다. 구현 방법은 아래와 같다.
1. PlugIn 인터페이스를 구현한 PlugIn 클래스를 만든다.
init() 과 destroy() 메서드를 구현하면 된다.
2. 해당 PlugIn 에 대한 설정을 struts-config.xml 에 정의한다.
자세한 방법은 아래 포스트를 참고하자.
이 기능을 프레임웍이 없는 일반 서블릿 환경에서 가볍게 한 번 구현해 볼까 한다.
해결책:
스트럿츠 프레임웍을 뜯어보지는 않아서 어떻게 구현되어 있는 지는 정확히 모르겠지만,
여기서는
서블릿 컨텍스트 리스너와 옵저버 패턴을 써서 구현했으며, 개략적인 클래스 다이어그램은 아래와 같다.
PlugIn 인터페이스는 옵저버 역할을 하며, init() 메서드를 구현하도록 한다.
init() 메서드에서는 실제 초기화 할 작업을 구현하면 된다.
또한 ConcreatePlugIn 은 PlugIn 인터페이스를 구현한 실제 PlugIn 클래스라고 보면 되겠다.
옵저버 패턴 Subject 역할을 하는 것이 PlugInRepository 이며,
여기서는 PlugIn 을 담아 두고, 알려주는 역할을 한다.
PlugInRepository 의 init() 메서드를 통해 각 PlugIn 의 초기화 작업을 수행할 수 있다.
실제로 컨텍스트가 로드될 때 호출되는 것은 PlugInLoader 클래스이며 서블릿 컨텍스트 리스너이다.
web.xml 에서 <listener-class>로 등록되어 있어 컨텍스트 로드 시점에서 호출되며,
로더에서는 PlugInRepository 과 PlugIn 을 생성하여 등록하고 초기화 시킨다.
전체 코드는 아래와 같다.
전체 코드 보기
/**
* 플러그인 인터페이스
* 플러그인으로 작동하게 하기 위해서는 이 인터페이스를 구현해야 한다.
*/
public interface PlugIn {
/**
* 플러그인 작업을 초기화 한다.
* 컨텍스트 로드와 동시에 이 작업이 수행된다.
*
* @param context 서블릿 컨텍스트
*/
public void init(ServletContext context);
}
/**
* PlugIn 을 담는 저장소 클래스
* 옵저버 패턴의 Subject 역할을 한다.
*/
public class PlugInRepository {
private Vector<PlugIn> repository;
private ServletContext context;
/**
* context 접근을 위해 생성자에
* 서블릿 컨텍스트를 넘겨준다.
*/
public PlugInRepository(ServletContext context) {
this.context = context;
repository = new Vector<PlugIn>();
}
/**
* 플러그인을 추가한다.
*/
public void addPlugIn(PlugIn plugIn) {
repository.add(plugIn);
}
/**
* 플러그인을 삭제한다.
*/
public void removePlugIn(PlugIn plugIn) {
repository.remove(plugIn);
}
/**
* 초기화 작업을 수행한다.
*/
public void init() {
for (PlugIn plugIn : repository) {
plugIn.init(context);
}
}
}
/**
* 플러그인 로더 클래스
* 컨텍스트 로드와 동시에 수행된다.
*/
public class PlugInLoader implements ServletContextListener {
/**
* 컨텍스트가 종료될 때
*/
public void contextDestroyed(ServletContextEvent event) {
// destory 될 때의 작업
}
/**
* 컨텍스트가 초기화 될 때
*/
public void contextInitialized(ServletContextEvent event) {
// PlugInRepository 를 생성한다.
PlugInRepository repository = new PlugInRepository(event.getServletContext());
// repository 에 각 PlugIn 을 추가한다.
repository.addPlugIn(new MockPlugIn());
repository.addPlugIn(new PathPlugIn());
// 초기화 수행
repository.init();
}
}
web.xml 설정 부분
<listener>
<listener-class>PlugInLoader</listener-class>
</listener>
초기화 수행 시 필요한 작업들을 PlugIn 객체로 구현한 후,
코드의 PlugInLoader 의 init() 메서드에서처럼 PlugInRepository 에 등록한 후 작업을 호출해주면 된다.
PlugInRepository 의 생성자에 ServletContext 를 넘기도록 한 것은,
PlugIn 객체에서 컨텍스트에 접근할 수 있도록 하기 위해서이다.
실제로 작동시켜 보니 예상했던 대로 잘 작동한다.
몇 가지 기능을 추가로 구현하면, 가벼운 라이브러리로 쓸 수도 있을 것 같다.
#1.
여기서는 추가하려는 PlugIn 객체를 PlugInLoader 에서 직접 추가해줬다.
스트럿츠에서는 수행할 PlugIn 에 대한 정보를 struts-config.xml 에 추가해야 한다.
그렇다면 아예, 특정 인터페이스를 구현한 클래스를 찾는 ClassFinder 따위(검색해보니 좀 있다)를 구현해서,
구현 PlugIn 객체를 자동으로 찾아 호출하도록 하면 더 편리할 것 같다.
사용자 입장에서는 단지 PlugIn 인터페이스만 구현하게 말이다.
#2.
PlugInRepository 는 싱글턴으로 구현해도 괜찮을 것 같다.
#3.
컨텍스트가 종료될 때의 작업을 추가해도 괜찮겠다. (contextDestroyed 시)
#4.
그 외, 예외 처리가 필요하다. (플러그인의 초기화가 실패했을 경우, 로드 자체가 불가능 하게 할 것인가?)
#5.
여기서는 서블릿 컨텍스트 리스너를 이용해서 컨텍스트가 리로드 될 때에도 초기화가 수행된다.
만약, 서버를 올릴 때에만 초기화 하고 싶다면,
1.
이미 로드되었는지 여부에 대한 flag 를 두어 처리하거나,
(
* 컨텍스트가 리로드 되면, 리스너 클래스 로딩를 새로 하는 모양이다. flag 를 static 으로 생성해도 그 값이 저장되지 않는다.)
2. 컨텍스트 리스너 대신 servlet 의 load-on-startup 값을 1로 설정하면 되겠다.
(PlugInLoader 를 서블릿으로 구현하고, 서블릿의 init() 메서드 부분을 구현한다.)