오래전 Inside Spring (5) PropertyPlaceholderConfigurer를 @Bean으로 정의해서는 안되는 이유라는 글을 쓴 적이있다. 나름 흥미로운 분석을 통해서 정리한 재밌는 글이라고 생각했는데 별 반응이 없었다. 굳이 스프링 내에서 어떤 일이 일어나는지, BeanFactoryPostProcessor(BFPP)의 동작원리가 뭔지 이해해야 필요를 못 느껴서였을 수도 있고 PPC를 XML에서 사용하면 그만이지, 당시엔 활용도가 낮았던 @Configuration 클래스 내에서 @Bean으로 정의해서 @Value로 값을 받는데 사용할 필요가 없었기 때문일 수도 있다.

그런데 이제는 No XML이라는 슬로건을 걸친 스프링 3.1이 나와서 자바 코드만으로 XML의 모든 설정을 대체할 수 있을 뿐만 아니라 XML보다 더욱 편리하게 사용할 수 있다는 점을 강조하고 있다. 물론 스프링 3.1이라고 XML을 사용하면 안되는 것은 아니며 @Configuration을 사용할 의무도 없다. 하지만 3.0에서 자바 콘픽을 도입한 이후로 이를 충분히 성숙한 레벨로 올려서 실전에서 본격적으로 사용할 수 있도록 만든 것이 스프링 3.1 변화의 핵심이니 3.1을 사용한다면 한번쯤 XML 없는 스프링 설정에 도전해볼만 하다.

스프링 3.1에서 새롭게 소개된 자바코드 설정용 각종 애노테이션과 기법들은 천천히 얘기하기로 하고(실은 지금 그 내용을 담은 토비의 스프링 3 개정판을 열심히 준비하고 있다) 오늘은 BFPP를 @Bean으로 사용할 수 없었던 문제만 다시 짚어보고 스프링 3.1은 이를 어떻게 해결했는지(해결하기는 했는지?) 살펴보자.

PPC를 @Bean의 정의하면 기대한대로 동작하지 않는 이유에 대해서는 이전 글에서 자세히 설명했다. @Configuration 클래스를 이용한 빈 설정을 가능하게 하는 것도 BFPP이며 프로퍼티 파일의 내용을 가져와 ${..}으로 지정된, 빈 프로퍼티 내의 치환자에 값을 넣어주는 PropertyPlaceholderConfigurer도 BFPP이다. 문제는 @Bean으로 정의된 빈은 @Configuration을 담당하는 ConfigurationClassPostProcessor(CCPP)에 의해서 빈 인스턴스가 만들어지는데, 그렇게 만들어진 BFPP 빈은 이미 빈 인스턴스가 만들어져 사용된 @Configuration 빈의 @Value 프로퍼티 값을 바꿀 수 없기 때문이다.

이 이야기가 어려운 듯 보이지만 사실.. 어렵지 않아요.

BFPP와 BPP(BeanPostProcessor)의 차이점만 이해하면 쉽게 이해할 수 있다. BFPP는 빈의 메타 데이터(클래스는 뭐고 프로퍼티 값의 정의는 뭐고 등등)를 조작하는 후처리기고, BPP는 빈의 인스턴스 값을 조작하는 후처리기다. PPC는 BFPP이므로 메타 데이터만 조작할 수 있다. 하지만 PPC가 @Bean에 의해서 만들어지는 시점에 이미 @Configuration 클래스로 정의된 빈은 메타 데이터 단계가 아니라 인스턴스까지 만들어진 상태다. @Bean 메소드를 실행해서 PPC빈을 만들어야 하니 당연히 이미 @Configuration 인스턴스가 필요할 수 밖에. 결국 PPC가 아무리 메타 데이터에 있는 ${..}을 찾아서 봐꿔봐야. 이미 인스턴스 생성이 끝난 @Configuration 빈의 @Value가 붙은 프로퍼티의 값은 변경할 수가 없다. 빈을 다시 만들지 않을테니까. new XXX() 해서 인스턴스를 만들고 나서 XXX.class 파일을 아무리 조작해봐야 이미 만들어진 인스턴스는 변하지 않는 것과 마찬가지라고 보면 된다.

그렇다면 이를 피할 방법은 뭐가 있을까?

방법은 간단하다. BFPP가 만들어진 후에 @Value가 붙은 @Configuration 클래스가 빈으로 만들어지도록 하면 된다.

가장 쉬운 방법은 BFPP를 @Configuration내의 @Bean이 아닌 XML에 정의하고 이를 @ImportResource로 가져오는 것이다. XML에는 다음과 같이 PPC가 생성되도록 빈을 정의한다. 친절한 스프링 덕에 커스톰 태그 하나면 충분하다.

<context:property-placeholder location="message.properties" />

그리고 이 XML을 @Configuration 클래스에서 @ImportResource로 불러온다.

