Spring Roo 소개

자바 개발자를 위한 신속한 애플리케이션 개발 툴 Spring Roo

clip_image002

Spring Roo는 무엇인가?

우선, 쉽게 오해 할 수 있는 부분부터 언급하는 것이 좋겠다. “Spring Roo”는 프레임워크가 아니라 개발 툴이다. 일종의 코드 생성 툴이다. 자바 프로젝트에서 런타임시에 사용할 아무런 라이브러리도 제공하지 않으며, 어떠한 코딩이나 아키텍처 스타일을 강요하지도 않는다. 따라서 프레임워크로 볼 수 없다. 그러므로, Ruby on Rails나 Play와 비교하는 것은 타당하지 않다. Spring Roo를 사용해서 개발할 수 있는 애플리케이션은 기본적으로 스프링 기반의 자바 (웹) 애플리케이션, GWT 애플리케이션, Google App Engine용 애플리케이션 등 다양하다. 단지, 빠른 개발을 가능케 해주는 개발 툴일 뿐이다. 그럼 어떤 방법으로 개발을 빠르게 해주는지 실습을 통해서 살펴보자.

Spring Roo 설치

Spring Roo는 개별적으로 다운로드 받아서 설치할 수도 있지만, 사실 별다른 설치 과정이 필요하지 않다. 다운받아서 압축을 푸는 것으로 끝이다. Spring Too Suite(STS)를 다운받아 설치했다면, Spring Roo를 별도로 설치하지 않아도 된다. 이미 STS를 설치하면서 같이 설치됐을 것이다.

Spring Roo 프로젝트 만들기

clip_image004

New -> Project -> Spring Roo Project를 선택하면, Roo 프로젝트를 생성할 수 있다. Spring Roo 초기 버전은 콘솔에서만 작업해야 했었지만, STS에서 Spring Roo 지원 기능을 추가한 뒤로는, 콘솔에서 작업해야 할 필요가 없어졌다. 이 메뉴를 사용해서 프로젝트를 만들고 나면 STS의 Package Explorer에 프로젝트가 생기고 STS 하단에 다음과 같은 Roo Shell을 볼 수 있다.

clip_image006

앞으로 이 뷰가 콘솔을 대신할 것이다. 여기서 roo가 제공하는 DSL을 사용해서 JPA를 설정하고, 엔티티 클래스를 만들고, 각 엔티티에 필드를 추가하고, 연관 관계를 설정하고, 컨트롤러를 만들고, 테스트를 만드는 등의 여러 작업을 할 수 있다.

clip_image008

프로젝트 구조는 평범한 메이븐 프로젝트 구조이고, 스프링 설정 파일인 applicationContext.xml을 볼 수 있다. 이와 같은 메이븐 프로젝트를 만들고 기본 스프링 설정과 pom.xml에 스프링 의존성까지 채워넣는 작업은 메이븐 아키타입 없이는 상당히 오래 걸리는 작업이다. 이것만으로도 충분히 Spring Roo의 가치가 있다고 생각하지만, 대부분의 독자는 여기서 만족하지 못할 것이다. 게다가, 본인이 직접 관리하고 있거나 사내에서 제공하는 메이븐 아키타입이 있다면, 방금 Spring Roo가 제공한 빠른 개발 지원 기능을 별 것 아닌 것처럼 느껴질 것이다.

계속해서 스프링 빈 설정 파일을 살펴보자.

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

<beans xmlns="http://www.springframework.org/schema/beans"

<context:property-placeholder location="classpath*:META-INF/spring/*.properties"/>

<context:spring-configured/>

<context:component-scan base-package="whiteship">

<context:exclude-filter expression=".*_Roo_.*" type="regex"/>

<context:exclude-filter expression="org.springframework.stereotype.Controller" type="annotation"/>

</context:component-scan>

</beans>

applicationContext.xml에 들어있는 빈 설정에서 주석을 제외하면 위와 같은 설정만 남는다. 이 중에서 Spring Roo와 관련해서 가장 중요한 설정은 <context:spring-configured/>라는 설정이다. 이 설정을 사용하면, 스프링 ApplicationContext에 빈으로 등록되지 않은 객체에도 스프링 Dependency Injection을 사용할 수 있다.

<context:spring-configured/>와 Spring Roo

빈으로 등록하지 않은 객체에 스프링이 관리하는 의존성을 주입하는 일은 결코 쉬운 일이 아니다. 즉, 어디선가, Book book = new Book(); 이라고 book 객체를 만들면, 이 객체에 BookRepository 타입의 빈을 주입한다는 것인데, 그렇게 하려면 Book 클래스의 생성자가 호출되는 시점에 부가적인 작업을 수행해야 한다. 하지만, 이런 기능을 스프링 AOP로는 할 수 없다. 이런 기능은 모든 AOP 기능을 제공하는 ApsectJ를 사용해야지만 가능하다.

AspectJ의 생성자 호출 Joinpoint(AOP 용어로, 부가 기능을 추가할 수 있는 지점을 말한다. 프록시 기반의 스프링 AOP에서 사용할 수 있는 Joinpoint는 메서드 호출 joinpoint 뿐이다.)를 사용하여 Book 클래스가 생성되는 시점에 스프링의 Dependency Injection 기능을 사용해서 스프링 ApplicationContext에 들어있는 BookRepository 타입의 빈을 주입하는 작업이 가능하다.

Spring Roo 아키텍처

