티스토리 툴바


요즘 저녁에 집에서 짬이 생길 때마다 공부겸 취미겸 간단한 웹 기반 어플리케이션을 만들고 있는데, 만들던 중 아래와 같은 기능이 필요하게 되었다.

  • WAR로 배포하고, 데이터 디렉토리를 외부에서 변경할 수 있어야 함
  • JNDI나 시스템 프로퍼티 값을 이용해서 디렉토리 경로를 지정할 수 있어야 함
위 기능을 직접 구현할까 하다가 누군가도 위와 같은 기능을 필요로 할 것 같아서 검색을 해 보았다. 아니나 다를까, 딱 들어맞는 기능을 제공하는 모듈이 있어 간단하게 기능을 정리해보았다. 이 모듈의 이름은 Data directory locator tool, 줄여서 datadirlocator (http://simplericity.org/datadirlocator)로서 사용법도 매우 간단하다.

모듈 다운로드

홈페이지에서 다운로드 받거나 Maven을 사용하는 경우 다음과 같이 의존을 추가해주면 된다.

<dependency>
    <groupId>org.simplericity.datadirlocator</groupId>
    <artifactId>datadirlocator</artifactId>
    <version>1.10</version>
</dependency>

지원하는 설정 방식

datadirlocator는 설정 파일이 위치하는 디렉토리나 어플리케이션의 홈 디렉토리와 같이 디렉토리 경로를 구하는 기능을 제공하며, 다음과 같이 4가지 방식으로 설정 경로를 구할 수 있도록 지원하고 있다.
  • JNDI 설정 이용 (기본 JNDI 명: java:com/env/dataDirectory)
  • 서블릿 컨텍스트 파라미터 이용 (기본 컨텍스트 파라미터 명: dataDirectory)
  • 시스템 프로퍼티 이용 (기본 시스템 프로퍼티  명: dataDirectory)
  • 환경 변수 이용 (기본 환경 변수 명: DATADIRECTORY)
JNDI부터 순서대로 값이 존재하는지 검색하고 값이 존재하면 그 값을 사용하고 존재하지 않으면 그 다음 방식의 값이 존재하는 검사한다. 위의 네 가지 경우에 대해 모두 값이 존재하지 않으면 기본 디렉토리로 $HOME/datadirectory를 사용한다.

사용법1, 직접 모듈 사용하기

가장 간단한 사용방법은 다음과 같다.
  • ServletContextListener를 추가한다.
  • ServletContextListener에서 DefaultDataDirectoryLocator를 사용해서 경로 값을 구한다.
예를 들어, 아래와 같은 코드를 구현해서 JNDI나 시스템 프로퍼티에 지정된 경로값을 구해서 시스템을 초기화하는데 사용할 수 있다.

public class ConfigInitializerServletContextListener implements ServletContextListener {

@Override
public void contextInitialized(ServletContextEvent sce) {
DefaultDataDirectoryLocator locator = new DefaultDataDirectoryLocator();
locator.setServletContext(sce.getServletContext());
locator.setJndiName("java:comp/env/rr4s/home");
locator.setSystemProperty("rr4s.home");
locator.setContextParamName("rr4shome");
locator.setEnvVarName("RR4SHOME");
locator.setDefaultDir("$HOME/rr4s.home");
File homeDirectory = locator.locateDataDirectory();
// homeDirectory를 이용한 설정 초기화
}
....
}

사용법2, 스프링 빈으로 사용하기

또 다른 방법은 스프링 빈으로 사용하는 것이다. DefaultDataDirectoryLocator를 스프링 빈 객체로 설정해서 사용할 수 있고, 만약 서블릿 컨텍스트 파라미터에 접근해야 한다면, ServletContextAware 인터페이스를 구현한 ServletContextAwareDataDirectoryLocator를 사용하면 된다. 다음은 설정 예이다.

<bean id="dataDirectoryLocator"
class="org.simplericity.datadirlocator.spring.ServletContextAwareDataDirectoryLocator">
<property name="jndiName" value="java:comp/env/rr4s/home" />
<property name="systemProperty" value="rr4s.home" />
</bean>

<bean id="contextReloader" class="org.chimi.rr4s.setup.ContextReloader">
<property name="dataDirectoryLocator" ref="dataDirectoryLocator" />
</bean>

위 코드에서 ContextReloader 클래스는 인젝션을 통해서 전달받은 dataDirectoryLocator를 이용해서 설정에 필요한 디렉토리 경로를 받아올 것이다. 

public class ContextReloader implements ApplicationContextAware,
ApplicationListener<ContextRefreshedEvent> {

private DataDirectoryLocator dataDirectoryLocator;
...
private File locateHomeDirectory() {
return dataDirectoryLocator.locateDataDirectory();
}
...
}

 




저작자 표시 비영리 변경 금지
Posted by madvirus

요즘 유행하는 빅데이터류의 기술을 사용하고 있지는 않지만, 빅이 아닌 나머지 분야에서의 대부분 자바 개발자들은 아마 웹 관련 프로젝트에 주로 참여하고 있을 거라 생각되어 최근에 진행중인 프로젝트에서 사용한 오픈 소스들에 대한 초간단 리뷰를 한번 해 보고자 한다. 이들 목록은 아래와 같다.

  • Spring Data JPA
  • Apache Shiro
  • Sitemesh
  • Bootstrap
  • Solr
  • Easyrec
Spring Data JPA

필자는 ORM 매니아이다. 아니 매니아를 넘어 ORM 신봉자에 가깝고 심지어 SQL은 (물론 필요할 땐 사용하지만) 쳐다보기도 싫을 정도이다. 이런 필자에게 Spring Data JPA는 하이버네이트에서 JPA로 넘어가는 계기를 만들어줬다. Spring Data를 사용하면 다음의 편리함들이 있다.
  • (거의 모든 리포지토리에 대해) 리포지토리 인터페이스만 정의하면 Spring Data가 런타임에 구현객체를 만들어 준다. 그래서 잡다하고 지겨운 코드 작성을 줄일 수 있다.
  • DDD의 Specification을 지원해서 검색 조건을 도메인 용어로 잘 표현할 수 있게 된다.
    • 덤으로 이들 스펙의 조합도 쉽게 할 수 있다.
  • 페이징, 정렬 등의 표준화된 인터페이스 제공

DB 연동과 관련된 지겨운 코드 타이핑을 덜 하게 해 주고 이는 더 중요한 부분에 시간을 더 많이 쏟을 수 있다는 걸 의미한다. 물론, DB 연동 관련 코딩 시간이 주니까 전반적인 개발 시간도 줄어드는 효과가 있다.


Apache Shiro


Apache Shiro는 인증과 권한을 위한 프레임워크로서 웹 URL 기반의 접근 제어나 코드에서 직접 권한 검사를 하기 위한 기능을 제공한다. 단, Shiro를 알맞게 커스터마이징해서 사용하려면 Shiro의 구조와 동작 방식에 대한 이해가 필요하다. 이와 관련해서는 예전에 필자가 정리한 http://javacan.tistory.com/entry/Apache-Shiro-Core-Diagram 글을 참고하기 바란다. 필자의 프로젝트의 경우는 권한 검사 부분을 커스터마이징 해서 사용했다. 예를 들어, DB로부터 역할과 기능 정보를 로딩하도록 커스텀 클래스를 구현했고, 쿠키를 이용해서 인증을 수행하도록 구현했다.


Sitemesh


예전부터 Tiles보다 Sitemesh가 좋았다. Sitemesh가 좋은 이유는 데코레이터를 적용하지 않아도 결과물이 완전한 HTML이 된다는 점이다. 예를 들어, Tiles를 사용하는 경우에는 내가 만드는 JSP가 Tiles 템플릿의 일부 영역을 만드는 것이기 때문에 완전한 HTML이 아니며, 따라서 필요한 자바 스크립트가 <head> 안에 들어가는 것이 아니라 <body> 태그 어딘가에 들어가게 된다. <head>에 넣으려면 별도의 JSP 파일에 넣어야 하는 불편함이 따른다. 반면에 Sitemesh를 사용하면 내가 만드는 코드가 완전한 HTML을 생성하게 된다. 즉, 데코레이터 적용 여부에 상관없이 완전한 하나의 결과물을 만들어내기 때문에, UI 관련 코드가 불필요하게 이 파일 저 파일에 쪼개지는 현상을 줄일 수 있다.


Bootstrap


프로토타입을 만들더라도 UI나 UX나 너무 개발자스러우면(^^;) 뭔가 만든 것 같지 않은 느낌이 들기 마련이다. 필자도 이걸로 고민을 좀 했는데, 아는 지인의 소개로 Bootstrap이란 걸 알게 되었다. Twitter에서 오픈한 CSS 소스인데, Bootstrap의 사용법을 조금만 익히면 최소한 개발자스러운 껍데기를 벗어날 수 있게 된다. 게다가 약간의 이미지만 곁들이면 있어 보이기까지 한다. 필자처럼 UI에 대한 감이 없는 개발자들이 디자인의 도움없이 뭔가 껍데기를 입혀야 한다면 적극 추천한다.


Apache Solr


Solr는 그 유명한 Lucene을 이용한 검색 서비스이다. 웹 서비스로 제공되기 때문에 플랫폼에 상관없이 쉽게 연동할 수 있다. 설치도 쉽고, 검색을 위한 스키마 설계만 간단하게 해주면 거의 바로 사용할 수 있다. 게다가 (필자처럼) 검색에 대한 지식이 약해도 빠르게 적용해 볼 수 있다는 장점이 있다. 한글 검색을 제대로 하려면 별도의 분석기가 필요하고 사전도 필요하겠지만, 단순 키워드 매칭 수준의 검색 용도르는 충분하다. 물론, 유사단어, 검색어 오류 수정 등의 기능을 제공하고 싶지만 많은 노력이 필요할 것이다.


Easyrec


이번에 PoC 성격의 프로젝트를 진행하면서 뭔가 개인화 추천 기능을 넣고 싶었다. CI(Collective Intelligence) 관련 내용은 이전부터 틈틈히 봤지만 그렇다고 이걸 직접 구현하고 싶진 않았다. 게다가 Mahout 같은 걸 삽질해 가면서 사용하고 싶진 않았다. 그런 와중에 지인(좋은 지인 열 개발자 안 부럽다인가요..)의 소개로 Easyrec라는 걸 알게 됐다. 정말이지 딱 필요한 기능만 제공하고 있어 이거다 싶을 정도였다. 내부 DB로는 MySQL을 사용하고 있고 자바 기반의 웹 어플리케이션으로 만들어졌기 때문에, 어지간한 환경에서 다 사용할 수 있다. 웹기반으로 동작하기 때문에 자바가 아닌 다른 언어에서도 쉽게 연동할 수 있다. 이쪽 분야의 전문가가 아니기에 품질이 어느 정도인지 아직 확인은 안 되지만, '빅'이 아닌 사이트에서 작게 사용하기에는 충분할 거라 생각된다.



저작자 표시 비영리 변경 금지
Posted by madvirus

Confluence Wiki를 설치해 본 사람은 설치 과정이 참 편하다는 것을 알 수 있다. 홈디렉토리 경로만 지정해주면 주요 설정은 웹 어플리케이션을 처음 구동할 때 설정하게 된다. 예를 들어, JDBC 연결 정보를 웹 브라우저를 이용해서 입력하고, DB를 생성하는 작업을 설치 마법사와 같은 방식으로 진행하게 된다.


많은 웹 어플리케이션들이 스프링을 이용해서 기능을 구현하는 경우가 많은데, 스프링 기반의 웹 어플리케이션을 사용하는 경우 어떻게 설치 위자드를 제공할 수 있을까? 요즘 이런 고민을 해 봤는데 답은 아주 간단하다. 스프링의 리로드 기능을 사용하는 것이다.


예를 들기 위해 다음과 같은 아주 간단한 웹 어플리케이션을 하나 만들어 보자.

  • WAR 파일로 배포되는 웹 어플리케이션
  • 최초 실행 시 설정 과정
    • 설치 과정1: 설치 안내
    • 설치 과정2: DB 정보 입력
    • 설치 과정3: 설정 완료
      • 지정 디렉토리에 설정 파일 저장
    • 설정 완료 후, 서비스 제공
  • 설정 완료 후, 이후 WAS를 재시작할 경우 설정 없이 서비스 제공
    • 설정 여부는 설정 파일 존재 여부로 확인
간단함을 위해 다음과 같이 코드를 구성했다.
  • 설정 과정 위한 파일
    • SetupController 클래스: 설정 과정을 처리하는 컨트롤러
    • ContextReloader 클래스: 설정 완료된 경우 또는 이미 설정된 경우 서비스 제공하도록 스프링 컨테이너 리로딩
    • setup-config.xml: 설정 과정에서 사용되는 스프링 설정 파일
  • 실 서비스 위한 파일
    • HomeController: 서비스의 첫 화면을 위한 컨트롤러
    • application-config.xml: 실제 서비스 용 스프링 설정 파일
web.xml 설정은 설정 위한 설정으로

web.xml 파일은 설정 과정을 위한 정보를 담는다.

<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:/setup-config.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

setup-config.xml 파일은 다음과 같이 사용자의 설정 과정을 처리하기 위한 SetupController와 설정이 완료되면 스프링 컨테이너를 리로딩해주는 ContextReloader로 구성된다.


<?xml version="1.0" encoding="UTF-8"?>

<beans ...>

<bean id="contextReloader" class="org.chimi.rr4s.setup.ContextReloader">

</bean>


<bean class="org.chimi.rr4s.setup.SetupController">

<property name="contextReloader" ref="contextReloader" />

</bean>

         .... 스프링 MVC 관련 나머지 설정 들

</beans>


SetupController는 설정 과정을 처리하고 설정이 완료되면 설정 정보를 저장한 뒤에 ContextReloader를 이용해서 컨테이너를 리로딩한다. 아래는 SetupController의 예시 코드이다.


public class SetupController {


private ContextReloader contextReloader;


@RequestMapping("/home")

public String home() {

                // 설정 과정에서의 첫 화면은 설정 관련 안내 페이지

return "setup/home";

}


@RequestMapping("/setup/step1")

public String step1() {

                // 설정 폼을 보여주기 위한 처리

return "setup/step1";

}


@RequestMapping("/setup/save")

public String step2(...) {

saveConfig(); // 설정 저장

contextReloader.reloadContext(); // 리로딩

return "setup/completed";

}


private void saveConfig() {

FileOutputStream fos = new FileOutputStream(new File("./target/conf.properties"));

// 설정 파일을 지정한 경로에 저장

}


public void setContextReloader(ContextReloader contextReolader) {

this.contextReloader = contextReolader;

}

}


위 코드에서 핵심은 설정을 입력하면 설정 정보를 지정한 위치의 파일에 저장하고, ContextReloader.reloadContext()를 호출한다는 점이다. (실제로는 필요한 DB를 생성하는 과정 등이 포함되겠지만 여기서는 설명에 필요한 것만 보여주었다.)


ContextReloader는 다음과 같다.


public class ContextReloader implements ApplicationContextAware,

ApplicationListener<ContextRefreshedEvent> {


private ApplicationContext applicationContext;


public void reloadContext() {

if (applicationContext instanceof ConfigurableWebApplicationContext) {

ConfigurableWebApplicationContext configurableCtx = 

(ConfigurableWebApplicationContext) applicationContext;

String[] configLocations = { "classpath:/application-config.xml" };

configurableCtx.setConfigLocations(configLocations);

configurableCtx.refresh();

}

}


@Override

public void onApplicationEvent(ContextRefreshedEvent event) {

// 설정 확인

if (new File("./target/conf.properties").exists()) {

// 설정 존재하면 reloadContext();

reloadContext();

}

}


@Override

public void setApplicationContext(ApplicationContext context)

throws BeansException {

this.applicationContext = context;

}


}


ContextReloader는 스프링 컨테이너를 리로딩해야 하기 때문에 ApplicationContextAware를 implement해서 ApplicationContext를 전달받을 수 있도록 했다. 또한, 컨테이너가 초기화가 완료되는 이벤트를 받을 수 있도록 ApplicationListener를 implement하였다. 이 이벤트를 받는 이유는 뒤에서 다시 설명한다.


reloadContext() 메서드는 현재 스프링 컨테이너가 사용할 설정 파일을 application-config.xml로 변경한 뒤에 refresh()를 수행한다. 즉, reloadContext()를 호출하면 application-config.xml에 설정된 내용을 이용해서 스프링 컨테이너가 초기화된다. 따라서, 앞서 SetupController 클래스에서 설정을 완료한 뒤에 reloadContext()를 호출하게 되면, 실제 서비스를 위한 스프링 설정이 사용되는 것이다.


onApplicationEvent() 메서드는 ContextRefreshedEvent를 처리하는데, 이 이벤트를 처리하는 이유는 최초에 한 번만 설정 과정을 거치고 이후에는 거치지 않기 위함이다. 예를 들어, WAS를 재구동해야 하는 경우 기존에 이미 설정을 완료했다면 설정 화면이 아닌 서비스 화면이 나와야 할 것이다. 그런데, web.xml 파일은 설정 과정을 위한 setup-config.xml 파일을 사용하므로, 다시 설정 화면이 나올 것이다. 이를 방지하기 위해 컨테이너 초기화가 완료될 때 기존 설정 정보가 존재하는 지 확인하는 과정을 구현할 필요가 있는데, 컨테이너 초기화 시에 발생하는 이벤트가 ContextRefreshedEvent 이다. 따라서, ContextRefreshedEvent 이벤트를 수신하는 onApplicationEvent() 메서드에서 설정 파일이 존재하는 지 확인하고, 만약 존재한다면 서비스용 스프링 설정 파일로 컨테이너를 리로딩함으로써 서비스를 제공할 수 있게 된다.


저작자 표시 비영리 변경 금지
Posted by madvirus