@Configuration
@ImportResource("context.xml")
public class Config {
    @Value("${name}") String name;

이렇게 해두면 친절한 스프링은 Config 빈을 만들기 전에 XML의 빈들을 먼저 만들고 이때 만들어진 PPC는 Config 빈의 메타 정보에 적용이되서 ${name}을 이용해서 name 프로퍼티 값이 설정이 된다.

또는, 이전 글에서 예로 보였던 것처럼 @Value가 붙은 @Configuration 클래스가 아닌 다른 빈 클래스를 정의해서 거기서 PPC가 만들어지게 하면 된다. PPC를 확장해서 @Component로 정의하는 것도 되고, 또 다른 @Configuration 클래스를 만들어서 그 안에 PPC를 만드는 @Bean을 넣어도 좋다.

@Configuration의 @Value에 ${..}를 적용할 수 있는 방법은 이 두가지 정도.

여기까지는 스프링 3.0의 경우고. 스프링 3.1의 새로운 방법을 살펴보자.

스프링 3.1에선 더이상 PPC를 쓰지 않는다. PropertySource라는 프로퍼티 값을 다루는 새로운 추상화 방식이 등장하면서 이전에 사용되던 다양한 접근방법들을 하나로 통합해버렸다.  이에 대한 자세한 설명은 나중에.

그래서 스프링 3.1에선 PPC 대신 PropertySourcePlaceholderConfigurer(PSPC)가 사용된다. <context:property-placeholder />에서도 PPC 대신 PSPC가 만들어진다. 이 두가지는 사실 차이점이 제법 있는데 일단 제껴두고 BFPP관점에서만 살펴보자.

PSPC도 여전히 BFPP다. 따라서 3.0에서와 마찬가지로 @Value에 ${..}을 적용하기 위해서는 같은 @Configuration 클래스에서 @Bean으로 PSPC를 정의하면 안된다. 물론 앞에서 소개한 두가지 방법을 이용해서 이를 피할 수 있다. 하지만 스프링 3.1은 이 문제를 보다 세련된 방식으로 해결해버렸다. 그래서 이제는 @Value와 PSPC @Bean 메소드가 같은 클래스에 정의해도 된다.

단, 이 PSPC @Bean은 다음과 같이 스태틱 메소드여야 한다.

@Bean static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
    return new PropertySourcesPlaceholderConfigurer();
}

3.0에선 @Bean 메소드는 final 또는 private이어도 안되고, static 이어도 안됐다. 앞에 두개는 CGLib으로 확장할 때 문제가 되기 때문에 안 되는 것이고, static은 @Bean 메소드 호출을 통한 빈 생성과 참조라는 목적에 맞지 않기 때문이다.

반면에 스프링 3.1은 static @Bean 메소드를 특별한 용도에 한해서 허용한다. 바로 BFPP 빈을 정의할 때다.

@Configuration 빈이 생성되기 전에, 즉 메타 데이터 상태로 있을 때 적용되야할 BFPP 빈이 있다면 이를 static 메소드로 정의한다. 스태틱이므로 빈 인스턴스를 만들지 않아도 호출할 수 있다. 스프링은 @Configuration 클래스 내에 @Bean이 붙은 스태틱 메소드가 있으면 이를 먼저 호출해서 빈을 생성해두고, 그 후에 @Configuration 빈을 만든다. 따라서 BFPP @Bean 빈은 @Configuration 클래스에도 영향을 줄 수 있는 것이다.

스태틱 @Bean 메소드는 BFPP 빈의 문제를 해결하기 위해서 사용되도록 의도된 것이므로 BFPP가 아닌 일반 빈을 만들 때는 사용하지 않는 것이 좋다. 스태틱 메소드이므로 다른 @Bean 인스턴스 메소드를 호출할 수도 없고, @Autowired로 DI받은 빈을 참조할 수도 없다. 그래도 억지로 BFPP가 아닌 빈을 스태틱 @Bean으로 정의하면 스프링이 WARN 레벨의 로그로 경고를 때려줄 것이다. 에러가 나지는 않지만 나중에 코드 리뷰하다 쪽팔릴 수 있으니 쓸데없이 사용하지는 말자.

그런데 위에서 정의한 PropertySourcesPlaceholderConfigurer 빈 메소드는 예전에 PPC를 만드는 @Bean 메소드와 다른 점이 있다. 메시지 파일의 location을 지정하지 않는다는 것인데 그 이유는 스프링 3.1의 프로퍼티 체계가 완전히 다르기 때문이다. PSPC는 프로퍼티 정보를 특정 파일이 아니라 Environment로부터 가져온다. 이 얘기를 마저 하려면 너무 길어져서 여기서 그만. 자세한 것은 조만간 나올 토비의 스프링 3 개정판을 참고 하…

오늘은 여기까지.

혹시 이 설명을 듣고 머리가 너무 아프다면, 잠시 머리를 식힐 겸 아웃사이더 님의 node.js 프로그래밍 책을 구해서 보는 것이 좋겠다. 최근 스프링 프레임워크 개발자들이 관심을 가지는 기술의 하나는 node.js다. 작년 VMForum에서 스프링 소스 호주 지부장이자 스프링 시큐리티, ROO를 만든 벤 알렉스는 키노트에서 스프링과 node.js를 함께 사용하는 멋진 예제를 소개하며 스프링의 미래를 전망했다. 스프링 소스가 참여해서 개발되고 있는 PaaS인 CloudFoundry에도 스프링과 함께 node.js 런타임이 지원되고, 스프링과 손쉽게 연동해서 사용할 수 있는 다양한 방법이 제공되고 있다. 스프링 개발자라면 한번쯤 관심을 가져야 할 기술이다. 아웃사이더님의 책은 내가 node.js를 공부(리뷰에 참여한 덕분에 원고를 좀 일찍 받아봤다)하면서 참고하고 있는데 아주 맘에 든다. 뭐, 내가 이 책의 추천사를 썼기 때문에 하는 말은 아니고…