그런데, 이런 기능이 Spring Roo에 왜 필요할까? 그것은 Spring Roo가 최근까지 고수했던 아키텍처 때문이다. 지금까지 Spring Roo는 PEAA(Patterns of Enterprise Application Architecture)에서 소개한 Active Record 패턴을 고수했다. Active Record 패턴은 DB의 한 행이 하나의 객체와 대응하도록 하고 DB 오퍼레이션도 해당 객체가 지니고 있게 하는 패턴이다(참고, http://en.wikipedia.org/wiki/Active_record_pattern).

Active Record 패턴을 선택한 이유는 애플리케이션 개발의 간편함 때문이다. “컨트롤러 -> 도메인 객체” 이렇게 단순한 호출로 모든 요청을 처리하니까 얼마나 간단한가. 하지만, 스프링 진영에서 달갑지 않아하는 static 메서드를 사용해서 Finder를 구현하고, 트랜잭션 경계를 어디로 두어야 하는지 애매하며, 엔티티 객체에 주입되는 여러 의존성으로 인해서 테스트하기 어렵다는 지적(참고, http://moleseyhill.com/blog/2009/07/13/active-record-verses-repository/) 등이 생겨나자. 최근에는 그 전략을 바꿨다.

clip_image010

(출처: http://blog.springsource.com/2011/09/14/new-application-layering-and-persistence-choices-in-spring-roo/)

SpringSource에서 최근에 배포한 Spring Roo 1.2 M1 부터는 일반적인 Serivce, Repository 계층 구조의 아키텍처도 지원한다. 따라서, Active Record 패턴을 선택적으로(기본값은 Active Record 패턴 스타일) 사용할 수 있다.

이제부터 JPA를 설정하고, 엔티티를 만들어보자.

JPA 설정

clip_image012

JPA 공급자와 관련 DB를 설정하라는 힌트를 보여준다. JPA를 설정하자. 입력창에 다음과 같이 입력한다.

jpa setup –provider HIBERNATE –database HYPERSONIC_IN_MEMORY

여기서 jpa setup 이후로는 한 글자도 직접 입력한 명령어가 없다. jpa setup만 입력한 뒤에 계속해서 Ctrl+Space를 사용해서 힌트로 제공되는 명령어 중에서 선택하여 입력했다. 이런 방법으로 저렇게 긴 명령어도 전혀 외우지 않고 입력할 수 있다.

이 명령어를 입력하면, hsqldb 1.8.0.10과 Hibernate 3.6.7 버전을 의존성을 가져오고, applicationContext.xml의 내용을 수정하며, persistence.xml 파일을 만들어 준다. 이런 작업을 손수 하려면 얼마나 오래 걸렸을까? 의존성 정보를 찾으려고 몇 분 소요했을 것이고, applicationContext에 JPA에 사용할 빈과 트랜잭션 설정을 추가하느라 또 몇 분 소요했을 것이다. 그 뒤에 JPA 설정 파일인 persistence.xml 파일까지 만들고 기본 설정을 추가하려면 역시 또 적지 않은 시간을 소비했을 것이다. 하지만, 지금 이 작업이 단 몇 초 이내로 완료됐다. 바로 이것이 Spring Roo가 제공하는 빠른 개발의 묘미 중 하나다.

applicationContext.xml에는 DataSource, TrasactionManager, tx:annotation-driven, EntityManagerFactory 빈이 등록됐고, persistence.xml에는 JPA 구현체인 Hibernate 설정이 추가됐다. 이 중에서도 DataSource 설정은 처음, 프로젝트를 생성했을 때 등록된 context:property-placeholder를 사용해서 프로퍼티 파일로 빈 설정 값을 외부로 빼두었기 때문에, 나중에 DB를 다른 것으로 교체하고 싶을 때는 database.properties 파일의 내용을 사용하려는 DB에 맞는 설정으로 수정하면 된다.

엔티티 추가

엔티티를 만들 때는 앞에서 말했던, 두 가지 아키텍처 중에 하나를 선택할 수 있다. 여기서는 자바 진영에서 자주 사용하고 있는 형태인 계층형 아키텍처로 만들어 보겠다. 입력창에 다음과 같이 입력하자.

entity –class ~.domain.Book –activeRecord false

ent까지 입력한 다음, Ctrl+Space를 입력하면, entity라는 키워드가 완성되고 그 뒤에 Ctrl+Space를 또 입력하면, –class가 자동 완성 된다. 그 다음, ~.domain.Book은 직접 입력해야 하는데, 여기서 ~의 의미는 Spring Roo 프로젝트를 만들 때 설정한 기본 패키지를 나타낸다. 여기까지 입력하고 명령을 실행하면 Active Record 패턴으로 생성되는데, 여기서는 계층형 구조로 만들고 싶기 때문에 –를 입력하고 다시 Ctrl+Space를 입력하면 부가적으로 사용할 수 있는 옵션 목록이 출력되고, 그 중에서 activeRecord 옵션을 선택할 수 있다. 그리고 다시 Ctrl+Space를 입력하면 다시 activeRecord 옵셥의 값으로 사용할 수 있는 목록이 출력되는데, 그 중에서 false를 선택했다.

clip_image014

Book이라는 도메인 클래스가 생기고, AspectJ 파일이 두개 생성됐다. 생성된 코드를 살펴보자.

package whiteship.domain;

import org.springframework.roo.addon.entity.RooJpaEntity;

import org.springframework.roo.addon.javabean.RooJavaBean;

import org.springframework.roo.addon.tostring.RooToString;

@RooJavaBean

@RooToString

@RooJpaEntity

public class Book {

}

애노테이션이 세개 붙어있는 텅 비어있는 클래스로 보인다. 하지만, 이미 이 클래스에는 toString()과 getter/setter가 자동 생성될 준비가 되어있다. @RooJavaBean과 @RooToString이 그 역할을 해준다. @RooJpaEntity는 JPA 엔티티에 필요한 id와 version 멤버 변수 등을 추가해준다. 따라서, Book 클래스 안에 main 메서드를 만들고 book 객체를 만든 다음에 setId/getId와 setVersion/getVersion 메서드를 확인할 수 있다.

@RooJavaBean

@RooToString

@RooJpaEntity

public class Book {

public static void main(String[] args) {

Book book = new Book();

book.setId(1l);

book.getVersion();

}

}

어떻게 된 것인지 궁금할 것이다. 이에 대한 설명은 뒤로 미루고 우선, 애플리케이션을 완성해보자.

필드 추가

main 메서드는 테스트 용도였으니, 코드를 삭제하고 이제 필드를 추가해보자. Roo Shell에 다음과 같이 입력한다.

field string –fieldName name –class whiteship.domain.Book

그럼 Book 클래스 코드가 다음과 같이 바뀐다.

@RooJavaBean

@RooToString

@RooJpaEntity

public class Book {

private String name;

}

물론, 이 정도는 굳이 Roo Shell을 거치지 않고, 직접 코딩을 해도 무관하다. 대신, 이런 기능을 어떻게 활용할 수 있는지 알려주고 싶기 때문에 보여준 것이다. 지금까지 Roo Shell에서 사용한 명령어를 모아보면 다음과 같다.

jpa setup –provider HIBERNATE –database HYPERSONIC_IN_MEMORY

entity –class ~.domain.Book –activeRecord false

field string –fieldName name –class whiteship.domain.Book

이 명령어를 모아서 스크립트 파일로 재사용할 수 있다. 이렇게 Roo 명령어를 모아서 스크립트 파일을 만들어서 sample.roo와 같은 형식으로 저장한 뒤에, Roo Shell에서 “script –file 파일명” 명령을 사용해서 해당 스크립트를 실행할 수 있다.

Repository 생성

계속해서 Repository를 만들어보자. Roo Shell에서 다음 명령어를 실행한다. 입력할 때는 수시로 Ctrl+Space를 눌러가며 자동완성과 힌트를 활용하기 바란다. –entity와 같은 옵션이 보이지 않을 때는 –를 입력한 상태에서 다시 Ctrl+Space를 입력하면 부가적인 옵션을 보여준다.

repository jpa –interface ~.modules.book.BookRepository –entity ~.domain.Book

@RooRepositoryJpa(domainType = Book.class)

public interface BookRepository {

}

이 순간 이미 기본적인 CRUD와 검색용 Data Access Operation이 구현됐다. 거의 항상 구현하게 되는 그런 기능을 Spring Data JPA를 이용해서 구현해준다.

서비스 구현

이제 서비스를 만들자. Roo Shell에서 다음 명령을 실행한다.

service –interface ~.modules.book.BookService –entity ~.domain.Book

이제는 Roo 명령어를 사용하는데 익숙해졌을 것이다. 결과 코드도 어느정도 예상할 수 있을 것이다. 역시나 코드가 별로 없다.

@RooService(domainTypes = { whiteship.domain.Book.class })

public interface BookService {

}

public class BookServiceImpl implements BookService {

}

이것으로 엔티티, Repository, Service 구현이 끝났다.

컨트롤러 구현

Roo의 웹 단은 현재 세 가지 기술로 구현할 수 있다. GWT, Spring Web Flow, Spring MVC 이 셋으로 코드를 생성할 수 있는데, 여기서는 Spring MVC 코드를 생성하겠다. Roo Shell에서 다음 명령을 실행하자.

web mvc setup

이렇게 하면 Spring MVC에 필요한 의존성을 추가하고, 스프링 웹 설정을 추가하고, 기본적인 뷰에 필요한 각종 리소스를 생성해 준다. 이때 상당히 여러 기능이 추가되는데, webmvc-config.xml을 보면 어떤 기능이 추가됐는지 알 수 있다. 이제 컨트롤러 코드를 생성해보자. Roo Shell에 다음 명령을 실행하자. 명령행이 길지만 Ctrl+Space를 잘 활용한다면, 별다른 어려움 없이 입력할 수 있다.

web mvc scaffold –class ~.modules.book.BookController –backingType ~.domain.Book –path /book

이 명령어를 실행하면 이제 웹 애플리케이션을 실행할 준비가 끝난다.

웹 애플리케이션 실행

STS에서 웹 애플리케이션을 실행하는 방법은 여러가지가 있다. Servers 뷰를 이용하는 방법도 있지만, 여기까지 진행했다면 pom.xml에, tomcat과 jetty 플러그인이 설정되어 있는 것을 확인할 수 있을 것이다. Jetty 메이븐 플러그인을 사용해서 실행해보겠다. 프로젝트 우클릭 -> Run As -> Maven Build를 클릭하고 Goal을 입력하는 곳에 jetty:run을 입력하고 Run을 클릭하자.

서버가 시작되면, 브라우저에서 http://localhost:8080만 입력하고 들어가면 404 에러 페이지가 뜨면서, roo_sample 애플리케이션 링크를 제공해 준다. 물론, 타이핑을 좋아한다면 직접 “http://localhost:8080/프로젝트_이름”을 전부 입력해도 상관없다.

clip_image016

이 애플리케이션에서 기본적인 CRUD 기능을 확인해보자. 잘 동작하라 것이다.

모든 마술은 ITD, Mixin

지금까지 생성한 자바 코드는 다음과 같다.

clip_image018

이 몇 개 안 되는 파일에는 지금까지 살펴봤듯이, 코드조차 몇 줄 되지 않는다. 아니 코드가 거의 없다고 봐도 무방하다. 애노테이션만 몇 개 붙어있을 뿐인데, 어떻게 이런 기본적인 CRUD 기능이 동작하는 것일까? 사실, 이런 기능을 제공하는 프레임워크를 만들어 본적이 있다면 이런 기능이 그렇게 어려운 것은 아니라는 것을 알 것이다. 어렵지는 않지만 수고롭다. 실제로, 나도 GenericDao, GenericService, GenericController 등을 사용해서 클래스와 인터페이스만 선언하면 기본적인 CRUD 기능을 제공해주는 프레임워크를 만들어본 적이 있다. 하지만 항상 아쉬움이 남았는데, 바로 지나친 “상속” 때문이었다. 모든 모듈의 컨트롤러, 서비스, DAO 코드에서 프레임워크가 제공하는 인터페이스와 클래스를 상속받아야 해다. 상속을 받아야만 하니까 컨트롤러, 서비스, DAO가 POJO 클래스가 아니게 되 버리고, Generic을 잔뜩 사용한 클래스 선언부를 보고 있자면.. 참 씁쓸하다. 하지만 Spring Roo는 이 문제를 아주 깔끔하게 해결했다.

AspectJ에 Inter-Type Declaration(ITD) 또는 Mixin이라 부르는 기능이 있다. 이 기능은 AspectJ에만 있는 개념이 아니라, 다이나믹 언어에서 흔히 사용하는 개념이라고 한다. 자바에서는 보통 단일 상속만 지원하기 때문에, 어떤 공통 기능 여러 개를 상속받을 수 없지만, Mixin은 그와 반대로 여러 객체에서 공통적으로 사용할 수 있는 기능을 만들어 두고, 그것을 여러 객체에 주입하여 기능을 추가하는 방법이다. Spring Roo는 바로 이러한 ITD를 사용한다. ITD를 사용함으로써, 상속은 더 이상 필요없디. 그리고 사용자가 작성한 코드와 Spring Roo가 제공하는 코드를 완벽하게 분리함으로써, Spring Roo로 코드를 생성한 뒤에 사용자가 코드를 변경해도 계속해서 Spring Roo로 코드 생성 기능을 활용할 수 있다는 장점이 있다. 기본적으로 생성해야 하는 코드는 Spring Roo의 ITD로 숨기고, 비즈니스 로직 관련 코드만 남길 수 있다.

ITD로 추가되는 코드가 무엇인지 궁금하다면, Cross References 뷰에서 확인할 수 있다.

clip_image020

이것은 편집기에서 BookController 클래스를 열었을 때 Cross References 화면이다.

이 코드가 어떤 AspectJ 파일로 추가됐는지도 궁금할 수 있다. AspectJ 파일도 Spring Roo가 생성해 주는데, 그 코드는 기본적으로 숨겨주고 있다. 비즈니스 로직 관련 코드에만 집중하도록 배려해준 것인데, 필요하다면 해당 코드를 수정해서 기본 CRUD 기능을 수정할 수도 있겠다. AsepctJ 파일을 찾는 방법은 간단하다. Package Explorer 우측 상단에 역 삼각형 모양의 메뉴를 클릭하고 Filters를 클릭하자.

clip_image022

여기 보이는 Hide gerenated Spring Roo ITDs에 기본적으로 체크가 되어있는데, 이것을 해제하고 OK를 클릭하면 AspectJ 파일을 Package Explorer에서 보여준다.

clip_image024

이것으로 Spring Roo 소개를 마치겠다.

참고

스프링 Roo 1.0.0.RC1 배포~

스프링 루 1.0 RC1이 배포됐네요.

    * Bundlor project support (written by Adrian Colyer)
    * Extra developer support on Windows (thanks to Rod Johnson)
    * Support for relationships in dynamic finders
    * Default error handlers for web applications
    * DataSource improvements (now DBCP by default, plus JNDI support)
    * No more beeps from the Roo shell under Windows
    * Enhanced development mode diagnostic information

주요 개선 사항들입니다. GA 버전으로 잡혀있는 이슈가 3개 밖에 안 되는걸 보면, 8월 초 쯤에 1.0 정식 버전이 배포 될 듯한 느낌이네요.

윈도에서 삑삑 소리 나던게 없어졌나 보군요.ㅋㅋㅋ
암튼 참 재밌는 프로젝트 같아서 요기 조기 뜯어보고 싶어지네요.

Getting Started with Spring Roo

참조 요약 편역: http://blog.springsource.com/2009/05/27/roo-part-2/

벤 알렉스가 다음 달에 결혼하나봅니다. 그래서 RSVP 받는 프로그램을 Roo로 만들어 볼까 한다는군요. 1석 2조네요. RSVP도 만들고 Roo도 소개하고. RSVP는 청첩장을 받은 것에 대한 응답으로 몇명이 참석할지 알려주는 응답이라고 봐도 되겠네요.

진행상황

오늘 M1 버전이 배포됐습니다. 31% 성능 향상, 이메일 서비스, JMS, 스프링 웹 플로, 시큐리티, 자동 셀레늄 테스트 지원등이 주요 기능이죠.

다음은 관련 사이트 입니다.
커뮤니티 포럼
이슈 트래커
SVN 저장소
소스코드 모니터링
Roo 트위터

프로젝트 이름 투표는 952명이 투표했고, 467표를 얻은 Spring Roo로 결정이 됐답니다.

설치하기

다운받고, 압축 풀고, bin 폴더를 path에 추가하면 끝입니다.
알파버전일 때는 ROO_HOME 환경 변수를 등록했었는데, 이건 필요없으니 지워야겠습니다.
STS 2.1.0.M1 버전을 사용할 때는 Roo 1.0.0.A2가 내장되어 있다고 합니다. STS 2.1.0.M2에는 Roo 1.0.0.M1이 내장 될 예정이라고 하네요. STS에서 Roo 쉘을 이용할 때는 탭키가 아니라 Ctrl+스페이스로 자동완성을 지원한다고 합니다. 이클립스 스타일에 따라서 말이죠.

애플리케이션 요구사항

Roo는 직접 코딩하고 커스터마이징 하는 것을 투명하고, 편하며, 자동생성과 잘 어울리도록 설계되었다는 겁니다.

청첩장을 아직 보내진 않았지만, 보낼 건데, 그 뒤에 “초대 코드”를 적어서 보낼 거라고 합니다. 그럼 받은 사람들은 “초대 코드”를 사이트에 입력한뒤에 RSVP 폼을 작성하도록 하는 것이죠. RSVP가 일종의 로그인 정보가 되고 그 뒤에 RSVP 폼에서 필요한 정보(참석할 인원, 하고 싶은 말 등)을 입력받는 겁니다.

프로젝트 만들기

$ mkdir wedding
$ cd wedding
$ roo

폴더를 만들고, 이동한 뒤, roo 쉡로 들어갑니다. 종종 hint를 입력해서 필요한 정보와 지시를 얻을 수 있습니다. 이 부분에 대한 한글 번역을 제공할 수 있다면 좋겠네요.

roo> create project -topLevelPackage com.wedding
Created /home/balex/blog/wedding/pom.xml
Created SRC_MAIN_JAVA
Created SRC_MAIN_RESOURCES
Created SRC_TEST_JAVA
Created SRC_TEST_RESOURCES
Created SRC_MAIN_WEBAPP
Created SRC_MAIN_RESOURCES/applicationContext.xml
Created SRC_MAIN_WEBAPP/WEB-INF
Created SRC_MAIN_WEBAPP/WEB-INF/wedding-servlet.xml
Created SRC_MAIN_WEBAPP/WEB-INF/web.xml
Created SRC_MAIN_WEBAPP/WEB-INF/jsp
Created SRC_MAIN_WEBAPP/WEB-INF/jsp/index.jsp
Created SRC_MAIN_WEBAPP/WEB-INF/urlrewrite.xml

프로젝트를 만듭니다. 이 때 최상위 패키지 정보를 줄 수 있군요. 메이븐 기본 폴더 구조로 프로젝트가 생성되고 스프링 설정 파일들이 보이네요. 이것으로 일단 기본 스프링 3.0 웹 애플리케이션이 만들어졌고, URL rewriting, 애노테이션 기반 클래스패스 스캐닝, 의존성 주입 등의 기능을 갖추었습니다.

roo> install jpa -provider HIBERNATE -database HYPERSONIC_PERSISTENT
Created SRC_MAIN_RESOURCES/META-INF
Created SRC_MAIN_RESOURCES/META-INF/persistence.xml
Created SRC_MAIN_RESOURCES/database.properties
Managed SRC_MAIN_RESOURCES/applicationContext.xml
Managed ROOT/pom.xml

이번에는 JPA를 추가했습니다. 영속성 관리를 하이버네이트로 하고, DB는 hsql을 사용하겠다고 했군요. 맨 밑에 두줄은 Managed로 표시되어 있는데, 어떤 문제가 있을 되돌리기 위해 Roo가 관리하는 파일을 표시해 둔 거라고 합니다. Roo가 생성한 코드는 성능 최적화를 고려하여 toString()에서 리플렉션을 사용하지 않는다고 하는군요. 흠.. 코드가 궁금해지네요. 아니면 지금 번역을 잘못한건지.. @_@

Roo does everything from avoiding reflection to optimizing string operations in your toString() methods (and everything in between) to maximise runtime performance of your applications.

데이터베이스 설정을 확인할 수 있습니다.

roo> database properties
database.driverClassName = org.hsqldb.jdbcDriver
database.password =
database.url = jdbc:hsqldb:${user.home}/wedding
database.username = sa

변경할 수도 있습니다.

roo> database set -key database.url -value jdbc:hsqldb:/home/balex/our-wedding
Managed SRC_MAIN_RESOURCES/database.properties

물론 Roo를 사용하지 않고 IDE에서 직접 수정해도 됩니다. 단, 이렇게 커맨드를 이용할 수 있게 만든 이유는 스크립트를 제작할 수 있도록 하기 위해서 랍니다.

엔티티 만들기

roo> new persistent class jpa -name ~.domain.Rsvp
Created SRC_MAIN_JAVA/com/wedding/domain
Created SRC_MAIN_JAVA/com/wedding/domain/Rsvp.java
Created SRC_MAIN_JAVA/com/wedding/domain/Rsvp_Roo_Plural.aj
Created SRC_MAIN_JAVA/com/wedding/domain/Rsvp_Roo_Entity.aj
Created SRC_MAIN_JAVA/com/wedding/domain/Rsvp_Roo_ToString.aj
Created SRC_MAIN_JAVA/com/wedding/domain/Rsvp_Roo_Configurable.aj

프로젝트를 만들 때 입력한 최상위 프로젝트 밑에 domain 패키지를 만들고 그 아래 Rsvp 도메인을 추가했습니다. ~가 바로 그 최상위 프로젝트를 나타내는 단축키이고, Rsvp 도메인을 만들면서 AspectJ 파일 네 개를 자동생성했네요. 이 파일들은 STS 1.0+에서 자동으로 숨겨주기 때문에 이클립스에서 그냥 보시면 보이지가 않습니다. hide 옵션에서 *.aj 파일 체크를 해제해야 볼 수 있죠.

roo> add field string code -notNull -sizeMin 1 -sizeMax 30
roo> add field string email -sizeMax 30
roo> add field number attending -type java.lang.Integer
roo> add field string specialRequests -sizeMax 100
roo> add field date jpa confirmed -type java.util.Date

필드를 추가합니다. 이 때 검증 관련 옵션을 주는데 이 옵션들은 JSR 303 Bean Validation 스펙을 이용합니다. 이를 사용하여 영속 계층에서도 검증을 하고, 웹에서도 검증을 하고, DDL을 생성할 때도 이용합니다. Roo를 사용할 때 얻을 수 있는 장점 중 하나가 바로 이렇게 별다른 수고 없이도 자연스래 표준을 이용하게 된다는 겁니다.

제대로 동작하는지 확인하기

roo> new integration test

통합테스트를 만듭니다. 아마도 DB, 도메인, 서비스를 연계한 테스트겠지요?

roo> new controller automatic ~.web.RsvpController

이번에는 컨트롤러를 만드는데, 자동 모드로 만듭니다. 스프링 3.0 REST 지원을 사용합니다. 사용자가 사용하게 될 URL은 완전한 RESTful 이며 깔끔하고 잘 정의된 URL이 될 겁니다. 이런 자동 웹 단은 다음의 경우에 유용합니다.

roo> new selenium test -controller ~.web.RsvpController

이번에는 앞에서 만든 컨트롤러에 대한 셀레늄 테스트를 만듭니다. 그래서 갑자기 컨트롤러를 만들었군요. @_@

이제 테스트를 실행할 차례입니다. roo 쉘에서 잠깐 나가야겠네요.

roo> quit
$ mvn test
$ mvn tomcat:run

roo 쉘에서 나가서, mvn test로 아마 통합 테스트까지 실행 한 뒤, 웹 테스트를 위해서 일단 톰캣을 가동합니다. 이상태에서 http://localhost:8080/wedding에 접속하여 애플리케이션이 잘 동작하나 확인할 수 있습니다.

$ mvn selenium:selenese

자 이번에는 자동으로 만들어진 셀레늄 테스트를 실행합니다. 뭐.. 잘 되겠지요. 자동으로 만든 코드를 자동으로 만든 테스트로 테스트를 하는데, 제대로 안 되면… 배포를 안 했겠죠.. @_@.. 이건 뭐 그냥 쑈를 위해 해본 듯한 느낌이지 진짜 테스트 같다는 느낌은 들지 않는군요.

보안과 로깅

roo> configure logging -level DEBUG -package WEB

웹 단이 잘 동작하나 보기 위해 로딩을 DEBUG 모드로 설정했네요. Log4J 기반 로깅을 사용한답니다..

다음은 시큐리티인데, 스프링 시큐리티를 한 줄로 설치할 수 있습니다. 참 쉽죠~

roo> install security

install web flow, install jms도 이런 식으로 한방 설치가 가능하다고 하는데 이건 뭐 거의.. Grails를 봤을 때의 충격과 맞먹는 기능입니다. 멋져부러~~!!!

컨트롤러, 동적 파인더, 이메일 기능 관리하기

roo> new controller manual -name ~.web.PublicRsvpController

이번에는 자동모드가 아니라 직접 코딩으로 만들 컨트롤러를 추가합니다. 이 컨트롤러는 HTTP GET과 POST 요청을 다룹니다. 두 개의 메서드가 자동으로 컨트롤러 클래스에 추가되어 있다고 합니다.

GET 요청이 오면 특정 “초대 코드”에 대한 RSVP를 가져오는 것입니다. 스프링 시큐리티를 사용하여 “요청 코드”를 일종의 username으로 취급하여 사용자가 로그인 할 수 있게 하고, 로그인 한 사용자의 “요청 코드
로 DB에서 RSVP 정보를 가져오는 역할을 할 겁니다. 이때 “요청 코드”로 RSVP를 찾아오는 JPA QL을 작성할 필요가 있겠지만. 이것 역시 Roo가 도와줄테니 걱정할 필요 없습니다.

만들고자 하는 동적 파인더를 찾아봅니다.

roo> list finders for -class ~.domain.Rsvp -filter code,equ
findRsvpsByCodeEquals(String code)
findRsvpsByCodeNotEquals(String code)

캬.. 이것참 멋집니다. 찾고자 하는게 딱 있네요. filter 옵션 사용법만 잘 익히면 원하는 파인더를 뚝딱 만들어 낼 수 있다니.. 멋집니다. 멋져.

roo> install finder -finderName findRsvpsByCodeEquals

이런 식으로 필요한 파인더를 추가할 수 있습니다.

POST 요청이 오면 RSVP 정보를 추가하고 이메일로 알림을 받고 싶습니다. 그럼 이제 이메일 공급자를 설치하고 email 필드를 컨트롤러에 추가하겠습니다.

roo> install email provider -hostServer 127.0.0.1
roo> add field email template -class ~.web.PublicRsvpController

너무 쉬워서 말이 안나오네요. Roo에서 Gmail을 사용하여 이메일을 보내는 방법도 간단한가 봅니다.

IDE 통합

roo> quit
$ mvn eclipse:eclipse
$ roo

roo 쉘에서 나가서 이클립스 프로젝트 설정을 추가한다음 다시 roo로 돌아옵니다. 이제 이클립스에서 import하면 되겠죠.

마지막 단계

이제는 이클립스에서 필요한 파일들을 수정, 추가하고 필요한 코딩을 하면 될 것 같습니다. 위에서 설명했던 컨트롤러 코딩을 해야겠죠. 시큐리티 설정도 손봐야겠구요.

   <http auto-config=”true”>
       <form-login login-processing-url=”/static/j_spring_security_check” login-page=”/static/login.jsp” authentication-failure-url=”/static/login.jsp?login_error=t”/>
       <logout logout-url=”/static/j_spring_security_logout”/>
       <intercept-url pattern=”/rsvp/**” access=”ROLE_ADMIN”/>
       <intercept-url pattern=”/resources/**” access=”IS_AUTHENTICATED_ANONYMOUSLY” />
       <intercept-url pattern=”/static/**” access=”IS_AUTHENTICATED_ANONYMOUSLY” />
       <intercept-url pattern=”/**” access=”IS_AUTHENTICATED_REMEMBERED” />
   </http>

   <authentication-provider>
       <user-service>
           <user name=”admin1234″ password=”ignored” authorities=”ROLE_ADMIN”/>
        <user name=”user12345″ password=”ignored” authorities=”ROLE_USER”/>
        <user name=”user67890″ password=”ignored” authorities=”ROLE_USER”/>
    </user-service>
</authentication-provider>

시큐리티 설정을 손봤습니다. 로그인 할 때 사용자의 password는 무시하고 username으로 “요청 코드”를 입력하면 로그인 되도록 수정했네요.

src/main/webapp/login.jsp 뷰도 수정해야겠죠. <input name=”j_password” type=”hidden” value=”ignored”/> 이렇게 추가하고 기존에 “j_password” 레이블을 가진 <div>는 삭제합니다. 자 이제 시큐리티 설정은 끝났고, 컨트롤러를 코딩합니다.

@RequestMapping(“/publicrsvp/**”)
@Controller
@SessionAttributes(“rsvp”)
public class PublicRsvpController {

    @Autowired
    private transient MailSender mailTemplate;

    @RequestMapping
    public String get(ModelMap modelMap) {
        modelMap.put(“rsvp”, getRsvp());
        return “publicrsvp”;
    }

    @RequestMapping(method = RequestMethod.POST)
    public String post(@ModelAttribute(“rsvp”) Rsvp rsvp, ModelMap modelMap) {
      rsvp.setConfirmed(new Date());
        if (rsvp.getId() == null) {
            rsvp.persist();
        } else {
            rsvp.merge();
        }
        if (rsvp.getEmail().length() > 0) {
            sendMessage(“Ben Alex <ben.alex@springsource.com>”, “RSVP to our wedding”, rsvp.getEmail(), “Your RSVP has been saved: ” + rsvp.toString());
        }
        modelMap.put(“rsvp”, rsvp);
        return “thanks”;
    }

    private Rsvp getRsvp() {
        Rsvp rsvp = new Rsvp();
        try {
            String code = SecurityContextHolder.getContext().getAuthentication().getName();
            rsvp.setCode(code);
            // Cast due to http://java.sun.com/javaee/5/docs/api/javax/persistence/Query.html#getSingleResult()
            rsvp = (Rsvp) Rsvp.findRsvpsByCodeEquals(code).getSingleResult();
        } catch (PersistenceException ignored) { /* no Rsvp for this code was found, so start a new Rsvp */ }
        return rsvp;
    }

    private void sendMessage(String mailFrom, String subject, String mailTo, String message) {
        SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
        simpleMailMessage.setFrom(mailFrom);
        simpleMailMessage.setSubject(subject);
        simpleMailMessage.setTo(mailTo);
        simpleMailMessage.setText(message);
        mailTemplate.send(simpleMailMessage);
    }
}

