http://www.theserverside.com/articles/article.tss?l=SpringFramework

<div class="note"> 이 글은 Spring 1.0M1버전을 기준으로 작성이 되어있습니다. 현재 1.1.5버전이므로 이글에서 문제시되거나 작자가 추후에 구현이 될것이라고 밝힌 많은 기능들이 이미 구현이 되어있는 상태입니다. 이 점 유의하시고 문서를 보시기 바랍니다.

1.2버전용 소개문서는 다음의 링크에서 보실수 있습니다. </div>

Spring 1.2버전 기준의 문서

SpringFramework.jpg

당신은 여름에 Spring프레임워크에 대해서 귀가 따갑도록 들었을것이다. 이 글에서 나는 Spring으로 무엇을 할 수 있는지와 이것이 J2EE애플리케이션 개발을 위해 당신을 어떻게 도울수 있을지에 대해서 설명할 것이다.

아직도 다른 프레임워크를 사용하는가.?#

당신은 "다른 프레임워크 없이" 라는 생각을 할지도 모른다. 당신은 왜 이 글을 읽거나 Spring프레임워크를 다운로드 받기를 꺼리는가.? 언제 오픈소스이고 당신이 선호하는 많은 J2EE프레임워크가 언제 있었는가.?

나는 여러가지 이유로 Spring이 유일하다고 믿는다.

  • 이것은 다른 많은 인기있는 프레임워크와 다른 위치를 차지한다. Spring은 당신의 비지니스 객체를 관리하는데 촛점을 두고 있다.
  • Spring은 포괄적이고 모듈적이다. Spring은 계층화된 구조를 가지고 있다. 이것은 당신이 격리되는 각각의 부분을 따로 사용할수 있다는 것을 의미한다. 물론 이것의 구조는 내부적으로 일관적이다. 그래서 당신은 학습으로 부터 최대값을 얻는다. 예들 들면 당신은 단순히 JDBC를 간단히 사용하기 위해서 spring을 선택하거나 당신의 모든 비지니스 객체를 관리하기 위해서 Spring을 사용하지도 모른다.
  • 이것은 당신이 테스트를 쉽게 하는 코드를 작성하도록 돕기 위해 디자인되었다. Spring은 테스트 지향 프로젝트에 이상적인 프레임워크다.

Spring은 당신의 프로젝트를 위해 한개 이상의 프레임워크 의존성을 필요로 하지 않는다.

비록 이것이 2003년 2월 이후로 오픈소스 프로젝트가 되었지만 Spring은 긴 기간동안 지속되었다. 늦은 2002년 Expert One-on-One J2EE Design and Development 이라는 이름의 책에서 배포된 내부구조 코드로부터 시작된 오픈소스 프로젝트이다. Expert One-on-One J2EE 는 또한 Spring의 기본적인 구조에 대한 나의 생각을 정리해놓았다. 어쨌든 이른 2000년으로 돌아가 구조적인 개념은 성공적인 상업적 프로젝트들을 위해 내부구조를 개발한 나의 경험을 반영했다.

2003년 1월 이후 Spring은 소스포지(SourceForge)에서 호스팅 되었다. 지금은 10명의 개발자로 이루어져 있고 이중에 6명은 굉장히 활발하게 활동중이다.

Spring의 구조적인 이익#

우리가 명시를 하기 이전에 Spring으로 프로젝트에서 가져올수 있는 몇가지 이익을 보자.

  • Spring은 당신이 EJB를 사용하든지 말든지 간에 당신의 미들티어 객체와 효과적으로 조합될수 있다. Spring은 특정 J2EE API를 연동하기 위해서 Struts또는 다른 프레임워크를 사용한다면 당신을 도와줄것이다.
  • Spring은 많은 프로젝트에서 보여지는 싱글톤의 급격한 증가를 제거한다. 내 경험에 비추어 보면 이것은 테스트가능성과 객체 지향의 개념을 감소시키는 커다란 문제이다.
  • Spring은 애플리케이션과 프로젝트 도처에 일관적인 방법으로 설정을 다룸으로써 다양한 사용자정의 프라퍼티 파일 형태 사용의 필요성을 제거한다. Spring을 사용하면 당신은 간단하게 클래스의 자바빈즈 프라퍼티를 찾을수 있다. 밑에서 설명되는 Inversion of Control의 사용은 이것을 간단하게 달성한다.
  • Spring은 클래스보다는 인터페이스의 사용으로 프로그래밍의 가격을 줄여서 좀더 좋은 프래그래밍 코드를 만들수 있다.
  • Spring은 가능한한 API에 적게 의존하도록 애플리케이션을 디자인한다. Spring 애플리케이션내의 대부분의 비지니스 객체는 Spring에 의존성을 가지지 않는다.
  • Spring을 사용하여 빌드된 애플리케이션은 단위테스트를 수행하기에 굉장히 쉽다.
  • Spring은 애플리케이션 구조를 한정하는것보다 구현 선택으로 EJB를 사용할수 있게 한다. 당신은 영향을 끼치는 호출코드없이 POJO나 로컬 EJB처럼 비지니스 인터페이스를 구현하도록 선택할수 있다.
  • Spring은 EJB사용 없이도 많은 문제를 해결하도록 도와준다. Spring은 많은 웹애플리케이션을 위해 선호되는 EJB의 대안되는 것을 제공할수 있다. 예를 들면 Spring은 당신이 하나의 데이터베이스를 가지고 작업을 수행할 필요가 있다면 JTA구현물이 없더라도 EJB컨테이너의 사용없이 선언적인 트랜잭션 관리를 달성하기 위해서 AOP를 사용할수 있다.
  • Spring은 JDBC나 Hibernate와 같은 O/R맵퍼를 사용하거나 사용하지 않더라도 데이터접근을 위한 일관적인 프레임워크를 제공한다.

Spring은 당신의 문제를 위해 가장 간단하고 가능한 해결책이 가능하도록 해준다.

Spring은 무엇을 하는가.?#

Spring은 많은 기능을 제공한다. 그래서 나는 순서대로 중요한 부분을 빨리 리뷰하도록 할 것이다.

Mission statement

첫번째로 Spring의 범위에 대해서 알아보자. 비록 Spring이 많은 부분을 담당한다고 하더라도, 우리는 이것이 위치하고 위치하지 않는 것에 대해서 분명한 것을 제시할것이다.

Spring의 가장 중요한 목표는 J2EE를 좀더 사용하기 쉽도록 하고 좋은 프로그래밍 형태를 촉진하는 것이다.

Spring은 외형적인 어떤것을 고치지 않는다. 게다가 당신은 Spring내에서 로깅, 커넥션 풀링, 분산 트랜잭션 코디네이터 패키지를 찾을수 없을것이다. 이런 모든것은 오픈소스 프로젝트(Commons Logging나 Commons DBCP와 같은)나 당신의 애플리케이션 서버에 의해서 제공된다. 이러한 이유로 우리는 O/R 맵핑 레이어를 제공하지 않는다. 여기엔 Hibernate와 JDO와 같은 좋은 솔루션이 있다.

Spring은 존재하고 있는 기술들을 좀더 사용하기 쉽도록 하는데에도 목적이 있다. 예를 들면 비록 우리가 하위 레벨 트랜잭션 코디네이터의 비지니스내에서가 아니더라도 우리는 JTA나 다른 트랜잭션 전략위의 추상적 레이어를 제공한다.

Spring은 우리가 어떤 새로운것을 제공할수 있다는 느낌을 받지 않는다면 다른 오픈소스 프로젝트와 직접적으로 경쟁하지 않는다. 예를 들면 많은 개발자들처럼 우리는 Struts와 결코 행복하지 않고 MVC웹 프레임워크내 진보된 형태의 패지키을 가진다. 가벼운 IoC컨테이너와 AOP프레임워크같은 몇가지 점에서 Spring은 직접적인 경쟁을 가지지만 좀더 보편화된 솔루션은 없다.

Spring은 내부적인 일관성으로 부터 이익을 가진다. 모든 개발자는 같은 점에서 노래를 부른다. 우리는 Inversion of Control같은 몇몇 중심이 되는 개념을 사용할수 있다.

Spring은 애플리케이션 서버 사이에 이식가능하다. 물론 이식가능은 언제나 변한다. 하지만 우리는 플랫폼에 종속적이거나 표준이 아닌것 그리고 WebLogic, Tomcat, Resin, JBoss, WebSphere 그리고 다른 애플리케이션 서버에서 사용자를 지원하는 부분은 피한다.

Inversion of control 컨테이너

Spring디자인의 핵심은 자바빈즈와 함께 작동하도록 디자인된 org.springframework.beans패키지이다. 이 패키지는 사용자에 의해서 직접적으로 사용되지는 않는다. 하지만 다른 수많은 기능을 지원하는 것처럼 제공한다.

추상화의 다음으로 높은 레이어는 "Bean Factory"이다. Spring bean factory은 객체가 이름에 의해서 가져올수 있고 객체들 사이에 관계를 관리할수 있는 일반적인 factory이다.

bean factory은 두가지의 객체 모델을 제공한다.

  • "싱클톤" : 이 경우에는 특정 이름으로 룩업을 통해 가져올수 있는 객체의 공유 인스턴스가 하나 있다. 이것은 디폴트이다 그리고 매우 자주 사용된다. 이것은 비상태유지 서비스 객체들에게 이상적이다.
  • "프로토타입" : 이 경우에는 각각의 검색이 독립적인 객체의 생성한다. 예를 들면 이것은 그들 자신의 객체를 가지기 위해 각각의 사용자에게 허락될수 있다.