http://www.yes24.com/24/goods/4020006 웹 서점에 나온 책의 목차를 보니 번호가 없는 3단까지 모두 나온데다, 들여쓰기가 안되있어서 목차를 살펴보기가 영 불편할 듯 싶다. 그래서 2단까지만 간단히 정리한 목차를 여기다 올린다.

자세히 보면 이상한 점이 하나 있다. 각 장마다 마지막 절은 ‘정리’인데, 14 장에는 그게 없다. 지금 써야 한다. ㅠㅠ

150페이지에 달하는 13장 @MVC와 난이도 대마왕급의 14장 @AspectJ를 쓰고 나서 정신이 혼미했던 것 같다.

 

토비의 스프링 3 목차

1부.    이해
   1장.    오브젝트와 의존관계
      1.1 초난감 DAO
      1.2 DAO의 분리
      1.3 DAO의 확장
      1.4 제어의 역전(IoC)
      1.5 스프링의  IoC
      1.6 싱글톤 레지스트리와 오브젝트 스코프
      1.6 의존관계 주입(DI)
      1.7 XML을 이용한 설정
      1.8 정리
   2장.    테스트
      2.1 UserDaoTest 다시보기
      2.2 UserDaoTest 개선
      2.3 개발자를 위한 테스팅 프레임워크 JUnit
      2.4 스프링 테스트 적용
      2.5 학습 테스트로 배우는 스프링
      2.6 정리 
   3장.    템플릿
      3.1 다시 보는 초난감 DAO
      3.2 변하는 것과 변하지 않는 것
      3.3 JDBC 전략 패턴의 최적화
      3.4 컨텍스트와 DI
      3.5 템플릿과 콜백
      3.6 스프링의 JdbcTemplate
      3.6 정리
   4장.    예외
      4.1 사라진 SQLException
      4.2 예외 전환
      4.3 정리
   5장.    서비스 추상화
      5.1 사용자 레벨 관리기능 추가
      5.2 트랜잭션 서비스 추상화
      5.3 서비스 추상화와 단일책임원칙
      5.4 메일 서비스 추상화
      5.5 정리
   6장.    AOP
      6.1 트랜잭션 코드의 분리
      6.2 고립된 단위 테스트
      6.3 다이나믹 프록시와 팩토리 빈
      6.4 스프링의 프록시 팩토리 빈
      6.5 스프링AOP
      6.6 트랜잭션 속성
      6.7 애노테이션 트랜잭션 속성과 포인트컷
      6.8 트랜잭션 지원 테스트
      6.9 정리
   7장.    스프링 핵심기술의 응용
      7.1 SQL과 DAO의 분리
      7.2 인터페이스의 분리와 자기 참조 빈
      7.3 서비스 추상화 적용
      7.4 인터페이스 상속을 통한 안전한 기능확장
      7.5 DI를 이용해 다향한 구현방법 적용하기
      7.6 정리
   8장.    스프링이란 무엇인가? 
      8.1 스프링의 정의
      8.2 스프링의 목적
      8.3 POJO 프로그래밍
      8.4 스프링의 기술
      8.5 정리
2부.    선택
   9장.    스프링 프로젝트 시작하기
      9.1 자바 엔터프라이즈 플랫폼과 스프링 애플리케이션
      9.2 개발도구와 환경
      9.3 애플리케이션 아키텍처
      9.4 정리
   10장.    IoC컨테이너와 DI
      10.1 IoC컨테이너 – 빈 팩토리와 애플리케이션 컨텍스트
      10.2 IoC/DI를 위한 빈 설정 메타정보 작성
      10.3 프로토타입과 스코프
      10.4 기타 빈 설정 메타정보
      10.5 정리
   11장.    데이터 액세스 기술
      11.1 공통 개념
      11.2 JDBC
      11.3 iBatis SqlMaps
      11.4 JPA
      11.5 하이버네이트
      11.6 트랜잭션
      11.7 정리
   12장.    스프링 웹 기술과 스프링 MVC
      12.1 스프링의 웹 프레젠테이션 계층 기술
      12.2 스프링 웹 애플리케이션 환경 구성
      12.3 컨트롤러
      12.4 뷰
      12.5 기타 전략
      12.6 정리 
   13장.    스프링 @MVC
      13.1 @RequestMapping 핸들러 매핑
      13.2 @Controller
      13.3 모델 바인딩과 검증
      13.4 JSP뷰와 form 태그
      13.5 메시지 컨버터와 AJAX
      13.6 mvc 네임스페이스
      13.7 @MVC 확장 포인트
      13.8 정리 
   14장.    AOP와 LTW
      14.1 애스팩트 AOP
      14.2 AspectJ와 @Configurable
      14.3 로드타임 위버(LTW) 
   15장.    컨텍스트 테스트 프레임워크
      15.1 컨텍스트 테스트 프레임워크
      15.2 트랜잭션 지원 테스트 
      15.3 정리
   16장.    스프링의 기타 기술과 효과적인 학습방법
      16.1 스프링 기술과 API를 효과적으로 학습하는 방법
      16.2 IoC컨테이너 DI
      16.3 SpEL
      16.4 OXM
      16.5 리모팅과 웹서비스, EJB
      16.6 태스크 실행과 스케줄링
      16.7 정리

(그림 협찬: 예스24)

http://www.yes24.com/24/goods/4020006

정식 출간은 8월 5일이지만 웹 서점에 미리 공개가 됐다. 주문도 되던가?