GET 요청을 다룰 때는 getRsvp() 메서드를 보시면 아시겠지만, 위에서 설명했던대로, 시큐리티를 이용해서 현재 로그인한 사용자의 “요청 코드”를 가져오고, 위에서 만들었던 파인더(findRsvpsByCodeEquals)를 이용해서 rsvp를 가져옵니다. 그리고 최종적으로 “publicrsvp” 라는 뷰 이름을 반환하죠.

POST 요청이 오면 날짜를 설정하고, 기존에 있던 정보면 수정하고 없던거면 새로 추가한 다음에 이전에 만들었던 이메일 공급자를 사용해서 이메일을 보내고 “thanks” 라는 뷰 이름을 반환합니다.

이젠 최종 뷰를 만들어줘야 합니다. thanks 뷰는 src/main/webapp/WEB-INF/index.jsp를 thanks.jsp로 수정해서 만듭니다. 사용자가 입력한 RSVP 정보를 보여주기 위해 “Your RSVP has been confirmed: ${rsvp}”. 이 한 줄을 추가하는 것도 괜찮겠죠.

다음은 publicrsvp 뷰를 만듭니다. 이 뷰는 src/main/webapp/WEB-INF/rsvp/create.jsp를 src/main/webapp/WEB-INF/publicrsvp.jsp 이 파일로 변경합니다. 그리고 이 파일을 수정해서 “code”와 “confirmed” 섹션을 삭제합니다. 또한 폼 액션도 위에서 작성한 컨트롤러가 다룰 수 있도록 “/wedding/publicrsvp” 이렇게 수정해줍니다.