org.springframework.beans.factory.BeanFactory은 간단한 인터페이스이기 때문에 이것은 저장소 메소드를 참조하는 범위를 위해 구현될수 있다. 비록 몇몇 사용자가 필요한것을 찾더라도 당신은 당신 자신의 것을 쉽게 구현할수 있다. 대부분 공통적으로 사용되는 BeanFactory정의는

  • XmlBeanFactory. 이것의 파서는 간단하다. 직관적인 XML구조는 명명된 객체의 클래스와 프라퍼티를 정의한다. 우리는 좀더 쉽게 사용하도록 DTD를 제공한다.
  • ListableBeanFactoryImpl: 이것은 프라퍼티 파일내에 bean 정의들을 파싱하고 프로그램적으로 BeanFactory들을 생성하기 위한 능력을 제공한다.

각각의 bean 정의는 POJO(클래스명과 자바빈즈 초기화 프라퍼티에 의해 정의되는)나 FactoryBean이 될수 있다. FactoryBean인터페이스는 우회의 단계를 추가한다. 전형적으로 이것은 AOP나 다른 접근법을 사용해서 프록시 객체를 생성하는데 사용된다. 예를 들면 선언적인 트랜잭션 관리를 추가하는 프록시(이것은 개념적으로 EJB인터셉터와 유사하지만 실제로는 좀더 간단하다).

BeanFactory는 계층적으로 관계한다. 이것은 전체 애플리케이션을 통해 컨트롤러 서블릿과 같은 개별적인 자원들이 그들 자신만의 독립적인 객체집합을 가지는 동안 공통적인 설정의 공유를 가능하게 한다.

자바빈즈의 사용을 위한 동기는 무료 PDF처럼 ServerSide(/articles/article.tss?l=RodJohnsonInterview)에서 유효한 Expert One-on-One J2EE Design and Development의 4장에서 설명이 된다.

BeanFactory개념을 통해 Spring은 Inversion of Control컨테이너이다.(EJB컨테이너와 같은 무거운 컨테이너를 좋아하지 않는다. Spring BeanFactory는 한줄의 코드로 생성될수 있는 컨테이너이고 어떠한 특정 배치단계를 요구하지 않는다.)

Inversion of Control의 개념은 종종 할리우드 원리(나를 호출하지 말라. 내가 당신을 호출할것이다.)내에서 종종 표현된다. IoC는 프레임워크내부로 어떤것이 생성되도록 책임을 넘기고 애플리케이션 코드내에서 빠져나온다. EJB와 같은 전통적인 컨테이너 구조내에서 컴포넌트는 컨테이너에게 "객체 X가 있는 곳, 내가 나의 작업을 수행할 필요가 있는 것"이라고 말한다. IoC는 컨테이너에게 객체 X가 필요할때 런타임시 그것을 제공한다. 컨테이너는 메소드 시그너처에 기반해서 이 것을 생성하지 않고 가능한한 XML과 같은 설정 데이터를 사용한다.

IoC는 다양하고 중요한 이익을 가진다. 예를 들면

  • 컴포넌트는 런타임시 협력자(collaborators)를 룩업할 필요가 없다. 그들은 쓰고 관리하는데 좀더 간단하다. Spring의 IoC버전내에서 컴포넌트는 자바빈즈 setter메소드를 표시함으로써 다른 컴포넌트의 의존성을 표현한다. EJB와 같은 부분은 개발자가 코드를 써야만 하는 JNDI룩업일것이다.
  • 같은 이유로 애플리케이션 코드는 테스트하기가 좀더 쉽다. 자바빈즈 프라퍼티는 간단하다. 객체를 생성하고 관련 프라퍼티를 셋팅하는 스스로 포함된 JUnit테스트 메소드를 쓴다.
  • 좋은 IoC구현은 강력한 타이핑을 유지한다. 만약 당신이 협력자(collaborators)를 룩업하기 위한 일반적인 factory을 사용할 필요가 있다면 당신은 기대하는 타입으로 결과를 변환할 것이다. 이것은 중요한 문제가 아니다. 하지만 이것은 세련되지 않았다. IoC와 함께 당신은 당신의 코드에 강력하게 쓰여진 의존성을 표시하고 프레임워크는 타입변환을 책임질수 있다. 이것은 프레임워크가 애플리케이션을 설정할때 에러처럼 나타날수 있는 타입 미스매치(mismatch)를 의미한다.
  • 대부분의 비지니스 객체는 IoC컨테이너 API에 의존하지 않는다. 이것은 기존 코드를 사용하기 쉽도록 만들고 IoC컨테이너 내부나 외부의 객체를 사용하기 쉽도록 만든다. 예를 들면 Spring사용자는 종종 Spring bean처럼 Jakarta Commons DBCP데이터소스를 설정한다. 여기엔 이것을 하기 위해서 사용자정의 코드를 쓸 필요가 없다. 우리는 IoC컨테이너가 침략적(이것을 사용해서 API에 의존적인 것을 당신의 코드에 넣는)이지 않다고 말한다. 어떠한 자바빈즈도 Spring bean factory내에서 컨포넌트가 될수 있다.

이 마지막 이익은 강조할만하다. IoC는 컨테이너의 애플리케이션 코드의 의존성 최소화라는 점에서 EJB와 같은 전통적인 컨테이너 구조와는 다르다. 이것은 당신의 비지니스 객체가 다른 IoC프레임워크내에서 또는 어떤 프레임워크 밖에서 어떠한 코드변경없이 잠재적으로 실행가능하다는것을 의미한다.

나의 경험과 Spring사용자의 경험에서 이것은 IoC가 애플리케이션 코드로 가져다 주는 이익을 지나치게 강조하는것은 힘들다.