지금 마지막 리뷰 작업에 매달리는 중이라 책에 대한 얘기를 꺼낼 여유가 없지만… 아무튼 이렇게 공개가 되니 기분이 묘하구나. 한편으로 마음이 무겁기도 하고. 이제 내 손을 떠나서 독자들 손에 들어갈 책. 마지막 인사를 해야지.

책 분량은 1400페이지다. 열심히 힘빼고 줄이고 줄였는데도 그 정도다.

추천사를 여러 분들이 써주셨는데 다들 고맙다. 영회 추천사가 가장 인상적이다. 한 마디로 하자면 "꿩(로드 존슨)대신 닭(토비)"이라고.

올해의 Spring3.0 개발의 첫 주자는 Arjen Poutsma이다. Arjen은 SWS(SpringWebService)의 리드를 맡고 있고, 스프링의 웹 부분에도 많은 기여를 한 개발자이다.

OXM모듈 추가

R-511에서 Arjen은 스프링의 OXM이라는 새로운 서브모듈을 추가했다. OXM은 Object/XML Mapping의 약자로 원래 SWS에 있던 기능이다. 자바 오브젝트와 XML의 매핑을 지원하는 프레임워크나 라이브러리는 다양하다. OXM의 목적은 그 다양한 Object/XML간의 변환(XML마샬링, XML직렬화라고도 한다)에 대한 추상레이어를 도입해주는 것이다. 스프링의 PSA(Portable Service Abstraction)의 전형적인 케이스라고 볼 수 있다.

현재 OXM이 지원하는 매핑 구현은 JAXB, Castor, XMLBeans, JiBX, XStream이다. 다양한 옵션과 장단점을 가진 이런 구현을 다음의 간단한 스프링이 제공하는 추상 인터페이스로 사용하는 것을 가능하게 해준다.

public interface Marshaller {

    /**
     * Marshals the object graph with the given root into 
     * the provided Result.
     */
    void marshal(Object graph, Result result)
        throws XmlMappingException, IOException;
}

public interface Unmarshaller {

    /**
     * Unmarshals the given provided Source into an object graph.
     */
    Object unmarshal(Source source)
        throws XmlMappingException, IOException;
}

스프링의 DI 개념대로 각각의 실제 구현기술은 설정에서 간단히 교환해서 사용할 수 있다.

 

이런 OXM기능이 Spring에 모듈로 추가된 것은 흥미로운 일이다. 그만큼 OXM의 활용이 보편화되어있다는 증거일 수도 있고, 스프링의 다른 모듈들에서 이 OXM을 활용할 필요가 많이 있다는 증거일 수도 있다.

Core XML Util

OXM모듈 외에 추가된 것은 core모듈의 util항목에 여러가지 XMLReader가 추가된 것이다. StAX 방식의 XMLReader들과 SimpleNamespaceContext개념이 추가되었다.

 

스프링은 그동안 서브프로젝트(또는 포트폴리오 프로젝트)의 기능중 일부를 계속 코어 프레임워크에 추가해왔다. 코어 프레임워크가 비대해지지 않아야 한다는 기본 전제를 가지고 있고, 특별한 도메인이나 상황에서만 적용 가능한 부분은 최대한 별도 프로젝트로 분리해 내는 것이 스프링 개발팀의 정책이다. 그럼에도 코어 프레임워크에 통합되거나 추가되어지는 부분이 있다는 것은 그만큼 보편적으로 많이 사용되어지는 것이라는 뜻이다. 물론 스프링 자체도 옵셔널 모듈을 선택해서 사용할 수 있다는 면에서 그만큼 통합에 대한 부담도 적어진 것일 수도 있다.

예고된 대로 JavaConfig과 SWF의 일부기능도 3.0에 통합될 예정이다.

Spring 3.0 m1이 지난 12월 중순에 발표되었다. Spring3.0 기준으로 책을 쓰고 있는 내 입장에서는 거의 실시간으로 3.0을 분석하고 연구하고 많은 테스트를 해가면서 책을 만들어가야한다. 그래서 앞으로 Spring3.0을 분석하면서 알게된 것들, 가졌던 생각들을 여기에 시리즈로 남겨보려고 한다. 일단 오늘은 그 첫번째로 새로운 프로젝트 구조와 빌드시스템의 변화에 대한 것이다. 스크롤의 압박이 살짝 있으니까 좀 여유있을 때 읽는 것이 좋겠다. (특히 영회! 대충 훑지 말고 꼼꼼히 읽을 것)

 

프레임워크 자체의 기능적인 변화는 2.5에서 도입된 애노테이션 설정을 좀 더 유연하고 폭넓게 사용할 수 있도록 조금 발전한 것 정도 외에 그리 주목할만한 것은 없다. 아마 3.0 전반에 걸쳐서도 2.5의 변화만큼 큰 폭의 변화는 없을 것이다. 그런면에서 메이저버전의 업그레이드 치고는 기대이하이다라고 생각할 사용자들도 있을지 모르겠다. 애노테이션에 EL정도 쓰고, 이미 큰 변화가 있었던 @MVC에 기능이 조금 추가된 것은 대단한 것이 아니라고 생각할 수도 있다. 하지만, 2.5의 새로운 스프링의 설정과 개발모델을 적극적으로 적용하려고 애썼던 개발자라면, 사실 3.0이 주는 그 미묘한 차이들이 "매우" 중요하다고 느낄 것이다. 어쩌면 2.5가 3.0이고 3.0이 3.1이 됐어야 했는지도 모르겠다. 2.5로 남았던 가장 큰 이유는 아마도 변화의 중간단계를 거침으로 해서 이후의 등장할 버전이 주는 변화의 충격 내지는 낯설음을 최소화하고, 또 새로운 시도에 대한 변화에 대해 얼리어답터들의 현장적용후 돌아오는 피드백을 충분히 받아서 그것을 토대로 부족한 부분을 보완하여 어느정도 검증된 안정적인 모습으로 3.x 단계에 안착하기 위함일 것이다.

 