마지막으로 src/main/webapp/WEB-INF/urlrewrite.xml 이 파일을 열고  / 대신에 /WEB-INF/jsp/index.jsp 이렇게 리다이렉트할 페이지를 수정해 줍니다.

이제 “mvn tomcat:run”로 톰캣을 실행하던지..
이클립스에서 Run As -> Run on Server를 하던지
STS에서 Spring Tools -> Open Roo Shell로 roo 쉘을 열고 “deploy -server someServer”를 이용하면 됩니다.

roo 쉘을 이용해서 프로젝트 빌드 배포를 할 수 있을지 의문이 드네요.
roo를 제대로 활용하려면 스프링 3.0 역시 제대로 알고 있어야겠습니다.
거저 얻을 수 있는 것에도 한계가 있어보입니다. 이것 저것 수정해서 입맛에 맞게 변경하려면 스프링 학습은 필수라는 거..

끝으로.. mvn package로 WAR 파일을 만들어서 tc 서버나 dm 서버에도 배포할 수 있다는 거~~

ps1: 새로운 기술을 보거나 공부할 때 마다 기억나는 말이 있는데, “맨날 다른 기술 공부하기만 하면, 자기 자신의 것은 없고 따라갈 줄만 알게 되지 않겠냐?” 라는 건데, 제 생각엔 뭘 보고 배워야 자기 자신의 것도 만들 수 있게 되지 아무것도 모르는 상태에서 어떻게 뭘 만들어 내겠냐는 겁니다. 좋은 건 좋다고 인정하고, 배울 건 배워야 성장하지 않을까요. 백년 만년 자기가 접하는 제한된 환경 속에서 제한된 기술만 가지고 노는건 자기 스스로를 우물안에 가두는 꼴이 아닐런지 말입니다.

 ps2: 결혼식 하기전에 오랜만에 스크린캐스팅을 하나 찍어볼까 생각중입니다. 이래저래 스프링 소스는 참 재미난 것들을 많이 제공해 주네요. 멋져!