IoC는 J2EE커뮤니티에서 최초의 일이지만 새로운 개념이 아니다. 대안적인 IoC컨테이너(Apache Avalon, PicoContainer 그리고 HiveMind)가 있다. Avalon은 비록 강력하고 오랜 역사를 가지고 있지만 결코 인기를 얻지는 못할것이다. Avalon은 무겁고 복잡하다. 새로운 IoC솔루션보다 좀더 침략적인 것으로 보인다. PicoContainer는 가볍고 자바빈즈 프라퍼티보다 생성자를 통해 의존성의 표현을 강조한다. Spring과는 다르게 이것은 각각의 타입 객체 하나만의 정의를 허락하도록 디자인 되었다. Spring과 PicoContainer 그리고 다른 IoC프레임워크와의 비교를 위해서 Spring웹사이트의 "The Spring Framework - A Lightweight Container"기사(http://www.springframework.org/docs/lightweight_container.html)를 읽어보라. 이 페이지는 PicoContainer웹사이트로의 링크를 포함한다.

Spring BeanFactory는 매우 가볍다. 사용자는 단독으로 작동하는 스윙 애플리케이션만큼 애플릿내에서 그것들을 성공적으로 사용한다. 특별한 배치단계와 발견될만한 시작시간이 없다. 컨테이너를 애플리케이션의 어느 티어로 인스턴스화 할수 있도록 구체화하는 능력은 매우 가치있다.

Spring BeanFactory개념은 Spring도처에서 사용된다. Spring이 내부적으로 일관적일수 있는 핵심적인 이유이다. Spring은 또한 전체 기능의 프레임워크 도처에 기본적인 개념으로 IoC를 사용하는 면에서 IoC컨테이너 사이에서는 유일하다.

애플리케이션 개발자를 위해 가장 중요한 것은 하나 이상의 BeanFactory가 비지니스 객체의 잘 정의된 레이어를 제공하는 것이다. 이것은 유사하지만 local세션빈즈의 레이어보다는 훨씬 더 간단하다. EJB와는 다르게 레이어내의 객체는 서로 밀접한 관계일수 있고 그들의 관계는 자신의 factory에 의해서 관리된다. 비지니스 객체의 잘 정의된 레이어는 성공적인 구조를 위해서 매우 중요하다.

Spring ApplicationContext는 다음의 사항들을 지원하는 BeanFactory의 하위 인터페이스이다.

  • 메시지 룩업, 국제화 지원
  • 이벤트 기법, 이벤트를 통지하기 위해 표시되거나 등록하는 애플리케이션 객체를 허락한다.
  • 이식가능한 파일과 자원 접근

XmlBeanFactory 예제

Spring사용자는 대개 XML "bean definition"파일내에 그들의 애플리케이션을 설정한다. Spring XML bean 정의 문서의 가장 상위는 <bean> 요소이다. <bean> 요소는 하나 이상의 <bean>정의를 포함한다. 우리는 대개 각각의 bean정의의 클래스와 프라퍼티를 명시한다. 우리는 우리의 코드내에 이 bean을 사용할 이름이 될 id를 명시해야만 한다.

J2EE애플리케이션내에서 공통적으로 보여지는 관계를 가진 3개의 애플리케이션 객체를 설정한 간단한 예제를 보자.

  • J2EE 데이터소스
  • 데이터소스를 사용하는 DAO
  • 이 작업의 과정내의 DAO를 사용하는 비지니스 객체

다음의 예제에서 우리는 Jakarta Commons DBCP프로젝트로 부터 BasicDataSource를 사용한다. 이 클래스는 Spring bean factory내에서 자바빈 스타일의 설정을 제공하는 것처럼 쉽게 사용될수 있다. shutdown이 호출될 필요가 있는 close메소드는 어떤 Spring인터페이스를 구현하기 위한 BasicDataSource의 필요성을 피하기 위해 spring의 "destroy-method"속성을 통해 등록될수 있다.

역자주 : Spring XML설정파일에서 destroy-method의 값을 대체하는 방법은 DisposableBean인터페이스를 구현해서 사용하면 된다. Spring의 개념에 좀더 들어가면 나오겠지만 destroy-method에 반대되는 값은 init-method이고 이 값은 InitializingBean인터페이스를 구현해서 사용하면 된다.

<beans>

  <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName"><value>com.mysql.jdbc.Driver</value></property>
    <property name="url"><value>jdbc:mysql://localhost:3306/mydb</value></property>
    <property name="username"><value>root</value></property>
  </bean>

BasicDataSource의 모든 프라퍼티는 문자열이다. 그래서 우리는 <value>요소와 함께 그 값을 명시한다. Spring은 문자열표현을 필요한 다른 어떠한 타입으로 변환하기 위한 표준적인 자바빈즈 PropertyEditor기법을 사용한다.

지금 우리는 데이터소스에 대한 bean 참조를 가지는 DAO를 정의한다. bean들 사이의 관계는 <ref>요소를 사용해서 명시한다.

  <bean id="exampleDataAccessObject" class="example.ExampleDataAccessObject">
    <property name="dataSource"><ref bean="myDataSource"/></property>
  </bean>

비지니스 객체는 DAO에 대한 참조와 int형 프라퍼티를 가진다.

<bean id="exampleBusinessObject" class="example.ExampleBusinessObject">
    <property name="dataAccessObject"><ref bean="exampleDataAccessObject"/></property>
    <property name="exampleParam"><value>10</value></property>
  </bean>
</beans>

객체간의 관계는 예제처럼 설정내에 명시적으로 셋팅한다. 우리는 좋은 것이 되기 위해 이것을 고려한다. 어쨌든 Spring은 bean사이에 의존성을 발견하는 곳에 우리가 "autowire" 지원이라고 부르는것을 제공한다. PicoContainer과 같은 이 제한은 만약 특정 타입의 다중 bean들이 있다면 해석되어야만 하는 타입의 인스턴스 의존성을 해결하는것은 불가능하다. 긍정적인 면에서 만족하지 않는 의존성은 factory가 초기화될때 잡힐수 있다.(Spring은 명시적 설정을 위해 옵션적인 의존성 체크를 제공한다.)

우리는 위 예제에서 설명된 autowire기능을 사용할수 있다. 만약 우리가 명시적인 관계를 작성하길 원하지 않는다면.

<bean id="exampleBusinessObject" class="example.ExampleBusinessObject" autowire="byType">
    <property name="exampleParam"><value>10</value></property>
 </bean>

이 사용법으로 Spring은 exampleBusinessObject의 데이터소스 프라퍼티가 현재의 BeanFactory내에 발견되는 데이터소스의 구현이 되기 위해 셋팅되어야만 하는것이라는것을 해결할것이다. 만약에 현재의 BeanFactory내에 요구되는 타입의 bean이 없거나 하나보다 많다면 에러이다. 우리는 여전히 레퍼런스가 아닌것처럼 exampleParam프라퍼티를 셋팅할 필요가 있다.

Autowire지원과 의존성 체크는 CVS버전에서는 제공되는 기능이고 Spring 1.0M2에서 유효하다. 모든 다른 기능은 현재의 1.0M1버전내에서 이 글을 설명한다.

역자주 : 2005년 5월 13일 현재 최신버전은 1.1.5이고 Autowire와 의존성 체크는 모두 지원이 된다.

자바코드로부터 관계를 구체화하는것은 그것을 하드코딩하는것보다 막대한 이익을 가진다. 이것은 자바코드내 어떤 라인의 변경도 없이 XML파일을 변경하는것으로 가능하다. 예를 들면 우리는 대안적인 커넥션풀이나 테스트 데이터소스를 사용하기 위해 다른 bean클래스를 참조하기 위한 myDataSource bean정의를 간단히 변경할수 있다. 우리는 대안적인 하나의 XML내에서 애플리케이션 서버로부터 데이터소스를 얻기 위해 Spring JNDI위치의 FactoryBean을 사용할수 있다.

지금 비지니스 객체의 예제를 위한 자바코드를 보자. 밑의 코드리스트에서 Spring의존적인것들은 없다. EJB컨테이너와는 달리 Spring BeanFactory는 침략적이지 않다. 당신은 애플리케이션 객체로 코드를 인식하도록 할 필요는 없다.

public class ExampleBusinessObject implements MyBusinessObject {

  private ExampleDataAccessObject dao;
  private int exampleParam;

  public void setDataAccessObject(ExampleDataAccessObject dao) {
    this.dao = dao;
  }

  public void setExampleParam(int exampleParam) {

    this.exampleParam = exampleParam;
  }

  public void myBusinessMethod() {
    // do stuff using dao
  }
}

프라퍼티 setter는 bean정의 문서내에서 XML참조에 부합된 필요는 없다는 것을 알라. 객체가 사용되기 전에 Spring에 의해 호출된다.

그러한 애플리케이션 bean은 Spring에 의존할 필요가 없다. 그들은 어떠한 Spring인터페이스를 구현하거나 Spring클래스를 확장할 필요가 없다. 그들은 단지 자바빈즈 명명 형태를 관찰할 필요는 있다. Spring애플리케이션 컨텍스트 밖에서 재사용은 쉽다. 디폴트 생성자를 사용해서 그것을 인스턴스화하고 그것의 프라퍼티를 setDataSource() 와 setExampleParam() 호출을 통해 수동으로 셋팅한다. 당신이 인자없는 생성자를 가진만큼 당신은 코드내에 한줄로 프로그램적인 생성작업을 지원하길 원한다면 다중 프라퍼티를 가져오는 다른 생성자를 정의하는데 자유롭다.

비지니스 인터페이스 호출자의 정의되지 않은 자바빈즈 프라퍼티는 함께 작동할것이다. 그들은 상세하게 구현한다. 우리는 연결 객체나 호출 코드에 영향없이 다른 bean프라퍼티를 가지는 구현클래스와 "플러그인" 할수 있다.

물론 Spring XML bean factory는 여기서 서술된 것보다 더 많은 능력을 가지고 있다. 하지만 이것은 당신에게 기본적인 접근법에 대한 것만 제시할것이다. 간단한 프라퍼티들과 당신이 자바빈즈 PropertyEditor를 가지기 위한 프라퍼티만큼 Spring은 자동적으로 리스트, 맵 그리고 java.util.Properties를 다룰것이다.

bean factory와 애플리케이션 컨텍스트는 언제나 J2EE서버에 의해 정의된 범위내에 속한다.

  • 서블릿 컨텍스트. Spring MVC프레임워크내에서 애플리케이션 컨텍스트는 공통객체를 포함하는 각각의 웹애플리케이션을 위해 정의된다. Spring은 Spring MVC프레임워크에 의존하는 것없이 리스너나 서블릿을 통해 그러한 컨텍스트를 인스턴스화하는 기능을 제공한다. 그래서 이것은 Struts, WebWork 또는 다른 웹프레임워크내에서도 사용이 될수 있다.
  • 서블릿 : Spring MVC프레임워크내의 각각의 컨트롤러 서블릿은 가장 상위 애플리케이션에서 파생된 자신만의 애플리케이션 컨텍스트를 가진다. 이것은 또한 Struts나 다른 MVC프레임워크와 함께 완성하기가 쉽다.
  • EJB : Spring은 EJB jar파일내의 XML문서로 부터 로드된 BeanFactory를 제공하고 EJB제작을 간단하게 하는 EJB를 위한 편리한 슈퍼클래스를 제공한다.

J2EE스펙에 의해 제공되는 사항은 일반적으로 bean factory를 시작하기 위해 싱글톤을 사용할 필요를 제거한다.

어쨌든 이것은 우리가 바란다면 BeanFactory를 프로그램으로 인스턴스화하는것은 자명하다. 예를 들면 우리는 다음 3줄의 코드로 위에서 정의된 bean factory를 생성할수 있고 비지니스 객체에 대한 참조를 얻을수 있다.

InputStream is = getClass().getResourceAsStream("myFile.xml");
XmlBeanFactory bf = new XmlBeanFactory(is);
MyBusinessObject mbo = (MyBusinessObjectbf.getBean("exampleBusinessObject");

이 코드는 애플리케이션 서버 밖에서 작동할것이다. 이것은 Spring IoC컨테이너가 순수 자바인것처럼 J2EE에 의존하지 않는다.

JDBC추상화 그리고 데이터접근 예외 구조

데이터접근은 Spring을 부각시키는데 다른 영역이다.

JDBC는 데이터베이스 참조로 부터 상당히 좋은 추상화를 제공한다. 하지만 사용하기 위해서 지겨운 API이다. 몇몇 문제는 다음과 같다.

  • ResultSets, Statements 그리고 (가장 중요한)Connections 보장하기 위해 장황한 에러 핸들링이 사용 후에 닫힌다. 이것은 JDBC의 정확한 사용이 많은 코드내에서 빨리 결과를 생성한다. 이것은 또한 에러들의 공통적인 원인이다. Connection누설은 애플리케이션을 빨리 다운시켜버릴수 있다.
  • 비교적 정보의 가치가 없는 SQLException. JDBC는 예외구조를 제공하지 않지만 모든 에러에 대해 응답으로 SQLException을 던진다. 정말로 나쁘게 되는것을 찾는건 예를 들면 SQLState와 에러코드를 포함하는 데드락이나 적합하지 않은 SQL의 문제인가.? 그러한 값들의 의미는 데이터베이스마다 다양하다.

Spring은 두가지 방법으로 이러한 문제를 담당한다.

  • API를 제공함으로써 지루하고 에러를 발생시키는 경향이 있는 예외 핸들링을 애플리케이션 코드에서 프레임워크로 이동시킨다. 프레임워크는 모든 예외 핸들링을 처리한다. 애플리케이션 코드는 SQL문과 결과를 추출하는데 집중을 할수 있다.
  • 당신의 애플리케이션 코드를 위한 의미있는 예외 구조를 제공함으로써 SQLException을 대신하여 작동한다. Spring이 데이터베이스로부터 Connection을 얻었을 때 이것은 데이터베이스를 확인하기 위해 메터데이터를 시험한다. 이것은 org.springframework.dao.DataAccessException으로 부터 유래된 자신만의 구조로 SQLException을 올바른 예외로 맵핑시키기 위해 이러한 정보를 사용한다. 게다가 당신의 코드는 의미있는 예외와 함께 작동을 할수 있고 선호하는 SQLState나 에러코드에 대해서 걱정할 필요가 없다. Spring의 데이터접근 예외는 JDBC에 특성화되어있지 않다. 그래서 당신의 DAO들은 그들이 던질 예외들 때문에 JDBC에 묶일 필요가 없다.

Spring은 두가지 레벨의 JDBC API를 제공한다. 첫번째는 org.springframework.jdbc.core패키지에 있고 애플리케이션 코드에서 부터 프레임워크내부로 컨트롤을 이동시키기 위해 콜백을 사용하고 에러핸들링과 connection취득 그리고 릴리즈를 한다. 이것은 Inversion of Control의 다른 타입이지만 설정 관리를 위해 사용될수 있는 동등한 가치를 가진다.

Spring은 JDO(PersistenceManager를 획득하고 버리는), JTA를 이용한 트랜잭션 관리 그리고 JNDI처럼 자원을 획득하고 없애는 특별한 단계를 포함한 다양한 API를 할당하기 위해 유사한 콜백 접근법을 사용한다. 콜백과 같은 것을 수행하는 Spring 클래스는 템플릿들을 호출한다.

예를 들면, Spring JdbcTemplate객체는 SQL쿼리를 수행하고 다음처럼 리스트로 결과를 저장하기 위해 사용될수 있다.

JdbcTemplate template = new JdbcTemplate(dataSource);
final List names = new LinkedList();
template.query("SELECT USER.NAME FROM USER",
  new RowCallbackHandler() {
    public void processRow(ResultSet rsthrows SQLException {
      names.add(rs.getString(1));
    }
  });

콜백내 애플리케이션 코드는 SQLException을 던지는데 자유롭다. Spring은 어떤 예외를 잡을것이고 자신의 구조내에서 그것들을 다시 던진다. 애플리케이션 개발자는 예외를 선택할수 있다.

JdbcTemplate은 prepared statement와 배치형태의 업데이트를 포함한 다른 시나리오를 지원하기 위해 많은 메소드를 제공한다. Spring JDBC추상화는 굉장히 큰 결과들을 포함하는 애플리케이션의 경우에도 표준 JDBC보다 낮은 성능상의 오버헤드를 가진다.

좀더 높은 레벨의 JDBC추상화는 org.springframework.jdbc.object패키지내에 있다. 이것은 핵심 JDBC콜백 기능에 내장되지만 자바 객체처럼 모델화된 쿼리, update또는 저장 프로시저등의 RDBMS작동내 API를 제공한다. 이 API는 내가 직관적이고 크게 유용하게 본 JDO쿼리 API에 의해 부분적으로 영감을 받았다.

사용자 객체를 반환하는 쿼리 객체는 이것처럼 보일것이다.

class UserQuery extends MappingSqlQuery {

  public UserQuery(DataSource datasource) {
    super(datasource, "SELECT * FROM PUB_USER_ADDRESS WHERE USER_ID = ?");
    declareParameter(new SqlParameter(Types.NUMERIC));
    compile();
  }

  // Map a result set row to a Java object
  protected Object mapRow(ResultSet rs, int rownumthrows SQLException {
    User user = new User();
    user.setId(rs.getLong("USER_ID"));
    user.setForename(rs.getString("FORENAME"));
    return user;
  }

  public User findUser(long id) {
    // Use superclass convenience method to provide strong typing
    return (UserfindObject(id);
  }
}

이 클래스는 다음처럼 사용될수 있다.

User user = userQuery.findUser(25);

그러한 객체는 종종 DAO내부의 inner클래스이다. 그것들은 하위클래스가 일반적이지 않은 어떤것이 아니라면 threadsafe한 상태이다.

org.springframework.jdbc.object패키지내 다른 중요한 클래스는 StoredProcedure클래스이다. Spring은 하나의 비지니스 메소드와 함께 자바 클래스에 의해 프록시된 저장된 프로시저를 가능하게 한다. 만약 당신이 좋아하지 않는다면 저장 프로시저의 사용에 의존적인 것으로 부터 당신의 애플리케이션 코드에 자유로울수 있도록 하는것을 의미하도록 저장 프로시저를 구현하는 인터페이스를 정의할수 있다.

Spring 데이터접근 예외구조는 체크되지(런타임) 않은 예외에 기반하고 있다. 다른 프로젝트에서 Spring을 이용한 작업이 나는 올바른 선택이라고 확신을 한다.

데이터접근 예외는 언제나 복구가능한것은 아니다. 예를 들어 만약 우리가 데이터베이스에 연결을 할 수 없다면 특정 비지니스 객체는 문제점들 가운데 작업을 가능하게 할것 같지 않다. 잠재적인 예외는 낙관적인 락위반이지만 모든 애플리케이션이 낙관적인 락을 사용하는것은 아니다. 현명하게 다룰수 없는 치명적인 예외를 잡기 위한 코드를 작성하는데 집중하는것은 언제나 나쁜것은 아니다. 서블릿또는 EJB와 같은 가장 높은 수준의 핸들러에 그것들을 전하는것은 언제나 좀더 선호된다. 모든 Spring데이터접근 예외는 DataAccessException의 하위클래스이다. 그래서 우리가 모든 Spring 데이터접근 예외들을 잡도록 선택한다면 우리는 다음처럼 쉽게 할수 있다.

만약에 우리가 체크되지 않은 데이터접근 예외로부터 복구하기를 원한다면 우리는 여전히 그렇게 할수 있다. 우리는 단지 복구가능한 상황만을 다루기 위해서 코드를 작성할수도 있다. 예를 들면 우리가 낙관적인 락위반만이 복구가능하다고 생각할때 우리는 다음처럼 Spring DAO내 코드를 작성할수 있다.

try {
  // do work
}
catch (OptimisticLockingFailureException ex) {
  // I'm interested in this
}

Spring 데이터접근 예외가 체크된다면 우리는 다음의 코드를 작성할 필요가 없을것이다. 우리는 어떻든 이것을 작성하는것을 선택할수 있다는 것을 알라.

try {
  // do work
}
catch (OptimisticLockingFailureException ex) {
  // I'm interested in this
}
catch (DataAccessException ex) {
  // Fatal; just rethrow it
}

첫번째 예외를 위한 하나의 잠재적인 객체화는 컴파일러가 두번째또한 적용하는 잠재적인 복구가능한 예외를 강제적으로 다룰수 없다. 우리가 기본적인 예외(DataAccessException)를 잡기위해서 강요하기 때문에 컴파일러는 하위클래스(OptimisticLockingFailureException)를 위해 체크를 강요할수 없을것이다. 그래서 컴파일러는 복구가능하지 않은 문제를 다루기 위한 코드를 작성하도록 우리에게 강요할것이지만 복구가능한 문제를 다루기 위해 우리에게 강요하는것은 도움을 주지 못한다.

Spring의 체크되지 않은 데이터접근 예외 사용은 많은면에서 일관적이다. JDBC는 체크된 예외를 사용하는 몇몇 데이터접근 API중 하나이다. 예를 들면 TopLink와 JDO가 체크되지 않은 예외를 사용한다. Gavin King는 Hibernate가 체크되지 않은 예외를 선택할것이라고 믿는다.

Spring JDBC는 여러가지 방법으로 당신을 도울수 있다.

  • 당신은 JDBC를 사용하기 위해 finally블럭을 다시 사용할 필요가 결코 없다.
  • 당신은 전체적으로 좀더 적은 양의 코드를 작성할 필요가 있을것이다.
  • 당신은 분명하지 않은 에러코드를 가지고 작업을 수행하기 위해 당신의 RDBMS문서를 통해 조사할 필요가 없을것이다. 당신의 애플리케이션은 RDBMS정의 에러 핸들링 코드의 의존적일 필요가 없을것이다.
  • 어떠한 persistence기술을 사용하든지 당신은 특정 데이터접근 API에 의존하는 비지니스 로직없이 DAO패턴을 구현하는것이 쉽다는 것을 알게 될것이다.

여기서 우리는 실질적인 생산성 획득과 적은 수의 버그를 보게 될것이다. 나는 JDBC코드를 쓰는것을 몹시 싫어한다. 지금 나는 부수적인 JDBC자원 관리보다 내가 수행하고자 하는 SQL문에 집중할수 있다는 것을 안다.

Spring의 JDBC추상화는 당신이 바란다면 독자적으로 사용될수도 있다.

O/R 맵핑 통합

물론 종종 당신은 관계적인 데이터접근보다 O/R맵핑을 사용하길 원할것이다. 당신의 전체 애플리케이션 프레임워크는 이것또한 지원해야만 한다. 게다가 Spring은 Hibernate 2.x와 JDO를 통합한다. 이것의 데이터접근 구조는 어떠한 데이터접근 기술과도 통합이 되도록 한다. Spring과 Hibernate는 특별히 잘 통합된다.

당신은 왜 Hibernate만 사용하지 않고 Hibernate에 Spring을 같이 사용하는가.? Spring은 다음과 같은 중요한 가치를 추가한다.

  • 세션 관리. Spring은 효과적이고 쉬우며 Hibernate세션의 안전한 핸들링을 제공한다. Hibernate를 사용한 관련 코드는 대개 효과적이고 적절한 트랜잭션 핸들링을 위해 Hibernate "Session"객체를 사용할 필요가 있다. Spring은 선언적인 방법이나 AOP메소드 인터셉터 접근법 또는 자바코드 레벨에서 "template"래퍼 클래스의 명시적인 방법을 사용해서 투명한 생성과 현재 쓰레드에 세션을 바인딩하기 쉽게 만들어져있다. 게다가 Spring은 Hibernate포럼에서 반복적으로 제기되는 사용상의 이슈를 많이 해결한다.
  • 자원 관리. Spring애플리케이션 컨텍스트는 경로와 Hibernate SessionFactories, JDBC 데이터소스 그리고 다른 관련 자원의 설정을 다룰수 있다. 이것은 관리와 변경을 쉽도록 만들어준다.
  • 통합된 트랜잭션 관리. Spring은 선언적인 방법이나 AOP메소드 인터셉터 또는 자바코드 레벨에서 "template"래퍼 클래스의 명시적인 방법을 사용해서 당신의 Hibernate코드를 랩핑하도록 한다. 이런 경우에 트랜잭션 의미는 당신을 위해서 다루어지고 예외의 경우에 적당한 트랜잭션 핸들링이 처리된다. 밑에서 논의되는 것처럼 당신은 Hibernate관련코드에 영향을 미치는 것 없이 다양한 트랜잭션 관리자를 사용하고 바꿀수 있는 이익을 가진다. 추가된 이익처럼 JDBC관련 코드는 Hibernate코드와 함께 트랜잭션적으로 완전히 통합될수 있다. 이것은 Hibernate내에서 구현하지 않더라도 기능적으로 매우 유용하다.
  • 위에서 서술된 예외 랩핑. Spring은 Hibernate예외들을 포장하거나 선호하거나 체크된 예외를 추상화된 런타임 예외들로 변환을 할수 있다. 이것은 복구할수 없고 지겨운 반복적 catches/throws코드와 예외 선언 없이 선호하는 레이어에서만 대부분의 퍼시스턴스 예외를 다룰수 있도록 한다. 당신은 여전히 필요한 어느곳이든지 예외들을 잡고 다룰수 있다. JDBC예외들은 같은 구조로 변환하고 일관적인 프로그램 모델내에서 JDBC와 함께 같은 작업을 수행할수 있다는 것을 의미한다.
  • 업체 락(lock-in)을 피하기. Hibernate가 강력하고 유연하며 오픈소스이고 free이긴 하지만 이것은 여전히 독점적인 API를 사용한다. Given는 표준적이거나 추상적인 API을 사용해서 주요한 애플리케이션 기능을 구현하는것이 바람직하다. 이 경우에 당신은 기능, 성능 또는 다른 어떠한 사항을 위해 다른 구현물로 교체해야 할 필요가 있다. Spring의 Hibernate트랜잭션과 예외들의 추상화는 매퍼/DAO객체 구현 데이터접근 기능을 쉽게 바꿀수 있도록 IoC를 사용한다. 당신의 애플리케이션 영역내에서 모든 Hibernate종속 코드를 Hibernate의 강력함의 손실없이 쉽게 분리할수 있게 만든다.
  • 테스팅의 쉬움. Spring의 Inversion of Control접근법은 Hibernate세션 factory, 데이터소스, 트랜잭션 관리자 그리고 매퍼 객체 구현물의 구현과 위치를 교체하는것을 쉽게 만든다. 이것은 퍼시스턴스 관련 코드의 각각의 일부분을 분리하거나 테스트를 좀더 쉽게 만든다.

트랜잭션 관리

데이터접근 API를 추상화는것은 충분하지 않다. 우리는 트랜잭션 관리에 대해서 생각해 볼 필요가 있다. JTA는 명백한 해결법이지만 이것은 직접적으로 사용하기에는 성가신 API이다. 그리고 EJB CMT가 트랜잭션 관리를 위한 단지 이론적인 옵션이라고 많은 J2EE개발자들이 느끼고 있다.

Spring은 트랜잭션 관리를 위해 자신만의 추상화를 제공한다. Spring은 이전을 위해 다음의 사항들을 사용한다.

  • 콜백 템플릿을 통한 프로그램 트랜잭션 관리는 직접적으로 JTA를 사용하는 것보다 좀더 쉬운 JdbcTemplate과 유사하다.
  • 선언적인 트랜잭션 관리는 EJB CMT와 유사하지만 EJB컨테이너는 필요없다.

Spring의 트랜잭션 추상화는 JTA나 다른 어떠한 트랜잭션 관리 기술에 묶이지 않는 유일한 것이다. Spring은 트랜잭션 내부구조를 참조하여 애플리케이션 코드를 분리하는 트랜잭션 전략의 개념을 사용한다.

왜 당신은 이것에 대해서 주의해야 하는가.? 모든 트랜잭션 관리를 위해 JTA가 가장 좋은 답이 아닌가.? 만약 당신이 하나의 데이터베이스를 사용하는 애플리케이션을 작성한다면 당신은 JTA의 복잡함을 사용할 필요가 없다. 당신은 XA트랜잭션이나 두 단계의 커밋에 흥미롭지는 않을것이다. 당신은 이러한 것들을 제공하는 높은 성능의 애플리케이션 서버 조차도 필요하지 않을것이다. 하지만 반면에 당신은 다중 데이터소스로 작업을 수행해야 할때 당신의 코드를 다시 쓰고 싶지는 않을것이다.

JDBC나 Hibernate트랜잭션을 직접적으로 사용함으로써 JTA의 오버헤드를 피할 결심을 했다고 생각해보라. 만약 당신이 다중 데이터소스와 작업할 필요가 있다면 당신은 트랜잭션 관리 코드를 쪼개야만 하고 그것을 JTA트랜잭션으로 교체해야만 한다. 이것은 그다지 매력적이지 않고 대부분의 J2EE에 전역적인 JTA트랜잭션을 사용하길 추천한다. Spring트랜잭션 추상화를 사용하는 것은 어쨌든 당신은 JDBC나 Hibernate보다 JTA를 사용하기 위해 Spring을 다시 설정해야만 한다. 이것은 코드상의 변경이 아닌 설정상의 변경이다. 게다가 Spring은 당신에게 애플리케이션 규모를 크게 하는것만큼 작게 하는 것도 가능하게 한다.

AOP

최근에 엔터프라이즈 관련 사항에 EJB에 의해 할당된 트랜잭션 관리와 같은 AOP솔루션을 적용하는 것이 굉장히 큰 관심사항이 되었다.

Spring의 AOP지원의 첫번째 목표는 POJO를 위해 J2EE서비스를 제공하는 것이다. 이것은 JBoas 4의 목적과 유사하다. 어쨌든 Spring AOP는 벤더 락(lock in)의 위험 없이 애플리케이션 서버사이에 이식가능한 장점을 가진다. 이것은 웹이나 EJB컨테이너와 잘 작동한다. 그리고 WebLogic, Tomcat, JBoss, Resin, Jetty, Orion 그리고 많은 다른 애플리케이션 서버와 웹 컨테이너에서 성공적으로 사용되었다.

Spring AOP는 메소드 인터셉션(interception)을 지원한다. 제공되는 AOP의 핵심적인 개념은 다음과 같다.

  • 인터셉션(Interception) : 사용자 정의 행위는 어떠한 인터페이스나 클래스에 대해서 메소드 호출전이나 후에 추가된다. 이것은 AspectJ 용어에서 "around advice"와 유사하다.
  • 소개(Introduction) : advice는 추가적인 인터페이스를 구현하기 위한 객체를 야기한다는 것을 명시한다. 이것은 복잡한 상속상태에 이르게 될수 있다.

역자주 : AOP개념을 모두 설명할수는 없지만 기본적으로 AOP에 의해 해당 메소드가 호출되는 부분을 joinpoints라고 하고 joinpoints에서 호출되는 코드들을 advice라고 부른다. joinpoints들의 묶음을 pointcuts라고 부른다. 그리고 advice와 pointcuts를 모아 Aspects라고 지칭한다.

  • 정적이고 동적인 pointcuts : 인터셉터가 위치해야만 하는 곳에 프로그램 수행내 point를 정의하라. 정적 pointcuts는 메소드 시그너처를 의미하고 동적 pointcuts는 그들이 값을 구하는 지점에 메소드 인자로 간주될수 있다. pointcuts는 다른 애플리케이션과 코드 컨텍스트내 적용될 표준적인 인터셉터를 가능하게 하는 인터셉터로 부터 개별로 정의된다.

Spring은 상태유지(adviced객체당 하나의 인스턴스)와 비상태유지(모든 advice를 위한 하나의 인스턴스) 인터셉터 모두 지원한다.

Spring은 필드 인터셉터를 지원하지 않는다. 이것은 신중한 디자인 결정이다. 나는 언제나 필드 인터셉터가 encapsulation을 어긴다고 느낀다. 나는 OOP와 충돌이 나는것보다 보완물처럼 AOP를 생각하는것을 선호한다. 나는 만약 5년이나 10년안에 우리가 AOP가 애플리케이션 디자인의 가장 상위부분을 차지한다 하더라도 놀라지 않을것이다.

Spring은 런타임시 동적 프록시(인터페이스가 존재하는 곳에)나 CGLIB바이트 코드 생성자(클래스의 프록시를 가능하게 하는)를 사용해서 AOP를 구현한다. 둘다 애플리케이션 서버내에서 잘 작동한다.

Spring은 AOP친화 인터페이스(http://www.sourceforge.net/projects/aopalliance)를 구현한 첫번째 AOP프레임워크이다. AOP프레임워크 사이의 인터셉터의 상호작용을 허락하는 인터페이스를 정의하기 위해 표현된다.

TheServerSide에서 진행중이고 논쟁된 이러한 종류의 가로채기(interception)은 "true AOP"이다. 나는 이것이 이렇게 불리는 것에 대해 특별히 걱정하지 않는다. 단지 실제로 유용할수 있다. 나는 이것을 declarative middleware" 라고 부르고 싶다. Spring AOP의 생각은 간단하고 local비상태유지 세션빈에 가벼운 대안이다. 획일적인 EJB컨테이너의 필요가 없어지고 당신이 필요한 서비스와 함께 당신 자신만의 컨테이너를 빌드하도록 한다. 우리는 어떠한 충고(advising)와 모든 POJO를 추천하지 않는다. 그래서 추천되는 것을 분명하게 하기 위해 local SLSBs로 유추한다. (어쨌든 EJB와는 달리 당신은 선호하는 지점에 드문 시나리오내 훌륭하게 얻어진 객체를 위해 Sping AOP을 적용하는것이 자유로울 것이다.)

Spring은 클래스로더보다 인스턴스에서 객체를 통지하기 때문에 이것은 다른 advice로 같은 클래스의 다중 인스턴스를 사용하거나 통지된 인스턴스와 함께 통지되지 않은 인스턴스를 사용하는 것이 가능하다.

아마도 Spring AOP의 가장 공통적인 사용은 선언적인 트랜잭션 관리이다. 이것은 위에서 서술된 TransactionTemplate추상화위에서 빌드되고 어떠한 POJO위에서 선언적인 트랜잭션 관리를 전달할수 있다. 트랜잭션 전략에 의존적으로 기법을 이해하는것은 JTA, JDBC, Hibernate 또는 다른 어떠한 트랜잭션 관리를 제공하는 API가 될수 있다.

Spring의 선언적 트랜잭션 관리는 다음과 같은 차이점으로 EJB CMT와 흡사하다.

  • 트랜잭션 관리는 어떠한 POJO에 적용될수 있다. 우리는 비지니스 객체가 인터페이스를 구현하는것을 추천하지만 이것은 프로그래밍 실제상황에서 좋은 방법이고 프레임워크에 의해 강요되지 않는다.
  • 프로그램적인 롤백은 Spring트랜잭션 API을 사용해서 전통적인 POJO내 달성될수 있다. 우리는 이것을 위해 ThreadLocal변수들을 사용해서 정적 메소드를 제공한다. 그리고 당신은 롤백을 확실히 하기 위해 EJBContext와 같은 컨텍스트 객체를 상속할 필요가 없다.
  • 당신은 선언적으로 "롤백 규칙들"을 정의할수 있다. EJB가 잡히지 않은 애플리케이션 예외(체크되지 않은 예외와 다른 Throwables에 대해서만)에서 트랜잭션을 자동적으로 롤백하는것에 반하여 애플리케이션 개발자는 어떠한 특정 예외에서 트랜잭션을 롤백하기를 원한다. Spring트랜잭션 관리는 당신에게 예외와 하위 클래스가 자동 롤백을 야기하는 것을 선언적으로 정의하도록 허락한다. 디폴트 행위는 EJB와 같지만 당신은 체크되지 않은 예외처럼 체크된 예외에서도 자동적으로 롤백을 수행할수 있다. 이것은 Spring트랜잭션 API에서 의존성(EJB프로그램적인 롤백은 EJBContext에서 수행되는 것처럼)을 생성하는 프로그램적인 롤백을 위한 필요성을 최소화하는 중요한 이익을 가진다.
  • 트랜잭션 관리는 JTA에 묶여있지 않다. 위에서 설명된 것처럼 Spring트랜잭션 관리는 다른 트랜잭션 전략과도 작동할수 있다.

물론 이것은 Spring AOP의 사용으로 애플리케이션 정의 aspect를 구현하는것이 가능하다. Spring의 가능성보다 AOP개념으로 당신의 안정적인 레벨에 의존적으로 이것을 하는것을 선택할수 있다. 하지만 이것은 매우 유용하다. 성공적인 예제는 우리가 볼때 다음을 포함한다.

  • 사용자 정의 보안 인터셉션, 요구되는 보안 체크의 복잡함은 표준적인 J2EE보안 구조의 능력을 넘어선다.
  • 개발 기간동안 사용을 위해 aspect를 디버깅하고 프로파일링하기
  • 유별난 시나리오에서 관리자나 사용자들에게 경고하기 위해 이메일을 보내는 인터셉션

애플리케이션정의 aspect는 많은 메소드를 통해 반복적인 코드를 위한 필요성을 제거하는 강력한 방법이 될수 있다.

Spring AOP는 Spring BeanFactory개념과 함께 투명하게 통합된다. Spring BeanFactory에서 나온 객체를 얻는 코드는 이것이 통지되든지 되지 않든지 알 필요가 없다. 어떤 객체처럼 계약은 인터페이스 객체 구현을 함으로써 정의될 것이다.

다음의 XML은 AOP프록시를 정의하는 방법을 그리고 있다.

<bean id="myTest" class="org.springframework.aop.framework.ProxyFactoryBean">
  <property name="proxyInterfaces">
    <value>org.springframework.beans.ITestBean</value>
  </property>
  <property name="interceptorNames">
    <list>
      <value>txInterceptor</value>
      <value>target</value>
    </list>
  </property>
</bean>

참조내 사용되거나 BeanFactory의 getBean()메소드에 의해 반환되는것처럼 bean의 타입이 proxy인터페이스에 의존할지라도 bean정의의 클래스는 항상 AOP프레임워크의 ProxyFactoryBean이라는것에 주의하라. ProxyFactoryBean의 "interceptorNames"프라퍼티가 문자열의 리스트를 가져온다.(bean이름이 bean참조보다 더 사용되어야만 한다. 만약 프록시가 "프로토타입"이라면 싱글톤 bean정의보다 상태유지 인터셉터의 새로운 인스턴스가 생성될 필요가 있다.). 위 리스트내 이름은 인터셉터나 pointcuts가 될수 있다. 리스트내 "target"값은 자동적으로 "호출 인터셉터"를 생성한다. 이것은 프록시 인터페이스를 구현하는 factory내 bean의 이름이다. 이 예제내 myTest빈은 bean factory내에서 다른 bean처럼 사용될수 있다. 예를 들면 다른 객체는 >ref<요소와 Spring IoC에 의해 셋팅될 참조들을 통해 참조될수 있다.

BeanFactory사용없이 프로그램적으로 AOP프록시를 생성하는 것은 가능하다. 비록 이것이 좀더 드물게 사용이 되더라도.

TestBean target = new TestBean();
DebugInterceptor di = new DebugInterceptor();
MyInterceptor mi = new MyInterceptor();
ProxyFactory factory = new ProxyFactory(target);
factory.addInterceptor(0, di);
factory.addInterceptor(1, mi);
// An "invoker interceptor" is automatically added to wrap the target
ITestBean tb = (ITestBeanfactory.getProxy();

우리는 이것이 자바코드로 부터 애플리케이션을 묶는 작업을 구체화하는 가장 좋은 방법이고 AOP는 예외가 아니라고 믿는다.

AOP성능의 개념에서 Spring의 가장 직접적인 경쟁자는 Jon Tirsen's Nanning Aspects(http://nanning.codehaus.org)이다.

나는 기업용 서비스를 달성하기 위해 EJB를 사용하는 것의 대안으로 AOP의 사용이 유력하게 증가할 것이라고 믿는다. 이것은 Spring의 중요한 중심점이 될것이다.

MVC 웹 프레임워크

Spring은 강력하고 설정가능한 MVC웹 프레임워크를 포함한다.

Sprign의 MVC모델은 Struts의 그것과 상당히 유사하다. Spring컨트롤러는 모든 클라이언트에 대해 한개의 인스턴스만이 수행되는 멀티쓰레드방식의 서비스 객체인 Struts Action과 유사하다. 어쨌든 우리는 Spring MVC가 Struts를 뛰어 넘어서 몇몇 중대한 장점을 가진다고 믿는다. 예를 들면

  • Spring은 컨트롤러와 자바빈 모델 그리고 뷰들 사이에 매우 분명한 분리를 제공한다.
  • Spring MVC는 당신의 액션과 폼객체를 견고한 상속을 강요하는 Struts와는 달리 매우 유연하다. Spring MVC는 전체적으로 인터페이스에 기초를 둔다. 게다가 Spring MVC프레임워크의 모든 부분에 대해서 당신 자신의 인터페이스내 플러깅(plugging)을 통해 설정가능하다. 물론 우리는 구현 옵션처럼 편리한 클래스를 제공하기도 한다.
  • Spring MVC는 진실로 뷰에 관용적이다. 당신이 원하지 않는다면 사용하기 위해 강요하지 않는다. 당신은 Velocity, XLST또는 다른 뷰 관련 기술을 사용할수 있다. 만약 사용자 정의 기법 즉 예를 들어 당신 자신만의 템플릿 언어를 사용하길 원한다면 당신은 그것과 통합하기 위해 Spring View인터페이스를 쉽게 구현할수 있다.
  • Spring컨트롤러는 다른 객체처럼 IoC를 통해 설정가능하다. 이것은 테스트를 쉽게 만들고 Spring에 의해 관리되는 다른 객체들과 함께 멋지게 통합된다.
  • 웹 티어는 비지니스 객체 레이어의 가장 상위의 작은(thin)레이어가 된다. 이것은 좋은 실제상황을 만들어준다. Struts와 다른 전용 웹프레임워크는 당신을 당신의 비지니스 객체의 구현을 하도록 만든다. Spring은 당신의 애플리케이션의 모든 티어(tier)를 위해 통합된 프레임워크를 제공한다.

Struts 1.1에서 처럼 당신은 당신이 Spring MVC애플리케이션내 필요한만큼의 많은 디스패치(dispatcher)서블릿을 가질수 있다.

다음의 예제는 간단한 Spring컨트롤러가 같은 애플리케이션 컨텍스트내 정의된 비지니스 객체에 접근하는 방법을 보여준다. 이 컨트롤러는 handleRequest()메소드내에서 구글(google)검색을 수행한다.

public class GoogleSearchController
    implements Controller {

  private IGoogleSearchPort google;

  private String googleKey;

  public void setGoogle(IGoogleSearchPort google) {
    this.google = google;
  }

  public void setGoogleKey(String googleKey) {
    this.googleKey = googleKey;
  }

  public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
    String query = request.getParameter("query");
    GoogleSearchResult result =
      // Google property definitions omitted...

      // Use google business object
      google.doGoogleSearch(this.googleKey, query,
        start, maxResults, filter, restrict,
        safeSearch, lr, ie, oe);

    return new ModelAndView("googleResults""result", result);
  }
}

프로토타입내에서 이 코드는 Spring FactoryBean에 의해 반환되는 IGoogleSearchPort(이것은 GLUE웹서비스 프록시이다)로 부터 얻어진다. 어쨌든 Spring IoC는 웹서비스 라이브러리에 기초를 둠으로써 이 컨트롤러를 분리시킨다. 인터페이스는 밑에서 논의되는 일반적인 자바객체, 테스트 스텁(stub), 모의(mock) 객체 또는 EJB프록시에 의해 동등하게 구현될수 있다. 이 컨트롤러는 어떠한 리소스 룩업(검색)을 포함하지 않는다. 코드를 제외하곤 어떠한 것도 이것의 웹 통합을 지원하기 위해 필요하지 않다.

Spring은 데이터바인딩, 폼, 마법사 그리고 다른 복잡한 워크플로우를 위한 지원도 제공한다.

Spring MVC프레임워크에 대한 좋은 소개는 Thomas Risberg의 Spring MVC튜토리얼(http://www.springframework.org/docs/MVC-step-by-step/Spring-MVC-step-by-step.html)이다. 또한 "Spring프레임워크를 사용한 웹 MVC(http://www.springframework.org/docs/web_mvc.html)."도 보라.

만약 당신이 좋아하는 MVC프레임워크를 사용하는게 좋다면 Spring의 레이어화된 하부구조가 당신이 당신의 MVC레이어 없이 Spring의 나머지를 사용하도록 허락할 것이다. 우리는 Struts, WebWork 또는 웹티어내 Tapestry를 사용하지만 미들티어 관리와 데이터접근을 위해 Spring을 사용하는 Spring 사용자들이 있다.

EJB들을 구현하기

만약 당신이 EJB를 사용하기로 선택한다면 Spring은 EJB구현과 EJB에 대한 클라이언트측 접근의 중요한 이익을 제공할수 있다.

이것은 지금 비지니스 로직을 EJB외관뒤 POJO로 리팩터하기 위한 가장 좋은 상황처럼 간주된다. (EJB가 컨테이너에 크게 의존적인것과 격리수준내에서 테스트가 어려운것처럼 다른 것 사이에 이것은 비지니스 로직의 단위 테스팅을 쉽게 만든다.) Spring은 EJB jar파일내 포함된 XML문서에 기반하여 자동적으로 BeanFactory를 로드함으로써 이것을 쉽게 만드는 세션빈과 메시지빈을 위해 편리한 슈퍼클래스들을 제공한다.

이것은 비상태유지 세션 EJB가 획득되고 협력자(collaborator)를 사용하는것을 의미한다.

import org.springframework.ejb.support.AbstractStatelessSessionBean;

public class MyEJB extends AbstractStatelessSessionBean implements MyBusinessInterface {
  private MyPOJO myPOJO;

  protected void onEjbCreate() {
    this.myPOJO = getBeanFactory().getBean("myPOJO");
  }

  public void myBusinessMethod() {
    this.myPOJO.invokeMethod();
  }
}

우리는 Spring에게 표준적인 ejb-jar.xml 배치서술자내 ejb/BeanFactoryPath라고 명명된 환경변수정의를 통해 XML문서를 로드하는 곳을 알린다. 마치 다음처럼:

<session>
  <ejb-name>myComponent</ejb-name>
  <local-home>com.test.ejb.myEjbBeanLocalHome</local-home>
  <local>com.mycom.MyComponentLocal</local>
  <ejb-class>com.mycom.MyComponentEJB</ejb-class>
  <session-type>Stateless</session-type>
  <transaction-type>Container</transaction-type>

  <env-entry>
    <env-entry-name>ejb/BeanFactoryPath</env-entry-name>
    <env-entry-type>java.lang.String</env-entry-type>
    <env-entry-value>/myComponent-ejb-beans.xml</env-entry-value></env-entry>
  </env-entry>
</session>

myComponent-ejb-beans.xml파일은 클래스패스로부터 로드될것이다. 이 경우에는 EJB jar파일의 가장 상위에서 각각의 EJB는 자신만의 XML문서를 정의할수 있다. 그래서 이 기법은 각각의 EJB jar파일마다 여러번 사용이 될수 있다.

Spring 슈퍼클래스는 setSessionContext() 와 ejbCreate()와 같이 Spring onEjbCreate메소드를 구현하기 위해 애플리케이션 개발자를 남겨주는 EJB생명주기 메소드를 구현한다.

EJB 사용하기

Spring은 또한 EJB를 구현하는 것 만큼 그것을 사용하기 쉽도록 만든다. 많은 EJB애플리케이션은 서블릿 로케이터(locator)와 비지니스 위임(delegate)패턴을 사용한다. 클라이언트 코드 도처에 JNDI룩업을 써대는 것보다 훨씬 좋다. 하지만 그들의 일반적인 구현은 분명한 단점을 가진다. 예를 들면 :

  • EJB를 사용하는 전형적인 코드는 테스트를 힘들게 하는 서블릿 로케이터와 비지니스 위임 싱글톤에 의존한다.
  • 서비스 로케이터 패턴의 경우에 비지니스 위임없이 사용된다. 애플리케이션 코드는 여전히 EJB home에서 create()메소드를 호출함으로써 종료되고 결과 예외를 처리한다. 게다가 이것은 EJB API에 묶이고 EJB프로그래밍 모델의 복잡함을 남긴다.
  • 비지니스 위임 패턴의 구현은 EJB에서 같은 메소드를 간단히 호출하는 수많은 메소드를 써야만 하는 명백한 코드중복의 결과를 야기한다.

이러한 그리고 다른 이유로 전통적인 EJB접근은 생산성을 제거하고 암시적인 복잡함이라는 결과를 만든다.

Spring은 코드가 없는 비지니스 위임을 소개함으로써 나가보겠다. Spring으로 당신은 실제값을 추가하지 않는 한 다른 서비스 로케이터, 다른 JNDI룩업 또는 손으로 직접 코딩한 비지니스 위임내 중복된 메소드를 쓸 필요가 결코 없을것이다.

예를 들면 우리가 local EJB를 사용하는 웹 컨트롤러를 가지고 있다고 생각을 하자. 우리는 가장 좋은 상황을 끄집어 내고 EJB비지니스 메소드 인터페이스 패턴을 사용할 것이다. 그래서 EJB의 local 인터페이스는 비 EJB스펙 비지니스 메소드 인터페이스를 확장한다. (이것을 수행하기 위해 가장 중요한 이유중에 하나가 local인터페이스와 bean구현클래스내 메소드 시그너처사이 동기화를 확실하게 하는것이다.). 이 비지니스 메소드 인터페이스를 MyComponent라고 부르자. 물론 우리는 local home인터페이스를 구현하고 SessionBean과 MyComponent비지니스 메소드 인터페이스를 구현할 bean구현 클래스를 제공할 필요가 있을것이다.

Spring EJB접근으로 EJB구현물로 우리의 웹티어 컨트롤러를 걸 필요가 있는 자바코딩은 우리의 컨트롤러에 MyComponent타입의 setter메소드를 보이는 것이다. 이것은 다음처럼 인스턴스 변수처럼 참조를 저장할 것이다.

private MyComponent myComponent;

public void setMyComponent(MyComponent myComponent) {
  this.myComponent = myComponent;
}

우리는 어떠한 비지니스 메소드내 인스턴스 변수를 계속적으로 사용할수 있다.

Spring은 이것처럼 XML bean정의 엔트리를 통해 자동적으로 나머지 작업을 수행한다. LocalStatelessSessionProxyFactoryBean은 어떠한 EJB를 위해 사용될수 있는 일반적인 factory bean이다. 이것은 Spring에 의해 자동적으로 MyComponent타입으로 형변환 될수 있다.

<bean id="myComponent" class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean">
  <property name="jndiName"><value>myComponent</value></property>
  <property name="businessInterface"><value>com.mycom.MyComponent</value></property>
</bean>

<bean id="myController" class "com.mycom.myController">
  <property name="myComponent"><ref bean="myComponent"/></property>
</bean>

이 장면뒤에 많은 마법같은 것들이 있다. Spring AOP프레임워크의 특별함, 비록 당신이 결과를 즐기기 위해 AOP개념과 함께 강요하지 않는다. "myComponent" bean정의는 EJB를 위해 비지니스 메소드 인터페이스를 구현하는 프록시를 생성한다. EJB local home은 시작시 캐시된다. 그래서 단지 하나의 JNDI룩업만 있을 뿐이다. 각각의 EJB호출은 프록시가 local EJB에서 create()메소드를 호출하고 EJB에서 관련된 비지니스 메소드를 호출한다.

myController bean정의는 프록시를 위해 컨트롤러 클래스의 myController프라퍼티를 셋팅한다.

EJB접근 기법은 대량의 명백한 애플리케이션 코드를 만든다.

  • 웹 티어 코드는 EJB사용에서 어떠한 의존성도 가지지 않는다. 만약 우리가 POJO나 모의객체 또는 다른 테스트 스텁으로 EJB참조를 대체하길 원한다면 우리는 간단히 자바코드의 변경없이 myComponent bean정의를 변경할수 있다.
  • 우리는 JNDI룩업의 한줄의 코드나 우리의 애플리케이션의 일부처럼 다른 EJB연관 코드를 쓰지 않아도 된다.

실제 애플리케이션내 벤치마크와 경험은 이 접근법(목표 EJB의 반영된 호출을 포함하는)의 성능상의 오버헤드가 매우 극미하고 전통적인 사용에서 간파당할수 있다는 것을 표시했다. 우리는 애플리케이션 서버내에서 EJB내부구조와 함께 연관된 가치인것처럼 EJB에 잘 얻어지는 호출을 만들지 않기를 원한다는 것을 기억하라.

우리는 단지 org.springframework.ejb.access.SimpleRemoteStatelessSessionProxyFactoryBean factory bean을 통해 remote EJB에 같은 접근법을 적용할수 있다. 어쨌든 우리는 remote EJB의 비지니스 메소드 인터페이스에 RemoteExceptions을 숨길수는 없다.

테스팅

당신들이 대개 추측하는 것처럼 나 그리고 다른 Spring개발자들은 포괄적인 단위 테스팅의 중요성을 믿는다. 우리는 프레임워크가 철저히 단위 테스트를 하고 프레임워크 디자인의 가장 중요한 목표는 애플리케이션을 단위 테스트를 위해 프레임워크를 쉽게 빌드하는 것이 필수라고 믿는다.

Spring스스로는 훌륭한 단위 테스팅을 가진다. 1.0M1의 단위 테스트 범위는 75%를 넘어선다. 그리고 우리는 1.0RC1을 위해 80이상의 단위테스트를 수행하도록 희망한다. 우리는 첫번째 개발 테스팅의 이익이 이 프로젝트에 매우 실질적으로 도움이 된다는것을 알았다. 예를 들면 이것은 내부적으로 분산된 팀의 대단히 큰 효과처럼 작동하고 사용자는 CVS스냅샷이 안정되고 사용하기 안전한것을 언급한다.

우리는 Spring에 빌드된 애플리케이션이 다음과 같은 이유로 테스트 하기 매우 쉽다고 믿는다.

  • IoC가 단위테스팅을 돕는다.
  • 애플리케이션들은 테스트가 전형적으로 힘든 JNDI와 같은 J2EE서비스를 직접적으로 사용하기 위한 연관된 코드를 포함하지 않는다.
  • Spring bean factory또는 컨텍스트는 컨테이너 밖에서 셋팅될수 있다.

컨테이너 밖에서 Spring bean factory을 셋팅하는 능력은 개발 프로세스를 위해 흥미로운 옵션을 제공한다. 다양한 웹 애플리케이션내에서 Spring을 사용하는 프로젝트는 작업이 비지니스 인터페이스와 웹 컨테이너밖에서 그들의 구현을 통합 테스팅을 정의함으로써 시작된다. 단지 비지니스 성능이 사실상 완벽해진 후에 웹 인터페이스를 제공하기 위해 추가된 가벼운(thin)레이어이다.

누가 Spring을 사용하는가.?

비록 Spring이 비교적 새로운 프로젝트지만 우리는 벌써 인상적이고 성장하는 사용자 커뮤니티를 가지고 있다. 여기엔 벌써 Spring을 사용한 많은 제품의 애플리케이션이 있다. 사용자는 주요한 세계적인 투자은행, 잘 알려진 닷컴, 다양한 웹 개발 컨설턴트, 건강관련 회사 그리고 교육기구를 포함한다.

많은 사용자는 Spring의 모든 부분을 사용하지만 몇몇 사용자는 격리수준내에서 컴포넌트들을 사용한다. 예를 들면 많은 수의 사용자는 우리의 JDBC나 다른 데이터접근 기능을 사용하여 시작한다.

로드맵

우리의 중요한 사항은 Spring1.0정식버전에서 구현된다. 어쨌든 우리는 몇몇 긴시간이 필요한 목표를 가지고 있다. 1.0정식버전을 위해 계획된 하나의 중요한 우선사항은 소스레벨의 메타데이터 지원이다. 우리의 AOP프레임워크를 사용하도록 하는 경향(물론 제한하지는 않는다.)이 될것이다. 이것은 C#스타일의 속성지향(attribute-driven) 트랜잭션 관리를 가능하게 할것이고 선언적인 기업용 서비스를 전형적인 사용을 위해 설정하기 쉽도록 만든다. 속성지원은 Spring 1.0에 포함될것이다. 그리고 JSR-175와 함께 통합되도록 디자인될것이다.

1.0을 넘어서 몇몇사항이 다음과 같은 향상을 보일것이다.

  • 우리의 JDBC와 트랜잭션 지원을 위해 비교가능한 추상화를 통해 JMS지원
  • bean factory의 동적인 재설정 지원
  • 웹서비스를 보이기 위한 능력
  • IDE와 다른 툴 지원

예민한 프로젝트처럼 우리는 사용자 요구사항에 의해 주로 진행을 한다. 그래서 우리는 누구도 사용하지 않는 기능은 개발하지 않고 사용자 커뮤니티의 의견을 주의깊게 들을것이다.

요약

Spring은 J2Ee에서 공통적으로 발생하는 많은 문제를 해결하는 강력한 프레임워크이다.

Spring은 클래스보다 인터페이스에 프로그래밍하는 것과 같은 좋은 상황을 장려하고 비지니스 객체를 관리하는 일관적인 방법을 제공한다. Spring의 구조적인 기본은 자바빈즈 프라퍼티의 사용에 기초를 둔 Inversion of Control이다. 어쨌든 이것은 전체 그림의 일부이다. Spring은 모든 구조적인 티어들을 할당하는 포괄적인 솔루션내 기본적인 빌딩블럭처럼 IoC컨테이너를 사용하는 것중에 유일하다.

Spring은 생산성을 증가시키고 에러를 줄이는 간단하고 제품같은 JDBC프레임워크를 포함한 유일한 데이터접근 추상화를 제공한다. Spring의 데이터접근 구조는 Hibernate와 다른 O/R맵핑 솔루션과 통합한다.

Spring은 또한 JTA나 JDBC같은 트랜잭션 기술에 기초를 둔 일관적인 프로그래밍 모델을 가능하게 하는 유일한 트랜잭션 관리 추상화를 제공한다.

Spring은 선언적인 트랜잭션 관리와 POJO를 적용하기 위한 다른 기업용 서비스 또는 당신이 바란다면 당신 자신의 사용자 정의 aspect를 구현하는 능력을 제공하는 표준적인 자바로 쓰여진 AOP프레임워크를 제공한다. 이 프레임워크는 EJB의 복잡함을 줄이도록 많은 애플리케이션에 가능하도록 할수 있도록 충분히 강력하다.

Spring은 또한 전체의 IoC컨테이너로 통합될수 있는 강력하고 유연한 MVC웹 프레임워크를 제공한다.

좀더 많은 정보를 위해서는

Spring에 대한 좀더 많은 정보를 위해서는 다음의 자원들을 보라.

  • Expert One-on-One J2EE Design and Development (Rod Johnson, Wrox, 2002).
  • 포럼과 소스포지에서의 다운로드
  • Spring 사용자 그리고 spring 개발자 메일링리스트

우리는 Spring문서와 예제를 향상시키는데 최선을 다하고 있다. 우리는 포럼과 메일링리스트의 훌륭한 응답비율에 자부심을 가지고 있다. 우리는 우리의 커뮤니티에 당신을 환영한다.

저자에 대해

Rod Johnson 는 자바개발과 구조 그리고 J2EE에 여러해의 경험을 가지고 있다. 그는 Expert One-on-One J2EE Design and Development (Wrox, 2002)의 저자이고 J2EE의 다른 책에도 많은 기여를 했다. 그는 현재 UK에서 컨설턴트로 일하고 있다.

Add new attachment

Only authorized users are allowed to upload new attachments.

List of attachments

Kind Attachment Name Size Version Date Modified Author Change note
jpg
SpringFramework.jpg 19.8 kB 1 06-Apr-2006 09:45 61.83.100.237
« This page (revision-1) was last changed on 06-Apr-2006 09:45 by UnknownAuthor