또 한가지 주목할만한 부분은 2.5가 기존의 프로젝트구조와 기반이 되는 자바기술레벨을 최대한 유지한 채로 기능을 중심으로 확장한 것이라면, 3.0은 그부분에서 과감한 변신을 시도한다. 즉, 2.5에서 바뀐 프로그래밍 스타일과 API와 같은 외부 인터페이스는 일정 부분 보완하는 것으로 그치지만, 프레임워크의 내부와 그 자체의 기술적인 특성은 1.0이래로 가장 큰 변화를 주게된다.

3.0은 자바의 언어적인 면에서 가장 큰 변화가 있었던 Java5로 소스코드 자체를 모두 업그레이드 하고 있다. 물론 기존에도 JDK1.5, 1.6의 최신 기능을 필요한 곳에 최대한 적용했다. 컨테이너 기동시 JDK버전을 체크해서 주요한 기능에서 각 JDK에 맞는 최적화된 또는 호환성이 보장되는 JDK API를 사용하도록 되어있었다. 따라서 당연히 스프링 자체가 사용하는 JDK레벨도 1.5+으로 상승될 뿐만 아니라, 그에 따른 언어적인 변화들, 애노테이션이나 제너릭스 같은 부분들이 스프링 자체에서 적극 사용된다. 당연히 API레벨에서도 그동안 불편했던 부분들이 최대한 보완이 될 것이다. 물론 그럼에도 기존버전을 기반으로 작성된 코드가 동작하는데는 아무런 문제가 없도록 API레벨에서는 완벽한 구버전호환성을 제공할 것이다.

보통 이렇게 코드의 전반적인 기술변화가 있을 때는 "새로운 버전은 모든 코드를 맨땅에서 부터 다시 깔끔하게 작성했습니다~"라고 내세우는 오픈소스 프로젝트들이 많이 있다. 사실 그 말은 "기존 코드는 엉망으로 만들어진 스파게티 코드라서 발전시키고 보완하는 것보다 새로 짜는게 났습니다"라는 뜻이다. 동시에 사용자 입장에서는 "이 새버전은 정식버전이 나와봤자 엄청난 버그투성일 것이 분명하니 멋모르고 쓴 멍청한 사용자들이 먼저 고생고생하는 과정을 거치며 몇 단계 더 마이너 업그레이드 되기 전에는 절대 사용하지 말자" 라고 받아들이게 된다. 그래서 나는 메이저 버전 업데이트시 코드를 처음부터 새로 개발하는 프로젝트는 일단 신뢰하지 않는다.

구지 기존 코드를 다 버리고 새로 개발하지 않더라도 오픈소스 제품의 버전 업그레이드는 구버전과의 호환성를 보장하지 못하는 경우가 많다. 사소한 API의 변화 정도라면 어떻게 수용해보겠지만, 프로그래밍 모델이 바뀌고 기존 버전에서 작성한 코드를 그대로 사용할 수 없는 경우엔 매우 불편하고 짜증이 난다. 호환성이 보장 안되는 이유는 프레임워크의 설계 자체가 자꾸 바뀌기 때문이다. 단순하고 빠르게 웹 애플리케이션을 개발할 수 있다는 RoR 같은 경우 이제 2.2버전이 나왔다. 큰 틀이 확 바뀐 것은 아니지만 버전이 올라갈 때마다 기존 버전으로 개발한 코드를 그대로 가져다 쓸 수 없는 호환성의 문제를 계속 가지고 있다. RoR의 최대 장점 중의 하나가 다양한 플러그인 시스템이다. 역시 버전에 따른 플러그인의 적용 가능성도 문제가 된다. 심지어 "플러그인을 사용하려면 그 소스를 모두 분석해볼 각오가 있어야 한다"라는 이야기도 들었다. 끔찍한 얘기다.

