Spring Roo 소개
자바 개발자를 위한 신속한 애플리케이션 개발 툴 Spring Roo
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 프로젝트 만들기
New -> Project -> Spring Roo Project를 선택하면, Roo 프로젝트를 생성할 수 있다. Spring Roo 초기 버전은 콘솔에서만 작업해야 했었지만, STS에서 Spring Roo 지원 기능을 추가한 뒤로는, 콘솔에서 작업해야 할 필요가 없어졌다. 이 메뉴를 사용해서 프로젝트를 만들고 나면 STS의 Package Explorer에 프로젝트가 생기고 STS 하단에 다음과 같은 Roo Shell을 볼 수 있다.
앞으로 이 뷰가 콘솔을 대신할 것이다. 여기서 roo가 제공하는 DSL을 사용해서 JPA를 설정하고, 엔티티 클래스를 만들고, 각 엔티티에 필드를 추가하고, 연관 관계를 설정하고, 컨트롤러를 만들고, 테스트를 만드는 등의 여러 작업을 할 수 있다.
프로젝트 구조는 평범한 메이븐 프로젝트 구조이고, 스프링 설정 파일인 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/) 등이 생겨나자. 최근에는 그 전략을 바꿨다.
(출처: 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 설정
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를 선택했다.
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/프로젝트_이름”을 전부 입력해도 상관없다.
이 애플리케이션에서 기본적인 CRUD 기능을 확인해보자. 잘 동작하라 것이다.
모든 마술은 ITD, Mixin
지금까지 생성한 자바 코드는 다음과 같다.
이 몇 개 안 되는 파일에는 지금까지 살펴봤듯이, 코드조차 몇 줄 되지 않는다. 아니 코드가 거의 없다고 봐도 무방하다. 애노테이션만 몇 개 붙어있을 뿐인데, 어떻게 이런 기본적인 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 뷰에서 확인할 수 있다.
이것은 편집기에서 BookController 클래스를 열었을 때 Cross References 화면이다.
이 코드가 어떤 AspectJ 파일로 추가됐는지도 궁금할 수 있다. AspectJ 파일도 Spring Roo가 생성해 주는데, 그 코드는 기본적으로 숨겨주고 있다. 비즈니스 로직 관련 코드에만 집중하도록 배려해준 것인데, 필요하다면 해당 코드를 수정해서 기본 CRUD 기능을 수정할 수도 있겠다. AsepctJ 파일을 찾는 방법은 간단하다. Package Explorer 우측 상단에 역 삼각형 모양의 메뉴를 클릭하고 Filters를 클릭하자.
여기 보이는 Hide gerenated Spring Roo ITDs에 기본적으로 체크가 되어있는데, 이것을 해제하고 OK를 클릭하면 AspectJ 파일을 Package Explorer에서 보여준다.
이것으로 Spring Roo 소개를 마치겠다.
참고