스프링 Roo petclic 예제 실행 성공

1. 프로젝트 디렉토리 만들기

# mkdir petclinic

2. 디렉토리로 이동하기

# cd petclinic

3. 루 실행

# roo

4. 스크립트 실행

roo> script clinic.roo
roo> exit

5. 이클립스 프로젝트로 변환

mvn eclipse:eclipse

6. 이클립스 프로젝트로 임폴트

7. 콘솔에서 테스트

mvn test

8. Run As -> Run on server

간단하네요.. 소스 코드를 봤더니, 애노테이션과 AspectJ의 향연입니다. 자바 소스 코드는 거의 없다시피 합니다. 하이버네이트를 사용했습니다. 도메인이 CRUD를 담당하는지(물론 어딘가로 위임하겠죠.), 도메인과 컨트롤러 밖에 보이지 않습니다. 도메인이 역할을 위임하는 녀석들이 보이지 않습니다.

@Entity
@RooJavaBean
@RooToString
@RooEntity(finders = { “findVisitsByDescriptionAndVisitDate”, “findVisitsByVisitDateBetween”, “findVisitsByDescriptionLike” })
public class Visit {

    @Size(max = 255)
    private String description;

    @NotNull
   @Past
    @Temporal(TemporalType.TIMESTAMP)
    private Date visitDate;