바로 그런면에서 스프링의 발전과정은 놀랍다. 특정 영역과 기술에 제한된 프레임워크도 아닌고, 엔터프라이즈 영역의 거의 모든 계층과 모든 기술을 총 망라하고 있고, 한 기술에 대해서도 다양한 계층의 추상 프로그래밍 모델을 지원하는 스프링이 1.0부터 시작해서 지금 3.0에 이르기까지 거의 완벽에 가까운 기존 버전 호환성을 유지하고 있는 것은 정말 대단한 일이 아닐 수가 없다. 스프링의 소스 자체를 깊이 분석해본 사람이라면 그만큼 빠르게 발전하고, 대규모인 소스코드가 그렇게 유연하고 확장성이 뛰어난 형태로 만들어 질 수 있었다는 사실에 놀라지 않을 수가 없을 것이다. 스프링은 이미 1.0부터 그런 확장을 위한 거의 모든 준비가 다 되어있었다. 예를 들어 @Autowired같은 애노테이션을 이용한 DI를 생각해보자. 이 기능을 추가하기 위해서 스프링의 핵심 컨테이너의 구조를 바꾸고 핵심 코드를 뜯어고졌을까? 아니다. 스프링이 그토록 강조하는 DI를 이용해서 컨테이너 자체를 간단히 확장시켰을 뿐이다. Open-Close-Principle을 가장 잘 지켜서 만든 프레임워크가 바로 스프링이 아닐까? 그만큼 스프링은 확장에 대해서 유연하다. 그것은 비단 스프링 자체 개발자 뿐만 아니라, 스프링을 사용하는 일반 개발자도 얼마든지 가능하다. 그래서 그토록 다양한 스프링 기반 또는 연관 프레임워크가 등장할 수 있고, 스프링이 버전이 올라가더라도 기존의 그런 확장기능들은 그대로 적용이 가능하다. 이런 것이 바로 스프링이 지지하고 주장하는 객체지향적인 장점을 살린 애플리케이션 개발이 주는 매력이 아닐까 싶다. 자바와 같은 스태틱타이핑 언어는 OO적이지 못하다고 주장하고, 디자인패턴이란 결국 그런 언어의 한계를 땜빵하기 위한 수단에 불과하다라고 비웃는 최신의 OO언어들에 과연 이만큼의 OO적인 장점을 살린 제품이 존재하기는 할까?

얘기가 좀 샜는데… 아무튼 스프링 3.0은 내부구조의 변화가 크다. Java5+ 이상의 호환코드로 발전한 것은 프레임워크 메인소스만이 아니다. M1에서 다 적용되어있지는 않지만 최신의 소스를 보면 테스트코드도 Java5+에서 사용가능한 JUnit4 방식으로 변환하고 있다. Easymock만 해도 generics을 사용할 수 있다면 그만큼 심플하고 편리하게 쓸 수 있으니까. 아마 3.0 정식버전이 나올즘에는 거의 7천여개에 달하는 거의 모든 테스트 코드도 JUnit4.x로 변환되어 있을 것으로 기대한다. 이왕이면 4.4+에서 지원하는 assertThat을 적용하고, mock은 Mokito를 썼으면 좋겠는데…

 

3.0의 또 다른 큰 변화는 프로젝트 레이아웃의 변화이다. 스프링을 패키징된 jar모듈(OSGi호환 번들이기도 하다)로 받아서 바로 사용하는 사람이라면 별 상관이 없을지도 모르겠지만, 스프링 소스를 관심있게 보는 개발자라면 프로젝트 레이아웃과 빌드시스템의 변화에 주목해 볼 필요가 있다.

스프링은 1.0이래로 Ant를 이용한 단일소스-프로젝트 빌드구조를 유지해왔다. 한때 Maven팀의 유혹도 있었지만(better build with maven 문서에 나온 스프링2.0의 메이븐 프로젝트로 마이그레이션 하는 예제를 생각해보라) 이게 흔들리지 않고 Ant기반의 단일소스-프로젝트 구조를 지켜왔다. 하지만 스프링이 2.0부터 본격적으로 세부모듈로 나뉘어 패키징되기 시작했고, 특히 OSGi번들화 되면서 각 모듈(번들)의 독립성이 중요하게 부각되었다. 게다가 각 스프링 세부 모듈의 3자 라이브러리에 대한 의존관계의 복잡성 때문에(스프링 탓은 아니다) 스프링을 이용해서 새 프로젝트를 구성하는 개발자들이 초반에 많은 삽질을 하게 만들기도 했다.

스프링이 단일 프로젝트(즉, 단일 소스 폴더를 가졌다는 말인데, 사실은 Tiger(Java5+)부분은 독립적이긴하다)이면서 멀티모듈로 손쉽게 패키징을 할 수 있었던 것은 스프링의 소스 레이아웃이 아주 완벽할 정도로 잘 구성이 되어있었기 때문이다. 유겐 할러는 스프링 초창기부터,스프링이 계속 발전하면서 소스 구조가 커지고 모듈화가 필요할 때를 고려해서 패키지 레벨의 순환참조가 전혀 없도록 소스를 잘 유지해왔다. 이전에 소스코드 순환의존관계에 대해서 한번 분석했던 글에서 얘기한 것처럼, 오픈소스 프로젝트 중에서 스프링만큼 완벽하게 순환의존관계(cyclic dependency) 없는 깔끔한 패키지구조와 의존성관리를 해온 제품도 없다.

패키지레벨의 순환의존관계가 없다면 어떤 장점이 있는가? 다양한 장점이 있지만 그 중 대표적인 것은 어떤 소스패키지라도 독립적인 모듈로 분리해내기가 쉽다는 점이다. 이렇게 분리한 모듈은 한 방향으로의 의존관계만 가지므로 꼭 필요한 모듈만을 삽입할 수 있다. 기존에 썼던 글에서 분석해본 바에 따르면 우습게도 세부 옵셔널 서브모듈로 기능을 분리해 놨는데 막상 메인 모듈이 옵셔널 모듈에 의존하고 있어서 결국은 jar만 분리되어있지 무조건 같이 사용해야 하는 경우도 있다. 그 외에도 복잡한 클래스-패키지 의존관계는 객체지향적으로 건전한 설계가 아니고, 따라서 깨지기 쉽고 변화에 취약한 문제 등을 가진다. 아무튼 스프링은 서브패키지를 포함해서 거의 모든 레벨의 패키지에서 상호의존관계가 없으므로 매우 세세하게 재 패키징해서 jar로 구분하고, jar단위의 최소 의존관계만 따라서 꼭 필요한 모듈만 가져다 사용할 수 있다. 작은 단위의 기능 모듈로 구분해서 OSGi번들화 하기에 이만큼 쉬운 프로젝트가 있었을까 싶을 정도다.

따라서 비록 단일 소스트리를 가졌지만 빌드시 세부 모듈로 구분해서 어렵지 않게 패키징을 할 수 있었다. 하지만, 뭔가 자연스럽지 않은 것은 사실이었다. 앞에서 말한 각 세부모듈이 가지는 라이브러리 및 다른 모듈에 대한 의존성도 바로 파악이 안되고 말이다.

결국 3.0에서는 이 프로젝트 빌드시스템과 프로젝트 레이아웃을 드디어 멀티 구조로 변경했다. 멀티 서브 프로젝트를 지원하는 가장 흔하게 사용하는 툴은 Maven이다. Maven을 이용하면 프로젝트 트리 형태로 서브 프로젝트를 구성하고 빌드설정을 상속하게도 할 수 있고, 한번에 손쉽게 전체 빌드를 수행하거나 관리할 수 있다. 하지만, 아직까지는 이클립스와 같은 대표적인 툴에서 지원이 완벽하지 않은데다 모든 빌드 방식을 아직은 완벽하다고 보기 힘든 Maven이라는 툴에 의존시켜야 한다는 단점이 있다. 또, Maven은 OSGi번들과 유사한 jar단위로 편집되고 POM정보를 가지는 작은 모듈들을 Central이라고 불리는 통합 리포지토리에 등록하게 하고 이를 통해서 의존라이브러리를 손쉽게 가져와서 적용할 수 있는 뛰어난 기능이 있다. 스프링이 OSGi를 적극 밀기 시작하면서 필연적으로 OSGi용으로 재 패키징된 다양한 자바 라이브러리들을 지원해줘야 했고, 결국 스프링 자체도 그렇게 리포지토리를 통해서 제공하는 것이 필요했다. 그래서 s3서비스에 스프링 전용 리포지토리를 만들어 스프링의 모듈을 마일스톤 단위까지 제공했다. 또, 스프링 소스의 리포지토리에서 OSGi용으로 패키징된 의존 라이브러리도 제공했고. 그래서 SpringDM/OSGi 프로젝트는 기본적으로 Maven빌드를 채용했다. 개발자들의 취향일 수도 있겠지만, 그보다 OSGi라는 작은 단위의 모듈을 기반으로 한 개발의 특성이 주는 영향이 컸을 것이라고 멋대로 추측해본다. -_-;

결국 스프링도 3.0에서 각 패키징 모듈 단위로 프로젝트를 구분하는 멀티프로젝트 구조로 변신한다. Ant를 이용한 빌드 스크립트 방식은 그대로 유치하고 있다. 하지만 같은 Ant를 이용하지만 기존의 Ant스크립스트를 그대로 사용하는 것이 아니다. 멀티 프로젝트를 기반으로 하는 매우 모듈화된 최신 Ant스타일(스프링3.0빌드는 Ant 1.7이상만 가능하다)이 적용되었다. 또 한가지 큰 변화는 Ivy라는 Ant의 확장기능을 이용한다. Ivy는 Maven과 비슷하게 의존관리가 가능하도록 해줄뿐더러, 리포지토리에서 빌드시 자동 다운로드도 지원한다. 이렇게 스프링3.0은 프로젝트 구조와 레이아웃, 빌드 방식을 완벽하게 바꿨다.

또 Spring3.0은 기존에 사용하던 소스포지의 CVS를 벗어나서 독자적인 SVN 리포지토리를 사용하고 있다. 흥미로운 것은 이 새로운 소스코드 리포지토리에는 Spring3.0과 함께 Spring JavaConfig이 자리잡고 있다. 그리고 이 JavaConfig이 바로 스프링 프로젝트에서 최초로 Ant-Ivy를 이용한 멀티 프로젝트 방식을 택한 것이다. 자세히 소스를 비교해보면 JavaConfig에 작년 초부터 적용했던 새로운 빌드방식과 프로젝트 구조가 거의 그대로 Spring3.0에 적용되어있다. 일단 간단한 프로젝트에 적용해서 검토해보고 스프링으로 이전한 것인지, 아니면 JavaConfig에서 써보니 좋다는 소문이 나서 가져다 사용하기로 한 것인지는 잘 모르겠다. 아무튼 이 JavaConfig 프로젝트를 현재 리드하고 있는 Chris Beams가 바로 Spring3.0의 레이아웃 변경과 Ant-Ivy적용을 거의 대부분 담당하고 있다. SVN 히스토리를 보면 최근에는 유겐 할러보다 이 크리스 빔스가 더 자주 등장하는 것도 그 이유에서 이다. M1에서 대략 프로젝트를 구분하기는 했지만, 그 이후에 계속해서 멀티 프로젝트로 변경한 것을 다듬는 작업을 하고 있다. 특히 testsuite으로 테스트 코드를 몰아놨던 것을 이제 각각 해당 모듈 프로젝트로 테스트 코드를 옮기고 JUnit 4.x대로 변경하는 작업이 작년 말에 마지막으로 한창 진행되었던 것이다.

 