    @NotNull
    @ManyToOne
   @JoinColumn
    private Pet pet;

    @ManyToOne
   @JoinColumn
    private Vet vet;
}

하늘색은 JPA 애노테이션(하지만 실제로 사용하는 라이브러리는 하이버네이트, 왜냐면 JPA가 표준이까) 녹색은 Roo 애노테이션, 남색은 javax.validation 애노테이션입니다. @Past는 항상 입력되는 Date가 입력되는 시점 기준으로 이전 날짜여야 한다네요. 오호..

Roo를 학습하려면 Roo가 제공하는 애노테이션과 그 기능을 파악하는게 주요해 보입니다. 그리고 Roo가 가정하고 있는 CoC도 익혀야겠죠. 좀 더 자세히 보려면 Roo가 제공하는 Aspect들을 조사해봐야겠습니다.

AspectJ 파일들을 보려면 이클립스에서 네비게이터로 보면 됩니다. 아니면 Filters 옵션에서 Hide generated ROO ITDs의 체크를 없애면 패키지 익스플로러에서도 볼 수 있습니다.

흠.. 기발한 것 같습니다. AspectJ를 자동생성해서 자바로 만든 클래스에 @Configurable을 붙이고 테스트까지 자동생성합니다. @_@ 와.. 정말이지 이건 애노테이션과 AspectJ의 향연입니다.

Roo 설치하기

1. 메이븐 버전확인

mvn -v

2.0.9 이상이어야 함.

2. 자바 버전 확인

java -version

자바 5 이상이어야 함.

3. roo 압축파일 풀기

4. ROO_HOME을 환경 변수에 추가하기

5. ROO_HOME/bin을 PATH에 추가하기

6. STS 2.0.1 준비하기

7. ROO 이클립스 플러그인을 STS/dropins 디렉터리에 추가하기

다음 STS 배포 부터는 이 플러긴도 포함되서 배포할 예정.

8. 메이븐 로컬 저장소에 Roo 추가하기

리눅스/맥: mvn install:install-file -DgroupId=org.springframework.roo -DartifactId=roo-annotations -Dversion=1.0.0.A1 -Dpackaging=jar -Dfile=$ROO_HOME/dist/roo-annotations-1.0.0.A1.jar

윈도: mvn install:install-file -DgroupId=org.springframework.roo -DartifactId=roo-annotations -Dversion=1.0.0.A1 -Dpackaging=jar -Dfile=%ROO_HOME%/dist/roo-annotations-1.0.0.A1.jar

9. 콘솔에서 roo 입력해보기

10. hint 입력해보기

11. q 입력해서 종료하기