과연 이렇게 멀티 프로젝트로 변경한 것이 좋은 선택일까? 일단은 그렇다고 봐야겠다. 개념적으로 독립된 번들을 지향하는 OSGi컨셉을 따라서 스프링도 재구성을 했으니, 당연히 프로젝트/번들이 맞다. 또한 모듈간 의존관계관리나, 라이브러리 의존성의 체크 등이 그런면에서 손쉬워진 것도 사실이다. 그럼 Ant-Ivy의 선택은 Maven으로 변환보다 나은 선택일까? 이건 개발자들이 어떤 방식에 익숙한가에 달린 것이지만 안정적인 빌드방식을 계속 유지하기 위해서는 잘한 선택이라고 본다. Maven도 플러그인을 만들어서 확장하는 것이 충분히 손쉽게 가능하지만, 여전히 불안한 듯한 Maven의 기본 플러그인 구조나 여러가지 꾸준히 지적된 2%쯤 부족한 기능과 완성도 등을 생각할 때, 세밀한 빌드제어를 위해서 모듈화된 Ant빌드를 사용하는 것이 더 나았으리라고 본다. Ivy덕분에 리포지토리도 손쉽게 사용할 수 있으니 말이다. 당연히 빌드도 한방에 손쉽게 할 수 있으니 CI에도 문제는 없다.

 

다음은 내 입장에서 본 3.0의 변화이다. 스프링 소스를 분석하고, 항상 최신의 소스를 가지고 스프링을 살펴보기를 좋아하는 나에게는 이번 변화가 사실은 좀 부담이다.

새 소스를 SVN에서 받아서 이클립스에 임포트 해보자. M1이후에 계속 이 시도를 하고 있는데 몇가지 불편한 점이 있다. 일단 이클립스는 Ivy설정등을 제대로 지원하지 못한다. Ivy플러그인이 있지만 설치하면 스프링 프로젝트는 몽땅 다 Ivy에러가 떠버린다. 아직은 성숙하지 않은 플러그인인듯 하다. 물론 Ant스크립트를 그냥 실행시키면 아무 문제는 없다. 하지만 당장 Ivy리포지토리에서 라이브러리를 참조해야 하는데, 그것부터 에러가 난다. 결국 Ant실행 후, Ivy리포지토리를 이클립스 빌드에 추가해줘야 한다. 최신의 Maven 플러그인에서 해주는 한방 Import가 부럽기도 하다.

두번째 문제는 멀티 프로젝트이다. 각각의 프로젝트를 매번 import 기능으로 가져오는 것도 지루한 작업인데다, 프로젝트가 많아질 수록 이클립스의 빌드시간과 성능은 대폭 떨어진다. 그래서 소스의 변화가 있을 경우 이를 가져오고 새로 빌드할 때 시간도 많이 걸릴 뿐더러, 종종 빌드에러가 난다. 요즘에 빌드에러가 나는 이유는 리포지토리에 있는 최신 trunk의 이클립스 프로젝트 설정이 잘못되어있는 경우가 있기 때문이다. 예를 들어 이클립스 설정 calsspath에는 aspectj 1.5로 세팅이 되어있는데, 막상 Ivy dependency항목에 보면 1.6이다. 아마도 Ant-Ivy의 빌드를 계속 재구성하고 다듬기만 하고 이클립스에서 순수한 개발작업을 하면서 이클립스 프로젝트 설정을 바로 반영해주지 못한 탓인 듯 하다. 그렇다고 Ivy->Eclipse 플러그인이 있어서 한방에 이를 반영할 수 있는 것도 아니고, 제대로 빌드를 성공하려고만 해도 한참을 프로젝트를 잡고 씨름해야 한다. 멀티 프로젝트이기 때문에 불편한 점은 점차로 개선되긴 하겠지만, 그래도 가끔은 옛날 단일 프로젝트 구조가 그립기도 하다. 참.. 내가 제일 싫어하는 src-main-java 스타일의 폴더구조도 적용되어있다. Maven도 아니면서 왜 이런 만행을!!

이클립스에서 빌드하려는 목적이 아니라면 단순히 Ant 스크립트를 실행해서 한방에 빌드할 수 있다. 조건은 JDK1.6+, ANT1.7+이면 된다. 단, ANT_OPTS에서 최소한 512m이상 메모리와 128m이상 MaxPermSize가 설정되어있어야만 한다. 아니면 빌드중 메모리 에러가 날 것이다.

마지막으로 한가지 아쉬운 것은 예제가 현재 달랑 하나만 있다. Pet Clinic인데.. 다른 다양한 예제들을 더 보강되었으면 좋을 것 같다. 또 한가지는 레퍼런스 문서의 부재이다. M1을 받아본 사람들은 알겠지만 API문서만 있고 레버런스 문서는 아직 없다!! 예제 코드를 가지고 대략 추정해서 새로운 기능을 살펴봐야 한다. 언제쯤 새로운 레퍼런스 문서를 제공하려는지… 현재 가장 아쉬운 부분이다.

 

아무튼 이런 큰 스프링의 내부구조의 변화가 3.0의 화려한 변신과 안정적인 프로젝트 관리에 모두 좋은 영향을 주기를 기대한다. 3.0의 정식 발표 계획은 4월이다. 이번 봄에는 새로운 스프링의 선물을 받을 수 있으려나..(스프링 책도…..)

© 2017 Toby's Epril Suffusion theme by Sayontan Sinha