태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.

자바 5 주석을 사용한 효율적인 테스트난이도 : 중급

2007 년 4 월 10 일

JUnit 4에서는 자바(Java™) 5 주석(annotation)의 효율적인 유연성을 위해 기존의 엄격한 명명 규칙 및 상속 계층 구조를 없앴다. 테스트 전문가로 활동하고 있는 Andrew Glover는 본 튜토리얼에서 코드 품질과 관련하여 자신이 연재한 인기 있는 기술문서의 내용을 보충하는 시간을 마련하여 매개변수 테스트, 예외 테스트, 제한 시간 테스트 등 주석을 통해 새로운 기능을 활용하는 방법에 대해 설명한다. 또한 JUnit 4의 유연한 픽스쳐(fixture)에 대해 소개하고 스위트(suite) 대신 주석을 사용하여 테스트를 실행하기 전에 논리적으로 그룹화하는 방법에 대해서도 설명한다. 본 튜토리얼에는 이클립스(Eclipse)에서 실행되는 일부 샘플 테스트와 호환되지 않는 이전 버전의 앤트(Ant)에서 JUnit 4 테스트를 실행하기 위한 지침이 포함되어 있다.

시작하기 전에

튜토리얼 소개

자바 5 주석은 JUnit에 커다란 변화를 가져왔으며 많은 테스트 프레임워크 개발자에게 효율적인 작업 방식으로 점차 인식되고 있지만 이들에게 익숙한 기술은 아니다. 본 튜토리얼에서는 JUnit 4의 가장 중요한 변경 사항에 대해 설명하고 독자가 이미 들어보았을지도 모르지만 아직 사용하고 있지는 않을 흥미로운 새 기능들에 대해 설명한다.

목적

본 튜토리얼에서는 JUnit 4의 기본 개념에 대해 단계별로 설명하고 특히 새로운 자바 5 주석 기능에 대해 자세히 다룬다. 한 시간 분량의 본 튜토리얼 학습을 마치면 JUnit 4의 주요 변경 사항에 대해 이해할 수 있을 뿐만 아니라 예외 테스트, 매개변수 테스트 및 유연한 새 픽스쳐 모델과 같은 기능에 대해 알게 된다. 또한 테스트를 선언하는 방법, 스위트 대신 주석을 사용하여 테스트를 실행하기 전에 논리적으로 그룹화하는 방법, 명령행뿐만 아니라 이클립스 3.2 또는 앤트에서 테스트를 실행하는 방법에 대해서도 설명한다.

필요한 사전 지식

본 튜토리얼을 최대한 활용하기 위해서는 일반적인 자바 개발에 익숙해야 한다. 본 튜토리얼에서는 또한 독자가 개발자 테스트의 중요성을 이해하고 있으며 기본 패턴 매칭에 익숙하다고 가정한다. JUnit 4 테스트 실행 섹션의 내용을 테스트하기 위해서는 이클립스 3.2를 IDE로 사용하고 앤트 1.6 이상을 사용해야 한다. 이전 버전의 JUnit에 익숙하지 않더라도 본 튜토리얼을 이해하는 데에는 문제가 없다.

시스템 요구 사항

본 튜토리얼의 코드를 시험해보려면 썬의 JDK 1.5.0_09 이상 또는 자바 기술 1.5.0 SR3용 IBM 개발자 킷이 설치된 시스템이 필요하다. 이클립스에서 JUnit 4 실행 섹션의 경우 이클립스 3.2 이상이 설치된 시스템이 필요하다. 앤트 섹션의 경우 버전 1.6 이상이 필요하다.

본 튜토리얼에서 권장하는 시스템 구성은 다음과 같다.

  • 기본 메모리가 최소 500MB 이상이고 썬 JDK 1.5.0_09 이상 또는 자바 기술 1.5.0 SR3용 IBM 개발자 킷을 지원하는 시스템
  • 소프트웨어 컴포넌트 및 예제를 설치하기 위한 최소 20MB 이상의 하드 디스크 여유 공간

본 튜토리얼의 지침은 마이크로소프트 윈도우 운영 체제를 기반으로 한다. 또한 본 튜토리얼에서 다루는 모든 도구는 리눅스와 유닉스 시스템에서도 작동한다.

JUnit 4의 새로운 기능

자바 5 주석 덕분에 JUnit 4가 이전보다 더욱 가벼워졌고 유연해졌다. 일부 흥미로운 새 기능을 위해 이전의 엄격한 명명 규칙과 상속 계층 구조가 사라졌다. 다음은 JUnit 4의 새로운 기능을 간략히 설명해 놓은 목록이다.

  • 매개변수 테스트

  • 예외 테스트

  • 제한 시간 테스트

  • 유연한 픽스쳐

  • 테스트를 쉽게 무시하는 방법

  • 테스트를 논리적으로 그룹화하는 방법

이러한 기능과 더 많은 새로운 기능을 이후 섹션에서 설명하기에 앞서 JUnit 4의 가장 중요한 변경 사항에 대해 설명하겠다.

기존 버전의 문제

JUnit 4에 자바 5 주석 기능이 추가되기 전에 이 프레임워크에는 기능을 사용하는 데 반드시 필요한 두 가지 규칙이 존재했다. 첫 번째는 JUnit에서 논리적 테스트로 작동하도록 작성된 모든 메서드는 test라는 단어로 반드시 시작해야 한다는 것이다. testUserCreate와 같이 이 단어로 시작하는 모든 메서드는 테스트 메서드 이전 및 이후에 픽스쳐 실행을 보장하는 잘 정의된 테스트 프로세스에 따라 실행되었다. 두 번째 규칙은 JUnit에서 테스트를 포함하는 클래스 객체를 인식하기 위해 클래스 자체가 JUnit의 TestCase에서 확장되어야 한다는 점이다(또는 일부 파생). 이러한 두 가지 규칙을 위반하는 테스트는 실행할 수 없었다.

Listing 1은 JUnit 4 이전에 작성된 JUnit 테스트를 보여준다.

//Listing 1. 이렇게 어렵게 작성해야 할 필요가 있을까?
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import junit.framework.TestCase;
public class RegularExpressionTest extends TestCase {
    
 private String zipRegEx = "^\\d{5}([\\-]\\d{4})?$";
 private Pattern pattern;
 protected void setUp() throws Exception {
  this.pattern = Pattern.compile(this.zipRegEx);
 }
 public void testZipCode() throws Exception{         
  Matcher mtcher = this.pattern.matcher("22101");
  boolean isValid = mtcher.matches();        
  assertTrue("Pattern did not validate zip code", isValid);
 }
}

새로운 버전의 이점

자바 5 주석을 사용할 수 있는 JUnit 4에서는 이러한 규칙이 모두 사라졌다. 클래스 계층 구조는 더 이상 필요하지 않으며 테스트로 작동할 메서드도 새롭게 정의된 @Test 주석으로만 기술하면 된다.

많은 사람들이 JUnit 4에서 주석이 사용된 것은 .NET의 NUnit과 TestNG의 
영향을 받은 것이라고 말한다. 다른 테스트 프레임워크에서의 주석에 대한 
자세한 내용은 참고자료를 참조하기 바란다.

Listing 2는 Listing 1에 표시된 것과 동일한 테스트를 보여주지만 주석을 사용하여 다시 정의되어 있다.

//Listing 2. 주석을 사용한 테스트
port java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.assertTrue;
public class RegularExpressionTest {
 private static String zipRegEx = "^\\d{5}([\\-]\\d{4})?$";
 private static Pattern pattern;
 @BeforeClass
 public static void setUpBeforeClass() throws Exception {
  pattern = Pattern.compile(zipRegEx);
 }
 @Test
 public void verifyGoodZipCode() throws Exception{         
  Matcher mtcher = this.pattern.matcher("22101");
  boolean isValid = mtcher.matches();        
  assertTrue("Pattern did not validate zip code", isValid);
 }
}

Listing 2에서 언급한 테스트는 코드로 작성하기가 더 쉽지 않을 수도 있지만 확실한 것은 더 쉽게 이해할 수 있다는 것이다.

간단한 문서화

주석이 갖는 한 가지 유용한 점은 프레임워크의 내부 모델에 대한 자세한 이해 없이도 각 메서드의 사용 의도를 명확하게 문서화한다는 점이다. @Test로 테스트 메서드를 표시하는 것 이상으로 더 명확한 방법이 있을까? 이는 각 메서드가 전반적인 테스트 케이스에서 어떤 역할을 담당하는지만 이해하고 싶어도 JUnit 규칙에 대한 상당한 이해가 필요했던 기존 JUnit 스타일에 비해 크게 향상된 점이다.

이미 작성된 테스트를 파싱할 때에도 주석은 큰 도움이 되지만 테스트 작성 중에 추가 작업이 발생할 경우에는 더욱 필수적인 요소가 된다.

크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by 때찌1
TAG junit, junit4

2부 : 이클립스와 CVS를 이용한 팀 작업

Martin Pllu
martinpllu-AT-dev.java.net

소개


이 튜토리얼 시리즈는 java.net에 호스팅 되는 오픈 소스 자바 어플리케이션을 개발하기 위해 이클립스가 어떻게 사용될 수 있는지에 대해 설명하고 있다. 이 글은 자바에 대한 지식은 있지만 이클립스는 다뤄보지 않는 개발자들을 대상으로 한다.


문의 사항 또는 피드백이 있다면 튜토리얼의 포럼을 이용해보길 바란다.


튜토리얼의 1부에서는 이클립스에서 프로젝트를 생성하는 방법에 관해 살펴 보았다. 이번 글에서는 팀 개발을 위해서 CVS와 함께 이클립스를 사용하는 방법에 대하여 알아보도록 하자.




CVS의 소개


CVS (Concurrent Versioning System)는 80년대부터 개발되어 오고 있으며, 오픈 소스 개발을 위한 사실 상의 표준이 되어 버린 버전 제어 도구이다.

CVS는 클라이언트/서버 방식의 시스템이다. 온라인상의 대부분의 CVS 문서는, Unix/Linux 시스템에 설치된 cvs 명령어나 윈도우용 cvsnt 포트와 같은 명령 행 클라이언트에 대한 것들이다. 이클립스는 훌륭한 CVS GUI를 제공하는데다가 Java IDE와 밀접하게 통합되어 있어서 명령행 클라이언트를 이용하는 것보다 편리하다.

먼저 몇 가지 CVS 개념을 살펴보자. 이미 이러한 개념들을 잘 알고 있다면, 다음 절로 이동해도 좋다.

CVS 저장소


CVS 서버는 다수의 모듈(modules)을 포함하고 있는 저장소(repository)를 관리한다. java.net의 저장소의 경우 개별 프로젝트는 자신만의 모듈을 가지고 있다.

모듈은 디렉토리와 함께 버전이 부여된 파일을 포함하고 있다. 버전이 부여된 파일은 하나의 파일에 대한 이력을 나타내며, 다수의 개정 이력(revisions)을 포함한다. 파일에 변경이 가해질 때마다, 새로운 개정본(revision)이 생성된다. .java, .xml, .properties 등의 텍스트 기반 파일의 경우, 각각의 개정본은 이전 개정본에서 개정된 내역만을 포함하며, .exe, .jar, .pdf 등의 이진 파일의 경우, 각각의 개정본은 완전한 파일 내용을 포함하게 된다.

CVS는 또한 개정본 생성자와 개정본에 대한 주석, 그리고 개정본 번호도 함께 기록한다. 개정본 번호들은 자동으로 갱신되는데, 대부분의 경우, 파일의 첫번째 개정본 번호는 1.1이고 이후에는 1.2 등의 순서로 갱신된다.

repository
    moduleA/
          versionedFile1
             (revision number="1.1" user="dave" comment="Created")
             (revision number="1.2" user="mike" comment="New cache implementation")
             (revision number="1.3" user="lucy" comment="Fixed defect D113")
          versionedFile2
            
(revision number="1.1" user="dave" comment="Created")
             (revision number="1.2" user="dave" comment="Updated after feedback from Lucy")

          directory1/
                versionedFile3
                   (revision number="1.1" user="lucy" comment="Initial version")
                ...
    moduleB/
        ...

저장소 접속과 권한


CVS 클라이언트는 다양한 프로토콜을 사용하여 서버에 접속할 수 있다. 가장 일반적으로 사용되는 프로토콜은 다음과 같다:
  • pserver, CVS를 위한 간단한 프로토콜로 java.net을 이용하여 일반적인 작업을 하는 정도에 적합하다.
  • ssh, 추가적인 보안을 위해 SSL을 사용하지만, 보통은 인증서 생성과 같이, 대개 약간의 추가적인 설정 작업이 요구된다.
접근 권한은 모듈 단위로 설정된다. java.net 저장소의 경우, java.net에 로그인한 모든 사용자는 누구라도 모든 프로젝트 모듈들에 대해서 읽기 권한을 갖게 된다. 프로젝트 모듈에 대한 쓰기 권한은 모듈을 등록한 소유주나 프로젝트 개발자들에만 부여된다.

작업을 수행하기 위한 파일 사본 가져오기


저장소에 접속한 이후에는 하나 이상의 모듈을 가져올 수 있다. (역자 주:이를 check out 이라고 한다. 앞으로 보다 분명하게 행위 자체를 명시할 필요가 있는 경우 체크아웃이라고 표현하도록 하겠다.) 체크아웃을 하면 CVS에서 여러분의 컴퓨터로 모듈의 최신 내용이 복사되어, 여러분은 모듈 복사본을 가지고 원하는 작업을 수행할 수 있게 된다.


(저장소의 파일을)동시 수정 가능 잠금 방식(Optimistic locking)

버전 관리 도구에 따라 저장소에 위치한 파일을 한 사람이 가져가는 경우 해당 파일에 대해 잠금(locking)을 적용한다. 잠금 상태에서는 체크아웃 한 사람이 파일을 다시 저장소로 보내기 전까지는 아무도 파일에 대한 수정 작업을 할 수 없게 된다. (역자 주: 저장소로 파일을 복사하는 과정을 체크인이라고 한다.)

이렇게 엄격하게 잠금을 하는 방식의 버전 관리의 단점은, 종종 다른 개발자들의 수정 작업이 끝날 때까지 대기해야 한다는 점이다. 또한 체크아웃하고 나서 체크인하지 않을 경우, 자신도 모르게 수정도 하지 않는 파일을 잠궈 버리게 된다.

CVS는 잠금을 필요로 하지 않는 동시 수정 가능 방식을 사용한다. 개발자들은 CVS의 상충 사항을 발견하는 기능을 통해, CVS에서 가져온 파일을 동시에 수정할 수 있다. 상충되는 사항이 발견될 경우, CVS에서는 이를 해결해주는 도구를 제공한다. 이러한 방식은 위험해 보일 수 있지만, 실제 개발 환경에서는 매우 유용한 방식이다.

아예 상충이 나는 일 자체를 피하고 싶다면 CVS가 제공하는 파일에 대한 작업자 추적 정보를 이용할 수 있다.

동기화(synchronize), 갱신(update), 작업 공간의 수정 사항 반영(commit)


CVS 클라이언트를 사용하여 로컬 파일과 저장소 파일 사이의 동기화를 할 수 있다. 동기화 과정에서 로컬 파일과 CVS의 최신 모듈 사이의 차이점을 볼 수 있다. 이러한 차이들은 다음과 같이 분류 할 수 있다:
  • Incoming changes - 저장소에 변경이 생겼지만 로컬 파일에는 반영이 안된 경우.
  • Outgoing changes - 여러분의 로컬 파일에 변경이 생겼지만 저장소에는 반영이 안된 경우.
CVS 클라이언트에서 저장소의 변경 사항(incoming changes)의 일부 혹은 전부를 반영하여 로컬 파일을 갱신(update)할 수 있다. 모듈에 대한 쓰기 권한을 가지고 있는 경우, 로컬 파일의 변경 사항(outgoing changes) 중 필요한 만큼을 저장소에 반영(commit)하는 것도 가능하다.

상충


CVS는 자동적으로 저장소의 변경 사항과 작업 공간의 변경 사항 가운데서 상충하는 내용을 감지한다. 개발자 A와 B가 동시에 같은 파일을 수정하고, A가 먼저 이를 저장소에 반영하고 난 후에 곧바로 B가 자신이 변경한 내용을 저장소에 반영하려고 할 때 이러한 상충이 발생한다.

아래 다이어그램을 보자. 숫자로 된 화살표는 일의 발생 순서를 나타낸다. Alice가 Bob보다 먼저 변경사항을 저장소에 반영하고 Bob이 동기화를 하게 되면 Bob은 상충이 발생했다는 것을 알게 된다


상충되는 변경 사항 해결하기


파일에서 상충되는 변경 사항이 발견 될 경우, 파일을 저장소에 반영하기 전에 상충되는 변경 사항에 대해 병합(merge)을 수행해야 한다. CVS는 한 파일의 서로 다른 행의 변경 사항들에 대한 병합은 자동으로 수행한다. 다만 같은 행에서 변경이 발생할 경우, 개발자가 직접 병합 을 실행해야 한다. 나중에 보겠지만 이크립스는 개발자의 병합을 돕는 매우 훌륭한 GUI를 제공한다.

수정 작업자 추적


CVS에서는 동시 수정 가능 잠금 방식(optimistic locking)을 사용하여 누구라도 CVS 저장소에 접근에 동시에 파일을 수정 가능하지만, 누가 어떤 것을 수정하고 있는지 알아낼 수 있도 있다. 파일 수정 작업을 시작할 경우, 클라이언트가 이 사실을 서버로 알리도록 설정될 수 있다. 만약 누군가 파일을 수정하려고 하면 이미 그 파일이 수정 중이라는 경고를 받을 수 있다. 이것은 수정 사항의 상충을 피하는 유용한 방법이다.

태그


개발할 때 여러분은 종종 다음과 같은 이정표(milestones)들에 도달하게 된다:
  • "기본 시스템은 작동중이고 모든 테스트는 통과 했음"
  • "account 작업 전의 리팩토링"
  • "account 작업 후의 리팩토링"
  • "릴리즈 1.4"
  • "발견된 D113에 대한 수정 완료"
이러한 이정표를 표시해두는 것(marking)은, 변경 사항을 추적하고, 필요한 경우, 이전 상태로 파일들을 되돌릴 수 있도록 보장해주는데 있어 필수적이다.

CVS에서는 모듈에 태그를 붙임으로써 이정표를 표시하게 된다. 태그는 모듈 안에 있는 각각의 파일들에 대한 단일 개정본(single revision)을 취합한다.


태그의 이름은 이정표와 관련있다. ALL_TESTS_PASS와 같은 것은 직관적인 예다.

사용자는 모듈의 태그가 부여된 개정본을 저장소에서 가져올 수 있다. 설령 태그가 삭제되거나 변경되었다 하더라도 태그가 붙었던 버전의 모듈 내용물은 항상 같을 것이다.

분기(branches)와 HEAD


분기(branch)는 일련의 독립적인 개발 흐름을 유지하기 위해 쓰인다. 기본적으로 저장소의 내용과 작업 공간의 내용의 동기화를 위해 update나 commit 명령을 사용하면, HEAD라고 불리는 기본 분기(trunk 라고도 불리운다) 상에서 작업이 이루어진다.

사용자는 필요에 따라 새로운 분기를 생성할 수 있다. 분기는 항상 루트 태그(root tag)라 불리는 태그부터 반드시 시작되어야 하며, 루트 태그의 이름에는 특별한 규칙이 없다.

분기를 사용하는 목적인 대개 공식 배포(release)의 병렬적인 개발을 관리하기 위함이다. 예를 들어 어플리케이션의 1.4 버전을 공식 배포(Release) 했고 계속해서 HEAD 분기에서 2.0 버전을 개발하기 시작했다고 해보자. 만약 치명적인 버그가 1.4 에서 발견되었다는 사용자의 보고가 있다면 RELEASE1-4-0을 루트 태그로 사용하여 분기를 만들 수 있다.


분기 상에서 작업을 시작했을 때, 모듈 내용은 1.4 정식 배포 버전과 완전 동일할 것이다. 이후부터 저장소와의 동기화는 해당 분기에만 적용될 것이다.

태그는 HEAD에서 정의되는 것과 마찬가지로 다른 분기에서도 정의 될 수 있다. 가능하다면 한 수준 정도(one level)의 분기를 고수하는 것이 가장 좋지만, HEAD 이외의 분기에서도 또 다른 분기를 만들 수 있다.

 
태그가 부여된 버전은 변하지 않는다.

모듈의 태그가 부여된 버전에 대해서는 변경 사항을 저장소에 반영할 수 없고, 수정을 하려면 루트 태그를 사용하여 분기를 생성해야 한다.

CVS에서는 분기에 속한 변경 사항들만 저장소에 반영 할 수 있다. HEAD는 기본 분기로 특수한 형태의 분기이다.

 

분기의 병합


필요한 경우에 변경 사항을 특정 분기에서 다른 분기로 병합 할 수 있다. 예를 들어 위 그림에서, 버그를 수정한 1.4.1의 변경 사항을 2.0.0 정식 배포판에 포함시키기 위해서 HEAD 분기로 병합을 할 수 있다. 두 개의 분기 사이의 병합은 저장소와 작업공간의 변경 사항 동기화와 유사하며, 마찬가지로 상충이 발생하는 경우는 이클립스 GUI를 활용하면 좀 더 수월하게 해결할 수 있다.

이력 및 차이점 보기


CVS는 개별 파일이나 파일 조합의 변경 이력을 볼 수 있는 다양한 방법을 제공한다. 또한, CVS를 이용하면 각각의 수정본, 태그 그리고 분기들 사이의 차이를 볼 수 있다.

참고 자료





2부 : 이클립스와 CVS를 이용한 팀 작업



선행 사항


  • 이클립스가 설치 되어 있어야 한다. 1부의 선행 사항을 참조하라.
  • java.net의 CVS 저장소에 연결하기 위해서는 최소한 읽기 권한을 갖는 사용자 계정이 필요하다. 쓰기 권한은 프로젝트 소유주나 개발자들만이 갖는다.
  • 방화벽때문에 원격으로 CVS 저장소에 접근하지 못할 수도 있다 (CVS는 2401 포트를 사용한다).
  • 여러분이 프로젝트의 소유주이며 CVS를 이용하여 프로젝트의 홈페이지를 관리하기 원한다면 프로젝트 preference의 설정을 다음과 같이 바꾼다.

 
  • 이클립스의 preferences를 열고(Window->Preferences), 아래와 같이 CVS 통신 해제 시간을 600초로 설정한다:



여러분이 프로젝트의 소유주이고 아직 CVS에 프로젝트를 공유하지 않았다면 다음 절의 설명을 참조하라. 이미 특정 프로젝트의 개발자이거나 아직 참여하지 않은 프로젝트 내용을 조회할 수 있는 권한만을 갖고자 한다면 CVS 저장소의 프로젝트를 로컬 작업공간에 복사하기를 설명하는 절로 바로 이동해도 된다.

처음으로 프로젝트를 CVS에 등록하기(프로젝트 소유주만 가능)


지난 튜토리얼의 마지막 부분에서 만든 프로젝트의 이름은 javatools-demo였다. 그리고 약간의 코드와 JUnit 테스트 코드를 작성하였다.



java.net의 CVS 저장소에 접속하여 이 프로젝트를 공유해보자. 먼저 프로젝트를 선택하고, 마우스 오른쪽 버튼을 클릭하고 Team->Share Project...를 선택한다.



Share Project 대화 창에서 저장소의 상세 정보를 입력한다 (아래 표를 보라).



설정 항목
Hostcvs.dev.java.net

Repository Path

/cvs
Userjava.net의 사용자이름(역자 주:java.net 가입이 필요함)
Passwordjava.net의 사용자 비밀 번호
Connection typepserver (추가적인 보안을 위하여ssh 를 선택하는 것도 좋다. 추가적인 설명을 요하는 경우 이 글 을 참고하여라).
Port그대로 사용한다.
Save Password비밀번호를 이클립스가 기억하게 할 것인지 결정하라.

표 1 - CVS 연결 설정


"Next"를 클릭한다.



"Next"를 클릭한다.



"HEAD"를 선택하고 "Next"를 클릭한다. 마법사의 다음 페이지는 로컬 프로젝트 내용과 저장소에 있는 프로젝트 내용 사이의 차이점을 모두 보여준다.



프로젝트에서 마우스 오른쪽 버튼을 클릭하고 "Commit"을 선택한다. 이 명령은 이클립스의 모든 로컬 파일을 CVS에 저장한다.



다음 대화창은 CVS에 로컬 파일이 처음으로 추가된다는 것을 알려준다. "Yes"를 선택한다.

이제 첫 수정본들에 대한 주석을 작성할 수 있다. 주석에 대한 작성이 끝나면 "OK"를 클릭한다.



이클립스가 모든 파일을 CVS로 저장 할 것이다.

"Use your project's www/index.html file as the project's description" 박스를 체크했다고 가정하면(선행 사항을 확인할 것.) 저장소의 변경 사항이 나열된 www/index.html 파일을 볼 수 있다. 프로젝트에서 마우스 오른쪽 버튼을 클릭하고 "Update"를 선택하여 CVS로 부터 이 파일을 작업 공간에 복사한다.



모든 것이 끝났다. 이제 여러분의 프로젝트는 이제 CVS와 연결되었다.


프로젝트와 프로젝트 내부의 모든 내용들에 이제 오렌지색 실린더가 표시되어 있다. 이는 CVS와 "연결되었음"을 나타내고 있는 것이다.




CVS 저장소의 프로젝트를 로컬 작업 공간에 복사하기 (프로젝트 개발자나 읽기 권한만을 가진 경우)


여러분이 프로젝트의 소유주이고 앞 절의 설명을 따라했었다면 이 절은 지나쳐도 무방하다.

Java perspective에서, Package Explorer 상에서 마우스 오른쪽 버튼을 클릭하고 "Import!!..."를 선택한다.



"Checkout Projects from CVS"를 선택하고 "Next"를 누른다.


다음 페이지에서 표 1을 참고하여 CVS 저장소와 사용자 상세 설정을 채워 넣는다.



"Next"를 누른다. 다음 페이지에서, 저장소로부터 복사해 오려는 프로젝트의 이름을 입력한다.


프로젝트의 이름을 모른다면 아래와 같이 "Use an existing module"을 선택한다.

이 목록을 가져오는데 몇 분이 소요 될 수 있으며
, 이 과정 동안 이클립스가 응답 없음으로 변할 수도 있다. 그러므로 위에서와 같이 프로젝트 이름을 직접 입력할 것을 권장한다.



"Next"를 누른다. 다음 대화창에서 "Check out as a project in the workspace"를 선택하고 "Finish"를 누른다.



이제 이클립스가 여러분의 작업공간으로 프로젝트를 복사해올 것이다.



이 작업이 끝나면 작업 공간에서 프로젝트를 확인할 수 있다. 파일과 디렉토리 옆에 오렌지색 실린더가 붙어 있다면 이는 CVS와 연결되어 있다는 것을 나타낸다.


CVS 저장소 서버의 호스트 이름은 프로젝트 옆에 표시된다. 프로젝트의 각 파일은 그 파일이 이진 파일(binary)인지 텍스트 파일(ASCII)인지 표시한다. 대부분의 경우 기본 설정을 그대로 두어도 무방하지만, CVS preferences(Window->Preferences)를 통해 파일 형태에 따른 분류를 조정할 수 있다.

 


이클립스 프로젝트가 아닌 프로젝트를 작업 영역에 복사해오기

java.net의 모든 프로젝트가 이클립스 프로젝트인 것은 아닌다. 이클립스 프로젝트인지 알아보는 가장 빠른 방법은 Package Explorer에 있는 프로젝트 아이콘을 보는 것이다. 만약 파란색 "J" 심볼(javatools-demo처럼)이 있다면 이는 이클립스 프로젝트이다. 그러므로, 이클립스가 제공하는 자바 개발 기능을 바로 사용할 수 있다.

만약 "J" 심볼(아래의 javacc와 같이)이 없다면 불행하게도 이클립스의 자바 개발 기능을 사용하기 위해 몇가지 선행 작업을 해야 한다.



먼저 프로젝트를 선택한 상태에서 마우스 오른쪽 버튼을 클릭한 다음 "Delete"를 선택하여 프로젝트를 삭제한다. 대화 상자가 떴을 때, "Also delete contents under..."를 선택한다.

 

프로젝트를 로컬 작업 공간으로 가져오기 위해 CVS 저장소의 프로젝트를 로컬 작업 공간에 복사하기절의 내용을 다시 수행한다. 이 때 Check Out As 페이지에서 "Check out as a project configured using the New Project Wizard"를 선택한다.



이제 "Finish"를 클릭하면 새로운 프로젝트 마법사가 나타날 것이다 .

"Java Project"를 선택하고 "Next"를 누른다.

 

다음 페이지에서 프로젝트의 이름을 적고 "Finish"를 클릭한다.



이제 Package Explorer에서 프로젝트 내용을 볼 수 있지만 아마도 많은 수의 빨간색 X표시를 볼 게 될 것이다.

 

만약 이러한 상황이 발생한다면, Problems 뷰를 살펴보아라. 아마도 프로젝트의 자바 컴파일 에러를 볼 수 있을 것이다.



새로운 소스 폴더와 새로운 JAR 추가를 위해 프로젝트 클래스 패스를 설정해야 할 것이다. 이 경우, 1부에서 했던 클래스 패스 설정 부분을 수행하면 된다. 이 부분에 대하여는 자세히 다루진 않겠다.

자바 1.5(혹은 5.0) 프로젝트는 Eclipse 3.1을 필요로 한다. 이클립스 다운로드 페이지에서 이를 구할 수 있을 것이다.

복잡한 프로젝트에 있어서는 클래스패스 설정이 상당히 번거로운 일이 될 수 있다. 만약 이것들이 분명하지 않은 경우, 프로젝트 소유주와 클래스패스에 대해서 논의를 해볼 필요가 있다.

이클립스는 .project.classpath 라는 이름을 가진 파일에 프로젝트와 프로젝트의 클래스 패스에 관한 정보를 저장한다. 이 두 파일은 프로젝트의 최상위 폴더에 있다. 만약 여러분이 프로젝트 개발자인 경우, 여러분이 프로젝트의 클래스 패스를 설정하고, 모든 것들을 제대로 컴파일 한 후, 이 두 파일을 CVS에 반영 할 수 있다. 여러분이 개발자가 아니라 하더라도, 소유주에게 이 파일들을 보내어 이들을 CVS 저장소에 반영하거나 다른 방법으로 이들을 사용할 수 있도록 만들어 달라고 부탁할 수도 있다. 다양한 종류의 IDE가 활용되는 프로젝트라면 이들에 대한 고려도 수반되어야 한다.

필자가 보았던 몇몇 프로젝트들에서 CVS가 .project.classpath파일을 무시하도록 설정해두고 있다. 필자는 그 이유를 잘 모르겠다. 만약 누군가 그 이유를 잘알고 있다면 포럼에 의견을 올려 공유해주길 바란다.



저장소와 작업 공간의 동기화 - 변경 내역에 상충되는 것이 없는 경우


이클립스의 리팩토링 도구를 이용하여 org.jtdemo.disply.CurrencyFomatter 클래스의 이름을 org.jtdemo.display.CurrencyForma으로 변경한다고 가정해보자. 이클립스는 자동적으로 연관된 클래스 파일에 바뀐 이름을 적용한다.

Package Explorer에서, Account.javaTextDisplay.java 파일에 '크다' 기호(>)가 붙게 된다. 이것은 로컬 작업 영역에서 변경이 일어났다는 것을 가리킨다. CurrnecyFormat.java물음표(?) 표시가 붙는데, 이는 CVS가 전혀 알지 못하는 새로운 파일이라는 것을 나타낸다.


파일의 이름을 변경한 경우, CVS는 새로운 이름의 파일이 만들어지고, 기존 파일은 삭제된 것으로 간주한다.






프로젝트에서 마우스 오른쪽 버튼을 클릭하여 Team->Synchronise with Repository를 선택한다.



 대화창이 열린다.

 

필자는 보통 "Remember my decision"과 "No"를 선택한다.(역자 주:각자의 기호에 따라 무엇을 선택하더라도 큰 문제는 없다.)

Synchronise 뷰가 나타나게 될 것이다. 이 화면은 저장소에 있는 프로젝트와 작업 영역의 복사본 파일 사이의 차이점을 모두 보여준다.



CVS에 추가할 변경 사항은 다음과 같다.
  • Account.java가 변경되었다.
  • CurrencyFormat.java가 생성되었다.
  • CurrencyFormatter.java가 삭제되었다.
또한 로컬 작업 영역에 반영해야 할 변경 사항들은 다음과 같다.
  • TransactionManager.java가 변경되었다.
  • TransactionTest.java가 생성되었다.
로컬 작업 영역에 반영해야 하는 변경 사항은 누가 수정했는지 살펴보려면, 변경된 파일을 선택하고, 마우스 오른쪽 버튼을 클릭한 후, "Show in Resource History"를 선택한다.




다음의 CVS Resource History 뷰는 spiffy라는 프로젝트의 다른 개발자가 이를 변경했다는 사실을 보여주고 있다.



Synchronise 뷰에서 아무 파일이나 더블 클릭 해보면 로컬 파일과 저장소 파일 사이의 차이점을 볼 수 있다. TransactionManager.java 파일의 차이점을 확인해 보자.

"Java Source Compare"와 "Java Structure Compare" 모두 차이점을 보여준다. 멋지지 않은가!



로컬 작업 공간에 저장소의 변경 사항을 반영해보자. Synchronise 뷰에서, 프로젝트를 선택하고 마우스 오른쪽 버튼을 클릭하여 "Update"를 선택한다. 이렇게 하면 저장소의 모든 변경 사항이 작업 공간에 반영된다.



파일을 전부 받으면 동기화 뷰에서 변경 사항이 사라지게 된다.



변경된 파일들은 이제 작업 공간에 존재한다. TransactionManager.java 파일의 수정본 버전이 1.2가 된 것을 확인할 수 있다.




항상 저장소에 변경 사항을 반영하기 전에 로컬 작업 영역의 파일을 먼저 갱신하라.

항상 저장소에 변경 사항을 반영하기 전에 작업 공간의 파일을 저장소의 최근 변경 사항을 반영하도록 갱신하고, 다시 테스트를 수행 해야 한다. 이러한 절차가 저장소의 최근 변경 사항에 본인의 수정 사항을 정확하게 반영했음을 보장해준다.



이제 작업 영역의 변경 사항을 저장소에 반영한다. Synchronise 뷰에서 프로젝트를 선택하고, 마우스 오른쪽 버튼을 클릭한 후 "Commit"을 선택한다.



CurrencyFormat.java가 아직 CVS에 추가되지 않았다는 경고가 발생한다. "Yes"를 클릭한다.



변경 사항에 대한 주석을 적는다. java.net 저장소는 몇 가지 템플릿과 함께 자주 사용되는 주석들이 설정되어 있다. 이 템플릿은 프로젝트의 온라인 이슈 관리기 안에 있는 관련된 이슈 사항들에 변화가 있을때 사용된다. https://javatools-demo.dev.java.net/servlets/ProjectIssues가 그 예이다.



그러나 프로젝트들은 아직 이슈 추적 단계에 들어가지 않았기 때문에 전부 선택(Ctrl-A) 하고 자유로운 형태로 주석을 덮어쓴다.



Synchronise 뷰는 이제 우리의 작업 영역과 저장소 간에 어떠한 차이점도 존재하지 않는다는 것을 보여주고 있다.



저장소와 작업 공간의 동기화 - 변경 내역에 상충되는 것이 있는 경우


javatools-demo 예제 어플리케이션에는, martinplluspiffy라는 두 명의 개발자가 참여하고 있다.

martinpllu는 방금 막 Transaction 객체가 문자열 형태로 표현되도록 로직을 리팩토링했다. 즉, 이 로직을TextDisplay.update() 메소드로부터 Transaction.toString() 메소드로 옮겼다. 그런 후, 그는 변경된 파일을 저장소에 반영했다.



반면, spiffyCurrencyFormat 파일을 삭제하였고, CurrencyFormat.format() 라는 메소드 호출 코드를 java.text.NumberFormat.getCurrencyInstance().format() 메소드 호출 코드로 바꾸어 버렸다.

spiffy가 작업 영역과 저장소의 동기화를 시도하자, 그는 저장소와 본인의 작업 영역의 파일 사이에서 상충되는 변경 사항이 있음을 발견하였다.



Account.java 파일을 더블 클릭을 하자, toString() 메소드가 로컬의 작업 영역에서 변경되었고, getName() 메소드가 다른 곳에서 추가되어서 CVS 저장소에 반영되었다는 사실을 보여주고 있다.





상충되는 부분이 있는데, 그 변경 사항이 서로 다른 행에 위치할 경우, CVS는 자동으로 변경 사항에 대한 병합을 수행한다.


TextDisplay.java 파일을 더블 클릭하면 update() 메소드가 작업 영역에서도 변경되었고, 저장소에서도 변경되었다는 사실을 보여주고 있다.





상충되는 부분이 있는데, 그 변경 사항이 동일한 행에 위치할 경우, 개발자가 직접 수정 사항에 대한 병합을 수행해야 한다.


spiffy는 프로젝트를 선택하고 마우스 우측 버튼을 클릭하여 "Update"를 선택했다.  그러자, 다음과 같은 경고 상자가 나타났다.



spiffy는 "OK"를 선택했다. 그러자, Account.java에 자동으로 병합이 적용되는 변경 사항뿐만 아니라, CVS 저장소의 모든 변경 사항들이 나타났다.


Account.java 파일의 변경 사항 중에서 저장소에 반영되지 않은 것은 하나(toString() 메소드를 추가한 것)뿐이다. 저장소의 변경 사항은 자동으로 병합되었다.

이제 spiffyTextDisplay.java 파일에서 상충되는 변경 사항만 직접 해결해 주면 된다. Java Source Compare 뷰어에서 Show Ancestor Pane 버튼 을 클릭하면, 변경된 두 개의 파일의 공통적인 원본을 볼 수 있다.



spiffyTransaction.toString() 메소드에 NumberFormat.getCurrencyInstance().format() 을 추가할 필요가 있다는 것을 알게 되어 Transaction.java 파일에 이를 반영하고, "Copy current change from right to left" 버튼 을 클릭하여, TextDisplay.java 파일의 변경 내용을 갱신하였다.

이 작업을 마친 후, spiffy는 TextDisplay.java 파일을 선택하고 "Mark as merged" 메뉴를 선택하였다.

 

TextDisplay.java 파일은 이제 작업 공간에만 변경이 가해진 다른 파일과 동일하게 보인다.



spiffy는 상충을 해결하고 나서 문제가 없음을 확인하기 위해서 관련된 모든 테스트를 수행하였다. 그리고 나서 저장소에 변경사항을 반영하였다.

주기적인 갱신


주기적인 갱신을 통해 상충되는 부분을 최소화하고 작업영역의 코드를 최신으로 유지하는 것이 좋다.

이클립스는 주기적으로 저장소와 작업 공간을 동기화 하는 자동화 기능을 제공한다. 이 때, 저장소의 변경 사항이 자동으로 갱신되지는 않는다 (이는 다소 위험을 초래할 수 있기 때문이다). 이클립스는 단순히 동기화를 실행하고 그 결과를 Synchronise 뷰에 보여준다.

일정에 따른 동기화를 수행하려면, Synchronise 뷰의 메뉴에서 "Schedule..."을 선택한다.



원하는 주기를 선택하고 OK를 클릭한다.



태그


프로젝트에서 오른쪽 마우스 버튼을 클릭하고, Team->Tag as Version 메뉴를 선택하면 프로젝트에 태그를 붙일 수 있다.



태그 이름을 입력한다.



"Move tag if it already exists" 옵션을 선택하면 기존의 태그를 이동시킬 수 있다.

이제 태그를 다양한 방법으로 사용할 수 있다. 예를 들면, 프로젝트에서 오른쪽 마우스 버튼을 클릭하고 Compare With->Another Branch or Version을 선택하면 "Versions" 아래에 태그가 나타난다.



또한 Replace With->Another Branch or Version을 선택하는 방법도 있다. 이렇게 하면 태그가 부여된 버전의 프로젝트 산출물을 작업 공간으로 가져온다:



비어있는 작업 공간으로 태그가 부여된 버전의 프로젝트를 불러오려면, 보통 체크아웃 마법사( checkout wizard)를 이용한다 (Checking out an existing project를 참조하여라). 그러나, 이 경우, 아래에 보여지는 것처럼 태그가 부여된 버전을 선택한다.




태그가 부여된 버전은 변하지 않는다.

만약 태그가 부여된 프로젝트를 작업 공간으로 가져올 경우, 이 프로젝트에 변경을 가할 수 없다는 사실을 명심하여라. 만약 변경 사항을 저장소로 반영하려 하게 되면, 다음과 같은 에러가 발생하는 것을 보게 될 것이다:

 

만약 태그 버전에 따라 변경을 가하고 싶다면, 태그를 루트로 사용하여 분기를 생성할 필요가 있다.




항상 프로젝트 수준에서 태그를 부여하는 것이 좋다.

비록 개별적인 파일, 패키지, 소스 폴더 등에 태그를 붙이는 것도 가능하지만, 항상 프로젝트 수준에서 태그를 부여하여라. 동일한 한 프로젝트 안에서 서로 다른 태그가 부여된 파일들과 폴더들을 섞어서 가져올 경우 혼란을 가져다 줄 수 있다. CVS는 이를 제대로 처리해주지 않는다.


CVS Repository Exploring perspective


"Window->Open Perspective->Other" 메뉴에서 CVS Repository Exploring perspective를 선택한다:



CVS Repositories 뷰는 사용 가능한 CVS 연결들을 보여준다.



저장소 내용들을 살펴보는 방법으로는 네 가지가 있다:



  • HEAD는 각 모듈의 가장 최신 내용을 보여준다.
  • Branches는 각 모듈에 위해 생성되었던 분기들을 보여준다.
  • Versions는 각 모듈의 태그 버전을 보여준다.
  • Dates는 사용자가 설정한 날짜에 지정한 모듈들의 내용을 보여준다.

이 뒤의 내용을 진행해 나가기 전에, CVS 통신 해제 시간을 600초로 설정했는지 확인한다 (선행 사항 부분을 참고한다).


"HEAD"를 펼친다 (프로젝트 목록을 가져오는데 다소 시간이 걸릴 수 있다).



아래로 스크롤 해가면서 자신의 프로젝트를 찾는다. 여기에서 가장 최근의 내용들을 볼 수 있을 것이다.





Branches, Versions 및 Dates 목록에 관한 부분은 여러분의 몫으로 남겨놓도록 하겠다.

분기


javatools-demo 개발자들은 공식 배포판(release) 0.1 빌드를 하나 작성하였고, 이 프로젝트에 V0-1-1이라는 태그를 부여하였다. 이것은 베타 테스트를 위해 배포되었으며, HEAD 상에서는 공식 배포판 0.2를 위한 개발이 진행되고 있다.
베타 테스터들은 급하게 변경을 요청하였다. 이 변경 사항에 대해서는 spiffy에게 맡겨졌다. spiffy는 우선 작업 공간으로 V0-1-1을 불러왔다:



그런 후, spiffy는 Team->Branch를 선택하였다:




새로운 분기에 대한 이름은 branchR0-1이 될 것이다.



spiffy가 OK를 클릭하게 되면, 이 분기는 작업 공간으로 불려지며, spiffy는 이에 대해 작업을 시작할 수 있게 된다.

spiffy가 변경 사항을 저장소에 반영하면, 이 변경 사항은 해당 분기에만 반영된다. 이 분기 상에서의 개발은 HEAD 상에서 진행되는 개발돠는 완전히 분리되어 있다.

변경 사항에 대한 작업 수행, 테스트 및 분기로의 반영이 이루어진 후, 이 어플리케이션은 빌드되고 테스터에게로 넘겨진다. 이 분기에는 V0-1-2이라는 태그를 부여하였는데, 이는 "공식 배포판 1.0의 빌드 2"라는 의미이다.

변경 사항들을 공식 배포판 0.2에 포함시키기 위해서는, 이 분기를 HEAD에 병합해야 할 것이다. spiffy는 먼저 Replace with->Another branch or version에서 HEAD를 선택하고 이를 자신의 작업 공간으로 불러왔다. 그런 후, Team->Merge를 선택하였다:



분기 루트 태그는 V0-1-1이었다.



"from" 분기는 branchR0-1이다:



이 분기는 이제 Synchronise 뷰를 사용하여 HEAD에 병합될 수 있다. 보면 알 수 있겠지만, 분기로부터의 저장소 변경 사항은 두 가지이다:



update를 선택하여 변경 사항들을 가져온다.



이제 변경 사항들은 작업 공간에 존재한다...



... 그런 후, 평소와 같이 변경 사항들이 저장소에 반영될 수 있다.

Watch/Edit를 통해 이력 정보(누가 무엇을 수정하고 있는지에 관한 정보) 추적하기


Introduction to CVS에서 언급했듯이, 누가 무엇을 수정하고 있는지 추적하도록 CVS를 설정할 수 있다.

Window->Preferences를 통한 preferences에서, "Configure projects to use Watch/Edit on checkout"을 선택한다.



프로젝트에 있는 어떠한 파일이라도 변경하게 되면, 변경한 사람은 해당 파일의 수정자(editor)로 등록된다. 이 파일 타인에 의해 변경되어 반영되거나, 해당 파일을 "unedit" (파일을 마우스 우측 버튼을 클릭한 후, Team->Unedit를 선택한다) 하지 않는 한, 해당 파일의 수정자 등록 정보는 그대로 남아있게 된다.

마우스 우측 버튼을 클릭해 Team->Show Editors를 선택하면, 누가 어떠한 파일을 수정하고 있는지 알아볼 수 있다:



만약 다른 누군가가 해당 파일을 수정하려 한다면, 이들은 다음과 같은 메시지를 보게 될 것이다:




이러한 방식 모든 프로젝트의 개발자가 "Configure projects to use Watch/Edit on checkout"을 사용할 경우에만 제대로 적용되어 동작한다.

CVS 웹 인터페이스


java.net에서는 CVS 저장소에 대해 읽기 전용 권한으로 접근할 수 있도록 해주는 웹 인터페이스를 제공하고 있다. 프로젝트 홈페이지에서 Version control - CVS 링크를 통해 이에 접근할 수 있다.




디렉토리들의 안에 들어 있는 내용들을 쭉 살펴볼 수 있다:



한 파일을 클릭하게 되면, 이 파일의 개정본 이력을 볼 수 있다:





HEAD로의 링크 구성

CVS에서 파일의 최신 버전을 항상 가리키도록 링크를 작성할 수 있다. 파이어폭스 브라우저의 경우, "Direct link to HEAD" 링크 상에서 마우스 우측 버튼을 클릭하고 "Copy Link Location"을 선택한다. 인터넷 익스플로러 브라우저의 경우, "Copy Shortcut"을 선택한다. 이는 해당 URL을 클립 보드록 복사한다.

예를 들면, 다음은 Account.java의 가장 최신 버전에 대한 링크이다: https://javatools-demo.dev.java.net/source/browse/javatools-demo/src/org/jtdemo/Account.java?view=markup

프로젝트의 웹 페이지 수정하기


여러분이나 프로젝트 소유주가 "Use your project's www/index.html file as the project's description" 박스(see 선행 사항을 살펴보아라)를 선택했다면, 프로젝트는 www/index.html 파일을 포함하게 된다.



다음은 예제 프로젝트인 https://javatools-demo.dev.java.net의 메인 프로젝트 페이지이다. 만약 이를 수정하고 CVS 저장소로 반영하게 되면, 해당 변경 사항은 즉시 온라인 상에 반영된다.

만약 더 많은 페이지가 필요하다면, www 폴더 아래에 필요한 페이지들을 두고 이들에 대한 링크를 포함시키면 된다:



만약 홈페이지가 복잡하다면, HTML 편집기를 사용하고 싶을 수도 있을 것이다. 이클립스를 위한 몇몇 HTML 편집기 플러그 인 들을 사용하거나 외부 편집기를 사용할 수도 있다 (Mozilla Composer는 매우 훌륭한 WYSIWYG 편집기이다).


외부 편집기 사용하기

만약 외부 편집기를 사용해 프로젝트 파일들을 편집하고 있다면, 이클립스에서 변경된 내용을 감지하기 위해 File->Refresh를 통해 프로젝트를 새로 고침(refresh)해야 한다.

CVS 키워드


키워드들을 사용해 CVS가 파일들에 동적으로 유용한 정보를 추가하도록 만들 수 있다. 이러한 정보의 예로는 마지막 가져온 날짜/시간에 관한 것을 들 수 있다. 자세한 내용은 도구 조언 부분을 참조하여라.

요약


여러분이 이클립스와 CVS 사용법에 대해서 충분히 이해했기를 바란다. 튜토리얼에 대한 질문이나 의견이 있다면 포럼을 이용하길 바란다.

다음 글에서는, 자동 빌드와 테스트 수행을 위해 이클립스에서 Ant와 JUnit을 사용하는 방법에 대해 살펴볼 것이다.

저자와 역자


Martin Pllu: 스코틀랜드 에든버러에 있는 Standard Life Assurance사의 Messaging & Java Infrastructure 팀 멤버이다. java.net에 호스트 되어있는 leafcuttertracetest같은 오픈 소스 어플리케이션의 개발자이기도 하다.

 

출처:다음카페

 

 

이올린에 북마크하기(0) 이올린에 추천하기(0)
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by 때찌1
저장소 공간:해당프로젝트명으로 저장소를 새로 할당 한다.

------------------------------------------------------------
# su - cvs
$ cvs -d /home/cvs/프로젝트명 init


계정

1.시스템사용자 등록

2.암호화된 패스워드 설정

3.CVS사용자 등록

------------------------------------------------------------

adduser 사용자명

cryptpasswd 패스워드

->암호화된 패스워드가 나온다.

vi %CVS_HOME%/CVSROOT/passwd

아이디:암호화된 패스워드:cvs(예:ddaejji:j3K9RCNhkOtwQ:cvs)형식으로 추가

 

- /usr/local/bin/cryptpasswd 파일을 하나 vi로 새로 만들고 실행권한을 주고 실행한다.

 

#!/usr/bin/env perl
srand(time());
$randletter = "(int (rand (26)) + (int(rand(1) + .5)% 2?65:97))";
$salt = sprintf("%c%c",eval$randletter,eval$randletter);
$plaintext =shift;
$crypttext = crypt ($plaintext,$salt);

print "${crypttext} ";

------------------------------------------------------------

이올린에 북마크하기(0) 이올린에 추천하기(0)
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by 때찌1

java api의 String을 보면 문자열 처리하기에는 제공하는 기능들이 너무 빈약합니다.

그래서 프로그래머들은 자기 나름데로의 독특한 Utils를 만들어 사용하지요.

하지만 Commons-lang에서 제공하는 StringUtils를 사용한다면 더이상 자신만의 Utils를 사용할 필요가 없습니다. 여기 모두 ㄷ ㅏ ~ 있기 때문이지요 ^^

한가지 더 장점이 있다면 String의 null 값때문에 더이상 NullPointerException을 생각하지 않아도 된다는 것입니다. StringUtils에서 ㄷ ㅏ ~ 알아서 해주기 때문입니다.

 

그럼 어떠한 기능들의 함수가 있는지 알아봅시다

 

반환값 함수명 파라미터
String abbreviate String str, int maxWidth
"…"를 포함한 지정한 사이즈로 문자열을 축소한다 maxWidth은 4보다 커야한다
String abbreviate String str, int offset, int maxWidth
"…"를 포함한 지정한 사이즈로 문자열을 앞/뒤로 축소한다 offset은 7보다 커야한다
String capitalise String str
deprecated
String capitaliseAllWords String str
deprecated
String capitalize String str
문자열중 첫번째 문자를 대문자로 만든다
String center String str, int size
str의 길이가 size가 되도록 문자열 좌우에 공백문자를 하나씩 추가한다 (우측부터 시작한다)
String center String str, int size, char padChar
str의 길이가 size가 되도록 문자열 좌우에 padChar를 하나씩 추가한다 (우측부터 시작한다)
String center String str, int size, String padStr
str의 길이가 size가 되도록 문자열 좌우에 padStr을 하나씩 추가한다 (우측부터 시작한다)
String chomp String str
문자열 맨 끝에있는 '\n' , '\r', '\r\n'을 제거한다
String chomp String str, String separator
문자열 맨 끝에 separator가 있으면 이를 제거한다
String chompLast String str
deprecated
String chompLast String str, String sep
deprecated
String chop String str
문자열 맨 끝에있는 문자 하나를 제거한다
String chopNewline String str
deprecated
String clean String str
deprecated
String concatenate Object array[]
deprecated
boolean contains String str, char searchChar
str이 searchChar를 포함하고 있으면 true
boolean contains String str, String searchStr
str이 searchStr을 포함하고 있으면 true
boolean containsNone String str, char invalidChars[]
str이 invalidChars의 각 문자를 모두 포함하고 있지 않으면 true
boolean containsNone String str, String invalidChars
str이 invalidChars를 모두 포함하고 있지 않으면 true
boolean containsOnly String str, char valid[]
str이 valid의 각 문자들만 포함하고 있어야 true
boolean containsOnly String str, String validChars
str이 validChars 들만을 포함하고 있어야 true
int countMatches String str, String sub
str중에 sub가 포함되어있는 갯수를 반환한다
String defaultString String str
str이 null이면 ""를 반환하고 아니면 str을 반환한다
String defaultString String str, String defaultStr
str이 null이면 defaultStr을 반환하고 아니면 str을 반환한다
String deleteSpaces String str
deprecated
String deleteWhitespace String str
문자열중 공백문자가 있으면 모두 제거한다
String difference String str1, String str2
str1과 str2를 비교하여 다른부분을 반환한다 (str2의 부분을 반환)
boolean equals String str1, String str2
str1이 null이면 str2가 null인지 유무를 반환하고 str1이 null이 아니면 str1과 str2의 equals를 반환
boolean equalsIgnoreCase String str1, String str2
equals와 동일하며 대소문자를 무시하고 비교한다
String escape String str
deprecated
String getChomp String str, String sep
deprecated
String getNestedString String str, String tag
deprecated
String getNestedString String str, String open, String close
deprecated
String getPrechomp String str, String sep
deprecated
int indexOf String str, char searchChar
str에서 첫번째 searchChar의 인덱스를 반환한다
int indexOf String str, char searchChar, int startPos
str의 startPos 인덱스부터 첫번째 searchChar의 인덱스를 반환한다
int indexOf String str, String searchStr
str에서 첫번째 searchStr의 인덱스를 반환한다
int indexOf String str, String searchStr, int startPos
str의 startPos 인덱스로부터 첫번째 searchStr의 인덱스를 반환한다
int indexOfAny String str, char searchChars[]
str에서 searchChars중 포함하고 있는 문자의 첫번째 인덱스를 반환한다
int indexOfAny String str String searchChars
str에서 searchChars중 포함하고 있는 문자열의 첫번째 인덱스를 반환한다
int indexOfAny String str, String searchStrs[]
str에서 searchStr중 포함하고 잇는 문자열의 첫번째 인덱스를 반환한다
int indexOfAnyBut String str char searchChars[]
str에서 searchChars중 포함되지 않은 문자 첫번째 인덱스를 반환한다
int indexOfAnyBut String str, String searchChars
str에서 searchChars중 포함되지 않은 문자 첫번째 인덱스를 반환한다
int indexOfDifference String str1, String str2
str1과 str2를 비교하여 문자열이 틀려지기 시작하는 인덱스를 반환한다
boolean isAlpha String str
문자열이 모두 Character.isLetter 이면 true (모두 문자이면 true)
boolean isAlphanumeric String str
문자열이 모두 Character.isLetterOrDigit 이면 true (문자거나 숫자이면 true)
boolean isAlphanumericSpace String str
문자열이 모두 Character.isLetterOrDigit 이거나 공백이면 true
boolean isAlphaSpace String str
문자열이 모두 Character.isLetter 이거나 공백문자이면 true
boolean isBlank String str
문자열이 공백문자이거나 길이가 0이거나 null인경우 true
boolean isEmpty String str
문자열이 길이가 0이거나 null인경우 true
boolean isNotBlank String str
문자열이 공백문자도 아니고 길이가 0도 아니고 null도 아니면 true
boolean isNotEmpty String str
문자열이 길이가 0이 아니고 null도 아니면 true
boolean isNumeric String str
문자열이 모두 Character.isDigit 이면 true (모두 숫자이면 true)
boolean isNumericSpace String str
문자열이 모두 Character.isDigit 이거나 공백문자이면 true
boolean isWhitespace String str
문자열이 모두 Character.isWhitespace 이면 true (모두 공백이면 true)
String join Object array[]
array에서 문자열을 읽어와 모두 연결시킨다
String join Object array[], char separator
array에서 문자열을 읽어와 separator를 구분자로 연결시킨다
String join Object array[], String separator
array에서 문자열을 읽어와 separator를 구분자로 연결시킨다
String join Iterator iterator, char separator
iterator에서 문자열을 읽어와 separator를 구분자로 연결시킨다
String join Iterator iterator, String separator
iterator에서 문자열을 읽어와 separator를 구분자로 연결시킨다
int lastIndexOf String str, char searchChar
str에서 마지막 searchChar의 인덱스를 반환한다
int lastIndexOf String str, char searchChar, int startPos
str의 startPos 인덱스부터 마지막 searchChar의 인덱스를 반환한다
int lastIndexOf String str, String searchStr
str에서 마지막 searchStr의 인덱스를 반환한다
int lastIndexOf String str, String searchStr, int startPos
str의 startPos 인덱스부터 마지막 searchStr의 인덱스를 반환한다
int lastIndexOfAny String str, String searchStrs[]
str에서 searchStr의 문자열들중 포함하고 있는 문자열의 마지막 인덱스를 반환한다
String left String str, int len
str의 좌측에서 len 길이만큼 문자열을 구한다
String leftPad String str, int size
str의 길이가 size가 되도록 문자열 왼쪽에 ' '을 추가한다
String leftPad String str, int size, char padChar
str의 길이가 size가 되도록 문자열 왼쪽에 padChar를 추가한다
String leftPad String str, int size, String padStr
str의 길이가 size가 되도록 문자열 왼쪽에 padStr을 추가한다
String lowerCase String str
str을 소문자로 변환한다
String mid String str, int pos, int len
str의 pos 인덱스부터 len 길이만큼의 문자열을 구한다
String overlay String str, String overlay, int start, int end
str의 start부터 end까지overlay로 변환한다
String overlayString String text, String overlay, int start, int end
deprecated
String prechomp String str, String sep
deprecated
String repeat String str, int repeat
str을 repeat만큼 반복하여 반환한다
String replace String text, String repl, String width
text에서 모든 repl을 width로 변환한다
String replace String text, String repl, String width, int max
text에서 모든 repl을 width로 변환하는데 최대 max개만큼 변환한다
String replaceChars String str, char searchChar, char replaceChar
str에서 searchChar를 replaceChar로 모두 변환한다
String replaceChars String str, String searchChars, String replaceChars
str에서 searchChars를 replaceChars로 모두 변환한다
String replaceOne String text, String repl, String width
text에서 repl를 width로 변환하는데 첫번째 하나만 변환한다
String reverse String str
문자열을 앞뒤 순서를 바꾼다
String reverseDelimited String str, char separatorChar
separatorChar를 구분으로 문자열을 나눈 후 나눠진 단어들을 역순으로 바꾼다
String reverseDelimitedString String str, String separatorChars
deprecated
String right String str, int len
str의 len길이만큼 우측에서 문자열을 구한다
String rightPad String str, int size
str의 길이가 size가 되도록 문자열 오른쪽에 ' '을 추가한다
String rightPad String str, int size, char padChar
str의 길이가 size가 되도록 문자열 오른쪽에 padChar를 추가한다
String rightPad String str, int size, String padStr
str의 길이가 size가 되도록 문자열 오른쪽에 padStr을 추가한다
String[] split String str
공백문자를 구분자로 사용하여 분리한다
String[] split String str, char separatorChar
separatorChar를 구분자로 사용하여 분리한다
String[] split String str, String separatorChars
separatorChars를 구분자로 사용하여 분리한다
String[] split String str, String seperatorChars, int max
separatorChars를 구분자로 사용하여 분리한며 최대 max개 만큼한다. 배열의 크기는 max가 된다
String strip String str
문자열 좌우에 있는 공백문자를 제거한다 trim과 동일하다
String strip String str, String stripChars
문자열 좌우에 stripChars에 존재하는 문자를 제거한다
String[] stripAll String strs[]
배열에 있는 모든 문자열 좌우에 있는 공백문자를 제거한다
String[] stripAll String strs[], String stripChars
배열에 있는 모든 문자열 좌우에 stripChars에 존재하는 문자를 제거한다
String stripEnd String str, String stripChars
문자열 우측에만 strip 한다
String stripStart String str, String stripChars
문자열 좌측에만 strip 한다
String stripToEmpty String str
str이 null이면 ""를 반환하고 아니면 strip 한다
String stripToNull String str
str이 null이면 null을 반환하고 아니면 strip 한다
String substring String str, int start
str의 start 인덱스부터 문자열을 구한다
String substring String str, int start, int end
str의 start 부터 end 인덱스 까지 문자열을 구한다
String substringAfter String str, String separator
str의 처음 separator 부분부터 문자열을 구한다
String substringAfterLast String str, String separator
str의 마지막 separator부분부터 문자열을 구한다
String substringBefore String str, String separator
str의 처음 separator 부분까지 문자열을 구한다
String substringBeforeLast String str, String separator
str의 마지막 separator부분까지 문자열을 구한다
String substringBetween String str, String tag
str에서 tag 사이에 있는 문자열을 구한다
String substringBetween String str, String open, String close
str에서 open부터 close 까지 사이에 있는 문자열을 구한다
String swapCase String str
대문자는 소문자로 변환하고 소문자는 대문자로 변환한다
String trimToNull String str
str이 null이거나 length가 0이면 null을 반환하고 그렇지 않으면 좌우 공백문자를 제거한다
String trim String str
문자열 좌우 공백문자를 제거한다
String trimToEmpty String str
str이 null이면 ""를 반환하고 그렇지 않으면 좌우 공백문자를 제거한다
String uncapitalise String str
deprecated
String uncapitalize String str
문자열의 첫문자를 소문자로 변환한다
String upperCase String str
str을 대문자로 변환한다

 

사용법

모두 static 함수이기때문에 다음과 같이 사용하면 됩니다.

String text = StringUtils.replace(str, repl, width);

 

자료만들면서 정리한 엑셀 첨부 합니다 ^^

 

=============================================

본문서는 자유롭게 배포/복사 할수 있지만

이문서의 저자에 대한 언급을 삭제하시면 안됩니다

저자 : GoodBug (unicorn@jakartaproject.com)

최초 : http://www.jakartaproject.com 

=============================================

 

 

출처:jakartaproject.com

이올린에 북마크하기(0) 이올린에 추천하기(0)
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by 때찌1

struts-config.xml 일부

.....

  <action path="/writePostForm" parameter=".writePost.form"
     type="org.apache.struts.actions.ForwardAction"/>


  <action path="/writePost" type="kr.pe.visharp.zlog.struts.action.WritePostAction"
           name="PostForm" scope="request" validate="true"
     input="/writePostForm.do">
   <forward name="success" path="/main.do" redirect="true"/>

.....


tiles-def.xml 일부

.....

 <definition name=".writePost.form" extends=".common.default">
     <put name="middle" value="/skin/default-gray/writePost.vm"/>
 </definition>

.....

다음과 같은 경우에

/writePostForm.do 로 요청이 들어오면 Tiles에 정의된 .writePost.form를 따라

Tile들이 나오고 한가운데에는 velocity로 정의된 Form이 나옵니다.

여기서 이거저거 입력해서 submit을 하면 /writePost.do로 요청이 들어갑니다.

만약 validation에 실패했다면 redirect로 다시 /writePostForm.do 로 갑니다.

tiles랑 struts를 공부하신 분이라면 여기까지는 아주 당연한 이야기입니다.


이렇게 해서 실행했는데 validation에 실패한 경우 Form이 채워지지 않더군요. 보통은 기존에 입력한 대로 채워지기 마련인데 말이죠.

그래서 한시간가량 이렇게 저렇게 해봐도 안됩습니다.

근데 view 페이지를  velocity말고, 같은 로직으로 jsp화일로 하니 Form이

채워지더군요. 그럼 velocity의 문제인거 같으니 velocity를 포기해야 하나 생각하다가 또 다시 이래저래 다시 연구해본 결과.

  <action path="/writePost" type="kr.pe.visharp.zlog.struts.action.WritePostAction"
           name="PostForm" scope="request" validate="true"
     input=".writePost.form">
   <forward name="success" path="/main.do" redirect="true"/>

과 같이 input페이지를 Tiles에 정의한 이름으로 바꾸니 velocity도 잘먹는군요.


여기저기 찾아봐도 별 얘기가 없는 듯하여 (적어도 한글이나 간단한 영어로는........) 올려봅니다.

이올린에 북마크하기(0) 이올린에 추천하기(0)
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by 때찌1
=============================================================================
====== Ajax 관련 자주 방문해야 하는 싸이트 ========
=============================================================================
 
 
 
 
 
 
 
 
 
 
 
=============================================================================
====================== Ajax 관련 오픈소스=======================
=============================================================================
=============================================================================
=============== Ajax 관련 라이브러리 & 툴 =================
=============================================================================
=> FAT(Fade Anything Technique)에 대한 글
 
http://www-128.ibm.com/developerworks/kr/library/os-ecl-ajax/ => Eclipse의 Ajax Toolkit Framework에서 지원되는 툴 (한글)
 
http://www.youngpup.net/2001/domdrag/ => dom dram 관련 싸이트
 
IBM AJAX Toolkit Framework =>IBM AJAX Toolkit Framework
An Eclipse Incubation Project Proposal =>IBM AJAX Toolkit Framework을 이해하는데 도움을 주는 제안서
 
http://httpunit.sourceforge.net/ => HttpUnit 테스팅 프레임웍
 
http://fitnesse.org/ => FitNesse 테스팅 프레임웍
 
http://jwebunit.sourceforge.net/ => 웹 테스팅 프레임웍으로 자바 개바자라면 추천해 본다.

http://www.edwardh.com/jsunit/ => JsUnit 홈페이지


http://devedge-temp.mozilla.org/toolbox/examples/2003/inheritFrom/index_en.html => 넷스케이프 커뮤니케이션의 Bob Clay 는 부모 클래스의 메소드를 자식 클래스에 복사할 수 있는 아주 간단한 메소드를 소개하였다.


http://chrispederick.com/work/webdeveloper/ => Web Developer Extension for FireFox 으로써 파이어폭스 브라우저가 제공해 주는 다양한 기능의 툴바를 다운/설치할 수 있는 싸이트이다.


http://hometown.aol.de/_ht_a/memtronic/ => 자바스크립트 파일을 압축하거나 Obfuscation(자신의 소스코드를 다른 사람이 악의적으로 도용하고나 훔쳐가는 것을 막기 위해서 멤버나 메소드 이름을 의미없는 문자들로 바꾸는 기법)하는 Freeware 싸이트이나 현재버젼에서는 아직까지 자바스크립트에 대한 Obfuscation 은 지원하지 않고 있다.


http://www.jslint.com/ => 자바스크립트 소스코드를 검증해 주는 싸이트


http://www.mozilla.com/ => 모질라 닷컴/파이어 폭스 최신버젼 다운로드


https://addons.mozilla.org/ => FireFox add on home page


https://addons.mozilla.org/extensions/?application=firefox => firefox add on extensions


http://www.activeperl.com/ => 펄의 런타임 환경인 ActivePerl 을 다운로드 받을 수 있다.


http://jsdoc.sourceforge.net/ => javadoc 명령으로 HTML API를 생성하듯이 자바스크립트의 주석을 바탕으로 HTML 다큐먼트를 생성하는 오픈소스


http://www.openqa.org/selenium/ => html 및 자바스크립트를 검사해주는 아주 훌륭한 오픈소스다. 실험적인 프로그램이지만 100점 주고 싶다.


http://www.activeperl.com/ => 펄 런타임 환경 다운로드 싸이트


http://jsdoc.sourceforge.net/ => jsDoc


http://www.json.org => JSON 홈페이지


http://www.ashleyit.com/rs/main.htm => Remote Scripting 관련하여 Brent Ashley 가 운영하는 싸이트




=============================================================================

======================= Ajax 응용 싸이트 =========================
=============================================================================
 
 
=> 구굴에서 만든 달력 및 메모
 
http://maps.a9.com/ => Ajax 를 이용한 map 싸이트
 

http://maps.google.com/ => 구굴 맵


http://www.google.com/ig => Draggable DOM pattern 을 아주 훌륭하게 적용한 싸이트


http://www.google.com/webhp?complete=1&hl=kor => 구굴 Suggest 한글 검색창


http://www.google.com/webhp?complete=1&hl=en => 구굴 Suggest 영문 검색창


http://www.netflix.com/BrowseSelection => ajax 를 이용한 툴팁을 구현한 싸이트


http://www.apple.com/itunes/ => AJAX 관련 refresh 기능을 구현해 놓은 싸이트.(애플 itunes 뮤직 다운로드 자동 카운트)


http://www.digg.com/spy => AJAX 관련 refresh 기능을 구현해 놓은 싸이트.(새로운 정보 컨텐츠 리스트 자동 소팅 기능)

 
 
 

=============================================================================

============== Ajax 관련  기타  참고 싸이트 ==============
=============================================================================

http://www.apress.com/book/supplementDownload.html?bID=10042&sID=3021 => Foundation of Ajax 소스 다운로드 url

이올린에 북마크하기(0) 이올린에 추천하기(0)
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by 때찌1

DBUtils에서 number 타입의 컬럼이 int형으로 안넘어올때..

 

데이터베이스의 컬럼이 NUMBER 타입인데 VO객체의 int형 setter를 통해 그 값이 안넘어 오는 경우가 있습니다

 

데이터베이스 테이블 스크립트

 

CREATE TABLE user_t (

    user_id VARCHAR2(12) PRIMARY KEY NOT NULL,

    user_point NUMBER(8)

)

 

데이터베이스의 값을 저장하는 VO 객체

public class UserVO

{

    String user_id;

    int user_point;

 

    public void setUser_id(String user_id)

    {

        this.user_id = user_id;

    }

 

    public void setUser_point(int user_point)

    {

        this.user_point = user_point;

    }

 

    public String getUser_id()

    {

        return user_id;

    }

 

    public int getUser_point()

    {

        return user_point;

    }

 

}

 

 

JSP

...

ResultSetHandler rsh = new BeanListHandler(UserVO.class);

 

QueryRunner qr = new QueryRunner();

 

List list = (List)qr.query(conn, "SELECT user_id, user_point FROM user_t WHERE user_id = ?", new String[]{"unicorn"}, rsh);

...

 

간단히 위와 같이 코딩을 하면 다음과 같은 에러 메세지가 납니다

java.sql.SQLException Cannot set user_point : argument type mismatch Query

user_point 컬럼이랑 먼지 모르지만 아규먼트랑 type이 맞지 않는다는 말 같군요

 

DBUtils 받아서 차근차근 소스를 보다보니..

org.apache.commons.dbutils.BasicRowProcessor.java

private void callSetter(Object target, PropertyDescriptor prop, Object value) throws SQLException

    {

 

        Method setter = prop.getWriteMethod();

        if (setter == null)

        {

            return;

        }

 

        Class[] params = setter.getParameterTypes();

        try

        {

 

            // Don't call setter if the value object isn't the right type

            if (this.isCompatibleType(value, params[0]))

                setter.invoke(target, new Object[] { value }); // --

 

        } catch (IllegalArgumentException e)

        {

            throw new SQLException("Cannot set " + prop.getName() + ": " + e.getMessage());

 

        } catch (IllegalAccessException e)

        {

            throw new SQLException("Cannot set " + prop.getName() + ": " + e.getMessage());

 

        } catch (InvocationTargetException e)

        {

            throw new SQLException("Cannot set " + prop.getName() + ": " + e.getMessage());

        }

    }

 

 

의 setter.invoke 에서 IllegalArgumentException 가 throw 되고 있었습니다

즉 값에 해당하는 setter 함수를 찾다가 setUser_point(int user_point) 가 있음에도 불고하고 적당한 것이 없어서 Exception을 던지고 있는 실정입니다

원인은 value 때문이었는데, 이는

value = rs.getObject(i+1)

와 같이 resultset에서 받아온 값입니다

invoke 함수에 두번째 파라미터로 Object형태의 객체형태로 넘겨주어야 하는데 이넘은 Integer형이 아닌것 같았습니다

Integer.class.isInstance(value) 로 값을 찍어보니 역시나 false가 리턴되었습니다

 

그래서 다음과 같이 약간 수정하였습니다

 

private void callSetter(Object target, PropertyDescriptor prop, Object value) throws SQLException

    {

 

        Method setter = prop.getWriteMethod();

        if (setter == null)

        {

            return;

        }

 

        Class[] params = setter.getParameterTypes();

        try

        {

 

            // Don't call setter if the value object isn't the right type

 

            if (params[0].equals(Integer.TYPE))

            {

                value = new Integer(value.toString()); // --

            }

 

            if (this.isCompatibleType(value, params[0]))

                setter.invoke(target, new Object[] { value }); // --

 

        } catch (IllegalArgumentException e)

        {

            throw new SQLException("Cannot set " + prop.getName() + ": " + e.getMessage());

 

        } catch (IllegalAccessException e)

        {

            throw new SQLException("Cannot set " + prop.getName() + ": " + e.getMessage());

 

        } catch (InvocationTargetException e)

        {

            throw new SQLException("Cannot set " + prop.getName() + ": " + e.getMessage());

        }

    }

 

과 같이 명시적으로 Integer 타입일때 Integer형태를 만들어 주었습니다

MySQL과 Oracle 두가지 테스트해보았는데, MySQL에서는 발생하지 않았지만 Oracle에서는 위와같은 문제가 발견되었습니다

아마도 M$SQL에서도 동일한 문제가 발생할것 같습니다

소스는 동일한데 어디선 되고 안되고를 보니 JDBC영향일것으로 추측이 되는데, JDBC 소스를 보아도 별 특별한데는 아직 찾지 못했습니다

 

사실 DBUtils는 잘쓰면 무척 편합니다

하지만 많이 좋아졌다고는 하나 reflect에 대한 비용 없잖아 들겁니다

DBUtils는 그 자체로 괜찮지만 아마 사용하다보면 소스에 손을 데야될겁니다

한글 인코딩, 디코딩이나 쿼리등을 DBUtils에 심어놓으면 코딩은 아마 더 줄어들겁니다

 

출처:자카르타프로젝트

 

때찌주

컬러만 조금 입혔습니다.

이올린에 북마크하기(0) 이올린에 추천하기(0)
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by 때찌1

Spring - Java/J2EE Application Framework

Reference Documentation

Rod Johnson

Juergen Hoeller

Alef Arendsen

Colin Sampaleanu

Darren Davison

Dmitriy Kopylenko

Thomas Risberg

Mark Pollack

Rob Harrop

Version 1.2.2

Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.

(Work in progress)


Table of Contents

서문
1. 소개
1.1. 개요
1.2. 사용 시나리오
2. 배경 지식
2.1. 제어의 역행 / 의존 주입(Inversion of Control / Dependency Injection)
3. Beans, BeanFactory 그리고 ApplicationContext
3.1. 소개
3.2. BeanFactory 와 BeanDefinitions - 기초
3.2.1. BeanFactory
3.2.2. BeanDefinition
3.2.3. bean 클래스
3.2.3.1. 생성자를 통한 Bean 생성
3.2.3.2. 정적 factory메소드를 통한 Bean 생성
3.2.3.3. 인스턴스 factory메소드를 통한 Bean 생성
3.2.4. bean 구분자 (id 와 name)
3.2.5. 싱글톤이나 비-싱글톤(non-singleton)
3.3. 프라퍼티, 협력자(collaborators), autowiring 과 의존성 체크
3.3.1. bean프라퍼티와 협력자(collaborators) 셋팅하기
3.3.2. 생성자의 인자 분석
3.3.2.1. 생성자의 인자 타입 대응(match)
3.3.2.2. 생성자의 인자 인덱스
3.3.3. bean프라퍼티와 상세화된 생성자의 인자
3.3.3.1. value와 ref 간략화한(shortcut) 폼
3.3.4. 메소드 삽입
3.3.4.1. 룩업(Lookup) 메소드 삽입
3.3.4.2. 임의의 메소드 교체
3.3.5. depends-on 사용하기
3.3.6. Autowiring 협력자
3.3.7. 의존성을 위한 체크
3.4. bean의 성질을 커스터마이징하기.
3.4.1. Lifecycle 인터페이스
3.4.1.1. InitializingBean / init-method
3.4.1.2. DisposableBean / destroy-method
3.4.2. 당신이 누구인지 알고 있다.(Knowing who you are)
3.4.2.1. BeanFactoryAware
3.4.2.2. BeanNameAware
3.4.3. FactoryBean
3.5. 추상 그리고 자식 bean정의
3.6. BeanFactory와 상호작동하기
3.6.1. BeanFactory의 생성물이 아닌 FactoryBean 얻기
3.7. BeanPostprocessors로 bean 커스터마이징하기
3.8. BeanFactoryPostprocessors를 가진 bean factory커스터마이징하기
3.8.1. PropertyPlaceholderConfigurer
3.8.2. PropertyOverrideConfigurer
3.9. 추가적인 사용자지정 PropertyEditors 등록하기
3.10. 존재하는 bean을 위한 별칭을 추가하기 위한 별칭 요소 사용하기.
3.11. ApplicationContext에 대한 소개
3.12. ApplicationContext에 추가된 기능
3.12.1. MessageSource 사용하기
3.12.2. events 전파하기
3.12.3. Spring내에서 자원(resources) 사용하기
3.13. ApplicationContext내에서 사용자정의 행위
3.13.1. ApplicationContextAware 표시자(marker) 인터페이스
3.13.2. BeanPostProcessor
3.13.3. BeanFactoryPostProcessor
3.13.4. PropertyPlaceholderConfigurer
3.14. 추가적인 사용자정의 PropertyEditors 등록하기
3.15. 프라퍼티 표현에서 bean프라퍼티 또는 생성자의 인자를 셋팅하기.
3.16. 필드값으로부터 bean프라퍼티 또는 생성자의 인자를 셋팅하기.
3.17. 다른 메소드를 호출하고 선택적으로 반환값을 사용한다.
3.18. 하나의 파일로부터 다른것으로 bean정의를 끌어오기
3.19. 웹 애플리케이션으로부터 ApplicationContext생성하기.
3.20. Glue 코드와 좋지않은 싱글톤
3.20.1. SingletonBeanFactoryLocator 와 ContextSingletonBeanFactoryLocator을 사용하기
4. PropertyEditors, data binding, validation and the BeanWrapper
4.1. 소개
4.2. Binding data를 사용한DataBinder
4.3. Bean 조작(manipulation)과 BeanWrapper
4.3.1. Setting 과 getting 기본과 내포된 설정들
4.3.2. 내장 PropertyEditors, 변환 타입들(Built-in PropertyEditors, converting types)
4.3.3. 언급할 가치가 있는 다른 기능들.
5. Spring AOP: Spring을 이용한 Aspect 지향적인 프로그래밍
5.1. 개념
5.1.1. AOP 개념
5.1.2. Spring AOP의 기능과 대상
5.1.3. Spring 내 AOP 프록시
5.2. Spring내 Pointcuts
5.2.1. 개념
5.2.2. pointcuts에서의 작업(operation)
5.2.3. 편리한 pointcut 구현물
5.2.3.1. 정적 pointcuts
5.2.3.2. 동적 pointcuts
5.2.4. Pointcut 수퍼클래스(superclasses)
5.2.5. 사용자지정 pointcuts
5.3. Spring 내 Advice타입들
5.3.1. Advice 생명주기
5.3.2. Spring내 Advice 타입들
5.3.2.1. Interception around advice
5.3.2.2. 전(Before) advice
5.3.2.3. Throws advice
5.3.2.4. advice를 반환한 후(after returning advice)
5.3.2.5. Introduction advice
5.4. Spring내 Advisors
5.5. AOP프록시를 생성하기 위한 ProxyFactoryBean사용하기
5.5.1. 기본
5.5.2. 자바빈 프라퍼티
5.5.3. 프록시 인터페이스
5.5.4. 프록시 클래스
5.5.5. 'global' advisor 사용하기
5.6. 편리한 프록시 생성
5.6.1. TransactionProxyFactoryBean
5.6.2. EJB 프록시
5.7. 간결한 프록시 정의
5.8. ProxyFactory로 프로그램적으로 AOP프록시를 생성하기.
5.9. advised 객체 조작하기.
5.10. "autoproxy" 기능 사용하기
5.10.1. autoproxy bean정의
5.10.1.1. BeanNameAutoProxyCreator
5.10.1.2. DefaultAdvisorAutoProxyCreator
5.10.1.3. AbstractAdvisorAutoProxyCreator
5.10.2. 메터데이타-지향 자동 프록시 사용하기.
5.11. TargetSources 사용하기
5.11.1. 핫 스왑가능한 대상 소스
5.11.2. 풀링 대상 소스
5.11.3. 프로토 타입 대상 소스
5.11.4. ThreadLocal 대상 소스
5.12. 새로운 Advice 타입을 정의하기
5.13. 추가적으로 읽을거리와 자원들
5.14. 로드맵
6. AspectJ 통합
6.1. 개요
6.2. Spring IoC를 사용하여 AspectJ 설정하기.
6.2.1. "싱글톤" aspects
6.2.1.1. 예제
6.2.1.2. 정렬 이슈
6.2.2. 싱글톤 형식이 아닌 aspect
6.2.3. Gotchas
6.3. 목표 Spring advice를 위한 AspectJ 포인트컷(pointcut) 사용하기
6.4. AspectJ를 위한 Spring aspect
7. 트랜잭션 관리
7.1. Spring 트랜잭션 추상화
7.2. 트랜잭션 전략
7.3. 프로그래밍적인 트랜잭션 관리
7.3.1. TransactionTemplate 사용하기
7.3.2. PlatformTransactionManager 사용하기
7.4. 선언적 트랜잭션 관리
7.4.1. BeanNameAutoProxyCreator, 또 다른 선언적 접근방법
7.5. 프로그래밍적/선언적 트랜잭션 관리 중 선택하기
7.6. 트랜잭션 관리를 위한 어플리케이션 서버가 필요한가?
7.7. 공통적인 문제
8. 소스 레벨 메타데이타 지원
8.1. 소스-레벨 메타데이타
8.2. Spring의 메타데이타 지원
8.3. Jakarta Commons Attributes과 통합
8.4. 메타데이타와 Spring AOP 자동 프록시
8.4.1. 기초
8.4.2. 선언적인 트랜잭션 관리
8.4.3. 풀링(Pooling)
8.4.4. 사용자정의 메타데이타
8.5. MVC 웹티어 설정을 최소화하기 위한 속성 사용하기
8.6. 메타데이타 속성의 다른 사용
8.7. 추가적인 메타데이타 API를 위한 지원 추가하기
9. DAO support
9.1. 소개
9.2. 일관된 예외 구조
9.3. DAO지원을 위한 일관된 추상클래스
10. JDBC를 사용한 데이터 접근
10.1. 소개
10.2. 기본적인 JDBC처리와 에러 처리를 위한 JDBC Core클래스 사용하기
10.2.1. JdbcTemplate
10.2.2. DataSource
10.2.3. SQLExceptionTranslator
10.2.4. Statements 실행하기
10.2.5. 쿼리문 실행하기
10.2.6. 데이터베이스 수정하기
10.3. 데이터베이스에 연결하는 방법을 제어하기
10.3.1. DataSourceUtils
10.3.2. SmartDataSource
10.3.3. AbstractDataSource
10.3.4. SingleConnectionDataSource
10.3.5. DriverManagerDataSource
10.3.6. DataSourceTransactionManager
10.4. 자바 객체처럼 JDBC작업을 모델링 하기.
10.4.1. SqlQuery
10.4.2. MappingSqlQuery
10.4.3. SqlUpdate
10.4.4. StoredProcedure
10.4.5. SqlFunction
11. 객체-관계 연결자(O/R Mappers)를 이용한 데이터 접근
11.1. 소개
11.2. Hibernate
11.2.1. 자원 관리
11.2.2. 애플리케이션 컨텍스트내에서 자원 정의
11.2.3. Inversion of Control: Template and Callback
11.2.4. 탬플릿 대신에 AOP인터셉터 적용하기.
11.2.5. 프로그램의 트랜잭션 구분(Demarcation)
11.2.6. 선언적인 트랜잭션 구분
11.2.7. 트랜잭션 관리 전략
11.2.8. 컨테이너 자원 대 로컬 자원
11.2.9. 샘플들
11.3. JDO
11.4. iBATIS
11.4.1. 1.3.x and 2.0 사이의 개요와 차이점
11.4.2. iBATIS 1.3.x
11.4.2.1. SqlMap을 셋업하기
11.4.2.2. SqlMapDaoSupport 사용하기
11.4.2.3. 트랜잭션 관리
11.4.3. iBATIS 2
11.4.3.1. SqlMap 셋업하기
11.4.3.2. SqlMapClientDaoSupport 사용하기
12. 웹 MVC framework
12.1. 웹 MVC framework 소개
12.1.1. 다른 MVC구현물의 플러그인 가능성
12.1.2. Spring MVC의 특징
12.2. DispatcherServlet
12.3. 컨트롤러
12.3.1. AbstractController 와 WebContentGenerator
12.3.2. 간단한 다른 컨트롤러
12.3.3. MultiActionController
12.3.4. CommandControllers
12.4. Handler mappings
12.4.1. BeanNameUrlHandlerMapping
12.4.2. SimpleUrlHandlerMapping
12.4.3. HandlerInterceptors 추가하기
12.5. view와 view결정하기
12.5.1. ViewResolvers
12.5.2. ViewResolvers 묶기(Chaining)
12.6. 로케일 사용하기.
12.6.1. AcceptHeaderLocaleResolver
12.6.2. CookieLocaleResolver
12.6.3. SessionLocaleResolver
12.6.4. LocaleChangeInterceptor
12.7. 테마(themes) 사용하기
12.8. Spring의 multipart (파일업로드) 지원
12.8.1. 소개
12.8.2. MultipartResolver 사용하기
12.8.3. 폼에서 파일업로드를 다루기.
12.9. 예외 다루기
13. 통합 뷰 기술들
13.1. 소개
13.2. JSP & JSTL
13.2.1. 뷰 해결자(View resolvers)
13.2.2. 'Plain-old' JSPs 대(versus) JSTL
13.2.3. 추가적인 태그들을 쉽게 쓸수 있는 개발
13.3. Tiles
13.3.1. 의존물들(Dependencies)
13.3.2. Tiles를 통합하는 방법
13.3.2.1. InternalResourceViewResolver
13.3.2.2. ResourceBundleViewResolver
13.4. Velocity & FreeMarker
13.4.1. 의존물들(Dependencies)
13.4.2. 컨텍스트 구성(Context configuration)
13.4.3. 생성 템플릿들(Creating templates)
13.4.4. 진보한 구성(Advanced configuration)
13.4.4.1. velocity.properties
13.4.4.2. FreeMarker
13.4.5. 바인드(Bind) 지원과 폼(form) 핸들링
13.4.5.1. 바인드(bind) 매크로
13.4.5.2. 간단한 바인딩
13.4.5.3. 폼 input 생성 매크로
13.4.5.4. HTML회피를 오버라이드하고 XHTML호환 태그를 만든다.
13.5. XSLT
13.5.1. 나의 첫번째 단어
13.5.1.1. Bean 정의
13.5.1.2. 표준적인 MVC 컨트롤러 코드
13.5.1.3. 모델 데이터를 XML로 변환하기
13.5.1.4. view프라퍼티 정의하기
13.5.1.5. 문서 변형
13.5.2. 요약
13.6. 문서 views (PDF/Excel)
13.6.1. 소개
13.6.2. 설정 그리고 셋업
13.6.2.1. 문서 view정의
13.6.2.2. 컨트롤러 코드
13.6.2.3. Excel view를 위한 하위클래스 만들기
13.6.2.4. PDF view를 위한 하위클래스 만들기
13.7. JasperReports
13.7.1. 의존성
13.7.2. 설정
13.7.2.1. ViewResolver 설정하기
13.7.2.2. View 설정하기
13.7.2.3. 리포트 파일에 대해
13.7.2.4. JasperReportsMultiFormatView 사용하기
13.7.3. ModelAndView 활성화하기
13.7.4. 하위-리포트로 작동하기
13.7.4.1. 하위-리포트 파일 설정하기
13.7.4.2. 하위-리포트 데이터소스 설정하기
13.7.5. 전파자(Exporter) 파라미터 설정하기
14. 다른 웹 프레임워크들과의 통합
14.1. 소개
14.2. JavaServer Faces
14.2.1. DelegatingVariableResolver
14.2.2. FacesContextUtils
14.3. Struts
14.3.1. ContextLoaderPlugin
14.3.1.1. DelegatingRequestProcessor
14.3.1.2. DelegatingActionProxy
14.3.2. ActionSupport 클래스들
14.4. Tapestry
14.4.1. 아키텍쳐
14.4.2. 구현체
14.4.2.1. 샘플 어플리케이션 컨텍스트
14.4.2.2. Tapestry pages에서 빈들을 얻어오기
14.4.2.3. 어플리케이션 컨텍스트를 Tapestry에 드러내기
14.4.2.4. Component 정의 파일들
14.4.2.5. abstract accessors 추가하기
14.4.3. 개요
14.5. WebWork
15. JMS
15.1. 소개
15.2. 도메인 단일화(unification)
15.3. JmsTemplate
15.3.1. ConnectionFactory
15.3.2. 트랜잭션 관리
15.3.3. 목적지(destination) 관리
15.4. JmsTemplate 사용하기
15.4.1. 메시지 보내기
15.4.2. 동기적으로 받기(Receiving)
15.4.3. 메시지 변환기(converter) 사용하기
15.4.4. SessionCallback 과 ProducerCallback
16. EJB에 접근하고 구현하기
16.1. EJB에 접근하기
16.1.1. 개념
16.1.2. local SLSBs에 접근하기
16.1.3. remote SLSB에 접근하기
16.2. Spring의 편리한 EJB구현물 클래스를 사용하기.
17. Spring을 사용한 원격(Remoting)및 웹서비스
17.1. 소개
17.2. RMI를 사용한 서비스 드러내기
17.2.1. RmiServiceExporter를 사용하여 서비스 내보내기
17.2.2. 클라이언트에서 서비스 링크하기
17.3. HTTP를 통해 서비스를 원격으로 호출하기 위한 Hessian 이나 Burlap을 사용하기.
17.3.1. Hessian을 위해 DispatcherServlet을 묶기.
17.3.2. HessianServiceExporter를 사용하여 bean을 드러내기
17.3.3. 클라이언트의 서비스로 링크하기
17.3.4. Burlap 사용하기
17.3.5. Hessian 이나 Burlap을 통해 드러나는 서비스를 위한 HTTP 기본 인증 적용하기
17.4. HTTP호출자를 사용하여 서비스를 드러내기
17.4.1. 서비스 객체를 드러내기
17.4.2. 클라이언트에서 서비스 링크하기
17.5. 웹 서비스
17.5.1. JAX-RPC를 사용하여 서비스를 드러내기
17.5.2. 웹 서비스에 접근하기
17.5.3. Register Bean 맵핑
17.5.4. 자체적인 핸들러 등록하기
17.5.5. XFire를 사용하여 웹 서비스를 드러내기
17.6. 자동-탐지(Auto-detection)는 원격 인터페이스를 위해 구현되지 않는다.
17.7. 기술을 선택할때 고려사항.
18. Spring 메일 추상 계층을 사용한 이메일 보내기
18.1. 소개
18.2. Spring 메일 추상화 구조
18.3. Spring 메일 추상화 사용하기
18.3.1. 플러그인할 수 있는 MailSender 구현클래스들
18.4. JavaMail MimeMessageHelper 사용하기
18.4.1. 간단한 MimeMessage 를 생성하고 보내기
18.4.2. 첨부파일들과 inline 리소스들을 보내기
19. Quartz 혹은 Timer 를 사용한 스케쥴링
19.1. 소개
19.2. OpenSymphony Quartz 스케쥴러 사용하기
19.2.1. JobDetailBean 사용하기
19.2.2. MethodInvokingJobDetailFactoryBean 사용하기
19.2.3. triggers 와 SchedulerFactoryBean을 사용하여 jobs를 묶기
19.3. JDK Timer support 사용하기
19.3.1. 임의의 timers 생성하기
19.3.2. MethodInvokingTimerTaskFactoryBean 사용하기
19.3.3. 감싸기 : TimerFactoryBean을 사용하여 tasks를 세팅하기
20. JMX 지원
20.1. 소개
20.2. 당신의 bean을 JMX로 내보내기(Exporting)
20.2.1. MBeanServer 생성하기
20.2.2. 늦게 초기화되는(Lazy-Initialized) MBeans
20.2.3. MBean의 자동 등록
20.3. 당신 bean의 관리 인터페이스를 제어하기
20.3.1. MBeanInfoAssembler 인터페이스
20.3.2. 소스레벨 메타데이타(metadata) 사용하기
20.3.3. JDK 5.0 Annotations 사용하기
20.3.4. 소스레벨 메타데이타 타입들
20.3.5. AutodetectCapableMBeanInfoAssembler 인터페이스
20.3.6. 자바 인터페이스를 사용하여 관리 인터페이스 정의하기
20.3.7. MethodNameBasedMBeanInfoAssembler 사용하기
20.4. 당신의 bean을 위한 ObjectName 제어하기
20.4.1. Properties로 부터 ObjectName 읽기
20.4.2. MetadataNamingStrategy 사용하기
20.5. JSR-160 연결자(Connectors)로 당신의 bean을 내보내기
20.5.1. 서버측 연결자(Connectors)
20.5.2. 클라이언트측 연결자
20.5.3. Burlap/Hessian/SOAP 곳곳의 JMX
20.6. 프록시를 통해서 MBean에 접속하기
21. Testing
21.1. 단위 테스팅
21.2. 통합 테스팅
21.2.1. 컨텍스트 관리와 캐슁
21.2.2. 테스트 클래스 인스턴스들의 의존성 주입
21.2.3. 트랜잭션 관리
21.2.4. 편리한 변수들
21.2.5. 예시
21.2.6. 통합 테스트 실행하기
A. spring-beans.dtd

서문

소프트웨어 애플리케이션을 개발하는것은 좋은 툴과 기술만으로는 충분하지 않다. 무겁지만 모든것을 약속하는 플랫폼을 사용하여 애플리케이션을 구현하는 것은 제어하기 힘들고 개발 주기가 더 어렵게 되는동안 효과적이지 않다. Spring은 기업용 애플리케이션을 빌드하기 위해 선언적인 트랜잭션 관리, RMI나 웹서비스를 사용하는 당신의 로직에 원격접근, 메일링 기능과 데이터베이스에 당신의 데이터를 지속하는 다양한 옵션을 사용하는 가능성을 지원하는 동안 가벼운 솔루션을 제공한다. Spring은 MVC프레임워크, AOP를 당신의 소프트웨어에 통합하는 일관적인 방법 그리고 선호하는 예외구조로부터 자동 맵핑을 포함한 잘 정의된 예외 구조를 제공한다.

Spring은 모든 당신의 기업용 애플리케이션을 위해 잠재적으로 one-stop-shop이 될수 있다. 어쨌든 Spring은 모듈적이고, 당신에게 나머지를 가져오는것 없이 이것의 일부를 사용하도록 허용한다. 당신은 가장 상위에 Struts를 사용하여 bean컨테이너를 사용할수 있다. 하지만 당신은 Hibernate통합이나 JDBC추상레이어를 사용하도록 선택할수 있다. Spring은 비침략적이고, 프레임워크에서 의존성이 의미하는것은 사용 영역에 의존해서 대개 아무것도 없거나 극도로 최소한적이다.

이 문서는 Spring의 기능에 대한 참조가이드를 제공한다. 이 문서는 여전히 작업중이기 때문에 만약 당신이 어떠한 요청이나 언급을 가진다면 사용자 메일링 리스트나 소스포지 프로젝트 페이지(http://www.sf.net/projects/springframework)의 포럼에 그것들을 올려달라.

시작하기 전 몇몇 감사의 말씀 : Chris Bauer(Hibernate팀의)이 준비하고 Hibernate의 참조가이트를 생성하는 것을 가능하도록 하기 위한 순서대로 DocBook-XSL 소프트웨어를 개작했다. 또한 우리에게 이것을 생성하도록 허용했다. 또한 자료의 몇몇 광대하고 가치있는 리뷰를 해 준 Russell Healy에게도 감사한다.

Chapter 1. 소개

1.1. 개요

Spring은 밑의 다이어그램에서 보여지는 7개의 모듈로 잘 조직된 많은 기능과 특성을 포함한다. 이 부분은 순서대로 각 모듈을 언급한다.

Spring 프레임워크의 개요

Core 패키지는 프레임워크의 가장 기본적인 부분이고 당신에게 bean컨테이너를 기능적으로 관리하는 것을 허용하는 의존성 삽입(Dependency Injection-DI)기능을 제공한다. 여기의 기본적인 개념은 프로그램에 따른 싱글톤의 필요성을 제거하는 factory패턴을 제공하고 당신의 실질적인 프로그램 로직으로부터 설정과 의존성 명시를 분리시키는 것을 당신에게 허용하는 BeanFactory이다.

Core 패키지의 가장 위에는 프레임워크 스타일의 방식으로 bean에 접근하기 위한 방법을 제공하는 다소 JNDI-등록기와 유사한 Context 패키지가 위치한다. context패키지는 bean패키지로부터 이 기능을 상속하고 예를 들어 resource bundle와 같은것을 사용하여 텍스트 메시지, 이벤트 위임, 자원-로딩 그리고 예를 들어 서블릿 컨테이너와 같은 것에 의해 투명한 컨텍스트 생성을 위한 지원을 추가한다.

DAO 패키지는 끔찍한 JDBC코딩과 데이터베이스 업체 특정 에러코드의 파싱을 할 필요를 제거하는 JDBC추상화 레이어를 제공한다. 또한 JDBC패키지는 특정 인터페이스를 구현하는 클래스를 위해서 뿐 아니라 당신의 모든 POJOs를 위해서도 선언적인 트랜잭션 관리만큼 프로그램에 따른 방식으로 할수 있는 방법을 제공한다.

ORM 패키지는 JDO, Hibernate 그리고 iBATIS를 포함하는 인기있는 객체-관계 맵핑 API를 위한 통합 레이어를 제공한다. ORM패키지는 사용하여 당신은 앞에서 언급된 간단한 선언적인 트랜잭션 관리와 같은 Spring이 제공하는 다른 모든 기능을 사용해서 혼합하여 모든 O/R매퍼를 사용할수 있다.

Spring의 AOP 패키지는 당신이 정의하는것을 허용하는 AOP 제휴 호환 aspect-지향 프로그래밍 구현물을 제공한다. 예를 들어 코드를 명백하게 분리하기 위한 메소드-인터셉터와 pointcut은 논리적으로 구별되어야 할 기능을 구현한다. 소스레벨 메터데이타 기능을 사용하여 당신은 .NET속성과 다소 비슷한 모든 종류의 행위적 정보를 당신의코드로 결합한다.

Spring의 Web 패키지는 멀티파트기능, 서블릿 리스너를 사용한 컨텍스트 초기화 그리고 웹-기반 애플리케이션 컨텍스트와같은 기본적인 웹-기반 통합 기능들을 제공한다. WebWork나 Struts와 함께 Spring을 사용할때 이것은 그것들과 통합할 패키지이다.

Spring의 웹 MVC 패키지는 웹 애플리케이션을 위한 Model-View-Controller구현물을 제공한다. Spring의 MVC구현물은 어떠한 구현물이 아니다. 이것은 도메인 모델 코드와 웹폼(Web forms)사이의 분명한 구분을 제공하고 유효성체크와 같은 Spring프레임워크의 다른 모든 기능을 사용하도록 당신에게 허용한다.

1.2. 사용 시나리오

위에서 언급된 빌드단위로 당신은 애플릿에서부터 Spring의 트랜잭션 관리 기능과 웹 프레임워크를 사용하는 완전한 기업용 애플리케이션까지 모든 종류의 시나리오로 Spring을 사용할수 있다.

전형적으로 완전한 Spring웹 애플리케이션

대부분의 Spring기능을 사용한 전형적인 웹 애플리케이션. TransactionProxyFactoryBeans를 사용하면 웹 애플리케이션은 EJB에 의해 제공되는것과 같은 컨테이너 관리 트랜잭션을 사용할때 되는것처럼 완벽하게 트랜잭션적이다. 당신의 모든 사용자 지정 비지니스 로직은 Spring의 의존성 삽입 컨테이너에 의해 관리되는 간단한 POJO를 사용해서 구현될수 있다. 메일을 보내거나 유효성체크와 같은 추가적인 서비스, 웹 레이어의 비의존성은 당신에게 유효성체크 규칙을 수행하기 위한 위치를 선택하도록 허용한다. Spring의 ORM지원은 Hibernate, JDO 그리고 iBATIS와 통합된다. 예를 들어 HibernateDaoSupport를 사용하면 당신은 존재하는 Hibernate맵핑을 재사용할수 있다. 폼 컨트롤러는 ActionForms이나 HTTP파라미터를 당신의 도메인 모델을 위한 값에 이동시키는 다른 클래스의 필요성을 제거하는 도메인모델을 가진 웹레이어와 유사하게 통합한다.

3자(third-party)의 웹 프레임워크를 사용한 Spring 미들티어

때때로 현재의 환경은 당신에게 다른 프레임워크로의 완벽한 교체를 허용하지 않는다. Spring은 이것내 모든것을 사용하도록 당신에게 강요하지 않는다. 이것은 모든것 또는 아무것도 아닌것(all-or-nothing)인 솔루션이 아니다. WebWork, Struts, Tapestry 또는 다른 UI프레임워크를 사용한 존재하는 앞부분은 당신에게 Spring이 제공하는 트랜잭션 기능을 사용하도록 허용하는 Spring기반의 미들티어와 완벽하게 통합될수 있다. 당신이 해야할 필요가 있는 오직 한가지는 ApplicationContext를 사용하여 당신의 비지니스 로직을 묶고 WebApplicationContext를 사용하여 당신의 웹 UI레이어를 통합하는 것이다.

원격 사용 시나리오

당신이 웹서비스를 통해 존재하는 코드에 접근할 필요가 있을 때, 당신은 Spring의 Hessian-, Burlap-, Rmi-JaxRpcProxyFactory클래스를 사용할수 있다. 존재하는 애플리케이션에 원격 접근을 가능하게 하는 것은 최근에는 어려운 일이 아니다.

EJB - 존재하는 POJO를 포장하기

Spring은 POJO를 재사용하는것을 당신에게 허용하고 그것들을 비상태유지(stateless) 세션빈으로 포장하고 선언적인 보안이 필요한 측정가능한 실패에 안전한(failsafe) 웹 애플리케이션내 사용하기 위한 EJB를 위해 존재하는 접근 레이어와 추상 레이어를 제공한다.

Chapter 2. 배경 지식

2.1. 제어의 역행 / 의존 주입(Inversion of Control / Dependency Injection)

2004년초, Martin Fowler는 그의 사이트 독자들에거 물었다 : Inversion of Control에 관해 이야기할때 "질문은, 제어의 측면에서 무엇이 역행하는가?" 였다. Martin은 Inversion of Control 용어에 대해서 설명한후 패턴 이름을 바꾸거나 적어도 더 나은 스스로-해석되는 이름을 가지길 제안했고, Dependency Injection 용어를 사용하기 시작한다. 그의 기사는 Inversion of Control 또는 Dependency Injection 뒤에 숨겨진 굉장한 아이디어들을 설명하기위해 계속된다. 어느정도 통찰력이 필요하다면 : http://martinfowler.com/articles/injection.html 을 방문하라.

Chapter 3. Beans, BeanFactory 그리고 ApplicationContext

3.1. 소개

Spring내에서 가장 기초적이고 가장 중요한 두개의 패키지는 org.springframework.beansorg.springframework.context패키지이다. 이 패키지내 코드는 Spring의 Inversion of Control(대안으로 Dependency Injection으로 불리는)기능의 기초를 제공한다. BeanFactory는 잠재적으로 어떤 종류의 저장 기능을 사용하여 어떤 성질의 bean을 관리하는 향상된 설정 기법을 제공한다. ApplicationContext는 BeanFactory(또는 하위클래스)의 가장 상위에 빌드하고 향상된 점 중에서도 Spring AOP기능의 좀더 쉬운 통합, 메시지 자원 핸들링(국제화내에서 사용하기 위한), 이벤트 위임, ApplicationContext와 옵션적으로 부모 컨텍스트를 생성하기 위한 선언적인 기법, WebApplicationContext와 같은 애플리케이션 레이어 특정 컨텍스트를 사용하는 것과 같은 다른 기능을 추가한다.

짧게 말하면 ApplicationContext가 그것들중 몇몇 좀더 J2EE이고 기업중심인 것을 위한 향상된 기능을 추가하는 반면에 BeanFactory는 설정 프레임워크와 기본적인 기능을 제공한다. 일반적으로 ApplicationContext는 BeanFactory의 완벽한 수퍼셋(superset)이고 BeanFactory기능과 행위의 어떤 설명은 ApplicationContexts에 잘 적용이 되도록 검토되어야 한다.

사용자들은 때때로 BeanFactory나 ApplicationContext중 어느것이 특정 상황에서 사용하기 위해 가장 적합한지 확신하지 못한다. 대개 J2EE환경내 대부분의 애플리케이션을 빌드할때 ApplicationContext는 일반적으로 선호하는 몇몇 기능의 사용을 위한 좀더 선언적인 접근법을 허용하는 동안 BeanFactory의 모든 기능을 제공하고 기능적인 면에서 이것을 추가하였기 때문에 가장 좋은 선택은 ApplicationContext를 사용하는 것이다.. 당신이 BeanFactory를 사용하기 위해 선택할 중요한 사용 시나리오는 메모리 사용이 가장 큰 관심사항일때(가장 최근 킬로바이트가 계산하는 애플릿과 같은 경우)이고 당신이 ApplicationContext의 모든 기능을 필요로 하지 않을때이다.

이 장은 BeanFactory와 ApplicationContext에 둘다 관련되는 사항들을 다룬다. BeanFactory를 언급할때 당신은 언제나 ApplicaitonContext에도 적용이 된다고 가정할것이다. 기능이 ApplicaitonContext에만 적용될때는 명시적으로 이것만 언급한다.

3.2. BeanFactory 와 BeanDefinitions - 기초

3.2.1. BeanFactory

BeanFactory 는 인스턴스를 생성하고 설정하고 많은 수의 bean을 관리하는 실질적인 컨테이너이다. 그 bean들은 일반적으로 서로 협력하고 그들 사이의 의존성을 가진다. 그 의존성들은 BeanFactory에 의해 사용된 설정 데이타에 반영된다(비록 몇몇 의존성은 설정 데이타에서 보이지 않을수도 있지만 실행시 bean사이의 프로그램마다 다른 상호작용의 기능이 될것이다.).

BeanFactory는 다중 구현물을 위한 org.springframework.beans.factory.BeanFactory인터페이스에 의해 나타난다. 가장 공통적으로 사용되는 간단한 BeanFactory구현물은 org.springframework.beans.factory.xml.XmlBeanFactory이다. (이것은 ApplicationContexts가 BeanFactory의 하위 클래스임을 알리는 자격을 갖추어야 한다. 그리고 대부분의 사용자는 결국에 ApplicationContext의 각각 다른 형태의 XML을 사용해야만 한다. )

비록 대부분의 시나리오를 위해 BeanFactory에 의해 관리되는 대부분의 모든 사용자 코드가 BeanFactory를 인식하지 못하더라도 BeanFactory는 어떻게 해서든지 인스턴스화되어야 한다. 이것은

InputStream is = new FileInputStream("beans.xml");
XmlBeanFactory factory = new XmlBeanFactory(is);

또는

ClassPathResource res = new ClassPathResource("beans.xml");
XmlBeanFactory factory = new XmlBeanFactory(res);

또는

ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext(
        new String[] {"applicationContext.xml", "applicationContext-part2.xml"});
// of course, an ApplicationContext is just a BeanFactory
BeanFactory factory = (BeanFactory) appContext;

과 같은 명시적인 사용자 코드를 통해 발생할수 있다.

많은 사용 시나리오를 위한 사용자 코드는 Spring프레임워크가 인스턴스화 한 이후 BeanFactory를 인스턴스화 하지 않을것이다. 예를 들면 웹 레이어는 J2EE웹 애플리케이션의 일반적인 시작 프로세스의 일부처럼 Spring ApplicationContext을 자동적으로 로드하기 위한 지원 코드를 제공한다. 이 선언적인 프로세스는 here에서 언급된다.

BeanFactory의 프로그램마다 다른 조작이 이후에 언급될때까지 다음 부분은 BeanFactory의 설정을 언급하는데 집중할것이다.

BeanFactory설정은 가장 기초적인 레벨, BeanFactory가 관리해야만 하는 하나 이상의 bean의 정의로 구성된다. XmlBeanFactory에서 가장 상위레벨의 beans 요소내 하나 이상의 beans요소로 설정된다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>
  
  <bean id="..." class="...">
    ...
  </bean>
  <bean id="..." class="...">
    ...
  </bean>

  ...

</beans>

3.2.2. BeanDefinition

각각의 다른 형태(XmlBeanFactory처럼)의 DefaultListableBeanFactory내 Bean정의는 다음의 상세사항을 포함하는 BeanDefinition객체처럼 표현된다.

  • 클래스명 : 이것은 bean정의내 서술되는 bean의 실질적인 구현 클래스이다. 어쨌든 bean이 일반적인 생성자를 사용하는 대신에 정적인 factory메소드를 호출함으로써 생성된다면, 이것은 factory클래스의 클래스명이 될것이다.

  • bean행위적 설정 요소, bean이 컨테이너내에서 어떻게 행위를 하는지의 상태(이를 테면 프로터타입 또는 싱글톤, autowiring모드, 의존성 체크 모드, 초기화(initialization)와 제거(destruction)메소드.)

  • 새롭게 생성되는 bean내에 셋팅할 생성자 인자와 프라퍼티 값. 예제는 Connection pool(프라퍼티나 생성자 인자처럼 명시되는)이나 pool크기 제한을 관리하는 bean내에서 사용하는 Connection의 수가 될것이다.

  • 이것의 작업을 하기 위해 필요한 bean. 이를 테면 collaborators(프라퍼티나 생성자 인자처럼 명시되는). 의존성에 의해 호출될수 있다.

위에서 나열된 개념은 bean정의를 구성하는 요소의 집합을 직접적으로 번역한다. 그러한 요소그룹의 몇몇은 각각에 대한 좀더 상세한 문서 링크를 포함해서 밑에 나열했다.

bean정의는 실질적인 org.springframework.beans.factory.config.BeanDefinition인터페이스와 다양한 하위 인터페이스 그리고 구현물들에 의해 표현된다. 어쨌든 이것은 대부분의 사용자 코드가 BeanDefinition와 함께 작동할 것 같지는 않다.

bean을 생성하는 방법에 대한 정보를 포함하느 bean정의 외에도 bean factory는 존재하는 bean인스턴스를 등록하는것을 허용할수 있다. DefaultListableBeanFactory는 org.springframework.beans.factory.config.ConfigurableBeanFactory인터페이스에 의해 정의된 registerSingleton메소드를 통해 이것을 지원한다. 전형적인 애플리케이션은 단순히 bean 정의를 가지고 작동한다.

3.2.3. bean 클래스

class 속성은 일반적으로 필수(두 예외를 위한 Section 3.2.3.3, “인스턴스 factory메소드를 통한 Bean 생성”Section 3.5, “추상 그리고 자식 bean정의”을 보라.)이고 두가지 목적중 하나를 위해 사용된다. BeanFactory자체가 생성자를 호출함(new를 호출하는 자바코드와 같은)으로써 bean을 직접적으로 생성하는 많은 공통적인 상황에서 class속성은 생성될 bean의 클래스를 명시한다. BeanFactory가 bean을 생성하기 위한 클래스의 정적인 factory 메소드를 호출하는 다소 적은 공통적인 상황에서는 class속성은 정적 factory메소드를 포함하는 실질적인 클래스를 명시한다. (정적 factory메소드로 부터 반환되는 bean의 타입은 같은 클래스이거나 완전히 다른 클래스일것이다. 이것은 문제가 아니다.)

3.2.3.1. 생성자를 통한 Bean 생성

생성자를 이용한 접근법을 사용하여 bean을 생성할때 모든 일반적인 클래스는 Spring과 Spring에 호환되는 것에 의해 사용가능하다. 생성된 클래스는 어떤 특정 인터페이스를 구현하거나 특정 형태로 작성될 필요가 없다. bean클래스를 명시하는 것만으로도 충분하다.어쨌든 특정 bean을 위해 사용하기 위한 어떤 타입의 IoC에 의존한다. 당신은 아마도 디폴트(빈) 생성자가 필요할것이다.

추가적으로 BeanFactory는 자바빈을 관리하는데 제한을 두지 않는다. 이것은 또한 당신이 관리하고자 하는 사실상의 어떤 클래스를 관리하는것이 가능하다. Spring을 사용하는 대부분의 사람들은 BeanFactory내 실질적인 자바빈(프라퍼티뒤 디폴트 생성자와 선호하는 ssetter와 getter메소드를 가지는)을 가지는 것을 선호한다. 하지만 이것은 BeanFactory내 색다른 bean스타일이 아닌 클래스를 가지는것이 가능하다. 만약 예를 들어, 당신이 자바빈 애플리케이션을 따르지 않는 예전(legacy) connection pool을 사용할 필요가 있다면 걱정하지 말라. Spring은 이것을 잘 관리할수 있다.

XmlBeanFactory을 사용하면 당신은 다음처럼 당신의 bean클래스를 명시할수 있다.

<bean id="exampleBean"
      class="examples.ExampleBean"/>
<bean name="anotherExample"
      class="examples.ExampleBeanTwo"/> 

생성자를 위한 인자, 또는 객체 인스턴스가 생성된후 객체 인스턴스의 프라퍼티를 셋팅하기 위한 기법은 짧게 언급될것이다.

3.2.3.2. 정적 factory메소드를 통한 Bean 생성

정적인 factory메소드를 포함하는 클래스를 명시하는 class 속성에 따라 정적인 factory메소드를 사용하여 생성되기 위한 bean을 정의할때 factory-method라는 이름의 다른 속성은 factory메소드 자체의 이름을 명시할 필요가 있다. Spring은 만약 생성자를 통해 일반적으로 생성되는 것처럼 처리되는 시점으로 부터 이 메소드(나중에 언급되는 것처럼 인자의 목록을 가지는)를 호출하고 시스템에서 살아있는(live)객체를 돌려받는 것이 가능하도록 기대한다. bean정의와 같은 것을 위해 사용하는것은 예전(legacy)코드내 정적 factory를 호출하는것이다.

다음은 factory메소드를 호출함으로써 생성되기 위한 bean을 명시하는 bean정의의 예제이다. 정의는 반환되는 객체의 타입(클래스)를 명시하지 않을뿐 아니라 클래스는 factory메소드를 포함한다는것을 알라. 예를 들어 createInstance정적 메소드가 되어야만 한다.

<bean id="exampleBean"
      class="examples.ExampleBean2"
      factory-method="createInstance"/>

factory메소드를 위한 인자나 factory로 부터 반환된 뒤 객체 인스턴스의 프라퍼티를 셋팅하는 것을 지원하기 위한 기법은 짧게 언급될것이다.

3.2.3.3. 인스턴스 factory메소드를 통한 Bean 생성

bean을 생성하기 위한 정적 factory메소드를 사용하는 것과 흡사한것은 factory로 부터 존재하는 bean의 factory메소드가 새로운 bean을 생성하기 위해 호출되는 인스턴스 (정적이 아닌) factory메소드 사용이다.

이 기법을 사용하는것은 class 속성이 빈값으로 남아야만 하고 factory-bean 속성은 최근 bean의 이름이나 factory메소드를 포함하는 조상(ancestor) bean factory를 명시해야만 한다. factory메소드 자체는 factory-method 속성을 통해 셋팅될것이다.

다음은 예제이다.

<!-- The factory bean, which contains a method called
     createInstance -->
<bean id="myFactoryBean"
      class="...">
  ...
</bean>
<!-- The bean to be created via the factory bean -->
<bean id="exampleBean"
      factory-bean="myFactoryBean"
      factory-method="createInstance"/>

비록 bean프라퍼티를 셋팅하기 위한 기법이 여전히 논의되어도 이 접근법의 하나의 함축은 factory bean자체가 컨테이너에 의해 의존성 삽입(Dependency Injection)을 통해 관리되고 설정될수 있다.

3.2.4. bean 구분자 (idname)

모든 bean은 하나 이상의 id(구분자, 이름이라고 불리는:그 용어들은 같은것을 지칭한다.)를 가진다. 그 id들은 bean을 생성한 BeanFactory 나 ApplicationContext내에서 유일해야만 한다. bean은 거의 대부분 하나의 id만을 가진다. 하지만 bean이 한개의 id보다 많은수를 가진다면 나머지 것들은 기본적으로 별칭으로 간주될수 있다..

XmlBeanFactory(여러가지의 ApplicationContext를 포함해서)에서 당신은 bean id를 명시하기 위해 idname속성을 사용하고 적어도 하나의 id는 그 속성중 하나나 둘다에 명시해야만 한다. id 속성은 당신에게 하나의 id를 명시하는것을 허용하고 실제 XML요소의 ID속성처럼 XML DTD(정의문서)내 표시된다. 파서는 다른 요소점이 이 요소로 돌아갈때 몇몇 별개의 유효성체크를 하는것이 가능하다. 그러한 것처럼 bean id를 명시하기 위한 선호되는 방법이 있다. 어쨌든 XML스펙은 XML ID들내 지역적인(legal) 문자들을 제한한다. 이것은 언제나 제약인것은 아니지만 만약 당신이 그러한 문사들중 하나를 사용할 필요가 있거나 bean에 대한 다른 별칭을 소개하길(introduce) 원한다면 당신은 그렇게 하거나 name 속성을 통해 하나이상의 bean id(,으로 구분되는) 또는 세미콜론(;)을 명시하는것으로 대신한다.

3.2.5. 싱글톤이나 비-싱글톤(non-singleton)

bean은 두가지 모드(싱글톤또는 비-싱글톤)중 하나로 배치되기 위해 정의된다. 후자(비-싱글톤)는 프로토타입이라도도 불린다. 비록 용어가 정확하게 적합한것이 아닌 느슨하게 대충 사용이 되더라도). bean이 싱글톤일때 bean의 오직 하나의 공유인스턴스만이 관리될것이고 id를 가진 bean을 위한 모든 요청이나 bean정의에 되는 id가 반환되는 하나의 특정 bean인스턴스를 야기한다.

비-싱글톤, bean배치의 프로토타입 모드는 특정 bean이 수행하기 위한 요청에 대해 매번 새로운 bean인스턴스의 생성이라는 결과를 만든다. 이것은 예를 들어 각각의 사용자가 비의존적인 사용자 객체나 유사한 어떤것이 필요한 상황에 이상적이다.

bean은 당신이 디폴트로 싱글톤모드로 배치된다. 타입을 비-싱글톤으로 변경함으로써 그것을 잊지말라. bean을 위한 각각의 요청은 새롭게 생성된 bean을 초래하고 이것은 당신이 실질적으로 원하는것이 되지는 않을것이다. 그래서 오직 절대적으로 필요할때 모드를 프로터타입으로 변경하라.

아래의 예제에서, 두개의 bean은 하나는 싱글톤으로 정의되고 다른 하나는 비-싱글톤(프로토타입)으로 선언되었다. yetAnotherExample이 오직 한번만 생성되는 동안 exampleBean은 각각 생성되고 매번 클라이언트는 이 bean을 위해 BeanFactory를 요청한다. 정확히 같은 인스턴스를 위한 참조는 이 bean을 위한 각각의 요청에 반환된다.

<bean id="exampleBean"
      class="examples.ExampleBean" singleton="false"/>
<bean name="yetAnotherExample"
      class="examples.ExampleBeanTwo" singleton="true"/>

메모: bean을 프로토타입모드내에서 배치할때 bean의 생명주기는 미세하게 변경한다. 정의에 의해 Spring은 이것이 생성된 후에는 비-싱글톤/프로토타입 bean의 완벽한 생명주기를 관리할수 없다. 이것은 더 이상 놓치지 않는 클라이언트와 컨테이너에게 주어진다.당신은 'new' 연산(operator)을 위한 교체품처럼 비-싱글톤/프로토타입 bean에 대하여 얘기할때 Spring의 역활에 대해 생각할수있다. 어떠한 생명주기 양상은 클라이언트에 의해 다루어질 시점을 지난다. BeanFactory내 bean의 생명주기는 Section 3.4.1, “Lifecycle 인터페이스”에서 좀더 상세하게 언급된다.

3.3. 프라퍼티, 협력자(collaborators), autowiring 과 의존성 체크

3.3.1. bean프라퍼티와 협력자(collaborators) 셋팅하기

IoC(Inversion of Control)는 이미 Dependency Injection처럼 간주되고 있다. 기본적인 원칙은 오직 생성자 인자, factory메소드를 위한 인자 또는 factory메소드로부터 생성되거나 반환된 후 객체 인스턴스에 셋팅하는 프라퍼티를 통해 bean이 그것들의 의존성(이를 테면, 그것들이 함께 작동하는 다른 객체들)을 명시한다는 것이다. 그 다음, 이것이 bean을 생성할때 그러한 의존성을 실질적으로 삽입하기 위한 컨테이너의 책임이다. 이것은 근본적으로 bean인스턴스화의 역전(inverse - 나아가 Inversion of Control)이거나 클래스의 직접적인 생성을 사용하여 그것의 의존성을 위치시키는 것이다. 또는 서비스 위치자(Locator) 패턴처럼 어떤것이다. 우리가 의존성 삽입의 장점에 정성을 들이지 않을동안 이것은 코드가 좀더 깔끔하게 되고 좀더 높은 디커플링 등급에 도달하는 것이 bean이 그것들의 의존성을 보지 않을때 좀더 쉽게 되도록 하는 명백한 사용법이 된다. 하지만 그것들과 함께 제공되고 추가적으로 의존성이 위치하는 곳과 실질적인 타입이 무엇인지 알지 않는다.

앞 단락에서 알아본것처럼 Inversion of Control/의존성 삽입은 두가지 큰 종류가 존재한다.

  • setter-기반 의존성 삽입은 당신의 bean을 인스턴스화 하기 위한 인자 없는 생성자나 인자 없는 정적 factory메소드를 호출한 후에 당신의 bean의 setter를 호출함으로써 구체화된다. BeanFactory내 명시된 bean은 setter-기반 의존성 삽입을 사용하는 것이 신뢰할수 있는(true) 자바빈이다. Spring은 많은 수의 생성자 인자가 다루기 어렵고, 몇몇 프라퍼티가 선택사항일때 유별나기 때문에 대개 setter-기반 의존성 삽입의 사용을 지지한다.

  • 생성자-기반 의존성 삽입은 각각 협력자(collaborator)나 프라퍼티를 표현하는 많은 수의 인자를 가진 생성자를 호출함으로써 구체화된다. 추가적으로 bean을 생성하기 위해 특정 인자를 가진 정적 factory메소드를 호출하는 것은 대부분 동일하게 간주될수 있고 이 글의 나머지는 생성자를 위한 인자와 정적 factory메소드를 위한 인자를 눈여겨 볼것이다. 비록 Spring이 대부분의 경우를 위해 setter-기반 의존성 삽입의 사용을 지지한다고 하더라도 당신이 setter없이 다중 인자를 가진 생성자만 제공하는 이미 존재하는 bean을 사용하길 바랄지도 모르기 때문에 생성자-기반 접근법또한 완벽하게 지원한다. 추가적으로 좀더 간단한 bean을 위해 몇몇 사람들은 bean이 유효하지 않은 상태에서 생성이 될수 없다는 것을 확신하는 의미처럼 생성자 접근법을 선호한다.

BeanFactory는 이것이 관리하는 bean으로 의존성을 삽입하기 위한 이러한 형태 둘다 지원한다(이것은 사실 몇몇 의존성이 생성자 접근법을 통해 이미 제공된 후 setter-기반으로 의존성을 삽입하는 것들 지원한다.). BeanDefinition의 형태로 들어오는 의존성을 위한 설정은 프라퍼티를 하나의 포맷에서 다른 포맷으로 변환하는 방법을 알기 위한 자바빈 PropertyEditors와 같이 사용된다. 여기저기 전달되는 실제 값들은 PropertyValue객체의 형태로 수행된다. 어쨌든 Spring의 대부분의 사용자는 직접적으로 이 클래스를 다루지는 않을것이지만(이를테면 프로그램에 따라 다르게) XML정의 파일은 내부적으로 그러한 클래스의 인스턴스로 변환될것이고 전체 BeanFactory 나 ApplicationContext를 로드하기 위해 사용된다.

Bean의존성 해석은 일반적으로 다음처럼 발생한다.

  1. BeanFactory는 모든 bean을 서술하는 설정으로 생성되고 초기화된다. 대부분의 Spring사용자는 XML형태의 설정 파일을 지원하는 BeanFactory 나 ApplicationContext 종류를 사용한다.

  2. 각각의 bean은 프라퍼티, 생성자 인자, 또는 보통의 생성자 대신에 사용되는 정적 factory메소드를 위한 인자의 형태로 표현되는 의존성을 가진다. 이러한 의존성은 bean이 실질적으로 생성되었을때 bean에 제공될것이다.

  3. 각각의 프라퍼티또는 생성자의 인자는 셋팅하기 위한 값의 실질적인 정의이거나 BeanFactory내에서 다른 bean에 대한 참조이다. ApplicationContext의 경우 참조는 부모 ApplicationContext내 bean에 대한 것이 될수 있다.

  4. 각각의 프라퍼티와 생성자의 인자는 이것이 명시하는 어떠한 타입에서 프라퍼티나 생성자의 인자의 실질적인 타입으로 변환될수 있어야만 하는 값이다. 디폴트에 의해 Spring은 문자열로 제공되는 값에서 int, long, String, boolean, 등등과 같은 모든 내장 타입으로 변환할수 있다. 추가적으로 XML기반 BeanFactory 형태에 대해서 이야기 할때 그것들은 List, Map, Set 그리고 프라퍼티 collection타입들을 정의하기 위한 내장 지원을 가진다. 추가적으로 Spring은 문자열 값에서 다른, 임의의 타입으로 변환될수 있는 자바빈 PropertyEditor정의를 사용한다.(당신은 당신 자신의 사용자 지정 타입으로 변환할수 있도록 하는 당신 자신의 PropertyEditor정의를 가진 BeanFactory를 제공할수 있다. PropertyEditors에 대한 좀더 상세한 정보와 사용자 지정 PropertyEditors를 직접 추가하는 방법은 Section 3.9, “추가적인 사용자지정 PropertyEditors 등록하기”에서 찾을수 있다.). bean프라퍼티 타입이 자바클래스 타입일때 Spring은 당신에게 클래스명인 문자열값 같은 프라퍼티를 위한 값을 명시하도록 허용하고 내장된 ClassEditor PropertyEditor는 실질적인 클래스 인스턴스를 위한 클래스명을 변환하는것을 다룰것이다.

  5. Spring이 BeanFactory가 생성될때 bean참조가 유효한 bean에 실질적으로 참조(이를 테면 참조될 bean은 BeanFactory내 명시되거나 ApplicationContext의 경우 부모 컨텍스트)하는 프라퍼티를 체크하는것을 포함해서 BeanFactory내 각각의 bean의 설정을 체크하는 것을 구체화하는것은 중요하다. 어쨌든 bean프라퍼티 자체는 bean이 실질적으로 생성이 될때까지 셋팅되지 않는다. 싱글톤이고 미리 인스턴스화(ApplicationContext내 싱글톤 bean과 같은)되기 위한 bean을 위해 생성은 BeanFactory가 생성될때마다 발생하지만 반면에 이것은 bean이 요청될때만 발생한다. bean이 실질적으로 생성되었다면 이것은 의존성과 의존성의 의존성이 생성되고 할당되는것처럼 생성되기 위한 다른 bean의 그래프(graph)를 잠재적으로 야기할 것이다.

  6. 당신은 일반적으로 적절한것을 하기 위해 Spring을 신뢰할수 있다. 이것은 BeanFactory가 로드되는 시점에 존재하지 않는 bean과 순환적인 의존성에 대한 참조를 포함해서 설정사항을 다룰것이다. 이것은 bean이 실질적으로 생성될때 가능한 늦게 실질적으로 프라퍼티를 셋팅하고 의존성을 해석(이를 테면 필요하다면 그러한 의존적인것을 생성한다.)한다. 이것은 정확하게 로드되는 BeanFactory가 bean이나 이것의 의존성중 하나를 생성할때 문제가 발생한다면 당신이 bean을 요청할때 나중에 예외를 생성할수 있다는 것을 의미한다. 이것은 예를 들어 bean이 잃어버리거나 유효하지 않은 프라퍼티의 결과처럼 예외를 던진다면 발생할수 있다. 몇몇 설정사항의 잠재적으로 늦은(delayed) 가시성(visibility)은 디폴트에 의해 ApplicationContext이 싱글톤 bean을 미리 인스턴스화하기 때문이다. 그것들이 실질적으로 필요하기 전에 그 bean을 생성하기 위한 몇몇 선행 시간과 메모리의 비용에서 당신은 나중이 아닌 ApplicationContext이 생성될때 설정사항에 대해 찾는다. 만약 당신이 바란다면 당신은 이 디폴트 행위를 오버라이드 할수 있고 늦은 로드(미리 인스턴스화되는것이 아닌)를 위한 싱글톤 bean을 셋팅할수 있다.

몇몇 예제들

첫번째 setter-기반 의존성 삽입을 위해 BeanFactory를 사용하는 예제. 아래는 몇몇 bean정의를 명시하는 XmlBeanFactory설정 파일의 작은 일부이다. 다음은 실질적으로 핵심적인 bean자체를 위한 코드이다. 선호하는 setter가 선언된것을 보자.

<bean id="exampleBean" class="examples.ExampleBean">
    <property name="beanOne"><ref bean="anotherExampleBean"/></property>
    <property name="beanTwo"><ref bean="yetAnotherBean"/></property>
    <property name="integerProperty"><value>1</value></property>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

public class ExampleBean {
    
    private AnotherBean beanOne;
    private YetAnotherBean beanTwo;
    private int i;
    
    public void setBeanOne(AnotherBean beanOne) {
        this.beanOne = beanOne;
    }
    
    public void setBeanTwo(YetAnotherBean beanTwo) {
        this.beanTwo = beanTwo;
    }
    
    public void setIntegerProperty(int i) {
        this.i = i;
    }    
}

당신이 볼수 있는 것처럼 setter는 XML파일내 명시되는 프라퍼티에 대응하기 위해 선언된다. (XML파일의 프라퍼티는 RootBeanDefinition로부터 PropertyValues객체와 직접적으로 관련된다.)

IoC타입 3(생성자-기반 의존성 삽입)을 위해 BeanFactory를 사용하는 예제. 아래는 생성자의 인자와 실질적인 bean코드를 명시하는 XML설정의 작은 조각이다. 생성자를 보자.

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg><ref bean="anotherExampleBean"/></constructor-arg>
    <constructor-arg><ref bean="yetAnotherBean"/></constructor-arg>
    <constructor-arg type="int"><value>1</value></constructor-arg>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

public class ExampleBean {

    private AnotherBean beanOne;
    private YetAnotherBean beanTwo;
    private int i;
    
    public ExampleBean(AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
        this.beanOne = anotherBean;
        this.beanTwo = yetAnotherBean;
        this.i = i;
    }
}

당신이 볼수 있는것처럼 bean정의내 명시되는 생성자의 인자는 ExampleBean의 생성자를 위한 인자처럼 전달될것이다.

지금 생성자를 사용하는것 대신에 사용되는 것들의 다양한 종류를 검토해보자. Spring은 객체의 인스턴스를 반환하기 위해 정적 factory메소드를 호출하는것을 말한다.

<bean id="exampleBean" class="examples.ExampleBean"
      factory-method="createInstance">
    <constructor-arg><ref bean="anotherExampleBean"/></constructor-arg>
    <constructor-arg><ref bean="yetAnotherBean"/></constructor-arg>
    <constructor-arg><value>1</value></constructor-arg>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

public class ExampleBean {

    ...

    // a private constructor
    private ExampleBean(...) {
      ...
    }
    
    // a static factory method
    // the arguments to this method can be considered the dependencies of the bean that
    // is returned, regardless of how those arguments are actually used.
    public static ExampleBean createInstance(
            AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
        ExampleBean eb = new ExampleBean(...);
        // some other operations
        ...
        return eb;
    }
}

정적 factory메소드를 위한 인자는 constructor-arg요소를 통해 제공된다. 생성자가 실질적으로 사용되는 것처럼 정확하게 같다. 그 인자들은 선택사항이다. 물론 정적 factory메소드를 포함하는 클래스와 같은 타입이 되지않을 factory메소드에 의해 반환될 클래스의 타입을 구체화하는것은 중요하다. 앞에서 언급된 인스턴스(정적이 아닌) factory메소드는 기본적으로 동일한 형태(class속성 대신에 factory-bean속성의 사용을 제외하고)로 사용되기 때문에 여기서는 상세하기 다루지 않을것이다.

3.3.2. 생성자의 인자 분석

생성자의 인자 분석 대응(matching)은 인자타입을 사용할때 발생한다. 다른 bean이 참조될때 타입은 알려지고 대응은 발생한다. <value>true<value>와 같은 간단한 타입이 사용될때 Spring은 값의 타입을 결정할수 없고 도움 없이는 타입에 의해 대응(match)할수 없다. 두개의 부분을 위해 사용된 다음의 클래스를 검토하라.

package examples;

public class ExampleBean {

    private int years;             //No. of years to the calculate the Ultimate Answer
    private String ultimateAnswer; //The Answer to Life, the Universe, and Everything

    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}

3.3.2.1. 생성자의 인자 타입 대응(match)

위 시나리오는 type 속성을 사용하여 생성자의 인자 타입을 명확하게 명시함으로써 간단한 타입으로의 타입 매치를 사용할수 있다. 예를 들면.

<bean id="exampleBean" class="examples.ExampleBean">
     <constructor-arg type="int"><value>7500000</value></constructor-arg>
     <constructor-arg type="java.lang.String"><value>42</value></constructor-arg>
</bean> 

3.3.2.2. 생성자의 인자 인덱스

생성자의 인자는 index 속성을 사용하여 명확하게 명시된 인덱스를 가질수 있다. 예를 들면.

<bean id="exampleBean" class="examples.ExampleBean">
     <constructor-arg index="0"><value>7500000</value></constructor-arg>
     <constructor-arg index="1"><value>42</value></constructor-arg>
</bean> 

여러개의 간단한 값들의 모호한 문제를 푸는것에 더하여 인덱스를 명시하는 것은 생성자가 같은 타입의 두개의 인자를 가지는 모호함의 문제도 해결한다. 인덱스는 0 부터 시작된다는것에 주의하라.

생성자의 인자 인덱스를 명시하는것은 생성자 IoC를 수행하는 방법이 선호된다.

3.3.3. bean프라퍼티와 상세화된 생성자의 인자

앞 부분에서 언급된것처럼 bean프라퍼티와 생성자의 인자는 다른 관리빈(협력자), 또는 인라인으로 명시된 값의 참조처럼 명시될수 있다. XmlBeanFactory는 이러한 목적을 위해 propertyconstructor-arg내 많은 수의 하위요소타입을 지원한다.

value 요소는 사람이 읽을수 있는 문자열 표현처럼 프라퍼티나 생성자의 인자를 명시한다. 앞서 상세하게 언급된것처럼 자바빈 PropertyEditors는 java.lang.String로 부터 문자열값을 실질적인 프라퍼티나 인자타입으로 변환하기 위해 사용된다.

<beans>
    <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <!-- results in a setDriverClassName(String) call -->
        <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>
</beans> 

null 요소는 null값을 다루기 위해 사용된다. Spring은 프라퍼티를 위한 빈 인자를 빈 문자열처럼 처리한다. 다음은 XmlBeanFactory설정이다.

<bean class="ExampleBean">
    <property name="email"><value></value></property>
</bean>        

email프라퍼티내 결과는 ""으로 셋팅되고 자바코드 exampleBean.setEmail("") 와 동일하다. 특별한 <null> 요소는 아마도 null값을 표시하는데 사용될것이다.

<bean class="ExampleBean">
    <property name="email"><null/></property>
</bean>        

이것은 자바코드 exampleBean.setEmail(null) 와 동일하다.

list, set, map, 그리고 props 요소는 명시하고 셋팅되기 위한 프라퍼티와 자바타입 List, Set, Map, 그리고 Properties 의 인자를 허용한다.

<beans>
    ...
    <bean id="moreComplexObject" class="example.ComplexObject">
        <!-- results in a setPeople(java.util.Properties) call -->
        <property name="people">
            <props>
                <prop key="HarryPotter">The magic property</prop>
                <prop key="JerrySeinfeld">The funny property</prop>
            </props>
        </property>
        <!-- results in a setSomeList(java.util.List) call -->
        <property name="someList">
            <list>
                <value>a list element followed by a reference</value>
                <ref bean="myDataSource"/>
            </list>
        </property>
        <!-- results in a setSomeMap(java.util.Map) call -->
        <property name="someMap">
            <map>
                <entry key="yup an entry">
                    <value>just some string</value>
                </entry>
                <entry key="yup a ref">
                    <ref bean="myDataSource"/>
                </entry>
            </map>
        </property>
        <!-- results in a setSomeSet(java.util.Set) call -->
        <property name="someSet">
            <set>
                <value>just some string</value>
                <ref bean="myDataSource"/>
            </set>
        </property>

    </bean>
</beans>

map의 값이나 set값은 어느 요소도 될수 있다는 것을 알라.

(bean | ref | idref | list | set | map | props | value | null)

property 요소내 bean 요소는 BeanFactory내 명시되는 bean을 위한 참조대신에 bean값을 인라인으로 명시하기 위해 사용된다. 인라인 bean정의는 명시되는 어떠한 id로 필요로 하지 않는다.

<bean id="outer" class="...">
    <!-- Instead of using a reference to target, just use an inner bean -->
    <property name="target">
        <bean class="com.mycompany.PersonImpl">
            <property name="name"><value>Tony</value></property>
            <property name="age"><value>51</value></property>
        </bean>
   </property>
</bean>

idref 요소는 컨테이너내 다른 bean의 문자열 idname으로 프라퍼티를 셋팅하기 위한 짧고(shorthand) 에러를 검사(error-proof)하는 방법이다.

<bean id="theTargetBean" class="...">
</bean>
<bean id="theClientBean" class="...">
    <property name="targetName">
        <idref bean="theTargetBean"/>
    </property>
</bean>

이것은 수행시 다음의 조각들과 동일하다.

<bean id="theTargetBean" class="...">
</bean>
<bean id="theClientBean" class="...">
    <property name="targetName">
        <value>theTargetBean</value>
    </property>
</bean>

첫번째 형태가 두번째를 선호하는 가장 중요한 이유는 idref 태그를 사용하는 것이 Spring이 다른 bean이 실질적으로 존재하는 배치시점에 유효하도록 허용한다는것이다. 두번째 에서 targetName프라퍼티의 클래스는 자기 자신이 유효하도록 강제하고 클래스가 Spring에 의해 가능하면 컨테이너가 실질적으로 배치한 후에 실질적으로 인스턴스화될때 발생할것이다.

추가적으로 만약 참조되는 bean이 같은 XML파일내에 있고 bean이름이 bean id라면 사용될 local 속성은 XML문서를 파싱하는 시점에 좀더 일찍 XML파서가 bean이름을 자체적으로 체크하는것을 허용할것이다.

    <property name="targetName">
        <idref local="theTargetBean"/>
    </property>

ref 요소는 property정의 요소내에서 허용되는 마지막 요소이다. 이것은 컨테이너나 협력자에 의해 관리되는 다른 bean을 위해 참조되기 위한 명시된 프라퍼티의 값을 셋팅하는데 사용된다. 앞 부분에서 언급했던것 처럼 참조되는 bean은 프라퍼티가 셋팅되는 bean의 의존성이 되는것이 검토되고 프라퍼티가 셋팅되기 전에 필요(만약 이것이 싱글톤 bean이라면 이것은 컨테이너에 의해 이미 초기화되었을것이다.)하다면 요구에 의해 초기화될것이다. 모든 참조는 궁극적으로 다른 객체에 대한 참조이지만 다른 객체의 id/name을 명시하는 방법은 3가지가 있다.

ref 태그의 bean 속성을 사용하여 대상 bean을 명시하는것이 가장 일반적인 형태이고 같은 BeanFactory/ApplicationContext(같은 XML파일이든 아니든)나 부모 BeanFactory/ApplicationContext내에서 어떠한 bean에 대한 참조를 생성하는 것을 허용할것이다. bean 속성의 값은 대상 bean의 id 속성이나 name 속성의 값중 하나처럼 같은것이 될것이다.

    <ref bean="someBean"/>

local 속성을 사용하여 대상 bean을 명시하는것은 같은 파일내 타당한 XML id 참조를 위한 XML파서의 기능에 영향을 미친다. local 속성의 값은 대상 bean의 id 속성과 같아야만 한다. XML파서는 대응되는 요소가 같은 파일내에 발견되지 않는다면 에러를 발생시킬것이다. 그런것처럼 만약 대상 bean이 같은 XML파일내 있다면 local 형태를 사용하는 것이 가장 좋은 선택(가능한한 빨리 에러에 대해 알기 위해)이다.

    <ref local="someBean"/>

parent 속성을 사용하여 대상 bean을 명시하는 것은 현재 BeanFactory(나 ApplicationContext)의 부모 BeanFactory(나 ApplicationContext)내 있을 bean을 위해 생성될 참조를 허용한다. parent 속성의 값은 아마 대상 bean의 id 속성이나 name 속성내 값중에 하나와 같을것이고 대상 bean은 최근것을 위해 부모 BeanFactory 나 ApplicationContext내 있어야만 한다. bean참조 형태의 가장 중요한 사용은 몇몇 프록시의 순서대로 부모 컨텍스트내 존재하는 bean을 포장하기 위해 필요할때이고 초기의 객체는 그것을 포장하기 위해 필요하다.

    <ref parent="someBean"/>

3.3.3.1. value와 ref 간략화한(shortcut) 폼

이것은 value이나 bean참조를 설정하기 위해 필요한 공통사항이다. 완전한 형태의 valueref를 사용하는것보다 다소 덜 장황하게 간략화한 몇가지 형태가 존재한다. property, constructor-arg, 그리고 entry 요소 모두 완전한 형태의 value 요소 대신에 사용된 value 속성을 지원한다. 결과로써 다음과 같다.

<property name="myProperty">
    <value>hello</value>
</property
<constructor-arg>
    <value>hello</value>
</constructor-arg>
<entry key="myKey">
    <value>hello</value>
</entry>

는 다음과 동일하다.

<property name="myProperty" value="hello"/>

<constructor-arg value="hello"/>

<entry key="myKey" value="hello"/>

대개 손으로 정의를 작성할때 당신은 다소 덜 장황한 간략화된 형태를 사용하는것을 선호할것이다.

propertyconstructor-arg 요소는 완전한 형태의 내포된 ref 요소 대신에 사용될 유사한 간략화된 ref 속성을 지원한다. 다음과 같다.

<property name="myProperty">
    <ref bean="myBean">
</property
<constructor-arg>
    <ref bean="myBean">
</constructor-arg>

는 다음과 동일하다.

<property name="myProperty" ref="myBean"/>

<constructor-arg value="myBean"/>

어쨌든 간략화된 형태는 <ref bean="xxx"> 요소와 동일하다. <ref local="xxx"> 를 위한 간략화된 형태는 없다. local ref를 위해 당신은 긴 형태를 사용해야만 한다.

마지막으로 entry 요소는 key-refvalue-ref속성의 형태로 map의 키 그리고/또는 값을 명시하기 위한 간략화된 형태를 허용한다. 다음과 같다.

<entry>
  <key><ref bean="myKeyBean"/></key>
  <ref bean="myValueBean"/>
</entry>

는 다음과 동일하다.

<entry key-ref="myKeyBean" value-ref="myValueBean"/>

다시 간략화된 형태는 <ref bean="xxx"> 요소와 동일하다. <ref local="xxx"> 를 위한 간략화된 형태는 없다.

3.3.4. 메소드 삽입

대부분의 사용자를 위해 컨테이너내 대부분의 bean은 싱글톤일것이다. 싱글톤 bean이 다른 싱글톤 bean과 협력할 필요가 있거나 비-싱글톤 bean이 다른 비-싱글톤 bean과 협력할 필요가 있을때 다른 것의 프라퍼티가 되기 위한 하나의 bean을 명시하여 이 의존성을 다루는 전형적이고 공통적인 접근법은 꽤 충분하다. 어쨌든 bean생명주기가 다를때 문제가 있다. 비-싱글톤(프로토타입) bean B를 사용할 필요가 있는 싱글톤 bean A가 A의 각각의 메소드 호출을 한다고 해보자. 컨테이너는 싱글톤 bean A를 단지 한번만 생성할것이고 그것의 프러퍼티를 셋팅하기 위한 기회를 오직 한번만 가진다. 그것이 필요할때마다 bean B의 새로운 인스턴스를 가진 bean A를 제공하기 위한 컨테이너를 위한 기회는 없다.

이 문제를 해결하기 위한 하나의 해결법은 몇몇 Inversion of Control을 버리는 것이다. bean A는 BeanFactoryAware를 구현해서 컨테이너( 여기에서 언급된것처럼)를 인식할수 있고 이것이 필요할때마다 (새로운) bean B를 위한 getBean("B") 호출을 통해 컨테이너에게 요청하기 위한 프로그램마다 다른 방법을 사용한다. 이것은 bean코드가 인식을 하고 Spring에 커플링이 되기 때문에 대개 바람직한 해결법은 아니다.

BeanFactory의 향상된 기능인 메소드 삽입은 몇몇 다른 시나리오에 따라 깔끔한 형태로 다루어지는 사용 상황을 허용한다.

3.3.4.1. 룩업(Lookup) 메소드 삽입

룩업 메소드 삽입은 컨테이너내 다른 명명된 bean를 룩업하는 결과를 반환하는 컨테이너내 관리빈의 추상및 구현된 메소드를 오버라이드하기 위해 컨테이너의 기능을 적용한다. 룩업은 위에서 언급(비록 싱글톤이 될수 있더라도)된 시나리오마다 비-싱글톤 bean이 될것이다. Spring은 CGLIB 라이브러리를 통해 바이트코드 생성을 사용하여 동적으로 생성된 하위클래스가 메소드를 오버라이드하는것을 통해 이것을 구현한다.

삽입되어야 할 메소드를 포함하는 클라이언트 클래스내에서 메소드 정의는 이 형태로 추상(또는 구현된) 정의가 되어야만 한다.

protected abstract SingleShotHelper createSingleShotHelper();

만약 메소드가 추상적이지 않다면 Spring은 존재하는 구현물을 간단하게 오버라이드 할것이다. XmlBeanFactory의 경우 당신은 bean정의내 lookup-method요소를 사용해서 컨테이너로부터 특정 bean을 반환하기 위한 이 메소드를 삽입/오버라이드 하도록 Spring에게 지시한다. 예를 들면

<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="singleShotHelper class="..." singleton="false">
</bean>

<!-- myBean uses singleShotHelper -->
<bean id="myBean" class="...">
  <lookup-method name="createSingleShotHelper"
                 bean="singleShotHelper"/>
  <property>
    ...
  </property>
</bean>

myBean처럼 구분되는 bean은 이것이 singleShotHelper bean의 새로운 인스턴스를 필요로 할때마다 그것 자신의 메소드인 createSingleShotHelper을 호출할것이다. bean을 배치하는 사람은 비-싱글톤(그것이 실질적으로 필요한 것이라면)처럼 singleShotHelper를 배치하기 위해 주의해야 한다고 알리는것이 중요하다. 만약 싱글톤(명시적이거나 이 플래그를 위해 디폴트로 true셋팅한 것에 의존하여)처럼 배치되었다면 singleShotHelper의 같은 인스턴스는 매번 반환될것이다 !

룩업 메소드 삽입은 생성자 삽입(생성되는 bean을 위한 선택사항인 생성자의 인자를 제공하는)과 함께 조합될수 있고 또한 setter삽입(생성되는 bean의 프라퍼티를 셋팅하는)과도 조합될수 있다.

3.3.4.2. 임의의 메소드 교체

룩업 메소드 삽입보다 메소드 삽입의 다소 덜 공통적으로 유용한 형태는 다른 메소드 구현물을 가진 관리빈내에 임의의 메소드를 교체하는 기능이다. 사용자는 이 기능이 실질적으로 필요할때까지 이 부분의 나머지(향상된 기능에 대해 언급하는)를 생략할수 있다.

XmlBeanFactory에서 replaced-method 요소는 배치된 bean위해 다른것을 가진 존재하는 메소드 구현물을 교체하기 위해 사용된다. 우리가 오버라이드하길 원하는 메소드 computeValue를 가진 다음의 클래스를 검토하라.

...
public class MyValueCalculator {
  public String computeValue(String input) {
    ... some real code
  }

  ... some other methods
}

org.springframework.beans.factory.support.MethodReplacer인터페이스를 구현하는 클래스는 새로운 메소드 정의를 제공할 필요가 있다.

/** meant to be used to override the existing computeValue
    implementation in MyValueCalculator */
public class ReplacementComputeValue implements MethodReplacer {

    public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
        // get the input value, work with it, and return a computed result
        String input = (String) args[0];
        ... 
        return ...;
}

원래의 클래스를 배치하고 오버라이드할 메소드를 명시하기 위한 BeanFactory배치 정의는 다음처럼 보일것이다.

<bean id="myValueCalculator class="x.y.z.MyValueCalculator">
    <!-- arbitrary method replacement -->
    <replaced-method name="computeValue" replacer="replacementComputeValue">
        <arg-type>String</arg-type>
    </replaced-method>
</bean>

<bean id="replacementComputeValue" class="a.b.c.ReplaceMentComputeValue">
</bean>

replaced-method 요소내 하나 이상이 포함된 arg-type 요소는 오버라이드된 메소드의 메소드 시그너처를 표시하기 위해 사용된다. 인자를 위한 시그너처는 메소드가 실질적으로 오버로드되고 클래스내 몇가지 종류가 되는 경우에만 필요하다. 편의상 인자를 위한 문자열 타입은 완전한 형태의 타입명의 일부가 된다. 예를 들면 다음은 java.lang.String과 대응된다.

    java.lang.String
    String
    Str

많은 수의 인자가 종종 각각의 가능한 선택들 사이에 구별하기 충분하기 때문에 이 간략화된 형태는 인자에 대응될 가장 짧은 문자열을 사용하여 많은 타입을 저장할수 있다.

3.3.5. depends-on 사용하기

대부분의 상황을 위해 bean이 다른 것들의 의존성이라는 사실은 하나의 bean이 다른것의 프라퍼티처럼 셋팅한다는 사실에 의해 간단하게 표현된다. 이것은 XmlBeanFactory내 ref 요소를 가지고 수행한다. 이것의 다양한 종류에서 때때로 컨테이너를 인식하는 bean은 간단하게 주어진 의존성(문자열 값이나 문자열 값과 같은것을 평가하는 idref 요소의 대안을 사용하여)의 id이다. 첫번째 bean은 이것의 의존성을 위해 컨테이너에 프로그램마다 다른 방식으로 요청한다. 어느 경우에나 의존성은 의존적인 bean이전에 초기화된다.

다소 덜 직접적인(예를 들면, 데이터베이스 드라이버 등록과 같은 클래스내 정적인 초기자가 트리거 될 필요가 있을때) bean들 사이의 의존성이 있는 비교적 드물게 발생하는 상황을 위해 depends-on 요소는 이 초기화된 요소를 사용하는 bean이전에 초기화되기 위한 하나 이상의 bean을 명시적으로 강제하기 위해 사용된다.

다음은 예제설정이다.

<bean id="beanOne" class="ExampleBean" depends-on="manager">
    <property name="manager"><ref local="manager"/></property>
</bean>

<bean id="manager" class="ManagerBean"/>

3.3.6. Autowiring 협력자

BeanFactory는 협력자 bean들 사이의 관계를 autowire 할수 있다. 이것은 BeanFactory의 내용을 조사함으로써 당신의 bean을 위해 Spring이 자동적으로 협력자(다른 bean)를 분석하는것이 가능하다는 것을 의미한다. autowiring 기능은 5개의 모드를 가진다. Autowiring은 다른 bean이 autowire되지 않는 동안 bean마다 명시되고 몇몇 bean을 위해 가능하게 될수 있다. autowiring을 사용하면 명백하게 많은 양의 타이핑을 줄이고 프라퍼티나 생성자의인자를 명시할 필요를 줄이거나 제거하는것이 가능하다. [1]XmlBeanFactory에서 bean정의를 위한 autowire모드는 bean요소의 autowire 속성을 사용하여 명시한다. 다음의 값이 허용된다.

Table 3.2. Autowiring 모드

모드 설명
no autowiring이 전혀없다. bean참조는 ref 요소를 통해 정의되어야만 한다. 이 값은 디폴트이고 좀더 큰 제어와 명백함을 주는 협력자를 명시하기 때문에 좀더 큰 배치를 위해 이것이 억제되도록 변경한다. 몇몇 확장을 위해 이것은 시스템의 구조에 대한 문서의 형태이다.
byName 프라퍼티 이름에 의한 Autowiring. 이 옵션은 BeanFactory를 조사하고 autowire될 필요가 있는 프라퍼티와 같은 이름의 bean을 찾는다. 예를 들면 당신이 만약 이름에 의해 autowire하기 위해 셋팅하는 bean정의를 가지고 이것이 master 프라퍼티(이것은 setMaster(...) 메소드를 가진다.)를 포함한다면 Spring은 master라는 이름의 bean정의를 찾을것이고 프라퍼티를 셋팅하기 위해 이것을 사용한다.
byType BeanFactory내 프라퍼티 타입의 bean이 정확하게 하나 있다면 프라퍼티를 autowire가 되도록 허용한다. 만약 하나 이상이 있다면 치명적인 예외가 던져지고 이것은 bean을 위한 byType autowiring을 사용하지 않는것을 나타낸다. 만약 대응되는 bean이 없다면 아무것도 발생하지 않는다(프라퍼티가 셋팅되지 않는다.). 만약 이 기능에 호감이 가지 않는다면 이 경우에 던져질 에러를 명시하는 dependency-check="objects" 속성값을 셋팅한다.
constructor 이것은 byType와 유사하지만 생성자의 인자에 적용한다. bean factory내 생성자의 인자타입의 bean이 정확하게 하나가 아닐경우 치명적인 에러가 발생한다.
autodetect bean클래스의 내성을 통해 constructorbyType를 선택하라. 만약 디폴트 생성자가 발견된다면 byType는 적용된다.

propertyconstructor-arg내 명싱적인 의존성이 언제나 autowiring을 오버라이드한다는것에 주의하라. Autowire 행위는 모든 autowiring가 완성된후 수행될 의존성 체크와 조합될수 있다.

autowiring 주위의 pros와 cons를 이해하는것이 중요하다. 다음은 autowiring의 몇몇 장점이다.

  • 이것은 요구되는 설정의 양을 명백하게 감소시킨다.(어쨌든 이 장 어디서든 언급되는 설정 "template"의 사용같은 기법은 여기서도 가치있다. )

  • 당신의 객체가 발전하는것처럼 그것 자체를 최신식으로 유지하는 설정을 야기한다. 예를 들면 만약 당신이 클래스에 추가적으로 의존성을 추가할 필요가 있다면 그 의존성은 설정을 변경할 필요없이 자동적으로 만족될수 있다. 게다가 배치하는 동안 코드기초가 좀더 안정화가 될때 명시적으로 wiring하기 위한 교체의 옵션이 없이 autowiring을 위해 견고한 경우가 된다.

autowiring 의 몇몇 단점

  • 명시적인 wiring보다는 좀더 마법적같다. 비록 위 테이블에서 언급된것처럼 Spring은 기대되지 않는 결과를 가지는 모호함과 같은 경우에 추측을 파하기 위해 주의한다. 당신의 Spring관리 객체들 간의 관계는 더 이상 명시적으로 문서화되지 않는다.

  • wiring정보는 아마도 Spring애플리케이션 컨텍스트로부터 문서를 생성하는 툴을 위해 사용가능하지는 않을것이다.

  • type에 의한 autowiring은 setter메소드나 생성자의 인자에 의해 명시되는 타입의 하나의 bean정의가 있을때만 작동할것이다. 당신은 어떠한 잠재적인 모호함이 있을경우 명시적인 wiring을 사용할 필요가 있다.

모든 경우에 "틀리다(wrong)" 나 "맞다(right)"가 답은 아니다. 우리는 프로젝트를 통한 일관성(consistency)의 정도(degree)를 추천한다. 예를 들면 autowiring이 대개 사용되지 않을때 이것은 개발자에게 하나또는 두개의 bean정의를 사용하는것에 혼동을 줄지도 모른다.

3.3.7. 의존성을 위한 체크

Spring은 BeanFactory로 배치되는 bean의 분석되지 않은 의존성의 존재를 체크하도록 시도하는 능력을 가진다. 그것들은 bean정의내 그것들을 위한 실제값을 셋팅하지 않거나 autowiring기능에 의해 자동적으로 제공되는 bean의 자바빈 프라퍼티이다.

이 기능은 모든 프라퍼티(또는 특정 타입의 모든 프라퍼티)가 bean에 셋팅되는지 확인하기를 원할때 때때로 유용하다. 물론 많은 경우에 bean클래스는 많은 프라퍼티 또는 모든 사용시나리오를 위해 적용하지 않는 몇몇 프라퍼티를 위한 디폴트 값을 가질것이다. 그래서 이 기능은 제한적으로 사용가능하다. 의존성체크는 autowiring기능처럼 bean단위로 사용가능하거나 사용불가능하다. 디폴트는 의존성을 체크하지 않는 것이다. 의존성체크는 다양한 모드로 다루어질수 있다. XmlBeanFactory에서 이것은 bean정의내 dependency-check속성을 통해 명시되고 다음의 값을 가진다.

Table 3.3. 의존성체크 모드

모드 설명
none 의존성 체크가 없다. 그것들을 위해 명시되는 값이 없는 bean의 프라퍼티가 간단하게 셋팅하지 않는다.
simple 원시타입과 collection(다른 beanㄷ처럼 협력자를 제외한 모든 것)을 위해 수행되는 의존성 체크.
object 협력자를 위해 수행되는 의존성 체크.
all 협력자, 원시타입 그리고 collection을 위해 수행되는 의존성 체크.

3.4. bean의 성질을 커스터마이징하기.

3.4.1. Lifecycle 인터페이스

Spring은 BeanFactory내 당신의 bean 행위를 변경하기 위한 다양한 표시자(marker)인터페이스를 제공한다. 그것들은 InitializingBeanDisposableBean를 포함한다. 이 인터페이스를 구현하는 것은 bean에게 초기화와 파괴화(destruction)의 작업을 수행하도록 허용하는 전자를 위해 afterPropertiesSet()을 후자를 위해 destroy()를 호출함으로써 BeanFactory내 결과를 생성한다.

내부적으로 Spring은 이것이 적당한 메소드를 찾고 호출할수 있는 어떠한 표시자(marker) 인터페이스를 처리하기 위해 BeanPostProcessors를 사용한다. 만약 Spring이 특별히 제공하지 않는 사용자 지정 기능이나 다른 생명주기 행위가 필요하다면 당신은 BeanPostProcessor를 구현할수 있다. 이것에 대한 좀더 상세한 정보는 Section 3.7, “BeanPostprocessors로 bean 커스터마이징하기”에서 찾을수 있다.

모든 다른 종류의 생명주기 표시자(marker)인터페이스는 아래에서 언급된다. 추가물중 하나에서 당신은 Spring이 bean을 어떻게 관리하고 그러한 생명주기 기능들이 당신의 bean의 성질을 어떻게 변경하고 그들이 어떻게 관리되는지 보여주는 다이어그램을 찾을수 있다.

3.4.1.1. InitializingBean / init-method

org.springframework.beans.factory.InitializingBean을 구현하는것은 bean의 필요한 모든 프라퍼티가 BeanFactory에 의해 셋팅된 후 bean에게 초기화작업을 수행하는것을 허용한다. InitializingBean인터페이스는 정확하게 하나의 메소드만 명시한다.

    * Invoked by a BeanFactory after it has set all bean properties supplied
    * (and satisfied BeanFactoryAware and ApplicationContextAware).
    * <p>This method allows the bean instance to perform initialization only
    * possible when all bean properties have been set and to throw an
    * exception in the event of misconfiguration.
    * @throws Exception in the event of misconfiguration (such
    * as failure to set an essential property) or if initialization fails.
    */
    void afterPropertiesSet() throws Exception;

메모 : 대개 InitializingBean 표시자(marker) 인터페이스의 사용은 제거될수 있다. (그리고 Spring에 코드를 불필요하게 결합한 후 억제된다.). bean정의는 명시되기 위한 일반적인 초기화 메소드를 위한 지원을 제공한다. XmlBeanFactory의 경우, 이것은 init-method 속성을 통해 수행된다. 예를 들면, 다음의 정의처럼.

<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>

public class ExampleBean {
    public void init() {
        // do some initialization work
    }
}

는 다음과 정확하게 같다.

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>

public class AnotherExampleBean implements InitializingBean {
    public void afterPropertiesSet() {
        // do some initialization work
    }
}

하지만 Spring에 코드를 결합하지는 않는다.

3.4.1.2. DisposableBean / destroy-method

org.springframework.beans.factory.DisposableBean를 구현하는것은 BeanFactory가 파괴된(destroyed)것을 포함할때 bean에게 콜백을 얻는 것을 허용한다. DisposableBean인터페이스는 하나의 메소드를 명시한다.

    /**
    * Invoked by a BeanFactory on destruction of a singleton.
    * @throws Exception in case of shutdown errors.
    * Exceptions will get logged but not re-thrown to allow
    * other beans to release their resources too.
    */
    void destroy() throws Exception;

메모 : DisposableBean 표시자(marker) 인터페이스의 사용은 제거될수 있다. (그리고 Spring에 코드를 불필요하게 결합한 후 억제된다.). bean정의는 명시되기 위한 일반적인 파괴(destroy) 메소드를 위한 지원을 제공한다. XmlBeanFactory의 경우에, 이것은 destroy-method 속성을 통해 수행된다. 예를 들면, 다음의 정의처럼.

<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>

public class ExampleBean {
    public void cleanup() {
        // do some destruction work (like closing connection)
    }
}

는 다음과 정확하게 같다.

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>

public class AnotherExampleBean implements DisposableBean {
    public void destroy() {
        // do some destruction work
    }
}

하지만 Spring에 코드를 결합하지는 않는다.

중요한 메모 : 프로토타입 모드로 bean을 배치할때, bean의 생명주기는 미세한게 변경된다. 정의에 의해 Spring은 이것이 생성된 이후 non-singleton/prototype bean의 완벽한 생명주기를 관리할수는 없다. 클라이언트와 컨테이너에 주어진 것은 더이상 추적하지 않는다. 당신은 'new' 연산자를 위한 대체물처럼 non-singleton/prototype bean에 대해 이야기 할때 Spring의 역활에 대해 생각할수 있다. 어떤 생명주기 형상은 클라이언트에 의해 다루어질수 있는 지점을 지난다. BeanFactory내 bean의 생명주기는 Section 3.4.1, “Lifecycle 인터페이스”에서 좀더 상세하게 언급된다. .

3.4.2. 당신이 누구인지 알고 있다.(Knowing who you are)

3.4.2.1. BeanFactoryAware

org.springframework.beans.factory.BeanFactoryAware인터페이스를 구현하는 클래스는 BeanFactory에 의해 생성되었을때 이것을 생성하는 BeanFactory에 대한 참조를 제공한다.

public interface BeanFactoryAware {
   /**
    * Callback that supplies the owning factory to a bean instance.
    * <p>Invoked after population of normal bean properties but before an init
    * callback like InitializingBean's afterPropertiesSet or a custom init-method.
    * @param beanFactory owning BeanFactory (may not be null).
    * The bean can immediately call methods on the factory.
    * @throws BeansException in case of initialization errors
    * @see BeanInitializationException
    */
    void setBeanFactory(BeanFactory beanFactory) throws BeansException;
}

이것은 bean에게 org.springframework.beans.factory.BeanFactory인터페이스를 통하거나 추가적인 기능을 드러내는 이것의 알려진 하위클래스에 대한 참조를 형변환함으로써 프로그램마다 다르게 그것들을 생성한 BeanFactory를 변경하는것을 허용한다. 원래 이것은 다른 bean의 프로그램마다 다른 검색으로 구성된다. 이 기능이 유용할때 이것이 Spring에 코드를 결합하고 Inversion of Control스타일을 따르지 않는 이 후 프라퍼티처럼 bean에 제공되는 협력자가 위치한 곳에 이것이 대개 제거될수 있다.

3.4.2.2. BeanNameAware

만약 bean이 org.springframework.beans.factory.BeanNameAware인터페이스를 구현하고 BeanFactory내 배치된다면 BeanFactory는 이것이 배치된 id의 bean을 알리기 위한 인터페이스를 통해 bean을 호출할것이다. 콜백은 일반적인 bean프라퍼티의 활성화 이후지만 InitializingBeanafterPropertiesSet 이나 사용자 지정 init-method같은 콜백을 초기화하기 전에 호출될것이다.

3.4.3. FactoryBean

org.springframework.beans.factory.FactoryBean인터페이스는 자체적으로 factory인 객체에 의해 구현되는 것이다. BeanFactory 인터페이스는 3개의 메소드를 제공한다.

  • Object getObject(): 이 factory가 생성하는 객체의 인스턴스를 반환한다. 인스턴스는 공유될수(이 factory가 싱글톤이나 프로토타입을 반환하는지에 대한 여부에 의존하여) 있다.

  • boolean isSingleton(): 만약 이 FactoryBean이 싱글톤을 반환한다면 true를 반환하고 다른경우라면 false를 반환한다.

  • Class getObjectType(): getObject() 메소드에 의해 반환되는 객체 타입이나 타입이 미리 알려지지 않았다면 null을 반환한다.

3.5. 추상 그리고 자식 bean정의

bean정의는 잠재적으로 컨테이너 특정 정보(이를 테면, 초기화 메소드, 정적 factory 메소드명, 등등)와 생성자의 인자와 프라퍼티 값을 포함하는 많은 양의 설정정보를 포함한다. 자식 bean정의는 부모 정의로부터 설정정보를 상속하는 bean정의이다. 이것은 필요하다면 몇몇값을 오버라이드하거나 다른것을 추가할수 있다. 부모와 자식 bean정의를 사용하는것은 잠재적으로 많은 양의 타이핑을 줄일수 있다. 효과적으로 이것은 템플릿형태이다.

프로그램마다 다르게 BeanFactory로 작업을 수행할때 자식 bean정의는 ChildBeanDefinition 클래스에 의해 표현된다. 대부분의 사용자는 XmlBeanFactory와 같은 몇가지내에서 선언적인 설정 bean정의대신에 이 수준에서 그것들과 함께 작동하지는 않을것이다. XmlBeanFactory bean정의에서 자식 bean정의는 이 속성의 값처럼 부모 bean을 명시하는 parent 속성을 사용하여 간단하게 표시될수 있다.

<bean id="inheritedTestBean" abstract="true"
    class="org.springframework.beans.TestBean">
    <property name="name"><value>parent</value></property>
    <property name="age"><value>1</value></property>
</bean>

<bean id="inheritsWithDifferentClass" class="org.springframework.beans.DerivedTestBean"
      parent="inheritedTestBean" init-method="initialize">
    <property name="name"><value>override</value></property>
    <!-- age should inherit value of 1 from parent -->
  </bean>

자식 bean정의는 아무것도 명시가 되어 있지 않지만 이것을 오버라이드할수 있다면 부모 정의의 bean클래스를 사용할것이다. 후자의 경우 자식 bean클래스는 부모 bean클래스와 호환되어야만 한다. 이를 테면 부모의 프라퍼티 값을 받을수 있어야 한다.

자식 bean정의는 생성자의 인자값, 프라퍼티값과 부모로 부터 상속된 메소드를 새로운 값을 추가하는 선택사항과 함께 상속할것이다. 만약 메소드를 초기화한다면 destroy메소드와/또는 정적 factory메소드는 명시된다. 그것들은 관련된 부모 셋팅을 오버라이드할것이다.

남은 셋팅들은 언제나 자식 정의로부터 가져올것이다.: depends on, autowire mode, dependency check, singleton, lazy init.

위 예제에서 우리는 abstract 속성을 사용하여 추상적으로 부모 bean정의를 명시적으로 표시했다는 것을 알라. 이 경우 부모 정의는 클래스를 명시하지 않는다.

<bean id="inheritedTestBeanWithoutClass">
    <property name="name"><value>parent</value></property>
    <property name="age"><value>1</value></property>
</bean>

<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
      parent="inheritedTestBeanWithoutClass" init-method="initialize">
    <property name="name"><value>override</value></property>
    <!-- age should inherit value of 1 from parent -->
</bean>

부모 bean은 불완전하고 또한 추상적이라고 생각된 이후에는 인스턴스화될수 없다. 정의가 이것처럼(명시적이거나 함축적인) 추상적이라고 생각될 때 이것은 자식 정의를 위한 부모 정의처럼 제공될 순수한 템플릿이나 추상 bean정의처럼 사용가능하다. 그것 자체(다른 bean의 ref프라퍼티를 참조하거나 부모 bean id를 가진 명시적인 getBean()호출을 하여)의 추상적인 부모 bean들을 사용하는것을 시도하면 에러를 보게될것이다. 유사하게도 컨테이너의 내부적인 preInstantiateSingletons 메소드는 추상적이라고 생각되는 bean정의를 완벽하게 무시할것이다.

중요한 메모: 애플리케이션 컨텍스트(간단한 bean factory가 아닌)는 디폴트에 의해 모든 싱글톤으로 미리 인스턴스화될것이다. 그러므로 이것은 만약 당신이 템플릿처럼만 오직 사용되는 경향이 있는 (부모) bean정의를 가지고 이 정의가 클래스를 명시한다면 당신은 abstract속성값을 true로 셋팅해야만 하는 반면에 애플리케이션 컨텍스트는 이것을 실질적으로 미리 인스턴스화할것이라는 것은 중요(적어도 싱글톤 bean을 위해서)하다.

3.6. BeanFactory와 상호작동하기

BeanFactory는 기본적으로 다른 bean과 그것들의 의존성의 등록을 유지하는 향상된 factory능력을 위한 인터페이스에 지나지 않는다. BeanFactory는 당신에게 bean factory를 사용하여 bean정의를 읽고 그것들에 접근하는 것을 가능하게 한다. BeanFactory를 사용할때 당신은 다음처럼 하나를 생성하고 XML형태의 몇몇 bean정의내에서 읽을것이다.

InputStream is = new FileInputStream("beans.xml");
XmlBeanFactory factory = new XmlBeanFactory(is);

getBean(String)를 사용하여 당신은 당신의 bean인스턴스를 가져올수 있다. 당신은 이것을 싱글톤(디폴트)처럼 이것을 명시하여 같은 bean의 참조를 얻거나 singletonfalse로 셋팅한다면 매번 새로운 인스턴스를 얻을것이다. BeanFactory의 클라이언트측 시각은 놀라울정도로 간단한다. BeanFactory 인터페이스는 호출할 클라이언트를 위해 오직 5개의 메소드만을 가진다.

  • boolean containsBean(String): BeanFactory가 bean정의나 주어진 이름에 대응되는 bean인스턴스를 포함한다면 true를 반환한다.

  • Object getBean(String): 주어진 이름하에 등록된 bean의 인스턴스를 반환한다. bean이 어떻게 설정되는지는 BeanFactory설정에 의존한다. 싱글톤과 공유 인스턴스나 새롭게 생성되는 bean은 반환될것이다. BeansException은 bean을 찾을수 없을때(이 경우 이것은 NoSuchBeanDefinitionException이 될것이다.)나 bean을 인스턴스화하거나 준비하는 동안 예외가 발생할때 던져질것이다.

  • Object getBean(String,Class): 주어진 이름하에 등록된 bean을 반환한다. 반환되는 bean은 주어진 클래스로 형변환될것이다. 만약 bean이 형변환될수 없다면 관련 예외(BeanNotOfRequiredTypeException)가 던져질것이다. 게다가 getBean(String) 메소드의 모든 규칙(위에서 본)을 적용한다.

  • boolean isSingleton(String): 주어진 이름하에 등록된 bean정의나 bean인스턴스가 싱글톤이거나 프로토타입인지 아닌지 조사한다. 만약 주어진 이름에 관련된 bean이 발견되지 않는다면 예외(NoSuchBeanDefinitionException)가 던져질것이다.

  • String[] getAliases(String): 만약 bean정의내 어떠한 것도 명시되어 있다면 주어진 bean이름을 위한 별칭을 반환한다.

3.6.1. BeanFactory의 생성물이 아닌 FactoryBean 얻기

때때로 BeanFactory가 생성하는 bean이 아닌 실질적인 FactoryBean인스턴스 자체를 위해 BeanFactory에 요청할 필요가 있다. 이것은 BeanFactory(ApplicationContext을 포함하는)의 getBean 메소드를 호출할때 &를 가진 bean id를 덧붙여서 수행된다. 그래서 id myBean를 가진 주어진 FactoryBean을 위해 BeanFactory의 getBean("myBean")를 호출하는것은 FactoryBean의 생성물을 반환할것이지만 getBean("&myBean")을 호출하는것은 FactoryBean인스턴스 자체를 반환할것이다.

3.7. BeanPostprocessors로 bean 커스터마이징하기

bean 후-처리자는 두개의 콜백메소드로 구성된 org.springframework.beans.factory.config.BeanPostProcessor인터페이스를 구현하는 자바클래스이다. 그러한 클래스가 BeanFactory에 의해 생성되는 각각의 bean인스턴스를 위해 BeanFactory와 함께 후-처리자처럼 등록되었을때 후-처리자는 어떠한 초기화 메소드(afterPropertiesSet와 어떤 선언된 초기화 메소드)가 호출되기 전과 나중에 BeanFactory로 부터 콜백을 얻을것이다. 후-처리자는 콜백을 완벽하게 무시하는것을 포함해서 bean으로 바라는것을 하는데 자유롭다. bean 후-처리자는 표시자(marker) 인터페이스를 체크하거나 프록시로 bean을 포장하는 것과 같은것을 수행한다. 몇몇 Spring 헬퍼(helper)클래스는 bean 후-처리자처럼 구현되었다.

BeanFactory가 ApplicationContext보다 미세할 정도로 다르게 bean 후-처리자를 처리하는것을 아는것은 중요하다. ApplicationContext는 BeanPostProcessor 인터페이스를 구현하고 bean생성하는 factory에 의해 적당히 호출되기 위한 후-처리자처럼 그것들을 등록하는 것으로 배치되는 어떠한 bean을 자동적으로 감지할것이다. 어떤 다른 bean으로 유사한 형태로 후-처리자를 배치 할 필요만 있다. 반면에 명백한 BeanFactory를 사용할때 bean 후-처리자는 다음같은 코드순으로 명시적으로 등록되어야 한다.

ConfigurableBeanFactory bf = new .....;     // create BeanFactory
   ...                       // now register some beans
// now register any needed BeanPostProcessors
MyBeanPostProcessor pp = new MyBeanPostProcessor();
bf.addBeanPostProcessor(pp);

// now start using the factory
  ...

이 수작업(manual) 등록 단계는 편리하지 않고 ApplictionContexts는 BeanFactory의 기능적으로 수퍼셋이기 때문에 bean 후-처리자가 필요할 때 사용되는 ApplicationContext종류가 대개 추천된다.

3.8. BeanFactoryPostprocessors를 가진 bean factory커스터마이징하기

bean factory 후-처리자는 org.springframework.beans.factory.config.BeanFactoryPostProcessor 인터페이스를 구현하는 자바클래스이다. 이것은 생성된 후 전체 BeanFactory를 위한 몇몇 종류의 변경을 적용하기 위해 수동( BeanFactory의 경우)이나 자동(ApplicationContext의 경우)으로 수행된다. Spring은 밑에서 언급되는 PropertyResourceConfigurerPropertyPlaceHolderConfigurer 그리고 이 문서 나중에 언급되는것처럼 다른 bean을 트랜잭션하게 포장하거나 다른 종류의 프록시와 매우 유용한 BeanNameAutoProxyCreator와 같은 많은 수의 미리존재하는 bean factory 후-처리자를 포함한다. BeanFactoryPostProcessor는 사용자지정 편집기(Section 3.9, “추가적인 사용자지정 PropertyEditors 등록하기”에서 언급되는것처럼)를 추가하기 위해 사용될수 있다.

BeanFactory내에서 BeanFactoryPostProcessor 적용의 처리는 수동이고 이것과 유사할것이다.

XmlBeanFactory factory = new XmlBeanFactory(new FileSystemResource("beans.xml"));
// create placeholderconfigurer to bring in some property
// values from a Properties file
PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));
// now actually do the replacement
cfg.postProcessBeanFactory(factory);

ApplicationContext는 BeanFactoryPostProcessor 인터페이스를 구현하는 것으로 배치되는 어떠한 bean을 감지할것이고 적당한 시점에 bean factory 후-처리자처럼 자동적으로 그것들을 사용한다. 다른 bean을 위한 유사한 형태로 이러한 후-처리자를 배치만하는것이다.

이 수작업(manual) 등록 단계는 편리하지 않고 ApplictionContexts는 BeanFactory의 기능적으로 수퍼셋이기 때문에 bean factory 후-처리자가 필요할 때 사용되는 ApplicationContext종류가 대개 추천된다.

3.8.1. PropertyPlaceholderConfigurer

bean factory 후-처리자처럼 구현된 PropertyPlaceholderConfigurer는 BeanFactory정의로부터 자바프라퍼티 형태의 다른 분리된 파일로 몇몇 프라퍼티값들을 구체화하기 위해 사용된다. 이것은 몇몇 key 프라퍼티(예를 들면 데이터베이스 URL, 사용자명, 비밀번호)를 복잡하거나 핵심이 되는 XML정의파일이나 BeanFactory을 위한 파일을 변경하는 위험없이 커스터마이징하기 위한 애플리케이션을 배치하는것을 사람에게 허용하는데 유용하다.

위치유지자(placeholder)값과 함께 데이터소스가 정의된 BeanFactory정의로부터의 일부를 검토하라.

아래의 예제에서 데이터소스가 명시되어 있고 우리는 외부 프라퍼티파일로부터 몇몇 프라퍼티를 설정할것이다. 실행시 우리는 데이터소스의 몇몇 프라퍼티를 대체할 BeanFactory로 PropertyPlaceholderConfigurer을 적용할것이다.

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName"><value>${jdbc.driverClassName}</value></property>
    <property name="url"><value>${jdbc.url}</value></property>
    <property name="username"><value>${jdbc.username}</value></property>
    <property name="password"><value>${jdbc.password}</value></property>
</bean>

실질적인 값들은 프라퍼티형태로 다른 파일로부터 나온다.

jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root

BeanFactory와 같이 이것을 사용하기 위해 bean factory 후-처리자는 수동으로 수행된다.

XmlBeanFactory factory = new XmlBeanFactory(new FileSystemResource("beans.xml"));
PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));
cfg.postProcessBeanFactory(factory);

ApplicationContexts는 BeanFactoryPostProcessor를 구현하는 그것들에 배치되는 bean을 자동적으로 인식하고 적용하는 것이 가능하다는 알라. 이것은 여기서 서술된것처럼 그것들을 의미한다. PropertyPlaceholderConfiguer를 적용하는 것은 ApplicationContext을 사용할때보다 좀더 편리하다. 이러한 이유로 이것이나 다른 bean factory 후-처리자를 사용하길 바라는 사용자는 BeanFactory대신에 ApplicationContext을 사용하는것이 추천된다.

PropertyPlaceHolderConfigurer는 당신이 명시한 프라퍼티파일내에서만 프라퍼티를 찾지는 않는다. 하지만 만약 당신이 사용하길 시도하는 프라퍼티를 찾을수 없다면 자바 시스템 프라퍼티에 대해 체크한다. 이 행위는 설정자의 systemPropertiesMode 프라퍼티를 셋팅함으로써 커스터마이징될수 있다. 이것은 3개의 값을 가진다. 언제나 오버라이드 하도록 설정하는 하나와 결코 오버라이드하지 않는 하나, 프라퍼티가 정의된 프라퍼티파일내 찾을수 없을때만 단지 오버라이드하는 것이 있다. 좀더 많은 정보를 위해서는 PropertiesPlaceholderConfigurer를 위한 JavaDoc를 보라.

3.8.2. PropertyOverrideConfigurer

PropertyOverrideConfigurer, 다른 bean factory 후-처리자는 PropertyPlaceholderConfigurer와 비슷하지만 후자와는 대조적으로 원래의 정의는 디폴트 값을 가지거나 bean프라퍼티를 위한 값을 가질수 없다. 만약 오버라이딩된 파일이 어떤 bean프라퍼티를 위한 항목을 가지지 않는다면 디폴트 컨텍스트 정의가 사용된다.

bean factory정의는 오버라이드된것을 인식하지 않는다. 그래서 이것은 사용될 설정자를 오버라이드한 XML정의 파일을 찾을 때 즉시 명확하지 않다는것에 주의하라. 다중 PropertyOverrideConfigurers가 같은 bean프라퍼티를 위해 다른 값을 정의하는 경우에 가장 마지막의 값이 사용될것이다.(오버라이드기법에 따라.)

프라퍼티 파일 설정 라인은 다음과 같은 형태로 될것이다.

beanName.property=value

예제 프라퍼티 파일은 다음처럼 보일것이다.

dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb

예제 파일은 driverurl 프라퍼티를 가진 dataSource라고 불리는 것안에서 bean을 포함하는 BeanFactory정의에 대해 사용가능할것이다.

3.9. 추가적인 사용자지정 PropertyEditors 등록하기

문자열 값처럼 bean프라퍼티를 셋팅할때 BeanFactory는 이 문자열을 프라퍼티의 복합(complex)타입으로 형변환하기 위한 표준적인 자바빈 PropertyEditors를 사용한다. Spring은 많은 수의 PropertyEditors를 미리 등록한다. (예를 들면, 문자열처럼 표현되는 클래스명을 실제 클래스객체로 변환하는). 추가적으로 자바의 표준적인 자바빈 PropertyEditor 룩업 기법은 적당하게 명명되기 위한 클래스를 위해 PropertyEditor를 허용하고 자동적으로 발견되기 위한 지원을 제공하는 클래스처럼 같은 패키지내 위치한다.

만약 사용자지정 PropertyEditors을 등록할 필요가 있다면 여기엔 사용가능한 다양한 기법이 있다.

대개 편리하지 않거나 추천되지 않는 대부분의 수동 접근법은 당신이 BeanFactory참조를 가진다고 가정하고 ConfigurableBeanFactory 인터페이스의 registerCustomEditor() 메소드를 간단히 사용한다.

좀더 편리한 기법은 CustomEditorConfigurer 라고 불리는 특별한 bean factory 후-처리자를 사용하는 것이다. 비록 bean factory 후-처리자가 BeanFactory와 함께 반 수동적으로 사용될수 있다 하더라도 이것은 여기에서 언급되는 것처럼 강력하게 추천되기 때문에 내포된 프라퍼티 셋업을 가진다. 이것은 다른 bean에 유사한 방법으로 배치되고 자동적으로 감지되며 적용되는 ApplicationContext과 함께 사용된다.

모든 bean factory와 애플리케이션 컨텍스트는 프라퍼티 전환을 다루기 위한 BeanWrapper라고 불리는 몇가지의 사용을 통해 자동적으로 내장된 프라퍼티 편집기를 사용한다. BeanWrapper이 등록하는 표준적인 프라퍼티 편집기는 다음 장에서 목록화된다 . 추가적으로 ApplicationContexts 또한 오버라이드하거나 애플리케이션 컨텍스트 타입을 명시하기 위한 선호하는 방법으로 자원 룩업을 다루기 위해 추가적인 3가지 편집기를 추가한다. 그것들은 InputStreamEditor, ResourceEditor 그리고 URLEditor이다.

3.10. 존재하는 bean을 위한 별칭을 추가하기 위한 별칭 요소 사용하기.

bean정의 자체에서 당신은 id속성을 통해 명시된 하나의 이름을 위한 조합을 사용하고 alias속성을 통해 많은 수의 다른 이름을 사용하는 방법을 통해 bean을 위한 한개 이상의 이름을 제공할것이다. 이 모든 이름은 같은 bean에 대해 동일한 별칭이 검토될수 있고 애플리케이션내 사용되는 각각의 컴포넌트가 컴포넌트 자체를 위해 명시하는 bean이름을 사용하는 공통적인 의존성을 참조하도록 허용하는것과 같은 몇가지 상황을 위해 유용하다.

bean이 실질적으로 정의될 때 모든 별칭을 명시하는것이 언제나 충분한것은 아니다. 이것은 때때로 어떤곳에 정의되는 bean을 위한 별칭을 소개하는것이 바람직하다. 이것은 단독으로 사용되는 alias 요소를 통해 수행될수 있다.

<alias name="fromName" alias="toName"/>

이 경우, 같은 컨텍스트내 fromName라는 이름의 bean은 toName처럼 참조될수 있는 별칭정의를 나중에 사용할수 있다.

견고한 예제처럼, A 컴포넌트가 XML일부내에서 componentA-dataSource라고 불리는 데이터소스 bean을 정의하는 경우를 검토하라. B 컴포넌트는 XML일부내에서 componentB-dataSource처럼 데이터소스를 참조할것이다. 그리고 주된 애플리케이션 MyApp는 자신만의 XML일부를 정의하고 모든 3개의 일부로부터 마지막 애플리케이션 컨텍스트를 조합한다. 그리고 myApp-dataSource처럼 데이터소스를 참조할것이다. 이 시나리오는 다음의 단독 별칭을 가진 MyApp XML일부를 추가함으로써 쉽게 다루어질수 있다.

<alias name="componentA-dataSource" alias="componentB-dataSource"/> <alias name="componentA-dataSource" alias="myApp-dataSource"/>

지금 각각의 컴포넌트와 주된 애플리케이션은 유일하고 다른 정의와 충돌하지 않도록 보증된 이름을 통해 데이터소스를 참조할수 있다. 게다가 그것들은 같은 bean을 참조한다.

3.11. ApplicationContext에 대한 소개

beans 패키지는 종종 프로그램마다 다른 방식으로 관리와 bean을 변경하기 위한 기초적인 기능을 제공하는 동안 context 패키지는 좀 더 프레임워크 기반 형태로 BeanFactory기능을 강화시키는 ApplicationContext를 추가한다. 많은 사용자는 이것을 수동으로 생성하지 않을뿐 아니라 J2EE 웹 애플리케이션의 일반적인 시작 프로세스의 일부처럼 자동적으로 ApplicationContext를 시작하기 위한 ContextLoader처럼 지원 클래스에 의존하는 대신에 완벽한 선언적인 형태로 ApplicationContext를 사용할것이다. 물론 이것은 ApplicationContext을 프로그램마다 다르게 생성하는것이 가능하다.

context 패키지는 위한 기초는 org.springframework.context 패키지에 위치한 ApplicationContext 인터페이스이다. BeanFactory 인터페이스에서의 파생물은 BeanFactory의 모든 기능을 제공한다. 좀더 프레임워크 기반의 형태로 작업하는것을 허용하기 위해 레이어와 구조적인 컨텍스트를 사용하라. context 패키지는 다음을 제공한다.

  • MessageSource, i18n 스타일로 메시지에 대한 접근을 제공한다.

  • 자원에 대한 접근, URL이나 파일과 같은 형태.

  • 이벤트 전달(propagation) ApplicationListener인터페이스를 구현하는 bean을 위한

  • 다중(구조적인) 컨텍스트의 로딩, 예를 들어 애플리케이션의 웹 레이어처럼, 각각을 하나의 특정 레이어에 집중될수 있도록 허용하는

ApplicationContext가 BeanFactory의 모든 기능을 포함하기 때문에 , 이것은 메소리 소비가 치명적이고 몇몇 추가적인 킬로바이트가 다른 아마도 애플릿과 같은 몇몇 제한된 상황을 위하는 것을 제외하고 BeanFactory에 우선하여 사용되는 것이 추천된다. 다음 부분은 ApplicationContext가 기본적인 BeanFactory기능에 추가한 기능을 언급한다.

3.12. ApplicationContext에 추가된 기능

이전 부분에서 벌써 밝힌것처럼 ApplicationContext는 BeanFactory로부터 이것을 구별하는 두어가지의 기능을 가진다. 우리는 그것들을 하나씩 먼저 알아보자.

3.12.1. MessageSource 사용하기

ApplicationContext 인터페이스는 MessageSource 라고 불리는 인터페이스를 확장해서 메시징(i18n또는 국제화)기능을 제공한다. NestingMessageSource와 함께 구조적인 메시지를 분석하는 능력을 가진다. 여기엔 Spring이 메시지 분석을 제공하는 기초적인 인터페이스가 있다. 여기에 정의된 메소드를 빨리 알아보자.

  • String getMessage (String code, Object[] args, String default, Locale loc): MessageSource로 부터 메시지를 받기 위해 사용되는 기초적인 메소드. 특정 로케일을 위해 발견되는 메시지가 없을 때 디폴트 메시지가 사용된다. 전달된 인자는 표준적인 라이브러리에 의해 제공되는 MessageFormat 기능을 사용해서 대체값처럼 사용된다.

  • String getMessage (String code, Object[] args, Locale loc): 이전 메소드와 기본적으로는 같다. 하지만 한가지가 다르다. 디폴트 메시지가 선언될수 없다. 만약 메시지가 발견될수 없다면, NoSuchMessageException가 던져진다.

  • String getMessage(MessageSourceResolvable resolvable, Locale locale): 위 메소드에서 사용된 모든 프라퍼티는 이 메소드를 통해 사용할수 있는 MessageSourceResolvable 라는 이름의 클래스에 포장된다.

ApplicationContext가 로드될때 이것은 컨텍스트내 정의된 MessageSource bean을 위해 자동적으로 찾는다. bean은 messageSource을 가진다. 만약 그러한 bean이 발견된다면 위에서 언급된 메소드에 대한 모든 호출은 발견된 메시지소스에 위임될것이다. 만약 발견되는 메시지소스가 없다면 같은 이름을 가진 bean을 포함하는 부모를 가진다면 ApplicationContext가 보기를 시도한다. 만약 그렇다면 이것은 MessageSource처럼 그 bean을 사용한다. 만약 메시지를 위한 어떤 소스를 발견할수 없다면 빈 StaticMessageSource는 위에서 정의된 메소드에 호출을 받을수 있기 위해 인스턴스화될것이다.

Spring은 현재 두개의 MessageSource 구현물을 제공한다. 여기엔 ResourceBundleMessageSourceStaticMessageSource가 있다. 둘다 메시지를 내포하기 위해 NestingMessageSource을 구현한다. StaticMessageSource는 소스에 메시지를 추가하기 위한 프로그램마다 다른 방법을 제공하지만 거의 사용되지 않는다. ResourceBundleMessageSource는 좀더 흥미롭고 우리가 제공할 예제이다.

<beans>
    <bean id="messageSource" 
            class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>format</value>
                <value>exceptions</value>
                <value>windows</value>
            </list>
        </property>
    </bean>
</beans> 

이것은 당신이 format, exceptionswindows라고 불리는 당신의 클래스패스내 정의된 3개의 자원번들을 가진다고 가정한다. ResourceBundles을 통한 메시지를 분석하는 JDK표준적인 방법을 사용하여 메시지를 분석하기 위한 요청이 다루어질것이다. TODO: SHOW AN EXAMPLE

3.12.2. events 전파하기

ApplicationContext내 이벤트 핸들링은 ApplicationEvent 클래스와 ApplicationListener인터페이스를 통해 제공된다. 만약 ApplicationListener 인터페이스를 구현하는 bean이 컨텍스트로 배치된다면 매번 ApplicationEvent는 통지될 bean인 ApplicationContext에 배포된다. 기본적으로 이것은 표준적인 Observer 디자인 패턴이다. Spring은 3가지 표준적인 이벤트를 제공한다.

Table 3.4. 내장된 이벤트

이벤트 설명
ContextRefreshedEvent ApplicationContext가 초기화되거나 재생(refresh)될때 배포되는 이벤트. 여기서 초기화는 모든 bean이 로드되고 싱글톤은 미리 인스턴스화되며 ApplicationContext는 사용할 준비가 된다는 것을 의미한다.
ContextClosedEvent ApplicationContext의 close()메소드를 사용하여 ApplicationContext가 닫힐때 배포되는 이벤트. 여기서 닫히는 것은 싱글톤이 없어지는(destroy)되는것을 의미한다.
RequestHandledEvent 웹 특정 이벤트는 HTTP요청이 서비스(이를 테면 요청이 종료될때 after가 배포될것이다.)되는 모든 bean을 말한다. 이 이벤트는 Spring의 DispatcherServlet을 사용하는 웹 애플리케이션에서만 적용가능하다.

사용자정의 이벤트를 구현하는것은 잘 작동될수 있다. ApplicationContext의 publishEvent() 메소드를 간단히 호출하는 것은 당신의 사용자정의 이벤트 클래스가 ApplicationEvent을 구현하는 파라미터를 명시하는 것이다. 이벤트 리스너(listener)는 이벤트를 동시에 받아들인다. 이것은 publishEvent() 메소드는 모든 리스너가 이벤트 처리를 종료할때 까지 블럭된다는것을 의미한다. 게다가 리스너가 이벤트를 받을때 이것은 배포자(publisher)의 만약 트랜잭션 컨텍스트가 사용가능하다면 트랜잭션 컨텍스트내 작동한다.

예제를 보자. 첫번째 ApplicationContext이다.

<bean id="emailer" class="example.EmailBean">
    <property name="blackList">
        <list>
            <value>black@list.org</value>
            <value>white@list.org</value>
            <value>john@doe.org</value>
        </list>
    </property>
</bean>

<bean id="blackListListener" class="example.BlackListNotifier">
    <property name="notificationAddress">
        <value>spam@list.org</value>
    </property>
</bean>

그리고 다음은 실질적인 bean이다.

public class EmailBean implements ApplicationContextAware {

    /** the blacklist */
    private List blackList;
    
    public void setBlackList(List blackList) {
        this.blackList = blackList;
    }
    
    public void setApplicationContext(ApplicationContext ctx) {
        this.ctx = ctx;
    }
    
    public void sendEmail(String address, String text) {
        if (blackList.contains(address)) {
            BlackListEvent evt = new BlackListEvent(address, text);
            ctx.publishEvent(evt);
            return;
        }
        // send email
    }
}

public class BlackListNotifier implement ApplicationListener {

    /** notification address */
    private String notificationAddress;
    
    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    public void onApplicationEvent(ApplicationEvent evt) {
        if (evt instanceof BlackListEvent) {
            // notify appropriate person
        }
    }
}

물론 이 특별한 예제는 기본적인 이벤트 기법을 설명하기에는 충분하지만 좀더 나은 방법(아마도 AOP기능을 사용하여)으로 구현될수 있을것이다.

3.12.3. Spring내에서 자원(resources) 사용하기

많은 애플리케이션은 자원에 접근할 필요가 있다. 자원을 파일을 포함할수 있지만 웹페이지나 NNTP 뉴스피드와 같은 것들도 포함할수 있다. Spring은 프로토콜에 비의존적인 방법으로 자원에 접근하는 깔끔하고 투명한 방법을 제공한다. ApplicationContext 인터페이스는 이것을 다루는 메소드(getResource(String))를 포함한다.

Resource클래스는 모든 Resource구현물을 통해 공유되는 두어가지의 메소드를 정의한다.

Table 3.5. Resource 기능

메소드 설명
getInputStream() 자원에 대해 InputStream을 열고 이것을 반환한다.
exists() 자원이 존재하는지 체크하고 존재하지 않는다면 false를 반환한다.
isOpen() true를 반환하면 다중 스트림(stream)이 이 자원을 위해 열릴수 없다. 이것은 몇몇 자원을 위해 false가 될것이다. 하지만 예를 들어 파일-기반의 자원은 동시에 여러번 읽을수 없다.
getDescription() 자원의 설명(description)을 반환한다. 종종 완전한 경로의 파일명이나 실질적인 URL이 반환된다.

두어가지의 Resource구현물이 Spring에 의해 제공된다. 그것들 모두 자원의 실질적인 위치를 표현하는 문자열값이 필요하다. 문자열값에 기반으로 하여 Spring은 당신을 위해 알맞은 Resource구현물을 자동적으로 선택할것이다. 처음으로 자원을 위해 ApplicationContext를 요청할 때 Spring의 모든것은 당신이 명시하고 어느 접두사로 찾는 자원의 위치를 조사할것이다. ApplicationContext의 구현물에 의존하여 하나 이상의 Resource구현물은 사용가능하다. Resource는 ResourceEditor를 사용하여 설절될때 가장 좋을수 있고 예로써 XmlBeanFactory가 있다.

3.13. ApplicationContext내에서 사용자정의 행위

BeanFactory는 이것(InitializingBean or DisposableBean처럼 표시자(marker) 인터페이스와 같은)에 배치된 bean들의 생명주기를 제어하는 많은 수의 기법을 이미 제공하고 있다. 그것들의 설정은 XmlBeanFactory 설정와 bean 후-처리자내 init-methoddestroy-method속성과 같이 동등하다. ApplicationContext에서 그것들 모두 작동을 하지만 추가적인 기법은 bean과 컨테이너의 사용자정의 행위를 위해 추가된다.

3.13.1. ApplicationContextAware 표시자(marker) 인터페이스

BeanFactory와 함께 사용가능한 모든 표시자(marker) 인터페이스는 여전히 작동한다. ApplicationContext는 org.springframework.context.ApplicationContextAware를 구현하는 bean인 하나의 추가적인 표시자(marker) 인터페이스를 추가한다. 이 인터페이스를 구현하고 컨텍스트로 배치되는 bean은 인터페이스의 setApplicationContext()를 사용하여 bean의 생성을 콜백될것이다. 그리고 나중에 컨텍스트와 함께 상호작동을 위해 저장될 컨텍스트에 대한 참조를 제공한다.

3.13.2. BeanPostProcessor

org.springframework.beans.factory.config.BeanPostProcessor인터페이스를 구현하는 자바 클래스인 bean 후-처리자는 벌써 언급되었다. 이것은 여기서 언급할 가치가 있다. 후-처리자는 명확한 BeanFactory보다 ApplicationContexts내에서 사용하는것이 좀더 편리하다. ApplicationContexts내에서 위 표시자(marker) 인터페이스를 구현하는 어떤 배치된 bean은 factory내 각각의 bean을 위해 생성시각에 적당하게 호출되도록 bean 후-처리자처럼 자동적으로 감지하고 등록된다.

3.13.3. BeanFactoryPostProcessor

org.springframework.beans.factory.config.BeanFactoryPostProcessor 인터페이스를 구현하는 자바 클래스인 Bean factory 후-처리자는 벌써 언급되었다. 이것은 여기서 언급할 가치가 있다. 그 bean factory 후-처리자는 보통의 BeanFactory내에서 보다 ApplicationContexts내에서 사용되는 것이 좀더 편리하다. ApplicationContexts에서 위 표시자(marker)인터페이스를 구현하는 어느 배치된 bean은 적절한 시각에 호출되기 위해 bean factory 후-처리자처럼 자동적으로 감지된다.

3.13.4. PropertyPlaceholderConfigurer

PropertyPlaceholderConfigurer 는 BeanFactory와 함께 사용되므로 벌써 설명되었다. 이것은 여기서 언급되는것이 가치있다. 이것은 컨텍스트가 자동적으로 어느 bean factory 후-처리자를 인식하고 적용하기 때문에 그것들이 다른 bean처럼 ApplicationContext으로 간단하게 배치될때 ApplicationContext와 함께 사용하는것이 대개 좀더 편리하다. 이것을 수행하기 위한 수동모드의 단계는 필요가 없다.

<!-- property placeholder post-processor -->
<bean id="placeholderConfig"
      class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="location"><value>jdbc.properties</value></property>
</bean>

3.14. 추가적인 사용자정의 PropertyEditors 등록하기

이미 언급된것처럼, 표준 자바빈 PropertyEditors는 프라퍼티를 프라퍼티의 실질적인 복합타입을 위한 문자열처럼 표시되는 값으로 변환하기 위해 사용된다. CustomEditorConfigurer, bean factory 후-처리자, ApplicationContext를 위해 추가적인 PropertyEditors를 위한 지원을 편리하게 추가하기 위해 사용된다.

사용자 클래스인 ExoticType와 프라퍼티처럼 ExoticType 셋을 필요로 하는 다른 클래스인 DependsOnExoticType를 검토해보자.

public class ExoticType {
    private String name;
    public ExoticType(String name) {
        this.name = name;
    }
}

public class DependsOnExoticType {    
    private ExoticType type;
    public void setType(ExoticType type) {
        this.type = type;
    }
}

이것들이 적절히 셋업될 때, 우리는 PropertyEditor는 실제 ExoticType객체로 변환할 문자열처럼 타입 프라퍼티를 할당하는것을 가능하게 하도록 원한다.

<bean id="sample" class="example.DependsOnExoticType">
    <property name="type"><value>aNameForExoticType</value></property>
</bean>

PropertyEditor 는 이것과 유사하게 보일수 있다.

// converts string representation to ExoticType object
public class ExoticTypeEditor extends PropertyEditorSupport {

    private String format;

    public void setFormat(String format) {
        this.format = format;
    }
    
    public void setAsText(String text) {
        if (format != null && format.equals("upperCase")) {
            text = text.toUpperCase();
        }
        ExoticType type = new ExoticType(text);
        setValue(type);
    }
}

마지막으로 우리는 필요할 때 처럼 이것을 사용할 ApplicationContext를 가진 새로운 PropertyEditor을 등록하기 위해 CustomEditorConfigurer을 사용한다.

<bean id="customEditorConfigurer" 
    class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    <property name="customEditors">
        <map>
            <entry key="example.ExoticType">
                <bean class="example.ExoticTypeEditor">
                    <property name="format">
                        <value>upperCase</value>
                    </property>
                </bean>
            </entry>
        </map>
    </property>
</bean>

3.15. 프라퍼티 표현에서 bean프라퍼티 또는 생성자의 인자를 셋팅하기.

PropertyPathFactoryBean은 주어진 대상 객체의 프라퍼티 경로를 평가하는 FactoryBean이다. 대상 객체는 직접적 또는 bean 이름을 통해 명시될수 있다. 이 값은 프라퍼티값이나 생성자의 인자처럼 다른 bean정의로 사용될수있다..

이것은 다른 bean에 대해 이름에 의해 사용되는 경로의 예제이다.

// target bean to be referenced by name
<bean id="person" class="org.springframework.beans.TestBean" singleton="false">
  <property name="age"><value>10</value></property>
  <property name="spouse">
    <bean class="org.springframework.beans.TestBean">
      <property name="age"><value>11</value></property>
    </bean>
  </property>
</bean>

// will result in 11, which is the value of property 'spouse.age' of bean 'person'
<bean id="theAge" class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
  <property name="targetBeanName"><value>person</value></property>
  <property name="propertyPath"><value>spouse.age</value></property>
</bean>

이 예제에서 경로는 내부 bean에 대해 평가된다.

// will result in 12, which is the value of property 'age' of the inner bean
<bean id="theAge" class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
  <property name="targetObject">
    <bean class="org.springframework.beans.TestBean">
      <property name="age"><value>12</value></property>
    </bean>
  </property>
   <property name="propertyPath"><value>age</value></property>
</bean>

bean이름이 프라퍼티 경로인 간략화된 형태 또한 있다.

// will result in 10, which is the value of property 'age' of bean 'person'
<bean id="person.age" class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>

이 형태는 bean의 이름내 이것이 경로인 같은 id를 사용할 어떤 참조인 선택사항이 없다는 것을 의미한다. 물론 내부 bean처럼 사용된다면 전부를 참조할 필요는 없다.

<bean id="..." class="...">
  <proprty name="age">
    <bean id="person.age"
          class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>
  </property>
</bean>

결과 타입은 실질적인 정의내 명시적으로 셋팅될수 있다. 이것은 대부분의 사용 상황을 위해 필요하지는 않지만 몇몇 사항을 위해서 사용될수 있다. 이 기능을 위한 좀더 다양한 정보를 위해서 JavaDoc를 보라.

3.16. 필드값으로부터 bean프라퍼티 또는 생성자의 인자를 셋팅하기.

FieldRetrievingFactoryBean은 정적이거나 비-정적인 필드값을 가져오는 FactoryBean이다. 이것은 다른 bean을 위해 프라퍼티값이나 생성자의 인자를 셋팅하기 위해 사용될수 있는 public 형태의 정적인 final 상수를 가져오기 위해 전형적으로 사용된다.

staticField 프라퍼티를 사용해서 정적 필드가 나타나는 방법을 보여주는 예제이다.

<bean id="myField"
      class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
  <property name="staticField"><value>java.sql.Connection.TRANSACTION_SERIALIZABLE</value></property>
</bean>

정적 필드가 bean이름처럼 정의되는 곳의 편리한 사용형태 또한 있다.

<bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
      class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"/>

이것은 bean id내(그래서 다른 bean은 더 긴 이름을 사용하기 위해 참조한다.) 결코 어떠한 선택사항도 없지만 이 형태는 정의하기에 매우 간결하고 id가 bean참조를 위해 명시되지 않은 이후 내부 bean처럼 사용하기 위해 매우 편리하다.

<bean id="..." class="...">
  <proprty name="isolation">
    <bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
      class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"/>
  </property>
</bean>

이것은 또한 JavaDoc에서 언급되는 것처럼 다른 bean의 비-정적인 필드를 접근하는것이 가능하다.

3.17. 다른 메소드를 호출하고 선택적으로 반환값을 사용한다.

이것은 몇몇 다른 클래스가 사용되기 전 몇몇 순차적인 초기화를 수행하기 위해 하나의 클래스내 정적이거나 비-정적인 메소드를 호출하는것이 때때로 필요하다. 추가적으로 이것은 컨테이너내 다른 bean의 메소드 호출 결과나 어느 임의의 클래스의 정적 메소드호출 결과처럼 bean의 프라퍼티를 셋팅하는것이 때때로 필요하다. 이 두가지 목적을 위해 MethodInvokingFactoryBean를 호출하는 헬퍼 클래스는 사용될수 있다. 이것은 정적이나 인스턴스 메소드 호출의 결과인 값을 반환하는 FactoryBean 이다.

우리는 어쨌든 추천된다. 두번째 사용 상황을 위해 이전에 언급된 factory 메소드는 거의 대부분의 선택에 훨씬 좋다.

bean 정의의 예제(XML기반의 BeanFactory정의내)는 몇몇 순차적인 정적 초기화를 강제로 수행하기 위한 이 클래스를 사용한다.

<bean id="force-init" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
  <property name="staticMethod"><value>com.example.MyClass.initialize</value></property>
</bean>

<bean id="bean1" class="..." depends-on="force-init">
  ...
</bean>

bean1을 위한 정의는 첫번째 초기화 강제 초기화(force-init)를 유발할 강제 초기화(force-init) bean을 참조하기 위해 depends-on 속성을 사용하였다. 그리고 bean1이 첫번재 초기화될때 정적인 초기화 메소드를 호출한다.

정적인 factory메소드를 호출하기 위한 이 클래스를 사용하는 bean정의의 예제이다.

<bean id="myClass" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
  <property name="staticMethod"><value>com.whatever.MyClassFactory.getInstance</value></property>
</bean>

자바 시스템 프라퍼티에서 얻기 위해 정적 메소드와 그 다음 인스턴스 메소드를 호출하는 예제이다. 어느정도 장황하지만 이것은 작동한다.

<bean id="sysProps" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
  <property name="targetClass"><value>java.lang.System</value></property>
  <property name="targetMethod"><value>getProperties</value></property>
</bean>
<bean id="javaVersion" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
  <property name="targetObject"><ref local="sysProps"/></property>
  <property name="targetMethod"><value>getProperty</value></property>
  <property name="arguments">
    <list>
      <value>java.version</value>
    </list>
  </property>
</bean>

이것은 factory메소드에 접근하기 위해 대부분 사용되는것이 기대되는것을 알라. 디폴트에 의해 MethodInvokingFactoryBean는 싱글톤 형태로 작동한다. 객체를 생성하는 factory를 위해 컨테이너에 의한 첫번째 요청은 최근과 하위 요청을 위해 캐시되고 반환될 값을 반환하는 명시된 메소드 호출을 야기할것이다. factory의 내부 singleton 프라퍼티는 객체를 요청하는 매번 대상 메소드를 호출하는것을 야기하기 위해 false로 셋팅될수 있다.

정적인 대상 메소드는 정적 메소드가 정의되는 클래스를 명시하는 targetClass를 가지고 정적 메소드이름을 표시하는 문자열을 위해 targetMethod을 셋팅함으로써 명시될수 있다. 대안으로 대상 인스턴스 메소드는 대상 객체처럼 targetObject와 대상객체의 호출을 위한 메소드 이름처럼 targetMethod을 셋팅하여 명시될수 있다. 메소드 호출을 위한 인자는 args 프라퍼티를 셋팅하여 명시될수 있다.

3.18. 하나의 파일로부터 다른것으로 bean정의를 끌어오기

컨테이너 정의를 다중 XML파일로 분류하는것은 종종 유용하다. 그 다음 그러한 XML부분들로부터 설정된 애플리케이션 컨텍스트를 로드하는 하나의 방법은 다중 Resource위치를 가지는 애플리케이션 컨텍스트 생성자를 사용하는 것이다. bean factory를 사용하여 bean정의 리더(reader)는 각각의 파일로 부터 정의를 순서대로 읽기 위해 여러번 사용될수 있다.

대개 Spring팀은 이것이 컨테이너 설정 파일이 다른것과 조합된 사실을 인지할수 없도록 유지하기 때문에 위 접근법을 선호한다. 어쟀든 대안적인 접근법은 하나의 XML bean정의 파일로 부터이다. 하나 이상의 다른 파일로부터 정의를 로드하기 위해 하나 이상의 import 요소의 인스턴스를 사용한다. 어느 import요소는 끌어오기(import)를 수행하는 파일내에서 bean 요소 앞에 위치되어야만 한다. 샘플을 보자.

<beans>

  <import resource="services.xml"/>

  <import resource="resources/messageSource.xml"/>

  <import resource="/resources/themeSource.xml"/>

  <bean id="bean1" class="..."/>

  <bean id="bean2" class="..."/>
  . . .

이 예제에서 외부 bean정의는 3개의 파일인 services.xml, messageSource.xml, 과 themeSource.xml로 부터 로드되고 있다. 모든 위치 경로는 끌어오기(import)를 수행하는 정의 파일에 상대적으로 검토된다. 그래서 이 경우 messageSource.xmlthemeSource.xml이 import파일의 위치 아래의 resources 위치에 있어야만 하는 동안 services.xml은 끌어오기(import)를 수행하는 파일처럼 같은 디렉토리나 클래스패스 위치에 있어야만 한다. 당신이 보는것처럼 앞쪽의 슬래쉬(slash)는 실질적으로 무시된다. 하지만 상대적인 경로를 검토할때 이것은 아마도 슬래쉬(slash)를 전혀 사용하지 않는것이 더 좋다.

끌어오기(import)를 수행중인 파일의 내용은 DTD를 통해 가장 상위레벨 beans요소를 포함하는 완전히 유요한 XML bean정의 파일이어야 한다.

3.19. 웹 애플리케이션으로부터 ApplicationContext생성하기.

프로그램마다 다르게 종종 생성될 BeanFactory에 적대되는 것처럼, ApplicationContexts는 예를 들어 ContextLoader을 사용하여 선언적으로 생성될수 있다. 물론 당신은 ApplicationContext 구현물중 하나를 사용하여 프로그램마다 다르게 ApplicationContext을 생성할수 있다. 첫번째 ContextLoader를 조사해보고 이것의 구현물들을 조사해보자.

ContextLoader는 ContextLoaderListenerContextLoaderServlet의 두가지의 구현물을 가진다. 그것들 모두 같은 기능을 가지지만 리스너(listener)는 서블릿 2.2 호환 컨테이너내에서는 사용될수 없다는 것이 다르다. 서블릿 2.4 스펙이후로 리스너(listener)는 웹 애플리케이션의 시작 후 초기화를 요구한다. 많은 2.3 호환 컨테이너는 이미 이 기능을 구현한다. 이것은 당신이 사용하는 것에 따르지만 모든것은 당신이 아마도 ContextLoaderListener를 선호하는것과 동일하다. 호환성에 대한 좀더 상세한 정보를 위해서는 ContextLoaderServlet을 위한 JavaDoc를 보라.

당신은 다음처럼 ContextLoaderListener을 사용하여 ApplicationContext을 등록할수 있다.

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- OR USE THE CONTEXTLOADERSERVLET INSTEAD OF THE LISTENER
<servlet>
    <servlet-name>context</servlet-name>
    <servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
-->

리스너(listener)는 contextConfigLocation 파라미터를 조사한다. 만약 이것이 존재하지 않는다면 이것은 디폴트로 /WEB-INF/applicationContext.xml을 사용할것이다. 이것이 존재할때, 이것은 미리 정의된 분리자(delimiter-콤마, 세미콜론 그리고 공백)를 사용하여 문자열을 분리하고 애플리케이션 컨텍스트가 검색되는 위치처럼 값을 사용할것이다. ContextLoaderServlet는 말하는것처럼 ContextLoaderListener대신에 사용될수 있다. 서블릿은 리스너(listener)가 하는것처럼 contextConfigLocation 파라미터를 사용할것이다.

3.20. Glue 코드와 좋지않은 싱글톤

애플리케이션내 코드의 대부분은 이것이 생성될때 컨테이너에 의해 자신만의 의존성이 제공되고 컨테이너를 완벽하게 자각하지 못하는 BeanFactory나 ApplicationContext 컨테이너에 코드가 제공되는 의존성 삽입(Dependency Injection-Inversion of Control)을 사용하여 쓰여지는게 가장 좋다. 어쨌든 다른 코드와 함께 묶기 위한 때때로 필요한 코드의 작은 glue레이어를 위해 BeanFactory나 ApplicationContext를 위해 접근하는 싱글톤(또는 준(quasi)-싱글톤 스타일을 위해 때때로 필요하다. 예를 들어 써드파티(third party) 코드가 BeanFactory의 객체를 얻기 위해 이것을 강제로 하게 할수 있는 능력없이 새로운 객체를 직접적으로 생성(Class.forName() 스타일)하도록 시도할수 있다. 만약 써드파티(third party)모드에 의해 생성된 객체가 작은 스텁(stub)나 프록시라면 위임하는 실제 객체를 얻기 위해 BeanFactory/ApplicationContext에 접근하는 싱글톤 스타일을 사용한다. inversion of control은 여전히 코드의 대부분을 위해 달성된다.(객체는 BeanFactory로 부터 나온다.). 게다가 대부분의 코드는 컨테이너나 이것이 접근되는 방법을 자각하지 않는다. 그리고 모든 이익을 가지는 다른 코드로 부터 커플링되지 않은체로 남게된다. EJB는 BeanFactory로 부터 나오는 명확한 자바 구현물 객체를 위해 위임하는 스텁/프록시 접근법을 사용할수 있다. BeanFactory는 이론상 싱글톤이 되지 않는 동안 이것은 비-싱글톤 BeanFactory를 사용하는 각각의 bean을 위해 메모리 사용이나 초기화 시점(Hibernate SessionFactory처럼 BeanFactory내 bean을 사용할 때)의 개념에서 비사실적일수 있다.

다른 예제처럼, 다중 레이어(이를테면, 다양한 JAR파일들, EJB들, 그리고 EAR처럼 패키지된 WAR파일)의 복잡한 J2EE애플리케이션에서 이것 자체의 ApplicationContext정의(구조를 효과적으로 형상화하는)를 가진 각각의 레이어와 함께 가장 상위 구조내 단 하나의 웹 애플리케이션(WAR)이 존재할때 각각의 레이어의 다중 XML정의 파일에서 하나의 복잡한 ApplicationContext를 간단하게 생성하기 위한 접근법이 선호된다. 모든 ApplicationContext종류는 이 형태로 다중 정의 파일로 부터 생성될수 있다. 어쨌든 구조의 가장 상위의 다중의 구성원(sibling) 웹 애플리케이션을 가진다면 이것은 아래쪽의 레이어로부터 대부분 일치하는 bean정의를 구성하는 각각의 웹 애플리케이션을 위한 ApplicationContext을 생성하는것이 메모리 사용을 증가시키거나 오랜 시간동안 초기화(이를테면, Hibernate SessionFactory)하는 그리고 부작용(side-effects)과 같은 여러개의 bean을 생성하는 문제의 소지가 있다. 대안으로 ContextSingletonBeanFactoryLocatorSingletonBeanFactoryLocator 와 같은 클래스는 웹 애플리케이션 ApplicationContexts의 부모처럼 사용될수 있는 효과적인 싱글톤형태로 다중 구조적 BeanFactory나 ApplicationContexts 로드를 요구하기 위해 사용될수 있다. 그 결과는 아래쪽의 레이어를 위한 bean정의가 필요할때만 오직 한번 로드되는것이다.

3.20.1. SingletonBeanFactoryLocator 와 ContextSingletonBeanFactoryLocator을 사용하기

당신은 각각의 JavaDoc를 봐서 SingletonBeanFactoryLocatorContextSingletonBeanFactoryLocator 를 사용하는 상세화된 예제를 볼수 있을것이다.

EJB장에서 언급되는것처럼, EJB를 위한 Spring의 편리한 기본 클래스는 필요하다면 SingletonBeanFactoryLocatorContextSingletonBeanFactoryLocator 의 사용으로 쉽게 대체되는 비-싱글톤 BeanFactoryLocator을 대개 사용한다.

Chapter 4. PropertyEditors, data binding, validation and the BeanWrapper

4.1. 소개

유효성이 있는지 없는지에대한 중대한 질문은 비지니스 논리(business logic)에서 충분히 고려되어야 한다. 답변들 사이에는 찬반 양론이 있다 그리고 Spring은 그것들중의 어떤것도 배타적이지 않은 유효성 (그리고 data binding)을 위한 디자인을 제공한다. 유효성은 명확하게 웹계층과 연관이 없어야하고, 어떤 장소에 배치하기(localize) 쉬워야한다 그리고 어떤 유효성을 가능하게하는 사람이 플러그인 할 수 있게 해야한다. 위에서 말한것을 고려하면, Spring은 어플리케인션 모든 계층내에 기본적이고 사용할 수 있는것 사이의 Validator 인터페이스를 제안하고 있다(has come up with).

Data binding은 어플리케이션 도메인 모델(또는 당신이 사용자 입력 진행을 위해 사용하는 객체들 무엇이든지)에 다이나믹하게 묶인것을 허락된 사용자가 입력하기에 유용하다. Spring은 Data binding을 정확하게 하기위해 소위 DataBinder을 제공한다. Validator와 DataBinder는 유효성 패키지를 구성하고, 처음에 사용되었던것 안에 구성되어있다. 그러나 MVC framework안에 제안되어있지는 않다.

BeanWrapper는 Spring Framework 내의 기본적인 개념이고, 많은곳에서 사용되어진다. 그러나, BeanWrapper를 직접 사용할 필요는 아마 결코 없을 것이다. 이것은 참고 문서이기 때문에, 우리는 적절한 몇몇 설명을 생각해 본다. 이번 장(chapter)에서는 BeanWrapper를 설명한다. 만약 여러분이 이것을 모두 사용한다면, 아마 BeanWrapper와 강하게 관련이 있는 객체인 bind data를 연습해 볼 수 있을것이다.

스프링은 모든 장소위에 PropertyEditors를 사용한다. PropertyEditor의 개념은 JavaBeans 명세(specification)의 일부분이다. BeanWrapper와 DataBinder가 밀접하게 연관된 이후로, BeanWrapper일때, 또한 이번 장(chapter)내에 PropertyEditors 사용을 잘 설명한다.

4.2. Binding data를 사용한DataBinder

DataBinder은 BeanWrapper 위에 만든다(builds).[2].

4.3. Bean 조작(manipulation)과 BeanWrapper

org.springframework.beans 패키지는 Sun에서 제공하는 JavaBeans 표준을 고수한다(adhere). JavaBean은 인수가 없는 디폴트 구성자로된 간단한 클래스이고, prop이란 이름의 속성(property)은 setter setProp(...) 과 getter getProp()을 가진 네이밍 규칙을 따른다. JavaBeans과 명세서에 관한 더 많은 정보를 위해서, Sun의 웹사이트(java.sun.com/products/javabeans)를 방문하기 바란다.

beans 패키지의 아주 중요한 한가지는 BeanWrapper 인터페이스이고 인터페이스에 대응하는 구현(BeanWrapperImpl)이다. JavaDoc의 주석과 같이 BeanWrapper는 기능적인 set과 get 속성값들(개별적인 하나하나 또는 대량)을 제공하고, 속성 서술자들(descriptors)을 얻고, 읽거나 쓸수있는지를 결정하는 속성들을 질의한다(query). 또한, BeanWrapper는 내포된 속성들을 위한 지원을 제공하고, 제한된 깊이의 하위-속성내의 속성을 설정 가능하게 해준다. 그 다음, BeanWrapper은 target 클래스안에 지원 코드의 필요 없이 PropertyChangeListenersVetoableChangeListeners 표준 JavaBeans를 더하는 능력을 지원한다. 마지막으로, BeanWrapper는 인덱스 속성들을 설정하기위한 지원을 제공한다. BeanWrapper은 보통 직접적으로 애플리케이션 코드에 사용되지 않는다. 하지만 DataBinderBeanFactory에는 사용된다.

BeanWrapper를 작업하는 방법은 부분적으로 이름에의해 지시되어진다: 설정속성들과 검색한 속성들과 같이 it wraps a bean은 bean안에서 활동이 수행된다. (it wraps a bean to perform actions on that bean, like setting and retrieving properties.)

4.3.1. Setting 과 getting 기본과 내포된 설정들

Setting과 getting 속성들은 setPropertyValue(s)getPropertyValue(s) 메소드를 사용한다. (Setting and getting properties is done using the setPropertyValue(s) and getPropertyValue(s) methods that both come with a couple of overloaded variants.) Spring JavaDoc안에 더 자세한 모든것이 설명되어있다. 중요하게 알아야할것은 한객체가 지시하는 속성에 맞는 연결된(a couple of) 규약이다. 연결된 예들:

Table 4.1. Examples of properties

Expression Explanation
name name과 대응하는 속성은 getName() 또는 isName() 그리고 setName()를 나타낸다.
account.name account 속성의 내포된 name과 대응되는 것은 속성은 예를드면 getAccount().setName() 또는 getAccount().getName() 메소드들을 나타낸다.
account[2] account의 인덱스(Indexed) 속성, 세가지요소를 나타낸다. 인덱스 속성은 array의 형태, list 또는 있는 그대로의 순서화된 collection의 형태로 나타낼수 있다.
account[COMPANYNAME] account Map 속성의 COMPANYNAME 키는 색인한 map entry의 값을 나타낸다.

get과 set 속성들을 BeanWrapper로 작업한 몇몇 예제들은 아래에 있다.

주의 : 이부분은 직접적으로 BeanWrapper를 작업할 계획이 없으면 중요하지 않다. 만약 DataBinderBeanFactory 그리고 아래 박스이외의(out-of-the-box) 구현을 사용한다면 PropertyEditors section으로 넘어가라.

다음 두 클래스들을 주시하라 :

public class Company {
    private String name;
    private Employee managingDirector;

    public String getName()	{ 
        return this.name; 
    }
    public void setName(String name) { 
        this.name = name; 
    } 
    public Employee getManagingDirector() { 
        return this.managingDirector; 
    }
    public void setManagingDirector(Employee managingDirector) {
        this.managingDirector = managingDirector;
    }
}

public class Employee {
    private float salary;

    public float getSalary() {
        return salary;
    }
    public void setSalary(float salary) {
        this.salary = salary;
    }
}

다음 코드 조각들은 검색하는 방법과 속성들을 사례를 들어 증명하는 조작에 대한 예들이 있다: CompaniesEmployees

Company c = new Company();
BeanWrapper bwComp = BeanWrapperImpl(c);
// setting the company name...
bwComp.setPropertyValue("name", "Some Company Inc.");
// ... can also be done like this:
PropertyValue v = new PropertyValue("name", "Some Company Inc.");
bwComp.setPropertyValue(v);

// ok, let's create the director and tie it to the company:
Employee jim = new Employee();
BeanWrapper bwJim = BeanWrapperImpl(jim);
bwJim.setPropertyValue("name", "Jim Stravinsky");
bwComp.setPropertyValue("managingDirector", jim);

// retrieving the salary of the managingDirector through the company
Float salary = (Float)bwComp.getPropertyValue("managingDirector.salary");

4.3.2. 내장 PropertyEditors, 변환 타입들(Built-in PropertyEditors, converting types)

Spring은 PropertyEditors의 개념을 많이(heavily) 사용한다. 때때로 객체 자신보다 다른 방법으로 속성들을 알맞게 나타낼수 있을지도 모른다. 예를들어, 날짜는 사람이 읽을수 있는 방법으로 나타내야한다. 게다가 우리는 여전히 최초의 날짜를 거꾸로하여 사람이 읽을 수 있는 형태로 변환한다. (또는 더 나은것: Date objects의 뒤, 어떤 날짜를 사람이 읽을수 는 형태로 넣어 변환한다.) (or even better: convert any date entered in a human readable form, back to Date objects). 이 행위자는 java.beans.PropertyEditor 형태의 등록한 사용자 에디터들(registering custom editors)에의해 목적을 이룰수 있다. BeanWrapper내 또는 3장에서 언급한것 같이 특정한 Application Context 안에 등록한 사용자 에디터들은 속성들이 원하는 형태로 변환하는 방법이 주어진다. Sun사에서 제공하는 java.beans 패키지의 JavaDoc문서에있는 더많은 PropertyEditors 정보를 읽어라.

Spring에서 사용하되는 편집속성 예

  • beans내의 설정 속성들은 PropertyEditors를 사용된다. XML 파일안에 선언한 몇몇 bean 속성값과 같이 java.lang.String을 언급할때, Spring은 (상응하는 setter가 Class-parameter를 가지고 있다면 ) Class object의 인수를 결정하기위해 ClassEditor를 사용한다.

  • Spring MVC framework내의 HTTP request parameters 분석에는 CommandController의 모든 하위클래스로 수동으로 바인드 할 수 있는 PropertyEditors종류가 사용된다.

Spring은 생명주기(life)를 쉽게 만들기 위한 내장(built-in) PropertyEditors를 가진다. 각각은 아래에 목록화되어있고, org.springframework.beans.propertyeditors 패키지 안에 모두 위치해 있다. 대부분, (아래에 나타나 있는것과 같이) 모두 그런것은 아니고 BeanWrapperImpl 디폴트로 저장되어있다. 속성 에디터가 몇몇 형태로 구성할수 있고, 디폴트 형태를 오버라이드하여 자신의 형상으로 등록하는 과정을 할 수 있다. :

Table 4.2. 내장 PropertyEditors(Built-in PropertyEditors)

Class 설명
ByteArrayPropertyEditor byte 배열을 위한 Editor. Strings은 간단하게 byte 표현과 상응하여 변환될 것이다. BeanWrapperImpl에 의해 디폴트로 등록된다.
ClassEditor Strings으로 표현된 클래스들을 실제 클래스와 다른 주위의 방법으로 분석하라. (Parses Strings representing classes to actual classes and the other way around.) 클래스를 찾을수 없을때, IllegalArgumentException을 던진다. BeanWrapperImpl에 의해 디폴트로 등록된다.
CustomBooleanEditor Boolean속성들을 위해 커스터마이즈할수 있는 속성 에디터(editor). BeanWrapperImpl에 의해 디폴트로 등록된다.그러나, custom 에디터에의해 등록된 custom 인스턴스에를 오버라이드할 수 있다.
CustomCollectionEdit Collection 형태의 목표가 주어졌을때 소스 Collection으로 전환하는, Collections을 위한 설정 editor.
CustomDateEditor custom DateFormat을 지원한, java.util.Date을 위한 커스터마이즈할 수 있는 설정 editor. 디폴트에의해 등록할 수 없다. 사용자가 적절한 포맷을 소유함으로써 등록되어져야 한다.
CustomNumberEditor Integer, Long, Float, Double과 같은 Number 서브클래스를 위한 커스터마이즈할 수 있는 설정 에디터. 그러나, 사용자(custom) 에디터로써 등록된 사용자(custom) 인스턴스를 오버라이드할 수 있다.
FileEditor Strings에서 File-객체들을 결정 가능.(Capable of resolving Strings to File-objects.) BeanWrapperImpl에 의해 디폴트로 등록된다.
InputStreamEditor 단방향 설정 에디터는 텍스트 문자열을 가질 수 있고, InputStream을 생성할 수 있다 (ResourceEditor와 Resource의 조정자를 통해서). 그래서 InputStream 속성들은 Strings에서 File-객체들을 결정하는데 직접적으로 setCapable을 쓸 수도 있다. 디폴트 사용은 InputStream을 끝맺지(close) 않는다는 것을 주의하라!. BeanWrapperImpl에 의해 디폴트로 등록된다.
LocaleEditor Strings에서 Locale-객체들을 결정 가능하고 반대로 Locale의 toString() 메소드도 같은 기능을 제공한다(String 포맷은 [language]_[country]_[variant]이다) . BeanWrapperImpl에 의해 디폴트로 등록된다.
PropertiesEditor Strings(Javadoc 안의 java.lang.Properties class에서 정의된 포맷되어 사용된 형태)을 Properties-객체들로 변환 가능하다. BeanWrapperImpl에 의해 디폴트로 등록된다.
StringArrayPropertyEditor String을 String-배열로 콤마-범위로 목록화하여 결정할 수 있고, 반대로도 가능하다. BeanWrapperImpl에 의해 디폴트로 등록된다.
StringTrimmerEditor Property 에디터는 Strings을 정리 정돈한다. 선택적으로 널(null) 값으로 되어있는 변형된 비어있는 문자열로 인정한다. 디폴트에의해 등록되어지지 않는다. 사용자는 필요할때에 등록해야한다.
URLEditor URL의 String 표현을 실제 URL-객체로 결정할 수 있다. BeanWrapperImpl에 의해 디폴트로 등록된다.

Spring은 설정 에디터들이 필요할지도 모르는 것을 위하여 경로 찾기를 정하는 java.beans.PropertyEditorManager를 사용한다. 경로 찾기는 또한 글꼴, 색상, 모든 원시 형태들의 PropertyEditors를 포함하고 있는 sun.bean.editors를 나타낸다. 만약 다루는 클개스가 같은 패키지 안에 있고, 클래스가 같은 이름을 가지고 있고, 'Editor'가 추가되어있다면, 또한 표준 JavaBeans 기초구조는 (등록하는 절차 없이) 자동적으로 PropertyEditors를 발견한다는 것을 주의하라.

4.3.3. 언급할 가치가 있는 다른 기능들.

전체 섹션에서 그렇게 가치가 있지는 않지만, 그밖에 여러분이 흥미로와 했을지도 모를 연관된 기능들은 전 섹션에서 본 기능들이다.

  • 가독성과 쓸수있는지 특성 결정: isReadable()isWritable() 메소드를 사용함은 속성이 가독성이 있는지 쓸수있는지를 결정할 수 있다.

  • 검색하는 PropertyDescriptors: getPropertyDescriptor(String)getPropertyDescriptors()를 사용함은 java.beans.PropertyDescriptor형태의 객체들을 검색할 수 있고, 때때로 편리할지도 모른다.



[2] 더 많은 정보를 위해 the beans chapter를 보아라.

Chapter 5. Spring AOP: Spring을 이용한 Aspect 지향적인 프로그래밍

5.1. 개념

Aspect-지향 프로그래밍 (AOP)는 프로그램 구조에 대한 다른 방식의 생각을 제공함으로써 OOP를 보완한다. OO가 애플리케이션을 객체구조로 분석하는 동안 AOP는 프로그램을 aspects관심사(concerns)로 분석한다. 이것은 다중 객체로부터 공통적으로 잘라낼수 있는 트랜잭션 관리와 같은 관심사의 모듈화(이러한 관심사는 종종 crosscutting 관심사라는 용어로 사용된다.) 를 가능하게 한다.

Spring의 핵심이 되는 컴포넌트중 하나는 AOP 프레임워크이다. Spring IoC컨테이너(BeanFactory와 ApplicationContext) 가 AOP에 의존하지 않는 동안 당신이 원하지 않는다면 AOP를 사용할 필요가 없다는 것을 의미한다. AOP는 미들웨어 솔루션의 기능을 제공하기 위해 Spring IoC를 보완할것이다.

AOP는 Spring내에서 사용된다.

  • 선언적인 기업용 서비스를 제공하기 위해 EJB 선언적 서비스를 위한 대체물처럼 사용될수 있다. 서비스처럼 가장 중요한 것은 Spring의 트랜잭션 추상화에서 빌드되는 선언적인 트랜잭션 관리이다.

  • 사용자 정의 aspect를 구현하는 것을 사용자에게 허용하기 위해 AOP를 사용하여 OOP의 사용을 기능적으로 보완한다.

게다가 당신은 EJB없이 선언적인 트랜잭션 관리를 제공하는 것을 Spring에 허용하도록 하는 기술을 가능하게 하는것처럼 Spring AOP를 볼수 있다. 또는 사용자 지정 aspect를 구현하기 위한 Spring AOP프레임워크의 강력한 힘을 사용할수 있다.

만약 당신이 일반적인 선언적 서비스나 풀링과 같은 다른 미리 패키징된 선언적 미들웨어 서비스만 관심을 가진다면 당신은 Spring AOP를 사용하여 직접적으로 작업할 필요가 없다. 그리고 이 장의 대부분을 그냥 넘어갈수 있다.

5.1.1. AOP 개념

몇몇 중심적인 AOP개념을 명시함으로써 시작해보자. 이 개념들은 Spring에 종속적인 개념이 아니다. 운 나쁘게도 AOP전문용어는 특히 직관적이지 않다. 어쨌든 Spring이 그 자신의 전문용어를 사용했다면 좀더 혼란스러울것이다.

  • Aspect: 구현물이 다중 객체를 짤라내는 것을 위한 concern의 모듈화. 트랜잭션 관리는 J2EE애플리케이션의 crosscutting concern의 좋은 예제이다. aspect는 advisor이나 인터셉터처럼 Spring을 사용하여 구현된다.

  • Joinpoint: 메소드 호출이나 특정 예외를 던지는 것과 같은 프로그램을 수행하는 지점. Spring AOP에서 joinpoint는 언제나 메소드 호출이다. Spring은 두드러지게 joinpoint개념을 사용하지는 않는다. joinpoint정보는 인터셉터로 인자를 전달하는 MethodInvocation의 메소드를 통해 접근가능하다. 그리고 org.springframework.aop.Pointcut인터페이스의 구현물에 의해 평가된다.

  • Advice: 특정 joinpoint에서 AOP프레임워크에 의해 획득되는 액션. advice의 각각의 타입은 "around," "before" 과 "throws"를 포함한다. advice 타입은 밑에서 언급된다. Spring을 포함한 많은 AOP프레임워크는 인터셉터처럼 advice를 모델화하고 joinpoint "주위"로 인터셉터의 묶음(chain)을 유지한다.

  • Pointcut: advice가 수행될때를 명시하는 joinpoint의 모음. AOP프레임워크는 개발자에게 예를 들면 정규식 표현을 사용하는 것과 같은 pointcut를 명시하도록 허용해야 한다.

  • Introduction: advised 클래스에 메소드나 필드추가하기. Spring은 어떠한 advised클래스에 새로운 인터페이스를 소개(introduce)하는것을 허용한다. 예를 들면 당신은 간단한 캐싱을 위해 IsModified인터페이스를 구현한 어떠한 객체를 만들기 위해 introduction을 사용할수 있다.

  • 대상 객체: 객체는 joinpoint를 포함한다. 또한 advisedproxied객체를 참조한다.

  • AOP 프록시: AOP프레임워크에 의해 생성되는 advice를 포함한 객체. Spring에서 AOP프록시는 JDK동적 프록시나 CGLIB프록시가 될것이다.

  • Weaving: advised객체를 생성하기 위한 aspect 조합. 이것은 컴파일(예를 들면 AspectJ컴파일러를 사용하는) 시각이나 수행시각에 수행될수 있다. 다른 순수한 자바 AOP프레임워크처럼 Spring은 수행시에 작성된다.

포함되는 다양한 타입의 advice

  • Around advice: 메소드 호출과 같은 joinpoint주위(surround)의 advice. 이것은 가장 강력한 종류의 advice이다. Around advice는 메소드 호출 전후에 사용자 지정 행위를 수행한다. 그것들은 joinpoint를 처리하거나 자기 자신의 반환값을 반환함으로써 짧게 수행하거나 예외를 던지는 것인지에 대해 책임을 진다.

  • Before advice: joinpoint전에 수행되는 advice. 하지만 joinpoint를 위한 수행 흐름 처리(execution flow proceeding)를 막기위한 능력(만약 예외를 던지지 않는다면)을 가지지는 않는다.

  • Throws advice: 메소드가 예외를 던질다면 수행될 advice. Spring은 강력한 타입의 Throws advice를 제공한다. 그래서 당신은 Throwable 나 Exception으로 부터 형변환 할 필요가 없는 관심가는 예외(그리고 하위클래스)를 잡는 코드를 쓸수 있다.

  • After returning advice: joinpoint이 일반적으로 예를 들어 메소드가 예외를 던지는것없이 반환된다면 완성된 후에 수행되는 advice.

Around advice는 가장 일반적인 종류의 advice이다. Nanning Aspects와 같은 대부분의 인터셉션-기반의 AOP프레임워크는 오직 around advice만을 제공한다.

AspectJ처럼 Spring이 advice타입의 모든 범위를 제공하기 때문에 우리는 요구되는 행위를 구현할수 있는 최소한의 강력한 advice타입을 사용하길 권한다. 예를 들어 당신이 메소드의 값을 반환하는 캐시만을 수정할 필요가 있다면 around advice가 같은것을 수행할수 있다고하더라도 around advice보다 advice를 반환한 후에 구현하는게 더 좋다. 대부분 특정 advice타입을 사용하는것은 잠재적으로 적은 에러를 가지는 간단한 프로그래밍 모델을 제공한다. 예를 들어 당신은 around advice를 위해 사용되는 MethodInvocation의 proceed()메소드를 호출할 필요가 없고 나아가 그것을 호출하는것을 실패할수도 있다.

pointcut 개념은 인터셉션(interception)을 제공하는 오래된 기술로 부터 AOP를 구별하는 수단이 되는 AOP의 핵심이다. pointcut는 OO구조가 비의존적으로 대상화되기 위한 advice를 가능하게 한다. 예를 들면 선언적인 트랜잭션을 제공하는 around advice는 다중 객체에 걸쳐있는 메소드 모음에 적용될수 있다. 게다가 pointcut는 AOP의 구조적인 요소를 제공한다.

5.1.2. Spring AOP의 기능과 대상

Spring AOP는 순수자바로 구현되었다. 특별한 편집 절차가 필요하지 않다. Spring AOP는 클래스로더 구조를 제어할 필요가 없고 J2EE웹 컨테이너나 애플리케이션 서버내 사용되는것이 적합하다.

Spring은 현재 메소드 호출의 인터셉션(interception)을 지원한다. 필드 인터셉션은 비록 필드인터셉션이 핵심 Spring AOP API에 영향없이 추가될수 있지만 구현되지 않았다.

필드 인터셉션은 어쩌면 OO캡슐화를 침범한다. 우리는 이것이 애플리케이션 개발에서 현명하다고 생각하지 않는다. 만약 당신이 필드 인터셉션을 요구한다면 AspectJ를 사용하는것을 생각해보라.

Spring은 pointcut과 다른 advice타입을 표현하기 위한 클래스를 제공한다. Spring은 aspect를 표현하는 객체를 위해 advisor라는 개념을 사용하고 joinpoint를 명시하기 위해 이것을 대상으로하는 advice와 pointcut모두를 포함한다.

다른 advice타입은 (AOP제휴(Alliance) 인터셉션 API로 부터)MethodInterceptor이고 advice인터페이스는 org.springframework.aop패키지내 정의된다. 모든 advice는 org.aopalliance.aop.Advice태그 인터페이스를 구현해야만 한다. advice는 MethodInterceptor; ThrowsAdvice; BeforeAdvice; 그리고 AfterReturningAdvice를 지원한다. 우리는 밑에서 advice타입에 대해서 상세하게 언급할것이다.

Spring은 AOP 제휴(Alliance)인터셉션 인터페이스(http://www.sourceforge.net/projects/aopalliance)를 구현한다. around advice는 AOP제휴 org.aopalliance.intercept.MethodInterceptor인터페이스를 구현해야만 한다. 이 인터페이스의 구현은 Spring내에서 뿐 아니라 다른 어떠한 AOP제휴 호환 구현물에서도 작동할수 있다. 현재 JAC는 AOP제휴 인터페이스를 구현하고 Nanning 과 Dynaop는 2004년 초 구현할 가능성이 있다.

AOP에 대한 Spring의 접근법은 대부분의 다른 AOP프레임워크와 다르다. 그 목적은 Spring AOP가 대부분 구현가능하더라도 완벽한 AOP구현물을 제공하기 않는것이다. 이것은 기업용 애플리케이션내 공통적인 문제점 해결을 돕기위해 AOP구현물과 Spring IoC사이의 닫힌(close) 통합을 제공하기 위해서이다.

게다가 예를 들어 Spring의 AOP기능은 Spring IoC컨테이너와 협력하여 작동된다. AOP advice는 비록 이것이 강력한 "autoproxying"기능을 허용한다고 할지라도 일반적인 빈 정의 문법을 사용해서 명시된다. advice와 pointcut는 Spring IoC에 의해 스스로 관리되는데 이것이 다른 AOP구현물과의 결정적인 차이점이다. Spring AOP를 사용하여 매우 잘 정리된 객체를 권하는(advise) 것처럼 당신이 쉽게 또는 효율적으로 할수 없는 것들이 있다. AspectJ는 아마도 이러한 경우에 최고의 선택이다. 어쨌든 우리의 경험은 Spring AOP가 AOP를 다루는 J2EE애플리케이션내 대부분의 문제를 위한 멋진 솔루션를 제공한다는 것이다.

Spring AOP는 복잡한 AOP솔루션을 제공하기 위해 AspectJ 나 AspectWerkz에 대해 침범하지 않을것이다. 우리는 Spring과 같은 프록시 기반 프레임워크와 AspectJ와 같은 성숙한 프레임워크 모두 가치있고 경쟁보다는 서로 보완할수 있다고 믿는다. 게다가 Spring 1.1의 가장 큰 우선사항은 일관적인 Spring기반의 애플리케이션 구조내 응답되는 위한 AOP의 모든 사용을 가능하게 하기 위해 Spring AOP와 AspectJ와의 IoC가 균일하게 통합되는 것이다. 이 통합은 Spring AOP API나 AOP제휴 API에 영향을 끼치지 않는다. Spring AOP는 호환적인 면만 남을것이다.

5.1.3. Spring 내 AOP 프록시

Spring은 AOP프록시를 위해 J2SE 동적 프록시(dynamic proxies)를 사용하는것이 디폴트이다. 이것은 프록시가 되기 위한 어떤 인터페이스나 인터페이스의 모음을 가능하게 한다.

Spring은 또한 CGLIB프록시도 사용가능하다. 이것은 인터페이스보다는 클래스를 프록시화 하기 위해 필요하다. CGLIB는 비지니스 객체가 인터페이스를 구현하지 않는다면 디폴트로 사용된다. 클래스보다는 인터페이스를 위한 프로그램을 위해 좋은 경험이기 때문에 비지니스 객체는 일반적으로 하나 이상의 비지니스 인터페이스를 구현할것이다.

이것은 강제로 CGLIB의 사용하도록 할수 있다. 우리는 이것을 밑에서 언급할것이다 당신이 왜 이렇게 하는것을 원하는지 설명할것이다.

Spring 1.0이후 Spring은 생성된 클래스들 전부를 포함하는 AOP프록시의 추가적인 타입을 제공한다. 이것은 프로그래밍 모델에 영향을 끼치지 않을것이다.

5.2. Spring내 Pointcuts

Spring이 중대한 pointcut개념을 어떻게 다루는지 보자.

5.2.1. 개념

Spring의 pointcut모델은 advice타입의 비의존성을 재사용한다. 이것은 같은 pointcut을 사용하여 다른 advice를 대상으로 하는것이 가능하다.

org.springframework.aop.Pointcut인터페이스는 특정 클래스와 메소드에 대해 대상 advice가 사용되는 중심적인 인터페이스이다. 완전한 인터페이스는 밑에서 보여준다.

public interface Pointcut {

    ClassFilter getClassFilter();

    MethodMatcher getMethodMatcher();

}

Pointcut인터페이스를 두개의 부분으로 쪼개는 것은 부분들에 적합한 클래스와 메소드, 그리고 잘 조직된 기능(다른 메소드 적합자(matcher)와의 "union"를 수행하는것과 같은)의 재사용을 허용한다.

ClassFilter인터페이스는 주어진 대상 클래스의 모음을 위해 pointcut를 제한하기 위해 사용된다. 만일 matches()메소드가 언제나 true를 반환한다면 모든 대상 클래스는 적합할것이다.

public interface ClassFilter {

    boolean matches(Class clazz);
}

MethodMatcher인터페이스는 일반적으로 좀더 중요하다. 완전한 인터페이스를 밑에서 보여준다.

public interface MethodMatcher {

    boolean matches(Method m, Class targetClass);

    boolean isRuntime();

    boolean matches(Method m, Class targetClass, Object[] args);
}

matches(Method, Class) 메소드는 이 pointcut이 대상 클래스에서 주어진 메소드에 적합할지 테스트하기 위해 사용된다. 이 평가는 모든 메소드 호출에서 테스트를 위한 필요성을 제거하기 위해 AOP프록시가 생성되었을때 수행될수 있다. 만약 두개의 인자가 적합한 메소드가 주어진 메소드를 위해 true를 반환한다면 MethodMatcher를 위한 isRuntime()메소드는 true를 반환한다. 세개의 인자가 적합한 메소드는 모든 메소드 호출에서 호출될것이다. 이것은 대상 advice가 수행되기 전에 즉시 메소드 호출로 전달되는 인자를 보기 위한 pointcut을 가능하게 한다.

대부분의 MethodMatchers는 그들의 isRuntime()메소드가 false를 반환하는 의미에서 정적이다. 이 경우 세개의 인자가 적합한 메소드는 결코 호출되지 않을것이다.

가능하다면 pointcut을 AOP프록시가 생성될때 pointcut의 평가결과를 캐시하기 위한 AOP프레임워크를 허용하도록 정적으로 만들도록 시도하라.

5.2.2. pointcuts에서의 작업(operation)

Spring은 pointcut에서의 작업(unionintersection)을 지원한다.

Union은 어느한쪽의 pointcut가 들어맞는 방식을 의미한다.

Intersection은 pointcut둘다 들어맞는 방식을 의미한다.

Union이 언제나 좀더 유용하다.

Pointcuts는 org.springframework.aop.support.Pointcuts클래스내 정적 메소드를 사용하거나 같은 패키지내 ComposablePointcut클래스를 사용하여 구성될수 있다.

5.2.3. 편리한 pointcut 구현물

Spring은 다양하고 편리한 pointcut구현물을 제공한다. 몇몇은 특별히 사용될수 있다. 다른것은 애플리케이션에 종속적인 pointcut내에서 하위클래스화 되는 경향이 있다.

5.2.3.1. 정적 pointcuts

정적 pointcut는 메소드와 대상 클래스에 기반하고 메소드의 인자를 고려할수는 없다. 정적 pointcut는 대부분의 사용상황을 위해 충분하고 가장 좋다. Spring이 메소드가 처음 호출될때 정적 pointcut를 한번만 평가하는것은 가능하다. 그 후 각각의 메소드 호출시 pointcut를 다시 평가하는것은 필요하지 않다.

Spring에 포함된 몇몇 정적 pointcut구현물을 생각해보자.

5.2.3.1.1. 정규표현식 pointcuts

정적 pointcut를 명시하는 하나의 분명한 방법은 정규표현식이다. Spring외 다양한 AOP프레임워크는 이것이 가능하다. org.springframework.aop.support.RegexpMethodPointcut는 Perl 5 정규표현식 문법을 사용하는 일반적인 정규표현식 pointcut이다.

이 클래스를 사용하면 당신은 문자열 형태의 목록을 제공할수 있다. 만약 이러한 것들이 들어맞는다면 pointcut는 true로 평가할것이다. (그래서 이 결과는 이러한 pointcut의 표과적인 union이다.)

사용법은 아래에서 보여준다.

<bean id="settersAndAbsquatulatePointcut" 
    class="org.springframework.aop.support.RegexpMethodPointcut">
    <property name="patterns">
        <list>
            <value>.*get.*</value>
            <value>.*absquatulate</value>
        </list>
    </property>
</bean>

RegexpMethodPointcut, RegexpMethodPointcutAdvisor의 편리한 하위클래스는 우리에게 advice또한 참조하도록 허락한다.(advice는 advice나 advice를 던지는등 이전에 인터셉터될수 있다는 것을 기억하라.) 이것은 하나의 빈이 pointcut와 advisor모두를 제공하거나 아래의 사항처럼 배선(wiring)을 단순화한다.

<bean id="settersAndAbsquatulateAdvisor" 
    class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
    <property name="advice">
        <ref local="beanNameOfAopAllianceInterceptor"/>
    </property>
    <property name="patterns">
        <list>
            <value>.*get.*</value>
            <value>.*absquatulate</value>
        </list>
    </property>
</bean>

RegexpMethodPointcutAdvisor는 어떠한 advice타입과도 사용될수 있다.

RegexpMethodPointcut클래스는 Jakarta ORO정규 표현식 패키지를 요구한다.
5.2.3.1.2. 속성지향(Attribute-driven) pointcuts

정적 pointcut의 중요한 타입은 metadata-driven pointcut이다. 이것은 메타데이타(전형적으로 소세레벨의 메타데이타) 속성들의 값을 사용한다.

5.2.3.2. 동적 pointcuts

동적 pointcut은 정적 pointcut보다 평가하는것이 손실이 크다(costlier). 그것들은 정적 정보만큼 메소드 arguments를 고려한다. 이것은 모든 메소드 호출에서 그들이 평가가 되어야 함을 의미한다. 결과는 인자가 다양하기 때문에 캐시될수 없다.

중요한 예제는 흐름 제어(control flow) pointcut 이다.

5.2.3.2.1. 흐름 제어(Control flow) pointcuts

Spring 흐름 제어 pointcut는 비록 덜 강력하지만 개념적으로 AspectJ cflow pointcut와 흡사하다. (현재 밑의 다른 pointcut를 수행하는 pointcut을 명시하는 방법은 없다.). 흐름 제어 pointcut는 최근의 호출 스택에 대응된다. 예를 들면 이것은 com.mycompany.web패키지내 메소드나 SomeCaller클래스에 의해 joinpoint가 호출된다면 실행한다. 흐름 제어 pointcut는 org.springframework.aop.support.ControlFlowPointcut클래스를 사용하여 몇시된다.

[Note] Note

흐름 제어 pointcut는 다른 동적 pointcut보다 수행시 평가되기 위해 명백하게 좀더 비싸다. Java1.4에서 다른 동적 pointcut의 5배이고 Java1.3에서는 10배이상이다.

5.2.4. Pointcut 수퍼클래스(superclasses)

Spring은 당신 자신의 pointcut를 구현하도록 당신을 돕기 위해 유용한 pointcut 수퍼클래스를 제공한다.

정적 pointcut이 가장 유용하기 때문에 당신은 밑에서 보여주는 것처럼 StaticMethodMatcherPointcut의 하위클래스를 만들것이다. 이것은 하나의 추상 메소드를 구현(비록 사용자 지정 행위를 위해 다른 메소드를 오버라이드하는것이 가능하더라도)하는것을 요구한다.

class TestStaticPointcut extends StaticMethodMatcherPointcut {

    public boolean matches(Method m, Class targetClass) {
        // return true if custom criteria match
    }
}

동적 pointcut를 위해 수퍼클래스도 있다.

당신은 Spring 1.0 RC2그리고 그 이상에서 어떠한 advice타입으로도 사용자 지정 pointcut를 사용할수 있다.

5.2.5. 사용자지정 pointcuts

Spring내 pointcut가 언어적(AspectJ처럼)인 면보다 자바클래스이기 때문에 이것은 정적이든 동적이든 사용자지정 pointcut를 선언하는것이 가능하다. 어쨌든 AspectJ문법내 작성될수 있는 정교한 pointcut표현식을 위해 특별한 지원은 없다. 어쨌든 Spring내 사용자 지정 pointcut는 임의로 복잡하게 될수 있다.

Spring의 가장 최근버전은 아마 JAC에 의해 제공되는 것처럼 "의미적인(semantic) pointcut"를 위한 지원이 제공될지도 모른다. 예를 들면 "대상 객체내에서 인스턴스 변수를 변경하는 모든 메소드"

5.3. Spring 내 Advice타입들

Spring AOP가 advice를 어떻게 다루는지에 대해서 지금 보자.

5.3.1. Advice 생명주기

Spring advices는 모든 adviced객체를 통해 공유되거나 각각의 advised객체에 대해 유일할 수 있다. 이것은 per-classper-instance advice에 일치한다.

Per-class advice는 매우 종종 사용된다. 이것은 트랜잭션 advisor처럼 일반적인 advice를 선호한다. 그것들은 프록시된 객체의 상태에 의존하지 않고 새로운 상태를 추가한다. 그것들은 단지 메소드와 인자에 옇양을 준다.

Per-instance advice는 도입이나 mixin을 지원하기 위해 선호한다. 이 경우 advice는 프록시된 객체에 상태를 추가한다.

이것은 같은 AOP프록시내에서 공유되는것들의 mixin과 per-instance advice를 사용하는것이 가능하다.

5.3.2. Spring내 Advice 타입들

Spring은 다양하고 특별한 advice타입들을 제공한다. 그리고 독단적인 advice타입을 지원하기 위해 확장가능하다. 기본적인 개념과 표준적인 advice타입을 보자.

5.3.2.1. Interception around advice

Spring내 가장 기본적인 advice타입은 interception around advice이다.

Spring은 메소드 인터셉션을 사용해서 around advice를 위한 AOP제휴 인터페이스와 잘 작동한다. around advice를 구현하는 MethodInterceptors는 다음의 인터페이스를 구현해야만 한다.

public interface MethodInterceptor extends Interceptor {
  
    Object invoke(MethodInvocation invocation) throws Throwable;
}

invoke()메소드를 위한 MethodInvocation인자는 호출될 메소드(대상 joinpoint; AOP프록시; 그리고 메소드를 위한 인자)를 드러낸다. invoke()메소드는 호출 결과(joinpoint의 반환값)를 반환한다.

간단한 MethodInterceptor 구현은 다음과 같다.

public class DebugInterceptor implements MethodInterceptor {

    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("Before: invocation=[" + invocation + "]");
        Object rval = invocation.proceed();
        System.out.println("Invocation returned");
        return rval;
    }
}

MethodInvocation의 proceed()메소드를 호출하는것에 주의하라. 이것은 joinpoint쪽으로 인터셉터 연계작업(chain)을 처리한다. 대부분의 인터셉터는 이 메소드를 호출할것이고 그 자신의 반환값을 반환한다. 어쨌든 어떠한 around advice와 같은 MethodInterceptor는 처리 메소드를 호출하는 것보다 다른 값을 반환하거나 예외를 던질수 있다. 어쨌든 당신은 좋은 이유없이는 이것을 원하지 않을것이다.

MethodInterceptors는 다른 AOP제휴 AOP구현물과 상호작용성을 제공한다. 다른 advice타입은 공통적인 AOP개념을 구현하는 이 부분의 나머지에서 Spring 특유의 방법으로 언급된다. 대부분의 특정 advice타입을 사용하는 방법으로 장점을 가지는 동안 만약 당신이 다른 AOP프레임워크내 aspect를 수행하길 원한다면 MethodInterceptor around advice에 충실하라. 현재 pointcut는 프레임워크 사이에 상호작용가능하지 않고 AOP제휴는 현재 pointcut인터페이스를 정의하지 않는다.

5.3.2.2. 전(Before) advice

좀더 간단한 advice타입은 전(before) advice이다. 이것은 오직 메소드에 들어가지전에 호출되기 때문에 MethodInvocation객체를 필요로 하지 않는다.

전(before) advice의 가장 중요한 장점은 proceed() 메소드를 호출할 필요가 없다는 것이다. 그리고 인터셉터 연계작업을 처리하는 것을 무심코 실패하는 가능성이 없다.

MethodBeforeAdvice 인터페이스는 아래에서 보여진다. (Spring API디자인은 비록 평상시 객체가 필드 인터셉션에 적용하고 Spring이 이것을 구현할 가능성은 없음에도 불구하고 advice앞의 필드를 허용한다.).

public interface MethodBeforeAdvice extends BeforeAdvice {

    void before(Method m, Object[] args, Object target) throws Throwable;
}

반환타입은 void라는것에 주의하라. 전(before) advice는 joinpoint가 수행되기 전에 사용자 지정 행위를 추가할수 있다. 하지만 반환값을 변경할수는 없다. 만약 전(before) advice가 예외를 던진다면 이것은 인터셉터 연계작업의 더이상의 수행을 취소할것이다. 예외는 인터셉터 연계작업을 뒤로 돌린다. 먄약 이것이 체크되지 않았거나 호출된 메소드의 시그너처라면 이것은 클라이언트로 직접적으로 전달될것이다. 반면에 이것은 AOP프록시에 의한 체크되지 않은 예외를 포장할것이다.

Spring내 전(before) advice의 예제는 모든 메소드 호출을 센다.

public class CountingBeforeAdvice implements MethodBeforeAdvice {
    private int count;
    public void before(Method m, Object[] args, Object target) throws Throwable {
        ++count;
    }

    public int getCount() { 
        return count; 
    }
}
전(before) advice 는 어떠한 pointcout와 사용될수 있다.

5.3.2.3. Throws advice

Throws advice는 joinpoint가 예외를 던진다면 joinpoint를 반환한후에 호출된다. Spring은 throws advice타입을 제공한다. 이것은 org.springframework.aop.ThrowsAdvice인터페이스가 어떠한 메소드도 포함하지 않는다는 것을 의미한다. 이것은 주어진 객체가 하나 이상의 throws advice타입의 메소드를 구현하는 것을 표시하는 태그 인터페이스이다. 그것들은 폼의 형태가 될것이다.

afterThrowing([Method], [args], [target], subclassOfThrowable) 

오직 마지막의 인자만이 요구된다. 게다가 한개에서 네개까지의 인자는 advice메소드가 메소드와 인자에 연관되는지에 의존한다. 다음은 throws advice의 예제이다.

이 advice는 RemoteException과 하위 클래스가 던져진다면 호출될것이다.

public  class RemoteThrowsAdvice implements ThrowsAdvice {

    public void afterThrowing(RemoteException ex) throws Throwable {
        // Do something with remote exception
    }
}

다음의 advice는 ServletException가 던져진다면 호출된다. 위 advice와는 다르게 이것은 네개의 인자를 명시한다. 그래서 이것은 메소드 인자와 대상 객체로 호출되는 메소드에 접근한다.

public static class ServletThrowsAdviceWithArguments implements ThrowsAdvice {

    public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
        // Do something will all arguments
    }
}

마지막 예제는 두개의 메소드가 RemoteExceptionServletException 둘다 다루는 하나의 클래스내에서 어떻게 사용되는지 설명한다. 많은 수의 throws advice 메소드는 하나의 클래스내에서 조합될수 있다.

public static class CombinedThrowsAdvice implements ThrowsAdvice {

    public void afterThrowing(RemoteException ex) throws Throwable {
        // Do something with remote exception
    }
 
    public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
        // Do something will all arguments
    }
}
Throws advice 어떠한 pointcut와 사용될수 있다.

5.3.2.4. advice를 반환한 후(after returning advice)

Spring내에서 advice를 반환한 후(after returning advice)는 밑에서 보여지는 것처럼 org.springframework.aop.AfterReturningAdvice인터페이스를 구현한다.

public interface AfterReturningAdvice extends Advice {

    void afterReturning(Object returnValue, Method m, Object[] args, Object target) 
            throws Throwable;
}

after returing advice는 반환하는 값(변경할수 없는), 호출된 메소드, 메소드 인자와 대상에 접근한다.

다음의 after returning advice는 예외를 던지지 않는 모든 성공적인 메소드 호출의 갯수를 센다.

public class CountingAfterReturningAdvice implements AfterReturningAdvice {
    private int count;

    public void afterReturning(Object returnValue, Method m, Object[] args, Object target) throws Throwable {
        ++count;
    }

    public int getCount() {
        return count;
    }
}

이 advice는 실행경로를 변경하지 않는다. 만약 이것이 예외를 던진다면 이것은 반환값 대신에 인터셉터 연계를 던질것이다.

After returning advice는 어떠한 pointcut도 함께 사용될수 있다.

5.3.2.5. Introduction advice

Spring은 특별한 종류의 interception advice처럼 introduction advice를 처리한다.

Introduction은 다음의 인터페이스를 구현하는 IntroductionAdvisorIntroductionInterceptor를 요구한다.

public interface IntroductionInterceptor extends MethodInterceptor {

    boolean implementsInterface(Class intf);
}

AOP제휴 MethodInterceptor 인터페이스로 부터 상속된 invoke() 메소드는 introduction을 구현해야만 한다. 만약 호출된 메소드가 introduced인터페이스에 있다면 introduction인터셉터는 메소드 호출을 다룰 책임을 가지고 있으며 이것은 proceed()을 호출할수 없다.

Introduction advice는 메소드보다는 클래스에만 적용하는 것처럼 어떠한 pointcut와 함께 사용될수 없다. 당신은 다음의 메소드를 가지는 InterceptionIntroductionAdvisor를 사용하여 introduction advice만을 사용할수 있다.

public interface InterceptionIntroductionAdvisor extends InterceptionAdvisor {

    ClassFilter getClassFilter();

    IntroductionInterceptor getIntroductionInterceptor();

    Class[] getInterfaces();
}

introduction advice와 관련하여 MethodMatcher가 없고 나아가 Pointcut도 없다. 오직 클래스 필터링이 논리적이다.

getInterfaces() 메소드는 이 advisor에 의해 소개된(introduced) 인터페이스만을 반환한다.

Spring테스트 묶음으로 부터 간단한 예제를 보자. 하나 이상의 객체에 다음의 인터페이스를 소개(introduce)하길 원한다고 가정하자.

public interface Lockable {
    void lock();
    void unlock();
    boolean locked();
}

이것은 mixin을 묘사한다. 우리는 그것들의 타입이 무엇이든 lock와 unlock메소드를 호출하든 advised 객체가 Lockable로 형변환될수 있길 원한다. 우리가 lock()메소드를 호출한다면 우리는 LockedException를 던지기 위해 모든 setter메소드를 원한다. 게다가 우리는 이것에 대한 어떠한 지식을 가진 그것들 없이도 객체를 바꿀수 없는 상태로 만들기 위한 능력을 제공하는 aspect를 추가할수 있다. AOP의 좋은 예제이다.

첫째로 우리는 대량으로 발생하는 IntroductionInterceptor이 필요할 것이다. 이 경우 우리는 org.springframework.aop.support.DelegatingIntroductionInterceptor를 확장한다. 우리는 직접적으로 IntroductionInterceptor를 구현할수 있다. 하지만 DelegatingIntroductionInterceptor을 사용하는 것은 대부분의 경우에 최고의 선택이다.

DelegatingIntroductionInterceptor는 introduced인터페이스의 실질적인 구현을 위해 introduction을 위임하도록 디자인되었고 그렇게 하기 위한 인터셉션의 사용을 숨긴다. 그 위임은 생성자 인자를 사용해서 어떠한 객체를 위한 셋팅될수 있다. 디폴트(인자없는 생성자를 사용할 때) 위임이 이것이다. 게다가 이것은 아래의 예제에 있다. 위임은 DelegatingIntroductionInterceptorLockMixin 하위 클래스이다. 주어진 위임(디폴트에 의해)님 DelegatingIntroductionInterceptor인스턴스는 위임(IntroductionInterceptor이 아닌)에 의해 구현된 모든 인터페이스를 찾는다. 그리고 그것들에 대해 introduction을 지원할것이다. 인터페이스를 억제하기 위한 suppressInterflace(Class intf) 메소드를 호출하기 위해 LockMixin과 같은 하위클래스가 드러낼수(exposed) 없다. 어쨌든 많은 인터페이스 IntroductionInterceptor가 지원되기 위해 준비되는지는 문제되지 않는다. 사용되는 IntroductionAdvisor는 실질적으로 드러나는 인터페이스를 제어할것이다. introduced인터페이스는 대상에 의해 같은 인터페이스의 어떠한 구현물을 숨길것이다.

게다가 LockMixin은 DelegatingIntroductionInterceptor의 하위클래스이고 Lockable를 구현한다. 수퍼클래스는 Lockable가 introduction를 지원할수 있다는 것을 자동적으로 포착한다. 그래서 우리는 그것을 명시할 필요가 없다. 우리는 이 방법으로 많은 인터페이스를 소개(introduce)할수 있다.

locked인스턴스 변수 사용에 주의하라. 이것은 대상 객체를 유지하는 추가적인 상태를 효과적으로 추가한다.

public class LockMixin extends DelegatingIntroductionInterceptor 
    implements Lockable {

    private boolean locked;

    public void lock() {
        this.locked = true;
    }

    public void unlock() {
        this.locked = false;
    }

    public boolean locked() {
        return this.locked;
    }

    public Object invoke(MethodInvocation invocation) throws Throwable {
        if (locked() && invocation.getMethod().getName().indexOf("set") == 0)
            throw new LockedException();
        return super.invoke(invocation);
    }

}

종종 이것은 invoke()메소드를 오버라이드(메소드가 소개(introduced)되었다면 위임메소드를 호출하는 DelegatingIntroductionInterceptor구현) 할 필요가 없다. 반면에 joinpoint를 향한 처리는 언제나 충분하다. 현재 상태에서 우리는 locked상태라면 호출될수 있는 setter메소드가 없다는 것을 체크할 필요가 있다.

요구되는 introduction advisor는 간단하다. 이것을 하기 위해 필요한 모든것은 구별되는 LockMixin인스턴스를 유지하고 Lockable의 경우에서 소개된(introduced) 인터페이스를 명시하는것이다. 좀더 복잡한 예제는 introduction인터셉터(프로토타입처럼 명시된)에 대한 참조를 가져온다. 이 경우 LockMixin에 관련된 설정은 없다. 그래서 new를 사용해서 이것을 간단하게 생성한다.

public class LockMixinAdvisor extends DefaultIntroductionAdvisor {

    public LockMixinAdvisor() {
        super(new LockMixin(), Lockable.class);
    }
}

우리는 매우 간단하게 이 advisor를 적용할수 있다. 이것은 설정을 요구하지 않는다. (어쨌든 이것은 필요하다. IntroductionAdvisor없이 IntroductionInterceptor을 사용하는 것은 불가능하다.) 늘 그렇듯이 introduction으로 advisor은 이것이 상태유지(stateful)인 것처럼 인스턴스당 하나가 되어야 한다. 우리는 각각의 advised객체를 위해 LockMixinAdvisor의 다른 인스턴스와 LockMixin이 필요하다. advisor는 advised객체의 상태의 일부로 구성된다.

우리는 프로그램에 따라 Advised.addAdvisor() 를 사용하거나 XML설정(추천되는 방식)으로 다른 advisor처럼 이 advisor를 적용할수 있다. 밑에서 언급되는 "자동 프록시 생성자"를 포함한 모든 프록시 생성 선택은 introduction과 상태유지 mixin을 다룬다.

5.4. Spring내 Advisors

Spring내 advisor는 aspect의 모듈이다. advisor는 전형적으로 advice와 pointcut를 결합한다.

introduction의 특별한 경우 이외에 어떤 advisor는 어떠한 advice와 사용될수 있다. org.springframework.aop.support.DefaultPointcutAdvisor는 가장 공통적으로 사용되는 advisor클래스이다. 예를 들면 이것은 MethodInterceptor, BeforeAdvice 또는 ThrowsAdvice과 사용될수 있다.

같은 AOP프록시로 Spring내에서 advisor과 advice타입들을 혼합하는 것은 가능하다. 예를 들면 당신은 하나의 프록시 설정으로 interception around advice, throws advice 그리고 before advice를 사용할수 있다. Spring은 자동적으로 필요한 생성 인터셉터 연계작업(create interceptor chain)을 생성할 것이다.

5.5. AOP프록시를 생성하기 위한 ProxyFactoryBean사용하기

만약 당신이 당신의 비지니스 객체를 위해 Spring IoC컨테이너(ApplicationContext나 BeanFactory)를 사용한다면 그리고 사용해야만 한다면 당신은 Spring의 AOP FactoryBean중 하나를 사용하길 원할것이다. (factory빈은 다른 타입의 객체를 생성하는 것을 가능하게 하는 indirection의 레이어를 소개(introduce)한다는 것을 기억하라.)

Spring내 AOP프록시를 생성하는 기본적인 방법은 org.springframework.aop.framework.ProxyFactoryBean을 사용하는 것이다. 이것은 적용할 pointcut와 advice의 완벽한 제어능력를 부여한다. 어쨌든 당신이 이러한 제어능력을 필요로 하지 않는다면 선호될수 있는 유사한 옵션들이 있다.

5.5.1. 기본

다른 Sprign indirection과 같은 FactoryBean구현물인 ProxyFactoryBean은 indirection의 레벨을 소개(introduce)한다. 만약 당신이 foo이름으로 ProxyFactoryBean를 정의한다면 foo를 참조하는 객체는 ProxyFactoryBean인스턴스 자신이 아니다. 하지만 객체는 getObject() 메소드의 ProxyFactoryBean's구현물에 의해 생성된다. 이 메소드는 대상 객체를 포장하는 AOP프록시를 생성할것이다.

AOP프록시를 생성하기 위해 ProxyFactoryBean나 다른 IoC형태로 감지되는 클래스를 사용하는 가장 중요한 이득중 하나는 이것이 IoC에 의해 advice와 pointcut가 관리될수 있다는 것이다. 이것은 다른 AOP프레임워크로는 달성하기 힘든 어떠한 접근법을 가능하게 하는 강력한 기능이다. 예를 들면 애플리케이션 객체(어떤 AOP프레임워크내 사용가능할수 있는 대상을 제외하고)를 참조할수 있는 advice는 의존성 삽입(Dependency Injection)의 의해 제공되는 모든 플러그인 가능한 능력으로 부터 이익을 취할수 있다.

5.5.2. 자바빈 프라퍼티

Spring에서 제공되는 대부분의 FactoryBean구현물처럼 ProxyFactoryBean은 자체가 자바빈이다. 이것의 프라퍼티는 다음을 위해 사용된다.

  • 당신이 프록시 되기를 원하는 대상을 명시

  • CGLIB를 사용할지에 대한 명시

몇몇 핵심적인 프라퍼티는 모든 AOP프록시 factory를 위한 수퍼클래스인 org.springframework.aop.framework.ProxyConfig로부터 상속된다. 그것들은 다음을 포함한다.

  • proxyTargetClass: 만약 우리가 인터페이스보다는 대상 클래스를 프록시화 한다면 true이다. 만약 이것이 true이면 우리는 CGLIB를 사용할 필요가 있다.

  • optimize: 생성된 프록시를 위해 공격적인 최적화를 적용할지에 대한 값. 만약 당신이 관련 AOP프록시가 최적화를 다루는 방법을 이해하지 못한다면 이 셋팅을 사용하지 말라. 이것은 현재 CGLIB프록시를 위해서만 사용된다. 이것은 JDK동적 프록시에 대해 영향을 끼치지 않는다.(디폴트)

  • frozen: advice변경이 프록시 factory가 설정되었을때 허용될지에 대한 값. 디폴트는 false이다.

  • exposeProxy: 현재 프록시가 대상에 의해 접근될수 있는 ThreadLocal으로 활성화될지에 대한 값. (이것은 ThreadLocal을 위한 필요성 없이 MethodInvocation을 통해 사용가능하다.) 만약 대상이 프록시와 exposeProxy을 얻을 필요가 있다면 true이다. 대상은 AopContext.currentProxy()메소드를 사용할수 있다.

  • aopProxyFactory: 사용하기 위한 AopProxyFactory의 구현물. 동적 프록시, CGLIB또는 다른 프록시 전략을 사용할지에 대한 커스터마이징의 방법을 제공한다. 디폴트 구현물은 동적 프록시나 CGLIB를 선호한다. 이 프라퍼티를 사용할 필요는 없다. 이것은 Spring 1.1내 새로운 프록시 타입의 추가를 허용하는 경향이 있다.

ProxyFactory빈을 위해 명시하는 다른 프라퍼티는 다음을 포함한다.

  • proxyInterfaces: Spring인터페이스 이름의 배열. 만약 이것을 제공하지 않는다면 대상 클래스를 위한 CGLIB프록시는 사용될것이다.

  • interceptorNames: 적용하기 위한 Advisor, 인터셉터 또는 다른 advice이름의 문자열 배열. 정렬은 중요하다. 처음들어와서 처음 제공하는 그것이다. 리스트내 첫번째 인터셉터는 만약 이것이 통상의 MethodInterceptorBeforeAdvice에 관계한다면 첫번째 호츨의 인터셉터를 가능하게 할것이다.

    이름들은 조상 factory로 부터 bean 이름을 포함한 현재 factory내 bean 이름들이다. 당신은 advice의 싱글톤 셋팅을 무시하는 ProxyFactoryBean으로 결과를 내기 때문에 여기에 bean참조를 언급할수 없다.

    당신은 별표(*) 를 인텃베터 이름에 덧붙일수 있다. 이것은 적용될 별표(*) 앞에 부분으로 시작하는 이름으로 모든 advisor 빈즈의 애플리케이션으로 결과를 생성한다. 이 특징을 사용하는 예제는 아래에서 찾을수 있다.

  • 싱글톤 : factory가 하나의 객체를 반환하거나 하지 않거나 종종 getObject()메소드가 호출되는 것은 문제가 아니다. 다양한 FactoryBean구현물은 그러한 메소드를 제공한다. 디폴트 값은 true이다. 만약 당신이 상태유지(stateful) advice를 사용하길 원한다면 예를 들어 상태유지 mixin. false의 싱글톤 값을 가지고 프로토타입 advice를 사용하라.

5.5.3. 프록시 인터페이스

액션내 ProxyFactoryBean의 간단한 예제를 보자. 이 예제는 다음을 포함한다.

  • 프록시 될 대상 bean. 이것은 밑의 예제내 "personTarget" bean 정의이다.

  • advice를 제공하기 위해 사용되는 advisor와 인터셉터

  • AOP프록시 bean정의는 적용할 advice에 따라 대상 객체(personTarget bean)와 프록시를 위한 인터페이스를 명시한다.

<bean id="personTarget" class="com.mycompany.PersonImpl">
    <property name="name"><value>Tony</value></property>
    <property name="age"><value>51</value></property>
</bean>

<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
    <property name="someProperty"><value>Custom string property value</value></property>
</bean>

<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor">
</bean>

<bean id="person" 
    class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces"><value>com.mycompany.Person</value></property>

    <property name="target"><ref local="personTarget"/></property>
    <property name="interceptorNames">
        <list>
            <value>myAdvisor</value>
            <value>debugInterceptor</value>
        </list>
    </property>
</bean>

interceptorNames 프라퍼티는 문자열 타입의 리스트(현재 factory내 인터셉터나 advisor의 bean이름들)를 가진다. advisor, 인터셉터, before, after return 그리고 throw advice겍체는 사용될수 있다. advisor의 절렬은 중요하다.

당신은 왜 리스트가 bean참조를 유지하지 않는지 이상할것이다. 이유는 만약 ProxyFactoryBean의 싱글톤 프라퍼티가 false로 셋팅된다면 이것은 비의존적인 프록시 인스턴스를 반환하는 것이 가능해야만 한다는 것이다. 만약 어느 advisor 자체가 프로토타입이라면 비의존적인 인스턴스는 반환될 필요가 있을것이다. 그래서 이것은 factory로 부터 프로토타입의 인스턴스를 얻는 것이 가능하게 되는 것이 필요하다. 참조를 유지하는 것은 중요하지 않다.

위의 "person" bean정의는 다음처럼 Person구현물을 대체하여 사용될수 있다.

Person person = (Person) factory.getBean("person");

같은 IoC컨텍스트내 다른 빈즈는 보통의 자바객체를 사용하는 것처럼 강력한 의존성을 표시할수 있다.

<bean id="personUser" class="com.mycompany.PersonUser">
    <property name="person"><ref local="person" /></property>
</bean>

이 예제내 PersonUser클래스는 Person타입의 프라퍼티를 드러낸다. 이것이 관여된만큼 AOP프록시는 "실제" person구현물을 대신해서 투명하게 사용될수 있다. 어쨌든 이 클래스는 동적 프록시 클래스가 될 것이다. 이것은 이것을 Advised인터페이스(밑에서 언급되는)로 형변환하는 것이 가능할 것이다.

다음처럼 익명의 내부 bean을 사용하는 대상과 프록시사이의 차이을 숨기는 것은 가능하다. 단지 ProxyFactoryBean정의만이 다르다. advice는 완성도를 위해서만 포함된다.

<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
    <property name="someProperty"><value>Custom string property value</value></property>
</bean>

<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor">
</bean>

<bean id="person" 
    class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces"><value>com.mycompany.Person</value></property>

    <!-- Use inner bean, not local reference to target -->
    <property name="target">
        <bean class="com.mycompany.PersonImpl">
            <property name="name"><value>Tony</value></property>
            <property name="age"><value>51</value></property>
        </bean>
   </property>

    <property name="interceptorNames">
        <list>
            <value>myAdvisor</value>
            <value>debugInterceptor</value>
        </list>
    </property>
</bean>

이것은 오직 Person타입의 객체만이 있다는 장점을 가진다. 우리가 애플리케이션 컨텍스트의 사용자가 un-advised객체에 대한 참조를 얻는것을 방지하길 원하거나 Spring IoC autowiring으로 어떤 모호함을 피할 필요가 있다면 유용하다. ProxyFactoryBean정의내 장점은 스스로 포함(self-contained)한다는 것이다. 어쨌든 factory로 부터 un-advised대상를 얻는것이 가능할때 실질적으로 장점이 될수 있다.

5.5.4. 프록시 클래스

만약 당신이 하나 이상의 인터페이스 보다 클래스를 프록시 할 필요가 있다면 무엇인가.?

위에 있는 우리의 예제를 생각해보라. Person인터페이스는 없다. 어떠한 비지니스 인터페이스도 구현하지 않는 Person을 호출하는 클래스를 advise할 필요가 있다. 이 경우 당신은 동적 프록시 보다 CGLIB프록시를 사용하기 위해 Spring을 설정할수 있다. 위 ProxyFactoryBean의 proxyTargetClass 프라퍼티를 간단하게 true로 셋팅하라. 클래스보다는 인터페이로 작업을 수행하는 것이 최상이지만 인터페이스를 구현하지 않는 클래스를 advise하는 기능은 기존 코드로 작업할때 유용할수 있다(일반적으로 Spring은 규범적이지 않다. 이것이 좋은 상황을 적용하는 것이 쉽다면 이것은 특정 접근법에 집중하는것을 피한다.)

만약 당신이 원한다면 당신이 인터페이스를 가지고 작업하는 경우조차도 CGLIB를 사용하는 것에 집중할수 있다.

CGLIB프록시는 수행시 대상 클래스의 하위 클래스를 생성함으로써 작동한다. Spring은 초기 대상에 메소드 호출을 위임하기 위한 생성된 하위클래스를 설정한다. 이 하위클래스는 advice내 짜여진 Decorator패턴을 구현한다.

CGLIB프록시는 사용자에게는 알기쉬워야한다. 어쨌든 여기에 생각해 볼 몇가지 사항들이 있다.

  • Final 메소드는 오버라이드가 될수 없는것처럼 advised 될수 없다.

  • 당신은 클래스패스내 CGLIB2 바이너리가 필요할것이다. 동적 프록시는 JDK와 함께 사용가능하다.

CGLIB프록시와 동적 프록시사이에는 작은 성능상의 차이가 있다. Spring 1.0에서 동적 프록시는 미세하게 좀더 빠르다. 어쨌든 이것은 차후에 변경될것이다. 성능은 이 경우에 명백하게 결정될수가 없다.

5.5.5. 'global' advisor 사용하기

인터셉터 이름에 별표(*) 를 덧붙임으로써 별표(*)앞의 부분과 대응되는 bean이름을 가지는 모든 advisor는 advisor연계에 추가될것이다. 이것은 당신이 표준적인 'global' advisor의 모음을 추가할 필요가 있다면 편리하다.

<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
  <property name="target"><ref bean="service"/></property>
  <property name="interceptorNames">
    <list>
      <value>global*</value>
    </list>
  </property>
</bean>

<bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/>
<bean id="global_performance"
  class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>

5.6. 편리한 프록시 생성

종종 우리는 트랜잭션 관리와 같은 하나의 aspect에만 관심을 가지지 때문에 ProxyFactoryBean의 모든 기능이 필요하지 않다.

특정 aspect에 집중하기를 원할때 우리가 AOP프록시를 생성하기 위해 사용할수 있는 편리한 많은 factory가 있다. 다른 장에서 언급되었기 때문에 우리는 여기서 그것들 중 몇가지의 빠른 조사결과를 제공할것이다.

5.6.1. TransactionProxyFactoryBean

Spring에 포함된 jPetStore 샘플 애플리케이션은 TransactionProxyFactoryBean의 사용을 보여준다.

TransactionProxyFactoryBeanProxyConfig의 하위클래스이다. 그래서 기초적인 설정은 ProxyFactoryBean와 공유한다. (위의 ProxyConfig 프라퍼티의 목록을 보라.)

jPetStore으로부터 다음의 예제는 이것이 어떻게 작동을 하는지 보여준다. ProxyFactoryBean를 사용하는 상태에서 여기에 대상 bean정의가 있다. 의존성은 대상 POJO("petStoreTarget")보다 프록시화된 factory bean정의(여기엔 "petStore")에 표시되어야 한다.

TransactionProxyFactoryBean은 대상과 트랜잭션 적인것과 요구되는 전달(propagation), 그리고 다른 셋팅이 될 방법을 명시하는 "트랜잭션 속성"에 대한 정보를 요구한다.

<bean id="petStoreTarget" class="org.springframework.samples.jpetstore.domain.logic.PetStoreImpl">
    <property name="accountDao"><ref bean="accountDao"/></property>
    <!-- Other dependencies omitted -->
</bean>

<bean id="petStore" 
    class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <property name="transactionManager"><ref bean="transactionManager"/></property>
    <property name="target"><ref local="petStoreTarget"/></property>
    <property name="transactionAttributes">
        <props>
            <prop key="insert*">PROPAGATION_REQUIRED</prop>
            <prop key="update*">PROPAGATION_REQUIRED</prop>
            <prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
        </props>
    </property>
</bean>

ProxyFactoryBean를 사용하는 상태에서 우리는 가장 상위레벨의 대상 bean에 대한 참조대신에 target프라퍼티의 값을 셋팅하는 내부 bean을 사용하는것을 선택해야만 한다.

TransactionProxyFactoryBean은 트랜잭션 속성에 기초를 둔 pointcut을 포함하는 트랜잭션 advisor를 자동적으로 생성한다. 그래서 오직 트랜잭션적인 메소드만이 advised된다.

TransactionProxyFactoryBean은 선인터셉터(preInterceptors)와 후인터셉터(postInterceptors) 프라퍼티를 사용하는 "선" 과 "후" advice의 명시를 허용한다. 트랜잭션 인터셉터의 앞이나 뒤 인터셉션 연계작업에 두기 위한 인터셉터의 객체 배열, 다른 advice또는 advisor를 가진다. 다음처럼 XML bean정의내 <list> 요소를 사용하여 활성화될수 있다.

<property name="preInterceptors">
    <list>
        <ref local="authorizationInterceptor"/>
        <ref local="notificationBeforeAdvice"/>
    </list>
</property>
<property name="postInterceptors">
    <list>
        <ref local="myAdvisor"/>
    </list>
</property>

이 프라퍼티들은 위의 "petStore" bean정의에 추가될수 있다. 공통적인 사용법은 선언적인 보안과 함께 트랜잭션을 조합하는 것이다. EJB에 의해 제공되는 것과 유사한 접근법이다.

ProxyFactoryBean처럼 bean이름보다 실질적인 인스턴스 참조의 사용때문에 선,후 인터셉터는 오직 공유-인스턴스 advice만이 사용될수 있다. 게다가 그것들은 상태유지(stateful) advice를 위해서는 유용하지 않다. 이것은 TransactionProxyFactoryBean의 목적과 일치한다. 이것은 공통적인 트랜잭션 셋업의 간단한 방법을 제공한다. 만약 당신이 좀더 복잡하고, 사용자 정의된 AOP를 원한다면 일반적인 ProxyFactoryBean을 사용하거나 자동 프록시 생성자를 사용하는것을 고려하라.

특별히 우리가 많은 경우에 EJB의 대체물로 Spring AOP를 본다면 우리는 대부분의 advice가 꽤 일반적이고 공유-인스턴스 모델을 사용하는것을 알게된다. 선언적인 트랜잭션 관리와 보안 체크는 표준적인 예제이다.

TransactionProxyFactoryBean은 이것의 transactionManager 자바빈 프라퍼티를 통해 PlatformTransactionManager구현물에 의존한다. 이것은 JTA, JDBC또는 다른 전략에 기초를 둔 플러그인 가능한 트랜잭션 구현물을 허용한다. 이것은 AOP보다 Spring트랜잭션 추상화와 관련이 있다. 우리는 다음 장에서 트랜잭션 내부구조에 대해 언급할것이다.

만약 당신이 선언적인 트랜잭션 관리에만 관심을 가진다면 TransactionProxyFactoryBean은 ProxyFactoryBean보다 간단하고 좋은 해결방안이다.

5.6.2. EJB 프록시

다른 전용 프록시는 호출 코드에 의해 직접적으로 사용되기 위한 EJB "business methods" 인터페이스를 가능하도록 하는 EJB를 위한 프록시를 생성한다. 호출 코드는 JNDI룩업을 수행하거나 EJB생성 메소드를 사용하기 위해 필요하지 않다. 이것은 읽기가능한 성질과 구조적인 유연성에 명백한 향상을 가진다.

더 많은 정보를 위해서 이 문서의 Spring EJB서비스 관련 장을 보라.

5.7. 간결한 프록시 정의

특별히 트랜잭션적인 프록시를 정의할때 당신은 많은 유사한 프록시 정의로 끝낼지도 모른다. 내부 bean정의에 따라 부모및 자식 bean정의의 사용은 좀더 깔끔하고 좀더 갈결한 프록시 정의라는 결과를 만들수 있다.

첫번째 부모, 템플릿, bean 정의는 프록시를 위해 생성된다.

<bean id="txProxyTemplate" abstract="true"
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <property name="transactionManager"><ref local="transactionManager"/></ref></property>
    <property name="transactionAttributes">
      <props>
        <prop key="*">PROPAGATION_REQUIRED</prop>
      </props>
    </property>
</bean>

이것은 자체적으로 인스턴스화가 결코 될수 없다. 그래서 실제로 완벽하지 않을지도 모른다. 생성될 필요가 있는 각각의 프록시는 내부 bean정의처럼 프록시의 대상을 포장하는 자식 bean정의이다. 대상은 자기 자신의 것이 결코 사용되지 않을것이다.

<bean id="myService" parent="txProxyTemplate">
    <property name="target">
      <bean class="org.springframework.samples.MyServiceImpl">
      </bean>
    </property>
</bean>

트랜잭션 위임(propagation) 셋팅과 같은 경우처럼 부모 템플릿으로부터 프라퍼티를 오버라이드 하는것은 물론 가능하다.

<bean id="mySpecialService" parent="txProxyTemplate">
    <property name="target">
      <bean class="org.springframework.samples.MySpecialServiceImpl">
      </bean>
    </property>
    <property name="transactionAttributes">
      <props>
        <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
        <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
        <prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
        <prop key="store*">PROPAGATION_REQUIRED</prop>
      </props>
    </property>
</bean>

위 예제에서 우리는 부모 bean정의를 앞서 서술된것처럼 abstract 속성을 사용해서 abstract처럼 명시적으로 표시한다. 그래서 이것은 실질적으로 인스턴스화 된 적이 없을것이다. 애플리케이션 컨텍스트(간단한 bean factory는 아닌)는 디폴트로 모든 싱글톤이 미리 인스턴스화될것이다. 그러므로 이것은 당신이 템플릿처럼 사용할 경향이 있는 (부모) bean정의를 가지고 이 정의가 클래스를 명시한다면 당신은 abstract속성을 true로 셋팅해야만 하고 반면에 애플리케이션 컨텍스트는 실질적으로 이것을 먼저 인스턴스화할 것이다.

5.8. ProxyFactory로 프로그램적으로 AOP프록시를 생성하기.

Spring을 사용해서 프로그램적으로 AOP프록시를 생성하는 것은 쉽다. 이것은 당신에게 Spring IoC에서 의존성없이 Spring AOP를 사용하는것을 가능하게 한다.

다음의 리스트는 하나의 인터셉터와 하나의 advisor로 대상 객체를 위한 프록시의 생성을 보여준다. 대상 객체에 의해 구현된 인터페이스는 자동적으로 프록시화될것이다.

ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl);
factory.addInterceptor(myMethodInterceptor);
factory.addAdvisor(myAdvisor);
MyBusinessInterface tb = (MyBusinessInterface) factory.getProxy();

첫번째 단계는 org.springframework.aop.framework.ProxyFactory 타입의 객체를 생성하는 것이다. 당신은 위 예제처럼 대상 객체와 함께 이것을 생성할수 있거나 대안이 되는 생성자로 프록시될 인터페이스를 명시할수 있다.

당신은 인터셉터나 advisor을 추가할수 있고 ProxyFactory의 생명을 위해 그것들을 조작할수 있다. 만약 당신이 IntroductionInterceptionAroundAdvisor를 추가한다면 당신은 추가적인 인터페이스를 위한 프록시를 야기할수 있다.

또한 ProxyFactory에는 당신에게 before advice와 throw advice와 같은 다른 advice타입을 추가하도록 허용하는 편리한 메소드(AdvisedSupport로부터 상속된)가 있다. AdvisedSupport는 ProxyFactory와 ProxyFactoryBean모두의 수퍼클래스이다.

IoC프레임워크를 사용해서 AOP프록시 생성을 통합하는 것은 대부분의 애플리케이션에서 최상의 선택이다. 우리는 당신이 일반적인 것처럼 AOP를 사용해서 자바코드로 부터 설정을 구체화하는것을 추천한다.

5.9. advised 객체 조작하기.

당신이 AOP프록시를 생성한다고 해도 당신은 org.springframework.aop.framework.Advised인터페이스를 사용해서 그것들을 조작할수 있다. 어떤 AOP프록시가 다른 어또한 인터페이스를 구현한다고 해도 이 인터페이스로 형변환될수 있다. 이 인터페이스는 다음의 메소드를 포함한다.

Advisor[] getAdvisors();

void addAdvice(Advice advice) throws AopConfigException;

void addAdvice(int pos, Advice advice) 
        throws AopConfigException;

void addAdvisor(Advisor advisor) throws AopConfigException;

void addAdvisor(int pos, Advisor advisor) throws AopConfigException;

int indexOf(Advisor advisor);

boolean removeAdvisor(Advisor advisor) throws AopConfigException;

void removeAdvisor(int index) throws AopConfigException;

boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException;

boolean isFrozen();

getAdvisors() 메소드는 모든 advisor, 인터셉터 또는 factory에 추가될수 있는 다른 advice타입을 위해 advisor을 반환할것이다. 만약 당신이 advisor를 추가한다면 이 시점에 반환되는 advisor는 당신이 추가한 객체가 될것이다. 만약 당신이 인텃베터나 다른 advice타입을 추가한다면 Spring은 이것을 언제나 true를 반환하는 pointcut를 가지고 advisor로 포장할것이다. 게다가 당신이 MethodInterceptor을 추가한다면 이 시점을 위해 반환된 advisor는 당신의 MethodInterceptor과 모든 클래스와 메소드에 대응되는 pointcut을 반환하는 DefaultPointcutAdvisor가 될것이다.

addAdvisor()메소드는 어떤 advisor을 추가하기 위해 사용될수 있다. 언제나 pointcut와 advice를 유지하는 advisor는 어떤 advice나 pointcut(introduction은 아닌)와 사용될수 있는 일반적인 DefaultPointcutAdvisor이 될것이다.

디폴트에 의해 한번 프록시가 생성되었을때 advisor나 인터셉터를 추가하거나 제거하는것은 가능하다. 오직 제한은 factory로 부터 존재하는 프록시가 인터페이스 변경을 보여주지 않을것이라는 것처럼 introduction advisor을 추가하거나 제거하는것이 불가능하다.(당신은 이 문제를 피하기 위해 factory로 부터 새로운 프록시를 얻을수 있다.)

AOP프록시를 Advised 인터페이스로 형변환하고 이것의 advice를 시험하고 조작하는것의 간단한 예제이다.

Advised advised = (Advised) myObject;
Advisor[] advisors = advised.getAdvisors();
int oldAdvisorCount = advisors.length;
System.out.println(oldAdvisorCount + " advisors");

// Add an advice like an interceptor without a pointcut
// Will match all proxied methods
// Can use for interceptors, before, after returning or throws advice
advised.addAdvice(new DebugInterceptor());

// Add selective advice using a pointcut
advised.addAdvisor(new DefaultPointcutAdvisor(mySpecialPointcut, myAdvice));

assertEquals("Added two advisors",
     oldAdvisorCount + 2, advised.getAdvisors().length);
이것은 비록 정확한 사용법이 의심스럽지 않더라도 제품내 비지니스 객체의 advice를 변경하는 것이 현명한것인지(advisable) 아닌지 의심스러울 것이다. 어쨌든 이것은 개발에서 매우 유용할수 있다. 예를 들면, 테스트에서 내가 테스트하길 원하는 메소드 호출내에서 얻어지는 인터셉터나 다른 advice의 형태내 테스트코드를 추가하는것이 가능하게 되는것이 매우 유용하다는 것을 때때로 발견하곤 한다.(예를 들어, advice는 그 메소드(예를 들면, 롤백을 위해 트랜잭션을 표시하기 전에 데이터베이스가 정확하게 수정되었는지 체크하기 위해 SQL수행하는것) 생성된 트랜잭션 내부에서 얻어질수 있다.

당신이 프록시를 생성하는 방법에 대해 의존하라. 당신은 Advised isFrozen()메소드가 true를 반환하고 추가나 삭제를 통해 advice를 변경하는 어떤 시도가 AopConfigException을 결과로 보내는 경우에 frozen 플래그를 셋팅할수 있다. advised 객체의 상태를 고정하기 위한 능력은 몇몇 경우에 유용하다. 예를 들면 보안 인터셉터를 제거하는 호출 코드를 제한하기 위해. 이것은 아마도 수행 advice 변경이 요구되지 않는다면 공격적인 최적화를 허용하기 위해 Spring 1.1내에서 사용된다.

5.10. "autoproxy" 기능 사용하기

지금까지 우리는 ProxyFactoryBean이나 유사한 factory bean을 사용해서 AOP프록시의 명시적인 생성을 설명했다.

Spring은 또한 자동적으로 선택된 bean정의를 프록시할수 있는 "autoproxy" bean정의를 사용하는 것을 허용한다. 이것은 Spring에 컨테이너 로드처럼 어떤 bean정의의 변경을 가능하게 하는 "bean 후 처리자" 구조로 내장되었다.

이 모델에서 당신은 자동 프록시 내부구조를 설정하는 XML bean정의파일내 몇몇 특별한 bean정의를 셋업한다. 이것은 당신에게 자동프록시를 위해 적당한 대상을 선언하도록 허용한다. 당신은 ProxyFactoryBean을 사용할 필요가 없다.

이것을 하기 위한 두가지 방법이 있다.

  • 현재 컨텍스트내 bean을 명시하기 위해 참조하는 autoproxy 생성자 사용하기.

  • 개별적으로 검토되기 위한 가치가 있는 autoproxy 생성의 특별한 경우. autoproxy 생성은 소스레벨 메타데이타 속성에 의해 이루어진다.

5.10.1. autoproxy bean정의

org.springframework.aop.framework.autoproxy패키지는 다음의 표준적인 autoproxy 생성자를 제공한다.

5.10.1.1. BeanNameAutoProxyCreator

BeanNameAutoProxyCreator는 이름과 문자값또는 와일드카드가 들어맞는 bean을 위해 자동적으로 AOP프록시를 생성한다.

<bean id="jdkBeanNameProxyCreator" 
    class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    <property name="beanNames"><value>jdk*,onlyJdk</value></property>
    <property name="interceptorNames">
        <list>
            <value>myInterceptor</value>
        </list>
    </property>
</bean>

ProxyFactoryBean를 사용하는것처럼 프로토타입 advisor을 위해 정확한 행위를 허용하는 인터셉터의 리스트보다 interceptorNames 프라퍼티가 있다. 명명된 "인터셉터"는 advisor나 다른 advice타입이 될수 있다.

일반적으로 자동 프록시를 사용하는 것처럼 BeanNameAutoProxyCreator를 사용하는 중요한 점은 다중객체에 일관적으로 같은 설정을 적용하고 최소한의 설정을 가진다. 이것은 다중 객체에 선언적인 트랜잭션을 적용하기 위해 널리 알려진 선택이다.

위 예제에서 "jdkMyBean" 과 "onlyJdk"처럼 이름이 대응되는 bean정의는 대상 클래스를 가지는 명백한 옛 bean정의이다. AOP프록시는 BeanNameAutoProxyCreator에 의해 자동적으로 생성될것이다. 같은 advice는 모든 대응되는 bean에 적용될것이다. 만약 advisor가 사용된다면(위 예제안의 인터셉터보다) pointcut은 다른 bean에 다르게 적용될것이다.

5.10.1.2. DefaultAdvisorAutoProxyCreator

좀더 일반적이고 굉장히 강력한 자동 프록시 생성자는 DefaultAdvisorAutoProxyCreator이다. 이것은 autoproxy advisor의 bean정의내 특정 bean이름을 포함할 필요없이 현재 컨텍스트에 적절한 advisor를 자동적으로 적용할것이다. 이것은 일관적인 설정의 장점과 BeanNameAutoProxyCreator처럼 중복의 회피를 제공한다.

이 기법을 사용하는 것은 다음을 포함한다.

  • DefaultAdvisorAutoProxyCreator bean정의를 명시하기.

  • 같거나 관련된 컨텍스트내 많은 수의 advisor명시하기. 인터셉터나 다른 advice가 아닌 advisor이 되어야만 한다. 이것은 평가하기 위한, 후보 bean정의를 위해 각각의 advice의 적절함을 체크하기 위한 pointcut가 되어야만 하기 때문에 필요하다.

DefaultAdvisorAutoProxyCreator는 각각의 비지니스 객체(예제내에서 "businessObject1" 과 "businessObject2" 와 같은) 를 적용해야하는 advice가 무엇인지 보기 위해 각각의 advisor내 포함된 pointcut를 자동적으로 평가할것이다.

이것은 많은 수의 advisor가 각각의 비지니스 객체에 자동적으로 적용될수 있다. 만약 어떠한 advisor내 pointcut이 비지니스 객체내 어떠한 메소드와도 대응되지 않는다면 객체는 프록시화 되지 않을것이다. bean정의가 새로운 비지니스 객체를 위해 추가된다면 그것들은 필요할때 자동적으로 프록시화될것이다.

일반적인 자동프록시는 un-advised객체를 얻기 위한 호출자나 의존적인 것을 불가능하게 만드는 장점을 가진다. 이 ApplicationContext의 getBean("businessObject1")을 호출하는 것은 대상 비지니스 객체가 아닌 AOP프록시를 반환할것이다. ("내부 bean" 표현형식은 좀더 빨리 보여지고 또한 이 이득을 제공한다.)

<bean id="autoProxyCreator"
    class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
</bean>

<bean id="txAdvisor"
    autowire="constructor"
    class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
    <property name="order"><value>1</value></property>
</bean>

<bean id="customAdvisor"
    class="com.mycompany.MyAdvisor">
</bean>

<bean id="businessObject1"
    class="com.mycompany.BusinessObject1">
    <!-- Properties omitted -->
</bean>

<bean id="businessObject2"
    class="com.mycompany.BusinessObject2">
</bean>

DefaultAdvisorAutoProxyCreator는 만약 당신이 많은 비지니스 객체에 일관적으로 같은 advice를 적용하길 원한다면 매우 유용하다. 내부구조 정의가 대체될때 당신은 특정 프록시 설정을 포함하는것 없이 새로운 비지니스 객체를 간단하게 추가할수 있다. 당신은 예를 들어 설정의 최소한의 변경으로 추적및 성능 모니터링 aspect처럼 추가적인 aspect를 매우 쉽게 감소시킬수있다.

DefaultAdvisorAutoProxyCreator는 필터링과 정렬을 위한 지원을 제공한다.(명명 규칙을 사용하는 것은 같은 factory내 AdvisorAutoProxyCreators를 오직 특정 advisor가 평가하고, 다중 사용을 허용하고, 다르게 설정된다.) advisor는 이것이 쟁점이라면 정확한 정렬을 보장하기 위한 org.springframework.core.Ordered 인터페이스를 구현할수 있다. 위 예제에서 사용된 TransactionAttributeSourceAdvisor는 설정가능한 정렬값을 가지지만 디폴트는 정렬되지 않는다.

5.10.1.3. AbstractAdvisorAutoProxyCreator

이것은 DefaultAdvisorAutoProxyCreator의 수퍼클래스이다. advisor정의가 프레임워크 DefaultAdvisorAutoProxyCreator의 행위를 위해 부족한 사용자지정을 제공하는 가능성이 희박한 경우에 당신은 이 클래스를 하위클래스화하여 당신 자신의 autoproxy생성자를 생성할수 있다.

5.10.2. 메터데이타-지향 자동 프록시 사용하기.

autoproxy의 특별히 중요한 타입은 메타데이타에 의해 다루어진다. 이것은 .NET ServicedComponents에 유사한 프로그래밍 모델을 생산한다. EJB처럼 XML배치 서술자를 사용하는 대신에 트랜잭션 관리와 다른 기업용 서비스를 위한 설정은 소스레벨 속성내 유지된다.

이 경우 당신은 메터데이타 속성을 이해하는 advisor와의 조합으로 DefaultAdvisorAutoProxyCreator을 사용한다. 이 메터데이타는 autoproxy생성 클래스 자체보다는 후보 advisor의 pointcut부분내 유지됨을 명시한다.

이것은 DefaultAdvisorAutoProxyCreator의 특별한 경우이다. 하지만 그것 자신의 보상을 할만하다. (메터데이타-인식 코드는 AOP프레임워크 자체가 아닌 advisor내 포함된 pointcut내에 있다.)

jPetStore샘플 애플리케이션 의 /attributes디렉토리는 속성-지향 자동프록시의 사용을 보여준다. 이 경우 TransactionProxyFactoryBean를 사용할 필요는 없다. 메터데이타-인식 pointcut의 사용이기 때문에 간단하게 비지니스 객체의 트랜잭션적인 속성을 정의하는 것은 춘분하다. bean정의는 /WEB-INF/declarativeServices.xml내 다음의 코드를 포함한다. 이것은 일반적이고 jPetStore밖에서 사용될수 있다.

<bean id="autoProxyCreator" 
    class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
</bean>

<bean id="transactionAttributeSource"
    class="org.springframework.transaction.interceptor.AttributesTransactionAttributeSource"
    autowire="constructor">
</bean>

<bean id="transactionInterceptor"
    class="org.springframework.transaction.interceptor.TransactionInterceptor"
    autowire="byType">
</bean>

<bean id="transactionAdvisor"
    class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor"
    autowire="constructor" >
</bean>

<bean id="attributes"
    class="org.springframework.metadata.commons.CommonsAttributes"
/>

이 경우 "autoProxyCreator"라고 불리는 DefaultAdvisorAutoProxyCreator bean정의는 이름이 중요하지 않지만(이것은 생략될수도 있다.) 현재의 애플리케이션 컨텍스트내 모든 적절한 pointcut를 가져올것이다. 이 경우 TransactionAttributeSourceAdvisor타압의 "transactionAdvisor" bean정의는 클래스나 트랜잭션 속성을 운반하는 메소드에 적용할것이다. TransactionAttributeSourceAdvisor는 생성자 의존성을 통해 TransactionInterceptor에 의존한다. 예제는 autowiring을 통해 이것을 해결한다. AttributesTransactionAttributeSourceorg.springframework.metadata.Attributes인터페이스의 구현물에 의존한다. 이 잔해에서 "attributes" bean은 속성정보를 얻기 위해 Jakarta Commons Attributes API 사용하는 것을 만족한다. (애플리케이션 코드는 Commons Attributes 집계작업을 사용하여 컴파일되어야만 한다.)

여기에 정의된 TransactionInterceptor는 이것은 애플리케이션의 트랜잭션 요구사항(전형적으로 예제에서 처럼 JTA, 또는 Hibernate, JDO, JDBC)에 명시될 것이기 때문에 이 일반적인 파일내 포함되지 않는 PlatformTransactionManager정의에 의존한다.

<bean id="transactionManager" 
    class="org.springframework.transaction.jta.JtaTransactionManager"/>
만약 당신이 선언적인 트랜잭션 관리만을 요구한다면 이러한 일반적인 XML정의를 사용하는 것은 Spring내에서 트랜잭션 속성을 가진 모든 클래스나 메소드를 자동적으로 프록싱하는 결과를 낳는다. 당신은 AOP로 직접적으로 작업을 하는것을 필요로 하지 않을것이고 프로그래밍 모델은 .NET ServicedComponents의 그갓과 유사하다.

이 기법은 확장가능하다. 사용자지정 속성에 기반하여 자동프록싱을 하는것은 가능하다. 당신이 다음 사항을 할 필요가 있다.

  • 당신의 사용자 지정 속성 명시하기.

  • 클래스나 메소드의 사용자지정 속성의 존재에 의해 처리되는 pointcut를 포함하는 필요한 advice를 가진 advisor명시하기. 당신은 사용자 지정 속성을 가져오는 정적 pointcut를 거의 구현하는 존재하는 advice를 사용하는것이 가능할지도 모른다.

각각의 advised클래스에 유일하기 위한 그러한 advisor(예를 들면 mixin)은 가능하다. 그것들은 싱글톤, bean정의보다는 프로토타입처럼 간단하게 정의될 필요가 있다. 예를 들어 위에서 보여진 Spring 테스트 슈트로부터의 LockMixin 소개(introduction) 인터셉터는 여기서 보여지는 것처럼 목표 mixin에 속성-지향 pointcut이 결합되어 사용되는 것이 가능하다. 우리는 자바빈 프라퍼티를 사용하여 설정된 포괄적인 DefaultPointcutAdvisor을 사용한다.

<bean id="lockMixin"
    class="org.springframework.aop.LockMixin"
    singleton="false"
/>

<bean id="lockableAdvisor"
    class="org.springframework.aop.support.DefaultPointcutAdvisor"
    singleton="false"
>
    <property name="pointcut">
        <ref local="myAttributeAwarePointcut"/>
    </property>
    <property name="advice">
        <ref local="lockMixin"/>
    </property>
</bean>

<bean id="anyBean" class="anyclass" ...

만약 속성 인식 pointcut이 anyBean이나 다른 bean정의내 어떠한 메소드와 대응된다면 mixin은 적용될것이다. lockMixinlockableAdvisor은 프로토타입이라는것에 주의하라. myAttributeAwarePointcut pointcut은 개별적인 advised객체를 위한 상태를 유지하지 않는 것처럼 싱글톤 정의가 될수 있다.

5.11. TargetSources 사용하기

Spring은 org.springframework.aop.TargetSource인터페이스내에서 표현되는 TargetSource의 개념을 제공한다. 이 인터페이스는 joinpoint를 구현하는 "대상 객체(target object)"를 반환하는 책임을 가진다. TargetSource구현은 AOP프록시가 메소드 호출을 다루는 시점마다 대상 인스턴스를 요청한다.

Spring AOP를 사용하는 개발자는 대개 TargetSources를 직접적으로 작업할 필요가 없다. 하지만 이것은 풀링, 핫 스왑 그리고 다른 정교한 대상을 지원하는 강력한 방법을 제공한다. 예를 들면 풀링 TargetSource는 인스턴스를 관리하기 위한 풀을 사용하여 각각의 호출을 위한 다른 대상 인스턴스를 반환할수 있다.

만약 당신이 TargetSource을 명시하지 않는다면 디폴트 구현물은 로컬 객체를 포장하는것이 사용된다. 같은 대상은 (당신이 기대하는것처럼) 각각의 호출을 위해 반환된다.

Spring에 의해 제공되는 표준적인 대상 소스를 보자. 그리고 당신이 그것들을 어떻게 사용할수 있는지도 보자.

사용자 지정 대상 소스를 사용할때 당신의 대상은 싱글톤 bean정의보다 프로토타입이 될 필요가 있을것이다. 이것은 요구될때 Spring이 새로운 대상 인스턴스를 생성하는것을 허용한다.

5.11.1. 핫 스왑가능한 대상 소스

org.springframework.aop.target.HotSwappableTargetSource는 이것에 대한 참조를 유지하기 위한 호출자를 허용하는 동안 교체되기 위한 AOP프록시의 대상을 허용하기 위해 존재한다.

대상 소스의 대상을 변경하는 것은 즉시 영향을 끼친다. HotSwappableTargetSource는 쓰레드에 안전하다(threadsafe).

당신은 다음처럼 HotSwappableTargetSource의 swap()메소드를 통해 대상을 변경할수 있다.

HotSwappableTargetSource swapper = 
    (HotSwappableTargetSource) beanFactory.getBean("swapper");
Object oldTarget = swapper.swap(newTarget);

요구되는 XML정의는 다음처럼 볼수 있다..

<bean id="initialTarget" class="mycompany.OldTarget">
</bean>

<bean id="swapper" 
    class="org.springframework.aop.target.HotSwappableTargetSource">
    <constructor-arg><ref local="initialTarget"/></constructor-arg>
</bean>

<bean id="swappable" 
    class="org.springframework.aop.framework.ProxyFactoryBean"
>
    <property name="targetSource">
        <ref local="swapper"/>
    </property>
</bean>

위의 swap() 호출은 스왑가능한 bean의 대상을 변경한다. 그 bean에 대한 참조를 유지하는 클라이언트는 변경을 인식하지 못할것이지만 새로운 대상에 즉시 도달할것이다.

비록 이 예제는 어떠한 advice를 추가하지 않고 TargetSource를 사용하기 위한 advice를 추가할 필요가 없다. 물론 어떤 TargetSource는 임의의 advice로 결합하여 사용될수 있다.

5.11.2. 풀링 대상 소스

풀링 대상 소스를 사용하는것은 일치하는 인스턴스의 풀이 메소드 호출로 풀내 객체가 자유롭게 되는 방식으로 유지되는 비상태유지(stateless) 세션 EJB와 유사한 프로그래밍 모델을 제공한다.

Spring풀링과 SLSB풀링 사이의 결정적인 차이점은 Spring풀링은 어떠한 POJO에도 적용될수 있다는 것이다. 대개 Spring을 사용하는 것은 이 서비스가 비-침락젹인 방법으로 적용될수 있다.

Spring은 상당히 효과적인 풀링 구현물을 제공하는 Jakarta Commons Pool 1.1을 위한 특별한 지원을 제공한다. 당신은 이 기능을 사용하기 위해 애플리케이션 클래스패스내 commons-pool.jar파일이 필요할것이다. 이것은 다른 풀링 API를 지원하기 위해 org.springframework.aop.target.AbstractPoolingTargetSource의 하위클래스를 구현하는 것이 가능하다.

샘플 설정은 아래에서 보여진다.

<bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject" 
    singleton="false">
    ... properties omitted
</bean>

<bean id="poolTargetSource" 
    class="org.springframework.aop.target.CommonsPoolTargetSource">
    <property name="targetBeanName"><value>businessObjectTarget</value></property>
    <property name="maxSize"><value>25</value></property>
</bean>

<bean id="businessObject" 
    class="org.springframework.aop.framework.ProxyFactoryBean"
>
    <property name="targetSource"><ref local="poolTargetSource"/></property>
    <property name="interceptorNames"><value>myInterceptor</value></property>
</bean>

예제내 대상 객체인 "businessObjectTarget"이 프로토타입이 되어야만 한다는 것에 주의하라. 이것은 PoolingTargetSource구현물이 필요할때 풀의 증가를 위한 대상의 새로운 인스턴스를 생성하는것을 허용한다. 이것의 프라퍼티에 대한 정보를 위해 사용하기 위한 AbstractPoolingTargetSource와 견고한 하위클래스를 위한 JavaDoc를 보라. maxSize는 가장 기본적이고 표현되기 위해 항상 보증된다.

이 경우 "myInterceptor"는 같은 IoC컨텍스트내 정의될 필요가 있는 인터셉터의 이름이다. 이것은 풀링을 사용하기 위해 인터셉터를 명시할 필요가 없다. 만약 당신이 오직 풀링만을 원하고 다른 advice는 원하지 않는다면 interceptorNames 프라퍼티를 전부 셋팅하지 말라.

소개(introduction)을 통해 풀의 설정과 현재 크기에 대한 정보를 드러내는 org.springframework.aop.target.PoolingConfig인터페이스를 위한 어떤 풀링된 객체를 형변환하는것을 가능하게 하는것처럼 Spring을 설정하는것은 가능하다. 당신은 이것처럼 advisor을 명시할 필요가 있을것이다.

<bean id="poolConfigAdvisor" 
    class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="targetObject"><ref local="poolTargetSource" /></property>
    <property name="targetMethod"><value>getPoolingConfigMixin</value></property>
</bean>

이 advisor는 AbstractPoolingTargetSource클래스의 편리한 메소드를 호출하고 나아가 MethodInvokingFactoryBean의 사용하여 얻을수 있다. 이 advisor의 이름(여기 "poolConfigAdvisor")은 풀링된 객체를 드러내는 ProxyFactoryBean내 인터셉터 이름의 목록이 될수 있다.

형변환은 다음처럼 보일것이다.

PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
System.out.println("Max pool size is " + conf.getMaxSize());
풀링 비상태유지(stateless) 서비스 객체는 언제나 필요한것은 아니다. 우리는 이것이 대부분의 비상태유지(stateless) 객체가 근본적으로 쓰레드에 안전하고 인스턴스 풀링은 자원이 캐시된다면 골치거리가 되는것처럼 디폴트 선택이 될것이라고 믿지는 않는다.

좀더 간단한 풀링은 자동프록싱을 사용하여 사용가능하다. 어떤 autoproxy생성자에 의해 사용될수 있는 TargetSources 셋팅은 가능하다.

5.11.3. 프로토 타입 대상 소스

"프로토타입" 대상 소스를 셋업하는 것은 풀링 TargetSource와 유사하다. 이 경우 대상의 새로운 인스턴스는 모든 메소드호출에서 생성될것이다. 비록 새로운 객체를 생성하는 비용이 요즘 JVM내에서는 높지않더라도 새로운 객체(IoC의존성을 만족하는)를 묶는 비용은 좀더 비쌀것이다. 게다가 당신은 매우좋은 이유없이 이 접근법을 사용하지 않을것이다.

이것을 하기 위해 당신은 다음처럼 위에서 보여진 poolTargetSource정의를 변경할수 있다. (나는 명백하게 하기 위해 이름을 변경했다.)

<bean id="prototypeTargetSource" 
    class="org.springframework.aop.target.PrototypeTargetSource">
    <property name="targetBeanName"><value>businessObjectTarget</value></property>
</bean>

여기엔 오직 하나의 프라퍼티(대상 빈의 이름)가 있다. 상속은 일관적인 명명을 확실히 하기 위한 TargetSource구현물내 사용되었다. 풀링 대상 소스를 사용하는 것처럼 대상 bean은 프로토타입 bean정의가 되어야만 한다.

5.11.4. ThreadLocal 대상 소스

ThreadLocal 대상 소스는 만약 당신이 들어오는 각각의 요청(쓰레드마다)을 위해 생성되기 위한 객체가 필요하다면 유용하다. ThreadLocal의 개념은 쓰레드와 함께 자원을 투명하게 저장하기 위한 JDK범위의 기능을 제공한다. ThreadLocalTargetSource를 셋업하는 것은 다른 대상 소스를 위해 설명되는 것과 거의 같다.

<bean id="threadlocalTargetSource"
  class="org.springframework.aop.target.ThreadLocalTargetSource">
  <property name="targetBeanName"><value>businessObjectTarget</value></property>
</bean>
            

ThreadLocals은 멀티-쓰레드와 멀티-클래스로더 환경내 그것들을 정확하게 사용하지 않았을때 다양한 문제(잠재적으로 메모리 누수와 같은 결과)를 발생시킨다. 하나는 몇몇 다른 클래스로 threadlocal를 포장하는 것을 언제나 검토해야만 하고 ThreadLocal자체(물론 래퍼클래스를 제외하고)를 결코 직접적으로 사용하지 말라. 또한 하나는 쓰레드를 위한 로컬 자원을 정확하게 셋팅하고 셋팅하지 않는 것을(후차는 ThreadLocal.set(null)에 대한 호출을 간단하게 포함한다.) 언제나 기억해야만 한다. 셋팅하지 않는것은 문제가 되는 행위를 야기하는 셋팅을 하기 때문에 이 경우 수행될수 있다. Spring의 ThreadLocal지원은 당신을 위해 이것을 하고 다른 임의의 핸들링 코드없이 ThreadLocals를 사용하여 검토되어야만 한다.

5.12. 새로운 Advice 타입을 정의하기

Spring AOP는 확장가능하기 위해 디자인되었다. 인터셉션 구현물 전략이 내부적으로 사용되는 동안 특별히 지원되는 임의의 advice타입에 추가적으로 인터셉션 around advice, before, throw advice그리고 after returning advice를 지원하는 것이 가능하다.

org.springframework.aop.framework.adapter패키지는 핵심 프레임워크 변경없이 추가되기 위한 사용자 지정 advice타입을 위한 지원을 허용하는 SPI패키지이다. 사용자 지정 advice타입의 제한은 org.aopalliance.aop.Advice태그 인터페이스를 구현해야만 한다는 것이다.

더 많은 정보를 위해서 org.springframework.aop.framework.adapter패키지의 JavaDoc를 참조하라.

5.13. 추가적으로 읽을거리와 자원들

나는 AOP에 대한 소개를 위해 Ramnivas Laddad (Manning, 2003)에 의해 쓰여진 훌륭한 AspectJ in Action을 추천한다.

Spring AOP의 좀더 다양한 예제를 위해 Spring샘플 애플리케이션을 참조하라.

  • JPetStore의 디폴트 설정은 선언적인 트랜잭션 관리를 위한 TransactionProxyFactoryBean의 사용을 설명한다.

  • JPetStore의 /attributes 디렉토리는 속성-지향 선언적인 트랜잭션 관리의 사용을 설명한다.

만약 당신이 Spring AOP의 좀더 향상된 기능에 관심이 있다면 테스트 슈트를 살펴보라. 테스트 적용범위는 90%이상이다. 그리고 이것은 이 문서에서 언급되지 않은 향상된 기능을 설명한다..

5.14. 로드맵

Spring의 나머지와 같은 Spring AOP는 활발히 개발되고 있다. 핵심 API는 안정적이다. Spring의 나머지처럼 AOP프레임워크는 기초적인 디자인을 보존하는 동안 확장을 가능하게 하는 매우 모듈적이다. 다양한 향상은 이전버전과의 호환성을 보존하는 Spring 1.1에 계획되어 있다. 그것들은 다음을 포함한다.

  • 성능향상: AOP프록시의 생성은 Strategy인터페이스를 통해 factory에 의해 다루어진다. 게다가 우리는 사용자 코드나 핵심 구현에 영향없이 추가적인 AopProxy타입을 지원할수 있다. CGLIB를 위한 명백한 성능 최적화는 1.0.3버전에 계획되어 있다. 이 경우 Spring 1.1의 최적화는 advice가 수행시 변경되지 않을것이다. 이것은 AOP프레임워크의 오버헤드를 명백하게 감소시킨다. 어쨌든 AOP프레임워크의 오버헤드는 일반적인 사용법에서는 문제가 되지 않는다.

  • 좀더 의미있는 pointcut: 현재 Spring은 의미있는 Pointcut인터페이스를 제공한다. 하지만 우리는 좀더 pointcut구현물을 추가함으로써 값을 추가할수 있다. 우리는 Spring 설정파일내에서 사용되기 위한 AspectJ pointcut표현을 허용할 AspectJ와의 통합을 본다. 그리고 만약 당신이 유용한 pointcut에 기여하길 바란다면 그렇게 해달라.

대부분의 명백한 향상은 AspectJ커뮤니티와 상호작용할 AspectJ와의 관련 통합일것이다. 우리는 이것이 다음과 같은 영역에서 Spring과 AspectJ둘다를 위한 명백한 이득을 제공할 것이라는것을 믿는다.

  • Spring IoC를 사용하여 설정되기 위한 AspectJ aspect허용하기. 이것은 선호하는 애플리케이션으로 AspectJ aspect를 Spring aspect가 애플리케이션 IoC컨텍스트와의 통합과 같은 방법으로 통합하는것의 가능성을 가진다.

  • AspectJ pointcut표현을 대상 Spring advice를 위한 Spring설정으로 허용하기. 이것은 우리 자신의 pointcut표현언어를 디자인하는데 명백한 이득을 가진다. AspectJ는 둘다 잘되고 문서화가 잘되어 있다.

이러한 통합 둘다 Spring 1.1에서 반드시 사용가능하게 될것이다.

Chapter 6. AspectJ 통합

6.1. 개요

Spring의 프록시 기반 AOP프레임워크는 많은 일반적인 미들웨어와 애플리케이션 특유의 문제를 다루는데 매우 적합하다. 어쨌든 좀더 강력한 AOP솔루션이 필요한 때(예를 들면 우리가 클래스에 추가적인 필드를 추가해야 할 필요가 있다거나 Spring IoC컨테이너에 의해 생성되지 않은 잘 정제된 객체를 알리는(advice) 것과 같은)가 자주 있다.

우리는 이러한 경우 AspectJ의 사용을 권한다. 따라서 1.1버전에서 Spring은 AspectJ와의 강력한 통합을 제공한다.

6.2. Spring IoC를 사용하여 AspectJ 설정하기.

Spring/AspectJ 통합의 가장 중요한 부분은 DI를 사용하여 Spring이 AspectJ를 설정하도록 하는것이다. 이것은 객체를 위해 aspect을 사용하는 것과 유사한 이득을 가져온다.

  • aspect를 위해 특별한 목적의 설정 기법을 사용할 필요가 없다. 그들은 같고, 일관적이고, 전체 애플리케이션을 위해 사용되는 접근법으로 설정될수 있다.

  • aspect는 애플리케이션 객체의 의존할수 있다. 예를 들면 보안 aspect는 짧은 예제에서 볼수 있는 것처럼 보안 관리자에게 의존할수 있다.

  • 이것은 관련 Spring컨텍스트를 통해 aspect에 대한 참조를 얻는것이 가능하다. 이것은 aspect의 동적 재설정을 위해 가능할수 있다.

AspectJ aspect는 setter삽입(Injection)을 위해 자바빈즈 프라퍼티를 드러낼수 있다. 그리고 BeanFactoryAware와 같은 Spring생명주기 인터페이스를 구현할수 있다.

AspectJ aspect는 생성자 삽입이나 메소드 삽입을 사용할수 없다는 것에 주의하라. 이 제한은 aspect가 객체의 생성자처럼 호출될수 있는 생성자를 가지지 않기 때문에 발생한다.

6.2.1. "싱글톤" aspects

대부분의 경우 AspectJ aspect는 클래스 로더당 하나의 인스턴스를 가지는 싱글톤이다. 이 하나의 인스턴스는 다중 객체 인스턴스를 알릴 책임을 가진다.

Spring IoC컨테이너는 aspect가 호출가능한 생성자를 가지지 않기 때문에 aspect를 인스턴스화 할수 없다. 하지만 이것은 AspectJ가 모든 aspect를 위해 명시하는 정적인 aspectOf()메소드를 사용하여 aspect에 대한 참조를 얻을수 있고 aspect로 의존성을 삽입할수 있다.

6.2.1.1. 예제

보안 관리자에 의존적인 보안 aspect를 고려해볼때 이 aspect는 Account클래스내 balance인스턴스 변수의 값의 모든 변경에 적용한다. (우리는 Spring AOP를 사용해서는 같은 방법으로 이것을 할수 없다.)

aspect를 위한 AspectJ코드(Spring/AspectJ 샘플중에 하나인)가 아래에서 보인다. SecurityManager인터페이스의 의존성은 자바빈 프라퍼티내에서 표현된다.

public aspect BalanceChangeSecurityAspect {

   private SecurityManager securityManager;

   public void setSecurityManager(SecurityManager securityManager) {
      this.securityManager = securityManager;
   }

   private pointcut balanceChanged() : 
      set(int Account.balance);

   before() : balanceChanged() {
      this.securityManager.checkAuthorizedToModify();
   }
}

우리는 보통의 클래스처럼 같은 방법으로 이 aspect를 설정한다. 우리가 프라퍼티 참조를 셋팅하는 방법은 동일하다는 것을 알라. 우리는 aspectOf() 정적 메소드를 사용해서 생성된 aspect를 원한다는 것을 명시하기 위해 factory-method속성을 사용해야만 한다. 사실 이것은 생성보다는 위치 선정(locating)이다. 하지만 Spring컨테이너는 관리하지 않는다.

<bean id="securityAspect" 
   class="org.springframework.samples.aspectj.bank.BalanceChangeSecurityAspect" 
   factory-method="aspectOf"
>
   <property name="securityManager">
      <ref local="securityManager"/>
   </property>
</bean>

우리는 이 aspect를 목표로 하기 위해 Spring설정내 어떤것도 할 필요가 없다. 이것은 적용할 곳에서 제어할 AspectJ코드내 포인트컷(pointcut)정보를 포함한다. 게다가 이것은 Spring IoC컨테이너에 의해 관리되지 않는 객체에도 적용가능하다.

6.2.1.2. 정렬 이슈

완성되기 위해

6.2.2. 싱글톤 형식이 아닌 aspect

** Complete material on pertarget etc.

6.2.3. Gotchas

완성되기 위해

- 싱글톤 이슈

6.3. 목표 Spring advice를 위한 AspectJ 포인트컷(pointcut) 사용하기

Spring의 차후 발표될 릴리즈에서 우리는 Spring XML이나 다른 빈 정의 파일내에서 목표 Spring advice를 위해 사용되기 위한 AspectJ 포인트컷 표현을 위한 기능을 제공할 계획중이다. 이것은 Spring의 프록시 기반의 AOP프레임워크에 적용되기 위한 AspectJ 포인트컷 모델의 몇가지 힘을 허락할것이다.이것은 순수한 자바에서도 작동하고 AspectJ컴파일러를 요구하지 않을것이다. 오직 AspectJ 포인트컷의 부분 집합이 사용가능한 메소드 수행에 관련된다.

이 기능은 Spring을 위해 포인트컷 표현 언어를 생성하는 이전의 계획을 대신한다.

6.4. AspectJ를 위한 Spring aspect

Spring의 차후 발표될 릴리즈에서 우리는 AspectJ aspect와 처럼 선언적인 트랜잭션 관리 서비스와 같은 몇몇 Spring서비스들을 패키징 할것이다. 이것은 Spring AOP 프레임워크의 의존성또는 어쩌면 Spring IoC컨테이너의 의존성없이 AspectJ사용자에 의해 사용될 그것들을 가능하게 한다.

이 기능은 아마도 Spring사용자 보다 AspectJ사용자에게 좀더 흥미로운 일일것이다.

Chapter 7. 트랜잭션 관리

7.1. Spring 트랜잭션 추상화

Spring은 트랜잭션 관리를 위한 일관된 추상화를 제공한다. 이 추상화는 Spring의 추상화들 중 가장 중요한 것 중 하나이며 다음과 같은 장점들을 가져다준다.

  • JTA, JDBC, Hibernate, iBATIS 데이터베이스 계층과 JDO와 같은 서로 다른 트랜잭션 API를 포괄하는 일관된 프로그래밍 모델을 제공한다.

  • 이러한 대부분의 트랜잭션 API들이 제공해주는 것보다 보다 간단하고 사용하기 쉬운 프로그래밍적인 트랜잭션 관리 API를 제공한다.

  • Spring의 데이터 접근 추상화와 통합된다.

  • Spring의 선언적 트랜잭션 관리를 지원한다.

전통적으로, J2EE 개발자들은 트랜잭션 관리에 있어 두 가지 선택사항들을 가지는데 글로벌 혹은 로컬 트랜잭션을 사용하는 것이다. 글로벌 트랜잭션은 JTA를 사용하여 어플리케이션 서버에 의해 관리된다. 로컬 트랜잭션은, 예를 들어 JDBC 커넥션과 연관된 트랜잭션처럼, 리소스 특성을 따른다. 이 선택은 심오한 의미를 가진다. 글로벌 트랜잭션은 다중 트랜잭션 리소스들을 가지고 동작할 수 있게 해준다. (이것은 대부분의 어플리케이션들이 하나의 트랜잭션 리소스를 사용하기 때문에 그다지 유용하지 않다.) 로컬 트랜잭션을 사용한다면, 어플리케이션 서버는 트랜잭션 관리에 관여하지 않으며, 다중 리소스에 걸쳐 정확함을 보증해주지 않는다.

글로벌 트랜잭션은 중요한 약점을 가진다. 사용하기에 번거로운(부분적으로 예외 모델의 탓인) API인 JTA를 사용해야만 한다는 것이다. 더군다나, JTA UserTransaction은 일반적으로 JNDI를 통해 얻어야만 한다. 이것은 우리가 JTA를 사용하기 위해서는 JNDI와 JTA 모두 사용해야만 한다는 것을 의미한다. 명백하게 글로벌 트랜잭션을 사용하는 것은 JTA가 일반적으로 오로지 어플리케이션 서버 환경에서만 가능하기 때문에, 어플리케이션 코드의 재사용성을 제약할 것이다.

글로벌 트랜잭션을 사용할 때 선호되는 방법은, 선언적 트랜잭션 관리 형태 (왜냐하면 이것은 프로그래밍적인 트랜잭션 관리와는 구별되기 때문이다.)인 EJBCMT(Container Managed Transaction)를 경유하는 방법이다. EJB CMT는 비록 EJB 자신이 JNDI의 사용을 필요로 함에도 불구하고, 트랜잭션과 연관된 JNDI 룩업의 필요성을 제거해준다. 이것은 또한 트랜잭션을 컨트롤하기 위한 자바 코드를 작성할 필요성 역시 대부분--전부는 아니지만-- 없애준다. 중요한 약점은 CMT는 (명백히) JTA와 어플리케이션 서버에 묶여 있다는 것이다; 그리고 이것은 우리가 비지니스 로직을 EJB 혹은 최소한 트랜잭션적인 EJB 퍼싸드의 뒤에서 구현하는 경우에만 가능하다. EJB를 둘러싼 부정적인 측면은 일반적으로 매우 크기 때문에 선언적 트랜잭션 관리에 대한 대안이 있을 때에는 그다지 매력적인 제안은 되지 못한다.

로컬 트랜잭션은 훨씬 더 사용하기 쉽지만, 중요한 단점 역시 가지고 있다. 이것은 다중 트랜잭션 리소스에 걸쳐서 사용할 수 없으며 프로그래밍 모델을 침범하는 경향이 있다. 예를 들어, JDBC 커넥션을 사용하여 트랜잭션을 관리하는 코드는 글로벌 JTA 트랜잭션 내에서는 동작하지 못한다.

Spring은 이러한 문제점들을 해결해준다. Spring은 어플리케이션 개발자들로 하여금 어떠한 환경에서라도 일관적인 프로그래밍 모델을 사용할 수 있게 해준다. 당신이 코드를 한 번 작성하면 그것은 다른 환경에서의 다른 트랜잭션 관리 전략에서도 (잘 작동함으로써) 이익을 가져다 줄 것이다. Spring은 선언적/프로그래밍적 트랜잭션 관리방법 모두를 지원한다. 선언적 트랜잭션 관리는 대부분의 사용자들에게 선호되며 대부분의 경우 추천되는 방법이다.

프로그래밍적인 트랜잭션 관리를 하는 개발자들은 어떠한 기반 트랜잭션 하부구조와도 동작할 수 있는 Spring 트랜잭션 추상화로 개발한다. 선호되는 선언적 모델 개발자들은 전형적으로 트랜잭션 관리와 관련된 코딩을 매우 적거나 혹은 아예 하지 않는다. 그리고 Spring 혹은 어떤 다른 트랜잭션 API에 의존하지 않는다.

7.2. 트랜잭션 전략

Spring 트랜잭션 추상화의 핵심은 transaction strategy에 대한 개념이다.

아래의 코드는 org.springframework.transaction.PlatformTransactionManager 인터페이스에서 캡쳐해온 것이다.

public interface PlatformTransactionManager {

    TransactionStatus getTransaction(TransactionDefinition definition)
        throws TransactionException;

    void commit(TransactionStatus status) throws TransactionException;

    void rollback(TransactionStatus status) throws TransactionException;
}

비록 이것이 프로그래밍적으로 사용될 수 있다고는 하지만, 근본적으로는 SPI 인터페이스이다. (역자주 : SPI 인터페이스는 하드웨어쪽 용어로 '두 개의 주변장치간에 직렬 통신으로 데이터를 교환할 수 있게 해주는 직렬 인터페이스(serial peripheral interface)'라는 의미인데, DB와 어플리케이션의 트랜잭션을 연결해주는 인터페이스라는 의미로 사용된 것으로 생각된다.) Spring의 철학처럼 이것은 인터페이스라는 데 주목하라. 따라서, 이것은 필요하다면 쉽게 mock되거나 stub될 수 있다.. 더군다나 이것은 JNDI처럼 룩업 전략에 묶이지도 않는다 : PlatformTransactionManager의 구현은 Spring IoC 컨테이너에서의 다른 객체들처럼 동일하게 정의된다. 이러한 장점은 심지어 JTA로 작업조차도 구현할 보람이 있는 추상화로 만들어 준다는 것이다 : 트랜잭션 코드는 직접 JTA를 사용하는 것보다 훨씬 더 쉽게 테스트될 수 있다.

Spring의 철학에서처럼, TransactionException unchecked 예외이다. 트랜잭션 하부구조의 실패는 대부분 늘 치명적이다. 어플리케이션 코드가 그것들로부터 복구될 수 있는 아주 드문 경우에는, 어플리케이션 개발자는 여전히 TransactionException을 캐치하고 핸들링하는 것을 선택할 수 있다.

getTransaction() 메써드는 TransactionDefinition 파라미터에 따라 TransactionStatus 객체를 반환한다. 반환된 TransactionStatus는 아마도 새롭게 생성되거나 (만약 현재의 call스택에 동일한 트랜잭션이 있다면) 존재하고 있는 트랜잭션을 의미할 것이다.

J2EE 트랜잭션 컨텍스트처럼 TransactionStatus는 실행 쓰레드와 연관되어 있다.

TransactionDefinition 인터페이스는 다음과 같이 명기하고 있다 :

  • 트랜잭션 고립성: 이 트랜잭션의 고립성의 등급은 다른 트랜잭션들의 작업으로부터 가진다. 예를 들어, 이 트랜잭션이 다른 트랜잭션들로부터 커밋되지 않은 쓰기작업 내용을 볼 수 있는가?

  • 트랜잭션 전달: 일반적으로 트랜잭션 영역 내에서 실행되는 모든 코드는 그 트랜잭션 내에서 실행될 것이다. 그러나, 만약 트랜잭션 컨텍스트가 이미 존재하는 상황에서 트랜잭션적인 메써드가 실행된다면 그 동작을 지정하는 몇가지 옵션들이 있는데, 예를 들어, (대부분의 경우) 현존하는 트랜잭션 내에서 단순히 실행되기 혹은 현존 트랜잭션을 중지하고 새로운 트랜잭션 생성하기 등이 그것이다. Spring은 EJB CMT로부터 익숙한 트랜잭션 전달 옵션을 제공한다.

  • 트랜잭션 타임아웃: 이 트랜잭션이 타임아웃(자동적으로 기반 트랜잭션 하부구조에 의해 롤백되는)되기까지의 시간

  • read-only 상태: read-only 트랜잭션은 어떠한 데이터도 수정하지 않는다. read-only 트랜잭션은 (Hibernate를 사용할 때와 같이) 몇몇 경우에서 유용한 최적화 방식이 될 수 있다.

이러한 세팅들은 기본적인 개념을 반영한다. 만약 필요하다면, 트랜잭션 고립성과 다른 핵심적인 트랜잭션 개념들에 대한 논의자료들을 참조하길 바란다. 그런 핵심 개념들을 이해하는 것은 Spring 혹은 다른 트랜잭션 관리 솔루션을 사용함에 있어서 필수적인 것이다.

TransactionStatus 인터페이스는 트랜잭션 실행과 쿼리 트랜잭션 상태를 제어하기 위한 트랜잭션 코드를 작성하기 위한 간단한 방법을 제공해준다. 모든 트랜잭션 API에 공통적인 것이기 때문에 기본적인 개념은 매우 친숙하게 느껴질 것이다.

public interface TransactionStatus {

    boolean isNewTransaction();

    void setRollbackOnly();

    boolean isRollbackOnly();
}

아무리 Spring 트랜잭션 관리를 사용한다고 해도 PlatformTransactionManager 인터페이스를 구현하는 것은 필수적이다. 훌륭한 Spring 형태에서는, 중요한 정의는 IoC를 사용하여 만들어진다.

PlatformTransactionManager의 구현은 일반적으로 작업환경이 JDBC인지, JTA인지, Hibernate인지 등에 대한 지식을 필요로 한다.

Spring jPetStore 샘플 어플리케이션의 dataAccessContext-local.xml에서 추출한 다음의 예제는 로컬 PlatformTransactionManager 구현이 정의되는 방법을 보여준다. 이것은 JDBC 환경에서 작동하는 것이다.

우리는 JDBC 데이터소스를 정의해야만 한다. 그리고나서 DataSource에 대한 참조를 넘겨줌으로써 Spring의 DataSourceTransactionManager를 사용할 것이다.

<bean id="dataSource" 
    class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName"><value>${jdbc.driverClassName}</value></property>
    <property name="url"><value>${jdbc.url}</value></property>
    <property name="username"><value>${jdbc.username}</value></property>
    <property name="password"><value>${jdbc.password}</value></property>
</bean>

PlatformTransactionManager 정의는 다음과 같을 것이다 :

<bean id="transactionManager" 
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource"><ref local="dataSource"/></property>
</bean>

동일한 샘플 어플리케이션에 있는 dataAccessContext-jta.xml 파일에서처럼, 만약 우리가 JTA를 사용한다면 JNDI를 경유해 얻어진 우리는 컨테이너 DataSource를 사용할 필요가 있으며, JtaTransactionManager를 구현해야 한다. JtaTransactionManager는 컨테이너의 글로벌 트랜잭션 관리를 사용할 것이기 때문에, DataSource 혹은 어떤 다른 리소스들에 대해 에 대해 알 필요가 없다.

<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName"><value>jdbc/jpetstore</value></property>
</bean>

<bean id="transactionManager" 
    class="org.springframework.transaction.jta.JtaTransactionManager"/>

우리는 Spring PetClinic 샘플 어플리케이션에서 가져온 다음의 예제에서처럼 Hibernate 로컬 트랜잭션을 쉽게 사용할 수 있다.

이 경우, 우리는 어플리케이션 코드가 Hibernate Session들을 가져오기 위해 사용할 Hibernate LocalSessionFactory를 정의할 필요가 있다.

DataSource 빈 정의는 위의 예제들과 비슷하지만 보여지지 않는다. (만약 이것이 컨테이너 DataSource라면, 이것은 Spring처럼 컨테이너보다 트랜잭션적이지 않을 것이다.)

이 경우 "transactionManager" 빈은 HibernateTransactionManager 클래스이다. DataSource에 대한 참조를 필요로 한 DataSourceTransactionManager에서와 비슷하게, HibernateTransactionManager는 세션 팩토리에 대한 참조를 필요로 한다.

<bean id="sessionFactory" class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
    <property name="dataSource"><ref local="dataSource"/></property>
    <property name="mappingResources">
        <value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
    </property>
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">${hibernate.dialect}</prop>
        </props>
    </property>
</bean>

<bean id="transactionManager" 
    class="org.springframework.orm.hibernate.HibernateTransactionManager">
    <property name="sessionFactory"><ref local="sessionFactory"/></property>
</bean>

Hibernate와 JTA 트랜잭션을 같이 사용하려면 우리는 JDBC 혹은 어떤 다른 리소스 전략들처럼 JtaTransactionManager를 그냥 사용하면 된다.

<bean id="transactionManager" 
    class="org.springframework.transaction.jta.JtaTransactionManager"/>

이것은 어떤 트랜잭션 리소스에 참여하는 글로벌 트랜잭션처럼, 어떤 리소스들에 대한 JTA 설정과 동일하다는 점을 명심하라.

이런 모든 경우에서, 어플리케이션 코드는 아무것도 변경될 필요가 없을 것이다. 우리는 로컬에서 글로벌 트랜잭션으로 혹은 그 반대의 경우 역시 단지 설정을 바꾸는 것만으로 트랜잭션이 관리되는 방법을 변경할 수 있다.

글로벌 트랜잭션을 사용하지 않을 때에는 하나의 특별한 코딩 규칙을 따를 필요가 있다. 운좋게도 이것은 매우 간단하다. 커넥션 사용을 끌어오고 필요에 따라 트랜잭션 관리를 적용하기 위해 적절한 PlatformTransactionManager 구현을 허용하는 특별한 방법으로 커넥션 혹은 세션 리소스를 획득할 필요가 있다.

예를 들어, JDBC를 사용할 경우, 당신은 DataSource의 getConnection() 메써드를 호출해서는 안되고, 다음의 예에서 보여지는 것처럼 Spring의 org.springframework.jdbc.datasource.DataSourceUtils 클래스를 사용해야만 한다.

Connection conn = DataSourceUtils.getConnection(dataSource);

이것은 어떤 SQLException도 Spring의 (Spring의 체크되지 않은 DataAccessExceptions 하위 클래스의 하나인) CannotGetJdbcConnectionException으로 싸여진다는 부가적인 이점을 가진다. 이러한 점은 당신이 SQLException으로부터 얻을 수 있는 것보다 더 많은 정보를 줄 것이며, 다른 데이터베이스들, 심지어 다른 영속전략들에 걸쳐 이식성을 보장해 줄 것이다.

이것은 Spring 트랜잭션 관리가 없이도 잘 동작할 것인데, 그래서 Spring을 쓰건 쓰지 않건 이 방법을 사용할 수 있다.

물론, 한 번 Spring의 JDBC 지원 혹은 Hibernate 지원을 사용한다면, 당신은 DataSourceUtils 혹은 다른 헬퍼 클래스들을 사용하고 싶어하지 않을 것이다. 왜냐하면 관련된 API를 가지고 직접 작업하는 것보다 Spring 추상화를 통해 작업하는 것이 훨씬 행복할 것이기 때문이다. 예를 들어, 만약 JDBC 사용을 간단하게 하기 위해 Spring의 JdbcTemplate 혹은 jdbc.object 패키지를 사용한다면, 정확한 커넥션 복구가 보이지 않는 곳에서 이루어질 것이고, 당신은 어떤 특별한 코드를 추가할 필요가 없을 것이다.

7.3. 프로그래밍적인 트랜잭션 관리

Spring 프로그래밍적인 트랜잭션 관리에 있어 두 가지 방법을 제시한다.

  • TransactionTemplate의 사용

  • 직접 PlatformTransactionManager 구현

우리는 일반적으로 전자의 접근방법을 추천한다.

두 번째 접근방법은 (비록 예외처리는 덜 성가시지만) JTA UserTransaction API의 사용과 비슷하다.

7.3.1. TransactionTemplate 사용하기

TransactionTemplateJdbcTemplateHibernateTemplate와 같은 다른 Spring templates와 동일한 접근 방식을 적용하고 있다. 이것은 콜백(callback) 접근방법을 사용하는데, 리소스 획득과 해제작업으로부터 어플리케이션 코드를 해방시켜준다.(더이상 try/catch/finally를 할 필요가 없다.) 다른 templates처럼, TransactionTemplate는 쓰레드 안전하다.

트랜잭션 컨텍스트 내에서 실행되어야 하는 어플리케이션 코드는 다음과 같을 것이다. TransactionCallback이 값을 반환하기 위해 사용되는 부분에 주목하라.

Object result = tt.execute(new TransactionCallback() {
    public Object doInTransaction(TransactionStatus status) {
        updateOperation1();
        return resultOfUpdateOperation2();
    }
});

만약 반환될 값이 없다면, 다음과 같이 TransactionCallbackWithoutResult를 사용하라.

tt.execute(new TransactionCallbackWithoutResult() {
    protected void doInTransactionWithoutResult(TransactionStatus status) {
        updateOperation1();
        updateOperation2();
    }
});

콜백 내의 코드는 TransactionStatus 객체의 setRollbackOnly() 메써드를 호출함으로써 트랜잭션을 롤백할 수 있다.

TransactionTemplate를 사용하려는 어플리케이션 클래스들은 반드시 PlatformTransactionManager를 통해야 한다. 항상 자바빈 프라퍼티나 생성자 인자처럼 노출된다.

mock 혹은 stub PlatformTransactionManager를 가진 그런 클래스들을 유닛 테스트 하기는 쉬운 일이다. 여기에는 JNDI 룩업 혹은 정적인 마법이 존재하지 않는다 : 이것은 단순한 인터페이스이다. 대개, 당신은 유닛 테스트를 간단하게 만들기 위해 Spring을 사용할 수 있다.

7.3.2. PlatformTransactionManager 사용하기

당신은 트랜잭션을 직접 관리하기 위해 org.springframework.transaction.PlatformTransactionManager도 역시 사용할 수 있다. 단순히 사용하고 있는 PlatformTransactionManager의 구현 클래스의 빈 참조를 당신의 빈에 넘기기만 하면 된다. 그리고나서, TransactionDefinitionTransactionStatus 객체를 사용함으로써, 당신은 트랜잭션을 초기화하고, 롤백, 커밋할 수 있다.

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

TransactionStatus status = transactionManager.getTransaction(def);

try {
    // execute your business logic here
} catch (MyException ex) {
    transactionManager.rollback(status);
    throw ex;
}
transactionManager.commit(status);

7.4. 선언적 트랜잭션 관리

Spring은 또한 선언적 트랜잭션 관리를 제공한다. 이것은 Spring AOP에 의해 가능하다.

대부분의 Spring 사용자들은 선언적 트랜잭션 관리를 선택한다. 이것은 어플리케이션 코드에 대한 최소한의 충격을 주는 선택이다. 그리고, 이것은 (어플리케이션 코드에)침입하지 않는 경량 컨테이너의 이상에 일관된다.

EJB CMT를 검토하고 Spring 선언적 트랜잭션 관리와의 유사점과 차이점을 설명함으로써 시작에 도움을 줄 수 있을 것이다. 기본적인 접근방법은 비슷하다 : 개별적인 메쏘드들에 대한 트랜잭션의 행동을 지정함으로써 가능하다. 그리고 만약 필요하다면, 트랜잭션 컨텍스트 내의 setRollbackOnly() 호출을 만드는 것 역시 가능하다. 하지만 차이점은 다음과 같다:

  • JTA에 묶여 있는 EJB CMT와 다르게, Spring의 선언적 트랜잭션 관리는 어떠한 환경에서도 동작한다. 이것은 JDBC, JDO, Hibernate 혹은 내부의 어떠한 다른 트랜잭션과도 동작할 수 있으며, 단지 설정의 변경만으로 가능하다.

  • Spring은 EJB와 같은 특별한 클래스들 뿐만 아니라, 어떠한 POJO에 대해서도 선언적 트랜잭션 관리를 적용할 수 있게 해준다.

  • Spring은 선언적 롤백 규칙을 제공한다 : 아래에서 논의할 EJB와 동등하지 않는 특징인데, 롤백은 단지 프로그래밍적인 방법만이 아니라 선언적으로도 제어가능해진다.

  • Spring은 AOP를 사용해서 트랜잭션적인 행위들을 커스터마이징하기 위한 기회를 제공한다. 예를 들어, 만약 당신이 트랜잭션 롤백 상황에 임의의 행위를 삽입하고자 한다면, 할 수 있다. 또한 트랜잭션적인 통보와 함께 부가적인 통보를 추가할 수도 있다. EJB CMT에서는 당신은 setRollbackOnly() 이상으로 컨테이너의 트랜잭션 관리에 영향을 끼칠 수 있는 방법이 없다.

  • Spring은 높은 성능의 어플리케이션 서버들처럼, 원격 호출에 걸쳐 트랜잭션 컨텍스트의 전달을 지원하지는 않는다. 만약 당신이 이러한 특성이 필요하다면, EJB를 사용하라고 권해주고 싶다. 그러나, 이러한 특성은 거의 사용되지 않는다. 일반적으로 우리는 원격 호출을 확장하기 위해 트랜잭션을 원하지는 않는다.

롤백 규칙의 개념은 중요하다: 이 규칙은 우리가 어떤 예외상황일 때 자동 롤백이 발생되어야 하는지를 지정할 수 있게 해준다. 우리는 이것을 자바 코드가 아니라 설정파일에 선언적으로 지정한다. 그래서 우리가 프로그래밍적으로 현재 트랜잭션을 롤백하기 위해 여전히 TransactionStatus 객체의 setRollbackOnly() 를 호출할 수 있을지라도, 대부분 MyApplicationException이 항상 롤백을 발생시키도록 규칙을 지정할 수 있다. 이것은 비지니스 오브젝트들은 트랜잭션 하부구조에 의지할 필요가 없다는 중요한 장점을 가진다. 예를 들어, 그 클래스들은 어떠한 Spring API, 트랜잭션 혹은 다른 무엇도 import할 필요가 없다.

EJB의 (트랜잭션 롤백에 대한) 디폴트 행위는 시스템 예외 (대개 런타임 예외)시에 EJB 컨테이너가 트랜잭션 롤백을 자동으로 수행하지만, EJB CMT는 어플리케이션 예외(java.rmi.RemoteException를 제외한 체크된 예외)시 자동으로 트랜잭션 롤백을 하지 않는다. 선언적 트랜잭션 관리를 위한 Spring의 기본적인 동작은 EJB 규칙(체크되지 않은 예외에 대해서만 자동 롤백)을 따르지만, 이것을 종종 커스터마이징하기에 유용하다.

우리의 벤치마크에 따르면, Spring 선언적 트랜잭션 관리의 성능은 EJB CMP의 것을 앞서고 있다.

Spring에서 트랜잭션적인 프록시를 세팅하는데 사용되는 일반적인 방법은 TransactionProxyFactoryBean를 통한 것이다. 우리는 트랜잭션 프록시로 감쌀 타겟 객체가 필요하다. 타겟 객체는 일반적으로 POJO 빈 정의를 따른다. TransactionProxyFactoryBean을 정의할 때, 관련된 PlatformTransactionManager에 대한 참조와 트랜잭션 속성을 넘겨주어야 한다. 트랜잭션 속성은 위에서 얘기한 트랜잭션 정의를 포함한다. 다음의 예시를 보자.

<!-- this example is in verbose form, see note later about concise
     for multiple proxies! -->
<!-- the target bean to wrap transactionally -->
<bean id="petStoreTarget">
  ...
</bean>
<bean id="petStore"
    class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <property name="transactionManager"><ref bean="transactionManager"/></property>
    <property name="target"><ref bean="petStoreTarget"/></property>
    <property name="transactionAttributes">
        <props>
            <prop key="insert*">PROPAGATION_REQUIRED,-MyCheckedException</prop>
            <prop key="update*">PROPAGATION_REQUIRED</prop>
            <prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
        </props>
    </property>
</bean>

트랜잭션 프록시는 타겟의 인터페이스를 구현한다. 위 예시의 경우, petStoreTarget이라는 아이디를 가진 빈이다. (CGLIB를 사용하여 타겟 객체에 트랜잭션 프록시를 구현하는 것이 가능하다. 이것을 위해 proxyTargetClass 프라퍼티를 true로 세팅하라. 만약 타겟 객체가 어떠한 인터페이스도 구현하고 있지 않다면 자동으로 발생할 것이다. 물론, 일반적으로 우리는 클래스보다는 인터페이스를 프로그래밍할 것을 원한다.) proxyInterfaces 프라퍼티를 사용하여 단지 특정한 타겟 인터페이스만 프록시하도록 트랜잭션 프록시를 제한하는 것은 가능하다. (그리고 대개의 경우 훌륭한 생각이다.) 그리고 또한 org.springframework.aop.framework.ProxyConfig로부터 상속받은 몇가지 프라퍼티들을 통해 TransactionProxyFactoryBean의 행위를 커스터마이징하고 모든 AOP 프록시 팩토리들과 공유하는 것 역시 가능하다.

여기에서의 transactionAttributes는 org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource 클래스에 정의된 프라퍼티 포맷을 사용하여 세팅된다. 와일드카드를 포함한 메써드명의 매핑은 매우 직관적이다. insert* 매핑의 값이 롤백 규칙을 포함하고 있다는 것을 눈여겨 보아라. 여기에서의 -MyCheckedException는 만약 메써드가 MyCheckedException 혹은 그 하위 예외 클래스를 던진다면, 트랜잭션은 자동으로 롤백될 것이다. 다중 롤백 규칙 역시 콤마 구별자로 여기에 지정될 수 있다. - 접두사는 롤백을 수행하고, + 접두사는 커밋을 지정한다. (+ 옵션을 주는 것은 체크되지 않은 예외시에조차 커밋을 허용한다. 당신이 무엇을 하고 있는지 확실히 알아야만 한다!)

TransactionProxyFactoryBean은 추가적으로 끼여드는 행위를 위해, "preInterceptors", "postInterceptors" 프라퍼티를 사용하여 "pre" 혹은 "post" advice를 세팅하게 해준다. pre, post advice는 얼마든지 세팅될 수 있고, 그 타입은 Advisor (이 경우 그것은 pointcut을 포함할 수 있다.) 일 것이다. MethodInterceptor 혹은 어떤 advice 타입도 현재의 Spring 설정 (ThrowsAdvice, AfterReturningtAdvice 혹은 BeforeAdvice 등이 기본적으로 지원된다.)에 의해 지원된다. 이러한 advice들은 반드시 공유-인스턴스 모델을 지원해야 한다. 만약 당신이 상태유지 믹스인 (stateful mixins)처럼 발전된 AOP 특성들을 가진 트랜잭션적인 프록싱을 필요로 한다면, 일반적으로 편리한 프록시 생성자인 TransactionProxyFactoryBean보다는 포괄적인 org.springframework.aop.framework.ProxyFactoryBean를 사용하는 것이 최선일 것이다.

오토프록싱을 세팅하는 것 역시 가능한데, AOP 프레임워크를 세팅함으로써 클래스들은 자동으로 개별 프록시 정의 없이도 프록시될 수 있다.

더 많은 정보와 예제들은 AOP 챕터를 참조하길 바란다.

노트: TransactionProxyFactoryBean 정의를 위에서와 같은 형태로 사용하는 것은 많은 동일한 트랜잭션 프록시들이 생성될 필요가 있을 때 과도하게 장황해보일 수 있다. Section 5.7, “간결한 프록시 정의”에 서술된 바처럼, 당신은 트랜잭션 프록시 정의의 장황함을 상당부분 제거하기 위해, 내부 빈 정의의 장점을 가진 부모/자식 빈 정의의 이점을 가지기를 바랄 것이다.

Spring의 선언적 트랜잭션 관리를 효과적으로 사용하기 위해서, 당신은 AOP 전문가가 될 필요가 없다--혹은 최소한, AOP의 대부분에 대해 알 필요도 없다. 하지만, 당신이 정말로 Spring AOP의 "끝내주는 사용자"가 되고자 한다면, 당신은 강력한 AOP의 능력을 가진 선언적 트랜잭션 관리를 조합하는 것이 간단한 일임을 알 수 있을 것이다.

7.4.1. BeanNameAutoProxyCreator, 또 다른 선언적 접근방법

TransactionProxyFactoryBean은 매우 유용하고, 트랜잭션 프록시로 객체들을 감쌀 때 전체적인 제어를 할 수 있게 해준다. 부모/자식 빈 정의와 타겟을 가지고 있는 내부 빈들을 함께 사용하는 것은 일반적으로 트랜잭션적인 감싸기를 위한 최고의 방법이다. 당신이 완전히 동일한 형태로 많은 수의 빈들을 감쌀 필요가 있을 경우(예를 들어, 반복 사용 어구, '모든 메쏘드들을 트랜잭션하게 만들어라'), BeanNameAutoProxyCreator라고 불리는 BeanFactoryPostProcessor를 사용하는 것은 덜 장황하고 간단한 대안적인 접근방법이 될 것이다.

재감싸기를 위해서, ApplicationContext이 그것의 초기화 정보를 읽을 때, 그 안에 있는 BeanPostProcessor 인터페이스를 구현하는 모든 빈들을 초기화하고, 그 빈들에게 ApplicationContext 내의 모든 다른 빈들을 이후에 처리할(post-process) 기회를 제공한다. 때문에 이러한 메카니즘을 사용하면, 적절하게 설정된 BeanNameAutoProxyCreator는 ApplicationContext 내의 모든 다른 빈들을 (이름으로 그것들을 인식하여) 이후에 처리하기 위해 사용될 수 있다. 생성된 실제 트랜잭션 프록시는 TransactionProxyFactoryBean를 사용하여 생성되었다는 점에서 반드시 동일한데, 여기에 대해서는 이후에도 논의되지는 않을 것이다.

아래의 설정 예시를 보도록 하자.

  <!-- Transaction Interceptor set up to do PROPAGATION_REQUIRED on all methods -->
  <bean id="matchAllWithPropReq" 
      class="org.springframework.transaction.interceptor.MatchAlwaysTransactionAttributeSource">
    <property name="transactionAttribute"><value>PROPAGATION_REQUIRED</value></property>
  </bean>
  <bean id="matchAllTxInterceptor" 
      class="org.springframework.transaction.interceptor.TransactionInterceptor">
    <property name="transactionManager"><ref bean="transactionManager"/></property>
    <property name="transactionAttributeSource"><ref bean="matchAllWithPropReq"/></property>
  </bean>

  <!-- One BeanNameAutoProxyCreator handles all beans where we want all methods to use 
       PROPAGATION_REQUIRED -->
  <bean id="autoProxyCreator" 
      class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    <property name="interceptorNames">
      <list>
        <idref local="matchAllTxInterceptor"/>
        <idref bean="hibInterceptor"/>
      </list>
    </property>
    <property name="beanNames">
      <list>
        <idref local="core-services-applicationControllerSevice"/>
        <idref local="core-services-deviceService"/>
        <idref local="core-services-authenticationService"/>
        <idref local="core-services-packagingMessageHandler"/>
        <idref local="core-services-sendEmail"/>
        <idref local="core-services-userService"/>
      </list>
    </property>
  </bean>

ApplicationContext 내에 이미 TransactionManager 인스턴스를 가지고 있다고 가정한 상태로, 첫번째 우리가 해야 할 일은 사용할 TransactionInterceptor 인스턴스를 생성하는 일이다. TransactionInterceptor는 프라퍼티로 넘겨진 TransactionAttributeSource 구현 객체에 기반하여 어떤 메써드가 인터셉트되어야 하는지를 결정한다. 위의 경우, 우리는 모든 메써드를 일치시키는 매우 간단한 경우를 다루고자 한다. 이것은 물론 가장 효율적인 접근방법은 아니지만, 매우 빨리 세팅될 수 있다. 왜냐하면 우리는 특별히 이미 정의된, 모든 메써드를 단순히 일치시켜주는, MatchAlwaysTransactionAttributeSource를 사용할 수 있기 때문이다. 만약 우리가 좀 더 기술하려면, MethodMapTransactionAttributeSource, NameMatchTransactionAttributeSource 혹은 AttributesTransactionAttributeSource와 같이 다른 것을 사용할 수 있다.

이제 트랜잭션 인터셉터를 가지게 되었다면, 우리가 정의한 BeanNameAutoProxyCreator 인스턴스를 동일한 형태로 포장되길 바라는 ApplicationContext 안의 6개의 빈들의 이름과 함께, 인터셉터에 단순히 넘겨주기만 하면 된다. 당신이 알 수 있듯이, 최종결과는 6개의 빈들을 TransactionProxyFactoryBean으로 감싸는 것보다 매우 간소하다. 7번째의 빈을 감싸는 것 역시 설정에서 한 줄을 추가하기만 하면 된다.

당신은 우리가 다중 인터셉터를 적용할 수 있다는 점을 알아차렸을지 모른다. 이런 경우, 우리는 역시 우리가 이전에 (bean id=hibInterceptor)라고 정의했던 HibernateInterceptor 또한 적용할 수 있으며, 이것은 우리를 위해 Hibernate Sessions를 관리해줄 것이다.

TransactionProxyFactoryBeanBeanNameAutoProxyCreator의 사용 사이를 왔다갔다 할 때, 빈 명명에 관해 당신이 기억해두어야 할 것이 하나 있다. 전자를 위해서는, 만약 타겟빈이 내부 빈으로 정의되지 않았다면, 당신은 일반적으로 당신이 감싸고자 하는 타겟빈을 myServiceTarget와 비슷한 형태의 id로 넘겨주고, 프락시 객체는 myService라는 id로 넘겨줄 것이다. 그리고 감싸여진 객체의 사용하는 모든 사람들은 단지 프락시, 다시 말해, myService만을 참조할 것이다. (이것은 단지 명명규칙의 예시이다. 중요한 점은 목표객체는 프락시와 다른 이름을 가진다는 것이고, 둘 다 ApplicationContext으로부터 사용가능하다는 점이다.) 그러나, BeanNameAutoProxyCreator를 사용할 경우, 당신은 타겟 객체에다 myService 비슷한 이름을 부여한다. 그러면, BeanNameAutoProxyCreator가 타겟 객체를 전처리하고 프락시를 생성할 때, 어플리케이션 컨텍스트의 원래의 빈 이름 아래에 그 프락시를 위치시킨다. 그 지점에서 단지 프락시(감싸여진 객체)만이 ApplicationContext로부터 사용가능하다. 내부 빈으로 정의된 타겟을 가진 TransactionProxyFactoryBean를 사용할 경우, 명명 이슈는 신경쓸 바가 아니다. 왜냐하면, 내부 빈에는 일반적으로 이름이 주어지지 않기 때문이다.

7.5. 프로그래밍적/선언적 트랜잭션 관리 중 선택하기

프로그래밍적인 트랜잭션 관리는 대개 당신이 매우 적은 수의 트랜잭션적인 동작들을 다룰 때에만 좋은 생각이다. 예를 들어, 만약 당신이 어떤 업데이트 동작들에만 트랜잭션이 필요한 웹 어플리케이션을 개발한다면, 당신은 Spring 혹은 다른 기술들을 사용해서 트랜잭션적인 프락시를 세업하는 것을 바라지 않을지도 모른다. 이 경우, TransactionTemplate을 사용하는 것은 좋은 접근방법이 될 것이다.

반면, 만약 당신의 어플리케이션이 매우 많은 트랜잭션적인 동작들을 가진다면, 선언적 트랜잭션 관리는 대개 매우 가치있는 판단일 것이다. 이것은 비지니스 로직의 외부에서 트랜잭션 관리를 유지해주며, Spring 에서 그 설정을 하는 것은 어려운 것이 아니기 때문이다. EJB CMT보다 Spring을 사용한다면, 선언적 트랜잭션 관리 설정의 비용은 매우 감소된다.

7.6. 트랜잭션 관리를 위한 어플리케이션 서버가 필요한가?

Spring의 트랜잭션 관리 능력들--그리고 특히 그것의 선언적 트랜잭션 관리--은 J2EE 어플리케이션이 어플리케이션 서버를 필요로할 때 트랜잭션적인 판단을 현저하게 변화시킨다.

특히, 당신이 단지 EJB를 통해 선언적 트랜잭션을 얻고자 한다면 어플리케이션 서버는 필요가 없다. 사실, 강력한 JTA의 능력을 가진 어플리케이션 서버를 가지고 있다고 할지라도, 당신은 EJB CMT보다 더욱 강력하고 더더욱 생산적인 프로그래밍 모델을 제공해주는 Spring 선언적 트랜잭션을 선택하는 것이 좋을 지도 모른다.

오로지 당신이 다중 트랜잭션 리소스를 지원할 필요가 있을 때에만, 어플리케이션 서버의 JTA 능력이 필요하다. 많은 어플리케이션들은 이러한 요구에 직면하지 않는다. 예를 들어, 많은 고성능 어플리케이션들은 Oracle 9i RAC와 같이 크게 확장가능한 하나의 데이터베이스를 사용한다.

물론 당신은 JMS와 JCA와 같은 어플리케이션 서버의 또 다른 능력들을 필요로 할 지 모른다. 그러나, 당신이 만약 JTA만을 필요로 하는 것이라면, 당신은 JOTM과 같은 JTA가 첨부된 오픈 소스의 사용을 고려할 수 있다. (Spring은 JOTM과 외부에서 통합된다.) 그렇지만, 2004년 초반, 고성능 어플리케이션 서버들은 XA 트랜잭션들에 대한 보다 튼튼한 지원을 제공한다.

가장 중요한 점은 Spring을 사용하게 되면, 언제 당신의 어플리케이션을 최종적인 어플리케이션 서버로 확장시킬 것인가에 대한 시점을 선택할 수 있다. EJB CMT 혹은 JTA를 사용하는 것에 대한 대안이라고는 오로지 JDBC 커넥션과 같은 로컬 트랜잭션들을 사용해서 코딩했다가 그 코드가 글로벌 컨테이너 관리 트랜잭션에서 작동할 필요가 생겼을 때 엄청난 개정작업에 직면해야만 했던 시절은 갔다. Spring을 사용한다면 바꾸기 위해 필요한 것은 오로지 설정뿐이다. 당신의 코드는 바꿀 필요가 없다.

7.7. 공통적인 문제

개발자들은 그들의 요구사항들에 맞는 적절한 PlatformTransactionManager 구현을 사용하는데 주의를 기울여야 한다.

Spring 트랜잭션 추상화가 JTA 글로벌 트랜잭션과 동작하는 방식을 이해하는 것은 중요한 일이다. 적절하게 사용되었을 때, 여기엔 아무런 문제가 없다. Spring은 단지 간소화하고 이식가능한 추상화만을 제공한다.

만약 당신이 글로벌 트랜잭션을 사용한다면, 당신은 모든 트랜잭션적인 동작들에 대해 Spring의 org.springframework.transaction.jta.JtaTransactionManager반드시 사용해야만 한다. 만약 그렇지 않으면 Spring은 컨테이너 데이터소스들과 같은 리소스들에서 로컬 트랜잭션을 수행하고자 할 것이다. 그런 로컬트랜잭션들은 말이 안되며, 좋은 어플리케이션 서버라면 그것들을 에러로 간주할 것이다.

Chapter 8. 소스 레벨 메타데이타 지원

8.1. 소스-레벨 메타데이타

소스-레벨 메타데이타 프로그램 요소(대개 클래스 그리고/또는 메소드)에 속성(attributes) 이나 annotations을 추가한 것이다.

예를 들어, 우리는 다음처럼 클래스에 메타데이타를 추가할수 있다.

/**
 * Normal comments
 * @@org.springframework.transaction.interceptor.DefaultTransactionAttribute()
 */
public class PetStoreImpl implements PetStoreFacade, OrderService {

우리는 다음처럼 메소드에 데타데이타를 추가할수 있다.

/**
 * Normal comments
 * @@org.springframework.transaction.interceptor.RuleBasedTransactionAttribute()
 * @@org.springframework.transaction.interceptor.RollbackRuleAttribute(Exception.class)
 * @@org.springframework.transaction.interceptor.NoRollbackRuleAttribute("ServletException")
 */
public void echoException(Exception ex) throws Exception {
    ....
}

이러한 예제들 모두 Jakarta Commons Attributes 문법을 사용한다.

소스-레벨 메타데이타는 트랜잭션, 풀링 그리고 다른 행위를 제어하기 위한 소스레벨 속성을 사용하는 Microsoft's .NET platform에 의해 릴리즈되었고 자바진영에서는 XDoclet에 의해 가담되어 소개되었다.

이 접근법내 값은 J2EE커뮤니티내에서 인식되고 있다. 예를 들어 EJB에 의해 사용되는 전통적인 XML배치 서술자보다는 다소 덜 장황하다. 프로그램 소스코드로부터 어떤것을 구체화하는것이 바람직할 동안 몇몇 중요한 기업용 셋팅들-명백한 트랜잭션 특성-어쩌면 프로그램 소스내 포함된다. EJB스펙의 가정에 대해 반대로 이것은 좀처럼 메소드의 트랜잭션적인 특성을 변경할려고 시도하지 않는다.(비록 트랜젹션 타임아웃(timeout)과 같은 파라미터가 변경될지라도)

비록 메타데이타 속성들은 대개 요구하는 서비스 애플리케이션 클래스를 서술하는 프레임워크 구조에 의해 주로 사용된다. 이것은 수행시 쿼리되는 메타데이타 속성이 가능하다. 이것은 EJB가공물같은 코드를 생성하는 방법처럼 메타데이타를 보는 XDoclet같은 솔루션으로부터 키가 되는 차이이다.

여기엔 다음을 포함해서 많은 수의 솔루션이 있다.

  • JSR-175: 자바 1.5에서 사용가능한 표준적인 자바 메타데이타 구현물, 하지만 우리는 자바 1.4그리고 1.3을 위해서도 이 솔류션이 필요하다.

  • XDoclet: 잘 적립된 솔류션, 주로 코드 생성을 시도하려고 한다.

  • 자바 1.3 그리고 1.4를 위한 다양한 오픈소스 속성 구현물들, Commons Attribute의 것은 대부분 약속된것처럼 보인다. 모든것들은 특별한 pre-(선) 또는 post-(후) 컴파일 단계를 요구한다.

8.2. Spring의 메타데이타 지원

중요한 개념을 넘어선 추상화 조항을 유지하여 Spring은 org.springframework.metadata.Attributes 인터페이스의 형태로 메타데이타 구현물을 위한 외관(facade)을 제공한다.

외관은 다양한 이유를 위해 값을 추가한다.

  • 현재 표준적인 메타데이타 솔루션이 없다. 자바 1.5는 하나를 제공할것이지만 이것은 Spring 1.0의 것처럼 여전히 베타상태이다. 게다가 최소한 2년 동안은 1.3과 1.4애플리케이션내 메타데이타 지원이 필요할것이다. Spring은 지금 작동중인 솔류션을 제공하는것이 목적이다. 1.5를 기다리는것은 중요한 영역에서 선택사항이 아니다.

  • Commons Attributes(Spring 1.0에 의해 사용되는)과 같은 최근의 메타데이타 API는 테스트하기가 힘들다. Spring은 모조품을 위해 좀더 쉬운 간단한 메타데이타 인터페이스를 제공한다.

  • 자바 1.5가 언어레벨에서 메타데이타 지원을 제공할때 추상화와 같이 제공하는 값이 될것이다.

    • JSR-175 메타데이타는 정적이다. 이것은 컴파일 시각에 클래스와 관련된다. 그리고 배치된 환경에서 변경이 될수 없다. 예를 들어, XML파일내에서 구조적인 메타데이타가 필요하고 배치내에서 어떤 속성값을 오버라이드하는 능력을 제공한다.

    • JSR-175 메타데이타는 자바 reflection API를 통해 반환된다. 이것은 테스트 시간동안 모방이 불가능하다. Spring은 이것을 허용하기 위한 간단한 인터페이스를 제공한다.

Spring Attributes 인터페이스는 이것처럼 보인다.

public interface Attributes {

    Collection getAttributes(Class targetClass);

    Collection getAttributes(Class targetClass, Class filter);

    Collection getAttributes(Method targetMethod);

    Collection getAttributes(Method targetMethod, Class filter);

    Collection getAttributes(Field targetField);

    Collection getAttributes(Field targetField, Class filter);
}

이것은 가장 낮은 공통적인 공통점(denominator) 인터페이스이다. JSR-175는 메소드 인자의 속성처럼 이것보다 좀더 많은 기능을 제공한다. Spring 1.0에서처럼 Spring은 자바 1.3이상에서 EJB나 .NET의 선언적인 기업용 서비스를 효과적으로 제공하기 위해 요구되는 메타데이타의 부분세트(subset)를 제공하는것이 목적이다. Spring 1.2에서 유사한 JSR-175 annotation은 Commons Attribute의 직접적인 대안처럼 JDK1.5에서 지원된다.

이 인터페이스는 .NET처럼 Object 속성을 제공한다. 이것은 오직 String 속성만 제공하는 Nanning Aspects 와 JBoss 4의 그것처럼 속성시스템으로 부터 이것과 구별된다. Object속성을 지원하는데는 명백한 장점을 가진다. 이것은 속성이 클래스 구조에 관계되는 것을 가능하게 하고 설정 파라미터로 속성이 현명하게 반응하는것을 가능하게 한다.

대부분의 속성 제공자(provider)에서, 속성 클래스는 생성자의 인자나 자바빈 프라퍼티를 통해 설정될것이다. Commons Attributes지원또한 설정된다.

모든 Spring 추상 API처럼 Attributes는 인터페이스이다. 이것은 단위 테스트를 위한 속성 구현물을 모방하는것을 쉽게 한다.

8.3. Jakarta Commons Attributes과 통합

현재 Spring은 비록 이것이 다른 메타데이타 제공자를 위한 org.springframework.metadata.Attributes의 구현물을 제공하는것이 쉽더라도 특별히 Jakarta Commons Attributes만을 지원한다.

Commons Attributes 2.1 (http://jakarta.apache.org/commons/attributes/) 은 필요한 능력을 가진 속성 솔루션이다. 속성 정의내 좀더 나은 문서화를 제공하는 이것은 생성자의 인자와 자바빈 프라퍼티를 통해 속성 설정을 지원한다.(자바빈 프라퍼티를 위한 지원은 Spring팀에 의해 요청되어 추가되었다.)

우리는 Commons Attributes 속성 정의의 두가지 예제를 이미 보였다. 대개 우리는 표현할 필요가 있을것이다.

  • 속성 클래스의 이름. 이것은 위에서 보여준 것처럼 FQN이 될수 있다. 만약 관련 속성 클래스가 이미 import되었다면 FQN은 요구되지 않는다. 이것은 속성 컴파일러-설정내에서 "속성 패키지"를 명시하는것이 가능하다.

  • 생성자의 인자나 자바빈 프라퍼티를 통해 필요한 파라미터로 나타내기

bean 프라퍼티는 다음처럼 보일것이다.

/**
 * @@MyAttribute(myBooleanJavaBeanProperty=true)
 */

(Spring IoC처럼) 생성자의 인자와 자바빈 프라퍼티를 조합하는것은 가능하다.

자바 1.5 속성과는 다르기 때문에 Commons Attributes는 자바언어와 통합되지 않는다. 이것은 빌드 처리의 일부처럼 특별한 속성 컴파일 단계를 수행할 필요가 있다.

빌드 처리의 일부처럼 Commons Attributes를 수행하기 위해 당신은 다음처럼 할 필요가 있다.

1. 필요한 라이브러리 jar파일을 $ANT_HOME/lib 로 복사하라. 4개의 jar파일이 요구되고 모두 Spring과 함께 배포된다.

  • Commons Attributes 컴파일러 jar와 API jar

  • XDoclet으로 부터의 xjavadoc.jar

  • Jakarta Commons으로 부터의 commons-collections.jar

2. 다음처럼 Commons Attributes ant작업을 당신의 프로젝트 빌드 스크립트에 추가하라.

<taskdef resource="org/apache/commons/attributes/anttasks.properties"/>

3. 소스내 속성을 "컴파일(compile)" 하기 위한 Commons Attributes 속성-컴파일 작업을 사용할 속성 컴파일 작업을 정의하라. 이 처리는 destdir속성에 의해 정의된 위치에 추가적인 소스의 생성한다. 우리는 임시 디렉토리의 사용한다.

<target name="compileAttributes" >

    <attribute-compiler 
        destdir="${commons.attributes.tempdir}" 
    >
        <fileset dir="${src.dir}" includes="**/*.java"/>
    </attribute-compiler>

</target>

소스에 Javac를 시행하는 컴파일 대상은 속성 컴파일 작업에 의존한다. 그리고 우리의 대상 임시 디렉토리에 결과를 만드는 생성된 소스를 컴파일해야만 한다. 만약 당신의 속성 정의에 문법적인 에러가 있다면 속성 컴파일러에 의해 잡힐것이다. 만약 속성 정의가 문법적으로 그럴듯하지만 유효하지 않은 타입이나 클래스명을 명시한다면 생성된 속성 클래스의 컴파일이 실패할것이다. 이 경우 당신은 문제를 야기하는 생성된 클래스를 찾을수 있다.

Commons Attributes 또한 Maven지원을 제공한다. 더 많은 정보를 위해서는 Commons Attributes 문서를 참조하라.

속성 컴파일 처리가 완벽해 보이는 동안 사실 이것은 한번만(one-off cost)에 이루어진다. 셋업할때 속성 컴파일은 증가한다. 그래서 이것은 언제나 눈에 띄게 빌드처리가 늦지는 않다. 컴파일 처리가 셋업된다면 당신은 이 장에서 언급되는것처럼 속성의 사용이 당신에게 다른 영역에서 많은 시간을 절약하게 한다는것을 알게 될것이다.

만약 당신이 속성 인덱스 지원(속성-대상이 된 웹 컨트롤러를 위해 Spring에 의해 요구되는)을 요구한다면 당신은 컴파일된 클래스의 jar파일에서 수행되어야만 하는 추가적인 단계가 필요할것이다. 이것은 선택적인 단계로 Commons Attributes는 수행시 효과적으로 찾기 위해 소스에 정의된 모든 속성의 인덱스를 생성할것이다. 이 단계는 다음처럼 보일것이다.

<attribute-indexer jarFile="myCompiledSources.jar">
    
    <classpath refid="master-classpath"/>

</attribute-indexer>
빌드 처리의 예제인 Spring jPetStore예제 애플리케이션의 /attributes 디렉토리를 보라. 당신은 당신의 프로젝트를 위해 이것을 포함하거나 변경하는 빌드 스크립트를 가질수 있다.

만약 당신의 단위 테스트가 속성에 의존한다면 Commons Attributes보다는 Spring Attributes 추상화에 의존성을 표시하라. 이것은 좀더 이식가능하다. 예를 들어 당신의 테스트가 나중에 자바 1.5로 교체된다고 하더라고 여전히 작동할것이다. 이것은 테스트를 좀더 쉽게 만든다. Spring이 쉽게 모방할수있는 메타데이타 인터페이스를 제공하는 반면에 Commons Attributes는 정적 API이다.

8.4. 메타데이타와 Spring AOP 자동 프록시

메타데이타 속성의 가장 중요한 사용은 Spring AOP와 결합하는것이다. 이것은 선언적인 서비스가 메타데이타 속성을 선언하는 애플리케이션 객체에 자동적으로 제공되는 .NET같은 프로그래밍 모델을 제공한다. 메타데이타 속성은 선언적인 트랜잭션 관리나 사용자 지정 사항처럼 프레임워크에 의해 특별히 지원될수 있다.

여기엔 AOP와 메타데이타 속성간의 넓은 시너지 효과를 가져다 준다.

8.4.1. 기초

이것은 Spring AOP 자동프록시 기능위에서 빌드된다. 설정은 다음과 같을수 있다.

<bean id="autoproxy" 
    class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
</bean>

<bean id="transactionAttributeSource"
    class="org.springframework.transaction.interceptor.AttributesTransactionAttributeSource"
    autowire="constructor">
</bean>

<bean id="transactionInterceptor"
    class="org.springframework.transaction.interceptor.TransactionInterceptor"
    autowire="byType">
</bean>

<bean id="transactionAdvisor"
    class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor"
    autowire="constructor" >
</bean>

<bean id="attributes"
    class="org.springframework.metadata.commons.CommonsAttributes"
/>

여기의 기본적인 개념은 AOP장의 자동프록시에서 언급된것과 유사하다.

가장 중요한 bean정의는 autoproxytransactionAdvisor 라는 이름으로 명명된다. 실질적인 bean이름은 중요하지 않다. 클래스가 중요하다.

org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator 클래스의 자동프록시 bean정의는 Advisor구현물에 대응되는 현재 factory내 모든 bean인스턴스에 자동적으로 advise("autoproxy")할것이다. 이 클래스는 속성에 대해 아무것도 모르지만 Advisor의 pointcut대응에 의존한다. pointcut는 속성에 대해서 안다.

게다가 우리는 속성에 기반한 선언적인 트랜잭션 관리를 제공할 AOP advisor이 필요하다.

임의로 사용자정의 Advisor구현물을 추가하는것은 가능하다. 그리고 그것들은 자동적으로 평가되고 적용될것이다. (당신은 필요하다면 같은 자동프록시 설정내 속성외에도 기준(criteria)에 대응되는 pointcut의 Advisor을 사용할수 있다.)

마지막으로 attributes bean은 Commons Attributes속성 구현물이다. 다른 소스로부터 소스 속성을 위한 org.springframework.metadata.Attributes의 구현물을 대체한다.

8.4.2. 선언적인 트랜잭션 관리

소스레벨 속성의 가장 공통적인 사용은 선언적인 트랜잭션 관리를 하는것이다. 위의 bean정의는 이를 대신한다. 당신은 선언적인 트랜잭션을 요구하는 많은 수의 애플리케이션 객체를 정의할수 있다. 트랜잭션 속성을 가진 클래스나 메소드는 트랜잭션 advice가 주어질것이다. 당신은 요구된 트랜잭션 속성을 정의하는것을 제외하고 아무것도 할 필요가 없다.

.NET 과는 다르게, 당신은 클래스나 메소드 레벨에서 트랜잭션 속성을 명시할수 있다. 모든 메소드에 의해 "상속"받는다면 클래스-레벨 속성, 클래스-레벨 속성을 전체적으로 오버라이드한다면 메소드 레벨 속성이다.

8.4.3. 풀링(Pooling)

다시 .NET처럼, 당신은 클래스-레벨 속성을 통해 풀링을 가능하게 할수 있다. Spring은 POJO에 이 행위를 적용할수 있다. 당신은 다음처럼 풀링되는 비지니스 객체네에서 풀링 속성을 정의할 필요가 있다.

 /** 
 * @@org.springframework.aop.framework.autoproxy.target.PoolingAttribute (10)
 * 
 * @author Rod Johnson
 */
public class MyClass {

당신은 대개 자동프록시 설정이 필요할 것이다. 당신은 다음처럼 풀링 TargetSourceCreator을 명시할 필요가 있다. 풀링은 대상의 생성에 영향을 끼치므로 우리는 정규(regular) advice를 사용할수 없다. 클래스에 적용가능한 advisor가 없고 클래스가 풀링 속성을 가진다면 풀링은 적용할것이다.

<bean id="poolingTargetSourceCreator"
    class="org.springframework.aop.framework.autoproxy.metadata.AttributesPoolingTargetSourceCreator"
    autowire="constructor" >
</bean>

관련 자동프록시 bean정의는 풀링 대상 소스 생성자를 포함하는 "사용자 정의 대상 소스 생성자"의 목록을 명시할 필요가 있다. 우리는 다음의 프라퍼티를 포함하기 위해 위의 예제를 변경할수 있다.

<bean id="autoproxy" 
    class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
>
    <property name="customTargetSourceCreators">
        <list>
            <ref local="poolingTargetSourceCreator" />
        </list>
    </property>
</bean>

대개 Spring에서 메타데이타를 사용하는것처럼 이것은 한번만에 이루어진다. 셋업이 이루어진다면 추가적인 비지니스 객체를 위한 풀링을 사용하는것은 매우 쉽다.

풀링의 필요성이 드물다는것은 논쟁의 여지가 있다. 그래서 많은 수의 비지니스 객체를 위해 풀링을 적용하는것은 좀처럼 필요하지 않다. 이 기능은 종종 사용되지 않는다.

좀더 상세항 사항을 위해서는 org.springframework.aop.framework.autoproxy 패키지를 위한 JavaDoc를 보라. 이것은 최소한의 사용자정의 코딩을 가진 Commons Pool보다 다른 풀링 구현물을 사용하는것이 가능하다.

8.4.4. 사용자정의 메타데이타

우리는 자동프록시 구조를 참조하는 유연성때문에 .NET메타데이타 속성의 기능을 능가할수 있다.

우리는 선언적인 행위의 종류를 제공하기 위해 사용자정의 속성을 정의할수 있다. 이것을 위해서 당신은 다음처럼 해야할 필요가 있다.

  • 사용자정의 속성 클래스 정의하기

  • 사용자정의 속성의 존재에서 수행되는 pointcut를 가진 Spring AOP Advisor정의하기

  • 일반적인 자동프록시 구조를 대체하는 애플리케이션 컨텍스트를 위한 bean정의처럼 Advisor를 추가하기

  • POJO에 속성 추가하기.

당신이 사용자정의 선언적인 보안이나 캐싱과 같은 것을 하길 원하는 여러가지 잠재적인 영역이 있다.

이것은 몇몇 프로젝트에서 효과적으로 설정을 줄일수 있는 강력한 기법이다. 어쨌든 AOP에 의존한다는것을 기억하라. 좀더 많은 Advisor은 좀더 복잡한 수행 설정이 될것이다. (만약 당신이 어느 객체에 적용되는 advice를 보길 원한다면 참조를 org.springframework.aop.framework.Advised로 형변환하라. 이것은 Advisor을 조사하는것을 가능하게 한다.)

8.5. MVC 웹티어 설정을 최소화하기 위한 속성 사용하기

1.0의 Spring 메타데이타의 다른 중요한 사용은 Spring MVC웹 설정을 단순화하기 위한 선택사항을 제공하는것이다.

Spring MVC는 들어온 요청을 컨트롤러(또는 다른 핸들러) 인스턴스로 맵핑하는 유연한 핸들어 맵핑을 제공한다. 대개 핸들러 맵핑은 관련 Spring DispatcherServlet을 위한 xxxx-servlet.xml파일내 설정된다.

DispatcherServlet 설정파일내 맵핑을 유지하는것은 좋은것이다. 이것은 최대한의 유연성을 제공한다. 특히

  • XML bean정의를 통해 Spring IoC에 의해 명시적으로 관리되는 컨트롤러 인스턴스

  • 맵핑은 컨트롤러를 위한 형식이다. 그래서 같은 컨트롤러 인스턴스는 같은 DispatcherServlet 컨텍스트내에서 다중 맵핑이 주어질수 있거나 다른 설정에서 재사용될수 있다.

  • Spring MVC는 대부분의 다른 프레임워크에서 사용가능한 요청 URL-대-컨트롤러(URL-to-controller) 맵핑보다 어느 기준(criteria)에 기반하는 맵핑을 지원하는것이 가능하다.

어쨌든 이것은 각각의 컨트롤러를 위해 우리는 대개 핸들러 맵핑(핸들러 맵핑 XML bean정의내에서)과 컨트롤러 자체를 위한 XML맵핑이 필요하다는것을 의미한다.

Spring은 좀더 간단한 시나리오에서 매력적인 선택사항인 소스-레벨 속성에 기반하는 좀더 간단한 접근법을 제공한다.

이 장에서 언급된 접근법은 비교적 간단한 MVC시나리오에 가장 적합하다. 이것은 다른 맵핑를 가진 같은 컨트롤러를 사용하기 위한 능력과 요청 URL보다 어떤것에 맵핑에 기초로 두는 능력과 같은 Spring MVC의 몇몇 강력함을 희생한다.

이 접근법에서 컨트롤러는 맵핑될 하나의 URL을 명시하는 하나 이상의 클래스-레벨 메타데이타 속성과 함께 표시된다.

다음의 예제는 접근법을 보여준다. 이 경우, 우리는 Cruncher타입의 비지니스 객체에 의존하는 컨트롤러를 가진다. 대개 이 의존성은 의존성 삽입(Dependency Injection)에 의해 해석될것이다. Cruncher는 관련 DispatcherServlet XML 파일이나 부모 컨텍스트내 bean정의를 통해 사용가능해야만 한다.

우리는 이것을 맵핑하는 URL을 명시하는 컨트롤러 클래스로 속성을 첨부한다. 우리는 자바빈 프라퍼티나 생성자의 인자를 통해 의존성을 표시할수 있다. 이 의존성은 autowiring에 의해 해석될수 있어야만 한다. 컨텍스트내 사용가능한 Cruncher타입의 비지니스 객체가 되어야만 한다.

/**
 * Normal comments here
 * @author Rod Johnson
 * @@org.springframework.web.servlet.handler.metadata.PathMap("/bar.cgi")
 */
public class BarController extends AbstractController {

    private Cruncher cruncher;

    public void setCruncher(Cruncher cruncher) {
        this.cruncher = cruncher;
    }

    protected ModelAndView handleRequestInternal(
            HttpServletRequest arg0, HttpServletResponse arg1) 
            throws Exception {
        System.out.println("Bar Crunching c and d =" + 
            cruncher.concatenate("c", "d"));
        return new ModelAndView("test");
    }

}

작동하기 위한 자동-맵핑을 위해, 우리는 속성 핸들러 맵핑을 명시하는 관련 xxxx-servlet.xml파일에 다음을 추가할 필요가 있다. 이 특별한 핸들러 맵핑은 위의 속성을 가진 많은수의 컨트롤러를 다룰수 있다. bean id("commonsAttributesHandlerMapping")는 중요하지 않다. 타입이 중요하다.

<bean id="commonsAttributesHandlerMapping"      
    class="org.springframework.web.servlet.handler.metadata.CommonsPathMapHandlerMapping"
/>

우리는 위 예제처럼 Attributes bean정의가 현재 필요하지 않다. 이 클래스가 Commons Attributes API와 직접적으로 자동하기 때문에 Spring 메타데이타 추상화를 통하지 않는다.

우리는 각각의 컨트롤러를 위한 XML설정이 필요하지 않다. 컨트롤러는 명시된 URL에 자동적으로 맵핑된다. 컨트롤러는 Spring의 autowiring능력을 사용하여 IoC로 부터 이득을 가진다. 예를 들어 위의 간단한 컨트롤러의 "cruncher" bean프라퍼티내 표현되는 의존성은 현재 웹 애플리케이션 컨텍스트내에서 해석된다. setter와 생성자 의존성 삽입(Constructor Dependency Injection) 모두 각각 설정을 가지지 않고 사용가능하다.

다중 URL경로를 보여주는 생성자 삽입의 예제이다.

/**
* Normal comments here
* @author Rod Johnson
* 
* @@org.springframework.web.servlet.handler.metadata.PathMap("/foo.cgi")
* @@org.springframework.web.servlet.handler.metadata.PathMap("/baz.cgi")
*/
public class FooController extends AbstractController {

    private Cruncher cruncher;

    public FooController(Cruncher cruncher) {
        this.cruncher = cruncher;
    }

    protected ModelAndView handleRequestInternal(
            HttpServletRequest arg0, HttpServletResponse arg1) 
            throws Exception {
        return new ModelAndView("test");
    }

}

이 접근법은 다음의 이익을 가진다.

  • 명백하게 제거된 설정양. 매번 우리는 XML설정을 추가할 필요가 없는 컨트롤러를 추가한다. 속성-기반 트랜잭션 관리처럼 기초적인 구조가 대체한다. 좀더 많은 애플리케이션 클래스를 추가하는것이 매우 쉽다.

  • 우리는 컨트롤러를 설정하기 위한 Spring IoC의 강력함을 유지한다.

이 접근법은 다음의 제한을 가진다.

  • 좀더 복잡한 빌드 처리에서 한번만의 처리(One-off cost). 우리는 속성 컴파일 단계와 속성 인덱스 단계가 필요하다.어쨌든 한번의 대체로 이것은 문제가 되지는 않을것이다.

  • 비록 나중에 추가될 다른 속성 제공자(provider)를 위한 지원이 있더라도 현재 Commons Attributes만 지원한다,

  • "타입에 의한 autowiring" 의존성 삽입은 컨트롤러를 위해 지원된다. 어쨌든 이것은 Struts Action( 프레임워크로 부터 IoC지원이 없는)과 WebWork Action(기본적인 IoC지원만 하는)의 장점으로 그것들을 남긴다.

  • 자동적인 마법같은 IoC해석의 신뢰는 혼동된다.

타입에 의한 autowiring은 명시된 타입의 의존성이 되어야만 하는것을 의미한다. 우리는 AOP를 사용한다면 주의할 필요가 있다. TransactionProxyFactoryBean을 사용하는 공통적인 경우에 예를 들어 우리는 Cruncher처럼 비지니스 인터페이스의 두가지 구현물(원래의 POJO정의와 트랜잭션적인 AOP프록시)이 된다. 이것은 애플리케이션 컨텍스트가 타입 의존성을 분명하게 해석하지 못하는것처럼 작동하지 않을것이다. 해결법은 자동프록시 구조를 셋업하는 AOP 자동프록시를 사용하는것이다. 그래서 정의된 Cruncher의 하나의 구현물만이 있고 구현물은 자동적으로 advised된다. 게다가 이 접근법은 위에서 언급된것처럼 속성-대상화된 선언적인 서비스와 잘 작동한다. 속성 컴파일 처리가 웹 컨트롤러 대상화(targeting)를 다루기 위해 대체되어야만 하는것처럼 이것은 셋업하기 쉽다.

다른 메타데이타 기능과는 달리, 사용가능한 Commons Attributes구현물( org.springframework.web.servlet.handler.metadata.CommonsPathMapHandlerMapping)만이 있다. 이 제한은 속성 컴파일이 필요하고 속성 인덱싱(PathMap속성을 가진 모든 클래스를 위해 속성 API에 요청하는 능력)이 필요하다는 사실이다. 인덱싱은 org.springframework.metadata.Attributes에서 현재 제공되지 않지만 나중에 지원될것이다.(만약 당신이 인덱싱을 지원하는 다른 속성 구현물을 위한 지원을 추가하길 원한다면 당신은 당신이 선호하는 속성 API를 사용하는 두개의 protected성격의 추상 메소드를 구현하는 CommonsPathMapHandlerMapping의 수퍼클래스인 AbstractPathMapHandlerMapping을 쉽게 확장할수 있다.)

게다가 우리는 빌드 처리내에서 두가지의 추가적인 단계(속성 컴파일과 속성 인덱싱)가 필요하다. 속성 인덱서(indexer) 작업의 사용은 위에서 보여준다. 현재 Commons Attribute는 인덱싱을 위한 입력처럼 jar파일을 요구한다.

만약 당신이 핸들러 메타데이타 맵핑 접근법으로 시작한다면 이것은 고전적인 Spring XML맵핑 접근법에 어느 지점(point)을 교체하는것이 가능하다. 그래서 당신은 이 선택사항을 닫지 않는다. 이러한 이유로 나는 내가 메타데이타 맵핑을 사용하여 웹 애플리케이션을 종종 시작하는것을 알았다.

8.6. 메타데이타 속성의 다른 사용

메타데이타 속성의 다른 사용은 인기가 증가되면 나타난다. Spring 1.2처럼 JMX노출(exposure)을 위해 Commons Attributes(JDK1.3이상)와 JSR-175 annotations(JDK 1.5) 모두를 통해 메타데이타 속성이 지원된다.

8.7. 추가적인 메타데이타 API를 위한 지원 추가하기

다른 메타데이타 API를 위한 지원을 제공하길 원한다면 이것은 그렇게 하기 쉽다.

당신의 메타데이타 API를 위한 외형처럼 org.springframework.metadata.Attributes 인터페이스를 간단히 구현한다. 당신은 위에서 보여진것처럼 당신의 bean정의내 이 객체를 포함할수 있다.

AOP 메타데이타-기반 자동프록시처럼 메타데이타를 사용하는 모든 프레임워크 서비스는 당신의 새로운 메타데이타 제공자(provider)를 사용하는것이 자동적으로 가능하게 될것이다.

Chapter 9. DAO support

9.1. 소개

Spring에서 DAO(데이터 접근 객체)지원은 JDBC, Hibernate또는 표준화된 방법으로의 JDO와 같은 데이터 접근 기술을 가지고 작업하는것을 쉽게 하자는데 가장 큰 목적이 있다. 이것은 당신에게 그것들 사이에 교체를 쉽게 하도록 하고 각각의 기술로 명시한 캐치하는 예외에 대한 걱정없이 코딩하도록 허락한다.

9.2. 일관된 예외 구조

Spring은 가장 상위 예외처럼 DataAccessException 과 함께 자기자신만의 예외구조를 위해 SQLException 처럼 예외를 서술하는 기술로부터 편리한 변환을 제공한다. 잘못된것처럼 어떤 정보를 손실하는 위험이 결코 없도록 이런 예외는 원래의 예외를 포장한다.

JDBC예외에 추가적으로 Spring은 Hibernate예외를 포장할수 있고 소유자, 체크되지 않은 예외로 부터 변환하고 추상화된 런타임예외의 설정할수 있다. 이것은 JDO예외에서도 같다. 이것은 괴로운 반복적 catches/throws구문과 예외선언 없이 적당한 레이어에서만 회복될수 없는 영속성 예외를 다루도록 허락한다. 당신은 여전히 당신이 필요한 어느곳에서든 예외를 잡고 다룰수 있다. 위에서 언급한 것처럼 JDBC예외(DB 정의 dialects)는 같은 구조로 변환한다. 변함없는 프로그래밍 모델내에서 JDBC와 함께 몇몇 작업을 수행할수 있다는 것을 의미한다.

위에서 ORM접근 프레임워크의 Template버전을 위해서 참이다. 만약 당신이 클래스에 기초한 Interceptor를 사용한다면 애플리케이션은 스스로 HibernateExceptions와 JDOExceptions을 다루어야만 한다. SessionFactoryUtilsconvertHibernateAccessException 또는 convertJdoAccessException 메 소드로 각각 위임하는것을 선호한다. 이 메소드들은 org.springframework.dao 예외 구조와 호환이 되는 것으로 변환한다. JDOExceptions이 체크되지 않은것 처럼 그들은 간단히 던져질수 있다. 예외의 개념에서 일반적인 DAO추상화를 희생한다.

Spring이 사용하는 예외 구조는 다음 그래프내에서 윤곽이 그려진다.

9.3. DAO지원을 위한 일관된 추상클래스

JDBC, JDO그리고 Hibernate같은 일관적인 방법으로 데이터접근하는 기술의 다양함을 사용해서 쉽게 작업을 수행하기 위해서 Spring은 당신이 확장할수 있는 추상화된 DAO클래스들을 제공한다. 이런 추상화된 클래스들은 데이터소스를 셋팅하고 당신이 현재 사용중인 기술을 명시하는 다른 설정상의 셋팅을 하기 위한 메소드를 가지고 있다.

DAO지원 클래스:

  • JdbcDaoSupport - JDBC데이터 접근 객체를 위한 슈퍼클래스(super class), 하위 클래스에 기초를 두는 JdbcTemplate을 제공하고 셋팅되기 위한 DataSource를 요구한다.

  • HibernateDaoSupport - Hibernate데이터 접근 객체를 위한 슈퍼클래스(super class), 하위 클래스에 기초를 두는 HibernateTemplate을 제공하고 셋팅되기 위한 SessionFactory를 요구한다. SessionFactory, flush mode, 예외 번역 등등 처럼 나중에 셋팅을 재사용하기 위해 HibernateTemplate를 통해 대안으로 직접 초기화될수 있다.

  • JdoDaoSupport - JDO데이터 접근 객체를 위한 슈퍼클래스(super class), 하위 클래스에 기초를 두는 JdoTemplate을 제공하고 셋팅되기 위한 PersistenceManagerFactory를 요구한다.

Chapter 10. JDBC를 사용한 데이터 접근

10.1. 소개

JDBC추상 프레임워크는 Spring에 의해 제공되는 4개( core , datasource , object , 그리고 support )의 패키지로 구성된다.

org.springframework.jdbc.core 패키지는 JdbcTemplate를 포함하고 이것의 다양한 callback인터페이스, 거기다가 다양한 관련 클래스를 포함한다.

org.springframework.jdbc.datasource 패키지는 쉬운 데이터소스 접근을 위한 유틸리티 클래스를 포함하고 J2EE컨테이너밖에서 변경이 되지 않은 JDBC코드를 테스트하고 실행하기 위해 사용될수 있는 여러가지 간단한 DataSource구현을 포함한다. 유틸리티클래스는 필요하다면 JNDI로 부터 Connection을 얻고 Connection을 닫는 정적 메소드를 제공한다. 이것은 DataSourceTransactionManager를 사용하는 것처럼 쓰레드범위의 연결을 지원한다.

그 다음 org.springframework.jdbc.object 패키지는 쓰레드에 안전하고 재사용가능한 객체처럼 RDBMS 쿼리, update 그리고 저장 프로시저를 표현하는 클래스를 포함한다. 이 접근법은 JDO에 의해 형상화 되었다. 쿼리에 의해 반환된 객체는 데이터베이스로 부터 “disconnected” 된다. JDBC추상화의 높은 레벨은 org.springframework.jdbc.core 패키지내에서 하위 레벨에 의존한다.

마지막으로 org.springframework.jdbc.support 패키지는 SQLException 번역 기능과 몇개의 유틸리티 클래스를 찾을수 있는 곳이다.

JDBC처리중에 던져진 예외는 org.springframework.dao 패키지내에서 정의된 예외로 번역이 된다. 이것은 Spring JDBC추상 레이어를 사용하는 코드가 JDBC또는 RDBMS특성 에러 처리를 구현할 필요가 없다는 것을 의미한다. 모든 번역된 예외는 호출자에게 전파되기 위한 다른 예외를 허락하는 동안 당신이 복구할수 있는 예외를 잡는 옵션을 제공하고 체크되지 않는다.

10.2. 기본적인 JDBC처리와 에러 처리를 위한 JDBC Core클래스 사용하기

10.2.1. JdbcTemplate

이것은 JDBC Core패키지에서 핵심 클래스이다. 이것은 자원을 생성하고 해재함으로써 JDBC의 사용을 단순화시킨다. 이것은 연결을 닫는것을 잊어버리는것처럼 공통적으로 발생할수 있는 에러를 피하도록 도와준다. 이것은 statement생성및 수행, SQL을 생성하고 결과물을 반환하고 애플리케이션 코드를 벗어나는 핵심적인 JDBC절차를 수행한다. 이 클래스는 SQL쿼리, update문 또는 저장 프로시저 호출, ResultSets를 넘어서 순환을 모방하고 반환된 인자값을 보여주는 작업을 수행한다. 이것은 또한 JDBC예외를 잡고 일반적인 것으로 그것들을 번역하고 좀더 다양한 정보를 제공하도록 하고 org.springframework.dao 패키지내에 정의된 예외 구조제공한다.

이 클래스를 사용하는 코드는 단지 명백하게 정의된 규칙을 제공하는 callback인터페이스만 구현할 필요가 있다. PreparedStatementCreator callback인터페이스는 SQL과 필요한 인자를 제공하는 클래스에 의해 제공되는 Connection으로 prepared statement를 생성한다. 호출 가능한 statement를 생성하는 것은 CallableStatementCreateor 인터페이스이다. RowCallbackHandler 인터페이스는 ResultSet으로 부터 각각의 row에서 값을 뽑아낸다.

이 클래스는 데이터소스 참조또는 애플리케이션 컨텍스트내에서 준비되고 빈(bean)참조처럼 서비스하기 위해 직접적인 초기화를 통해 서비스구현내에서 사용될수 있다. 주의: 데이터소스는 애플리케이션 컨텍스트내에서 언제나 빈처럼 설정되어야 한다. 이 클래스는 callback인터페이스와 SQLExceptionTranslator인터페이스에 의해 인자화 되기 때문에 이것을 하위클래스화 할 필요가 없다. 이 클래스에 의해 발생된 모든 SQL은 로그화된다.

10.2.2. DataSource

데이터베이스로부터 데이터작업을 수행하기 위해서 우리는 데이터베이스로 부터 Connection을 얻을 필요가 있다. Spring은 DataSource 을 통해서 이것을 수행한다. DataSource 는 JDBC스펙의 일부이고 생성된 connection공장처럼 볼수 있다. 이것은 컨테이너또는 프레임워크에게 높은 성능의 Connection pooling와 애플리케이션 코드로 부터 트랜잭션 관리 부분을 숨길수 있도록 한다. 개발자의 입장에서 당신은 데이터베이스에 연결하는 상세내역을 알 필요가 없다. 이것은 데이터베이스를 셋팅하는 관리자의 책임이다. 당신은 당신의 코드를 개발하고 테스트하는 동안 두가지 책임을 모두 수행해야 할지도 모르지만 어떻게 데이터소스가 설정이 되는지에 대해서 알필요는 없다.

Spring의 JDBC레이어를 사용할때 당신은 JNDI로 부터 데이터소스를 얻거나 Spring배포내에서 제공되어 있는 구현물로 자신만의 설정을 할수도 있다. 후자의 경우 웹 컨테이너밖에서 단위테스팅을 능숙하게 할수 있게 한다. 우리는 나중에 다루어질 여러개의 추가적인 구현물이 있지만 이 섹션에서 DriverManagerDataSource 을 사용할것이다. DriverManagerDataSource 는 당신이 JDBC Connection을 얻었을때 작업하기 위해서 사용되어진 것과 같은 방식으로 작동한다. 당신은 DriverManager 가 드라이버클래스를 로드할수 있도록 JDBC드라이버의 패키지를 포함한 전체이름을 명시해야 한다. 그 다음 당신은 JDBC드라이버사이에 변경이 되는 url을 제공해야만 한다. 여기서 정확한 값을 위해서 당신 드라이버의 문서를 찾아보아야 한다. 마지막으로 당신은 데이터베이스 연결에 사용되는 사용자명과 비밀번호를 제공해야만 한다. 이것은 DriverManagerDataSource :을 설정하기 위한 방법을 보여주는 예제이다.

DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName( "org.hsqldb.jdbcDriver");
dataSource.setUrl( "jdbc:hsqldb:hsql://localhost:");
dataSource.setUsername( "sa");
dataSource.setPassword( ""); 

10.2.3. SQLExceptionTranslator

SQLExceptionTranslator 은 SQLExceptions과 우리의 데이터접근 전략에 얽매이지 않는 org.springframework.dao.DataAccessException 사이에 해석할수 있는 클래스에 의해 구현될수 있는 인터페이스이다.

구현은 좀더 정확성을 위해서 일반적(예를 들면, JDBC를 위해 SQLState코드를 사용하는)이거나 소유(예를 들면, Oracle에러코드를 사용하는)될수있다.

SQLErrorCodeSQLExceptionTranslator 는 초기설정에 의해서 사용이 되는 SQLExceptionTranslator의 구현이다. 이 구현은 업체코드를 명시하는데 사용한다. SQLState 구현보다 좀더 정확하지만 업체에 종속적이다. 에러코드해석은 SQLErrorCodes 이라고 불리는 자바빈 타입의 코드에 기초를 둔다. 이 클래스는 "sql-error-codes.xml"라는 이름의 설정파일의 내용에 기초를 두는 SQLErrorCodes 를 생성하기 위한 공장같은 이름의 SQLErrorCodesFactory 에 의해서 생성되고 활성화된다. 이 파일은 업체코드에 의해 활성화되고 DatabaseMetaData로 부터 얻어진 DatabaseProductName에 기초를 둔다.

SQLErrorCodeSQLExceptionTranslator 는 다음의 일치규칙(matching rules)을 적용한다.

  • 어느 하위 클래스에 의해서 구현되는 사용자지정해석(custom translation). 이 클래스는 이 규칙을 적용하지 않는 경우에 견고해지고 스스로 사용되는 것에 주의하라.

  • 에러코드일치를 적용하라. 에러코드는 초기설정에 의해서 SQLErrorCodesFactory으로 부터 얻어진다. 이것은 클래스패스로부터 에러코드를 찾고 데이터베이스 메타데이터로부터 데이터베이스 이름으로 부터 키를 입력한다.

  • fallback해석자를 사용하라. SQLStateSQLExceptionTranslator는 초기설정의 fallback해석자이다.

SQLErrorCodeSQLExceptionTranslator 는 다음과 같은 방법으로 확장할수 있다.

public class MySQLErrorCodesTranslator extends SQLErrorCodeSQLExceptionTranslator {
    protected DataAccessException customTranslate(String task, String sql, SQLException sqlex) {
        if (sqlex.getErrorCode() == -12345)
            return new DeadlockLoserDataAccessException(task, sqlex);
        return null;
    }
}

이 예제에서 에러코드 '-12345'는 해석되었거나 초기설정 해석자구현에 의해서 해석되기 위해서 남겨진 다른 에러코드이다. 이 사용자지정 해석자(custom translator)를 사용하기 위해서 setExceptionTranslator 메소드를 사용하고 이 해석자가 필요한 데이터 접근 처리를 위해 JdbcTemplate 를 사용하기 위한 JdbcTemplate 으로 값을 넘길필요가 있다. 여기에 사용자지정 해석자가 어떻게 사용되는지에 대한 예제가 있다.

// create a JdbcTemplate and set data source 
JdbcTemplate jt = new JdbcTemplate(); 
jt.setDataSource(dataSource); 
// create a custom translator and set the datasource for the default translation lookup 
MySQLErrorCodesTransalator tr = new MySQLErrorCodesTransalator(); 
tr.setDataSource(dataSource); 
jt.setExceptionTranslator(tr); 
// use the JdbcTemplate for this SqlUpdate 
SqlUpdate su = new SqlUpdate(); 
su.setJdbcTemplate(jt); 
su.setSql("update orders set shipping_charge = shipping_charge * 1.05"); 
su.compile(); 
su.update();

이 사용자지정 해석자는 sql-error-codes.xml 내의 에러코드를 찾기위해 디폴트 해석자를 원하기 때문에 데이터소스를 전달했다.

10.2.4. Statements 실행하기

SQL문을 실행하기 위해 필요한 작은 코드가 있다. 당신이 필요한 모든것은 DataSourceJdbcTemplate 이다. 당신이 그것을 가졌을때 당신은 JdbcTemplate 과 함께 제공되는 많은 편리한 메소드를 사용할수 있다. 여기에 작지만 새로운 테이블을 생성하는 모든 기능적인 클래스를 위해 필요한 짧은 예제를 보여준다.

import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;

public class ExecuteAStatement {
    private JdbcTemplate jt;
    private DataSource dataSource;

    public void doExecute() {
        jt = new JdbcTemplate(dataSource);
        jt.execute("create table mytable (id integer, name varchar(100))"); 
    }

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }
}

10.2.5. 쿼리문 실행하기

메소드를 수행하는 것에 추가적으로 여기엔 많은 수의 쿼리 메소드가 있다. 그 메소드의 몇몇은 하나의 값을 반환하는 쿼리를 위해 사용되는 경향이 있다. 아마도 당신은 하나의 레코드로 부터 카운트나 특정값을 가져오길 원할지도 모른다. 만약 그 경우라면 당신은 queryForInt , queryForLong 또는 queryForObject 를 사용할수 있다. 후자는 반환된 JDBC타입을 인자처럼 전달된 자바 클래스로 변환할 것이다. 만약 타입변환이 유효하지 않다면 InvalidDataAccessApiUsageException 를 던질것이다. 여기에 int 를 위한것과 String 을 위한 두개의 쿼리 메소드를 포함하는 예제가 있다.

import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;

public class RunAQuery {
    private JdbcTemplate jt;
    private DataSource dataSource;
  
    public int getCount() {
        jt = new JdbcTemplate(dataSource);
        int count = jt.queryForInt("select count(*) from mytable");
        return count;
    }

    public String getName() {
        jt = new JdbcTemplate(dataSource);
        String name = (String) jt.queryForObject("select name from mytable", java.lang.String.class);
        return name;
    }

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }
}

하나의 결과물을 위한 쿼리 메소드에 추가적으로 쿼리가 반환하는 각각의 레코드를 가지는 List를 반환하는 다양한 메소드가 있다. 가장 일반적인 하나는 각각의 레코드를 위한 칼럼값을 표현하는 Map 형태의 List 를 반환하는 queryForList 이다. 만약 우리가 모든 레코드의 리스트를 가져오는 메소드를 추가한다면 다음과 같을것이다.

    public List getList() {
        jt = new JdbcTemplate(dataSource);
        List rows = jt.queryForList("select * from mytable");
        return rows;
    }

반환되는 리스트는 이것저것 보일것이다. [{name=Bob, id=1}, {name=Mary, id=2}].

10.2.6. 데이터베이스 수정하기

당신이 사용할수 있는 많은 update메소드가 있다. 나는 어떠한 기본키를 위한 칼럼을 수정하는 예제를 보여줄것이다. 이 예제에서 나는 레코드 파라미터를 위한 위치자(place holders)를 가진 SQL문을 사용한다. 대부분의 쿼리및 update메소드는 이 기능을 가진다. 파라미터 값은 객체의 배열내에 전달된다.

import javax.sql.DataSource;

import org.springframework.jdbc.core.JdbcTemplate;

public class ExecuteAnUpdate {
    private JdbcTemplate jt;
    private DataSource dataSource;

    public void setName(int id, String name) {
        jt = new JdbcTemplate(dataSource);
        jt.update("update mytable set name = ? where id = ?", new Object[] {name, new Integer(id)});
    }

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }
}

10.3. 데이터베이스에 연결하는 방법을 제어하기

10.3.1. DataSourceUtils

헬퍼 클래스는 필요하다면 JNDI로 부터 connection을 얻거나 connection을 닫기 위한 정적 메소드를 제공하고 쓰레드 범위의 connection을 위한 지원 예를 들면 DataSourceTransactionManager를 사용한다.

주의 : getDataSourceFromJndi메소드는 BeanFactory를 사용하지 않는 애플리페이션을 목표로 한다. 이것은 factory내 당신의 빈즈나 JdbcTemplate 인스턴스를 먼저 설정하는것을 선호한다. JndiObjectFactoryBean 는 JNDI로 부터 DataSource 를 꺼내고 다른 빈즈에게 DataSource 빈참조를 주는데 사용될수 있다. 다른 DataSource 로의 교체는 설정의 문제이다. 당신은 non-JNDI의 DataSource 를 가진 FactoryBean 의 정의를 교체할수 있다.

10.3.2. SmartDataSource

클래스에 의해 구현되기 위한 인터페이스는 관계 데이터베이스로의 connection을 제공할수 있다. javax.sql.DataSource 인터페이스를 확장하는것은 클래스가 주어진 작업후에 닫혀야만하는 connection이거나 그렇지 않더라도 쿼리하기 위해 사용하도록 허락한다. 이것은 때때로 우리가 connection을 재사용하길 원한다는 것을 안다면 효율을 위해 유용할수 있다.

10.3.3. AbstractDataSource

Spring의 DataSource 구현을 위한 추상 기본 클래스는 "시시함(uninteresting)"을 처리한다. 이것은 당신이 자신의 DataSource 구현을 쓴다면 확장할 클래스이다.

10.3.4. SingleConnectionDataSource

하나의 connection을 포장한 SmartDataSource 의 구현은 사용후에 닫히질 않는다. 분명히 이것은 다중 쓰레드 성능은 아니다.

만약 퍼시스턴스 툴들을 사용할때 suppressClose 을 true로 설정한 것처럼 클라이언트코드가 풀링된 connection의 소비내 close를 호출할 것이다. 이것은 물리적 connection대신에 close-억제 프록시를 반환할것이다. 당신은 이것을 고유의 Oracle connection이나 다른 어떠한 것처럼 형변환할수 없다는것을 알라.

이것은 기본적으로 테스트 클래스이다. 예를 들면 이것은 간단한 JNDI환경과 함께 연결되어 애플리케이션 서버밖의 코드를 쉽게 테스팅 가능하도록 한다. DriverManagerDataSource 와는 대조적으로 이것은 언제나 물리적 connection생성 초과를 피하고 같은 connection을 재사용한다.

10.3.5. DriverManagerDataSource

SmartDataSource 의 구현은 빈 프라퍼티를 통해 일반적인 예전 JDBC드라이버를 설정하고 모든 시점에 새로운 connection을 반환한다.

각각의 ApplicationContext내 DataSource 빈 이나 간단한 JNDI환경과의 연결내에서 어느쪽이건 테스트나 J2EE컨테이너 밖의 단독 환경을 위해 유용하다. 풀 성격의 Connection.close() 호출은 간단하게 connection을 닫을 것이다. 그래서 어떠한 DataSource-인식 퍼시스턴스코드도 작동할것이다.

10.3.6. DataSourceTransactionManager

하나의 JDBC데이터 소스를 위한 PlatformTransactionManager구현은 특정 데이터 소스로 부터 쓰레드에 JDBC connection을 바인드한다. 잠재적으로 데이터 소스당 하나의 쓰레드 connection을 허락한다.

애플리케이션 코드는 J2EE의 표준적인 DataSource.getConnection 대신에 DataSourceUtils.getConnection(DataSource) 을 통해 JDBC connection을 가져와야만 한다. 이것은 어쨌든 추천된다. 그리고 이것은 체크된 SQLException 대신에 체크되지 않은 org.springframework.dao 예외를 던진다. JdbcTemplate 와 같은 모든 프레임워크 클래스는 절대적으로 이 전략을 사용한다. 만약 이 트랜잭션 관리자를 사용하지 않는다면 룩업 전략은 공통된 것과 같이 정확하게 작동한다.

사용자 지정 격리 레벨을 지원하고 선호하는 JDBC statement쿼리 중단처럼 적용된 것을 중단한다. 후자를 지원하기 위해 애플리케이션 코드는 JdbcTemplate 나 각각의 생성된 statement를 위한 DataSourceUtils.applyTransactionTimeout 메소드 호출을 사용해야만 한다.

이 구현은 하나의 자원일 경우 JTA를 지원하기 위해 컨테이너를 요구하지 않는것처럼 JtaTransactionManager 대신에 사용될수 있다. 두가지 사항 사이의 전환은 설정상의 문제이다. 만약 당신이 요구되는 connection룩업 패턴을 고집한다면 JTA는 사용자 지정 격리 레벨을 지원하지 않는다는 것에 주의하라.!

10.4. 자바 객체처럼 JDBC작업을 모델링 하기.

org.springframework.jdbc.object 패키지는 좀더 객체 지향적인 방법으로 데이터베이스에 접근하는 것을 허락하는 클래스를 포함한다. 당신은 쿼리를 수행하고 관계적인 칼럼 데이터를 비지니스 객체의 프라퍼티로 맵핑하는 비지니스 객체를 포함하는 리스트처럼 결과를 얻을수 있다. 당신은 또한 저장 프로시저와 update, delete그리고 insert 구문을 실행할수 있다.

10.4.1. SqlQuery

SQL궈리를 표현하기 위한 쓰레드에 안전한 객체를 재사용가능하다. 하위 클래스는 ResultSet를 반복하는 동안 결과를 저장할수 있는 객체를 제공하기 위해 newResultReader()메소드를 구현해야만 한다. 이 클래스는 드물게 직접적으로 사용된다. 이 클래스를 확장하는 MappingSqlQuery 가 레코드를 자바 플래스로 맵핑하기 위한 좀더 편리한 구현을 제공한다. SqlQuery 을 확장하는 다른 구현은 MappingSqlQueryWithParametersUpdatableSqlQuery 이다.

10.4.2. MappingSqlQuery

MappingSqlQuery 는 명확한 하위 클래스가 JDBC ResultSet 의 각각의 레코드를 객체로 변환하기 위한 추상메소드인 mapRow(ResultSet, int) 를 구현함으로써 재사용가능한 쿼리이다.

모든 SqlQuery 구현으로 이것은 매우 종종 사용되고 이것은 사용하기 가장 쉬운 것중 하나이다.

여기에 사용자 지정 테이블로부터 데이터를 Customer라고 불리는 자바 객체로 맵핑하는 사용자 지정 쿼리의 예제를 간단히 설명하는것이 있다.

  private class CustomerMappingQuery extends MappingSqlQuery {
    public CustomerMappingQuery(DataSource ds) {
      super(ds, "SELECT id, name FROM customer WHERE id = ?");
      super.declareParameter(new SqlParameter("id", Types.INTEGER));
      compile();
    }
    public Object mapRow(ResultSet rs, int rowNumber) throws SQLException {
      Customer cust = new Customer();
      cust.setId((Integer) rs.getObject("id"));
      cust.setName(rs.getString("name"));
      return cust;
    } 
  }

우리는 오직 파라미터로 DataSource 를 가지는 사용자지정 쿼리를 위한 생성자를 제공한다. 이 생성자에서 우리는 DataSource 와 이 쿼리를 위해 레코드를 가져오기 위해 수행되어야 하는 SQL을 가진 수퍼클래스의 생성자를 호출한다. 이 SQL은 수행되는 동안 전달될 어떠한 파라미터를 위한 위치자를 포함한 PreparedStatement 를 생성하기 위해 사용될 것이다. 각각의 파라미터는 SqlParameter 내에 전달될 declareParameter 메소드를 사용해서 선언되어야 한다. SqlParameter 는 이름과 java.sql.Types 내에 정의될 JDBC타입을 가진다. 모든 파라미터가 compile 메소드를 호출해서 정의된 후에 statement는 준비되고 나중에 수행된다.

이 사용자 정의 쿼리가 초기화되고 수행되는 코드를 보자.

    public Customer getCustomer(Integer id) {
        CustomerMappingQuery custQry = new CustomerMappingQuery(dataSource); 
        Object[] parms = new Object[1];
        parms[0] = id;
        List customers = custQry.execute(parms);
        if (customers.size() > 0)
            return (Customer) customers.get(0);
        else
            return null;
    }

예제내 메소드는 오직 파마리터로써 전달되는 id와 함께 customer를 가져온다. CustomerMappingQuery 클래스의 인스턴스를 생성한 후에 우리는 전달될 모든 파라미터를 포함할 객체의 배열을 생성한다. 이 경우에 오직 한개의 파라미터가 있고 이것은 Integer 로 전달된다. 지금 우리는 파라미터의 배열을 사용해서 쿼리를 수행할 준비가 되었고 우리의 쿼리를 통해 반환되는 각각의 레코드를 위한 Customer 객체를 포함하는 List 를 얻게된다. 이 경우에 적합한 경우 하나의 항목이 될것이다.

10.4.3. SqlUpdate

RdbmsOperation 하위 클래스는 SQL update를 상징한다. 쿼리처럼 update객체는 재사용가능하다. 모든 RdbmsOperation객체처럼 update는 파라미터를 가지고 SQL내 정의된다.

이 클래스는 쿼리 객체의 execute()메소드를 위한 많고 유사한 update()메소드를 제공한다.

이 클래스는 명확하다. 비록 이것이 하위 클래스(예를 들면 사용자 정의 update메소드를 추가하는)가 될수 있지만 이것은 SQL을 셋팅하고 파라미터를 선언함으로써 쉽게 파라미터화 될수 있다.

import java.sql.Types;

import javax.sql.DataSource;

import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.object.SqlUpdate;

public class UpdateCreditRating  extends SqlUpdate {
    public UpdateCreditRating(DataSource ds) {
        setDataSource(ds);
        setSql("update customer set credit_rating = ? where id = ?");
        declareParameter(new SqlParameter(Types.NUMERIC));
        declareParameter(new SqlParameter(Types.NUMERIC));
        compile();
    }

    /**
     * @param id for the Customer to be updated
     * @param rating the new value for credit rating
     * @return number of rows updated
     */
    public int run(int id, int rating) {
        Object[] params =
            new Object[] {
                new Integer(rating),
                new Integer(id)};
        return update(params);
    }
}

10.4.4. StoredProcedure

RDBMS 저장 프로시저의 객체 추상화를 위한 수퍼클래스이다. 이 클래스는 추상적이고 execute메소드들은 protected상태이다. 좀더 단단하게 타이핑된 하위 클래스를 통해 다른것보다 좀더 사용이 제한적이다.

상속된 sql프라퍼티는 RDBMS에 저장된 저장프로시저의 이름이다. JDBC 3.0은 명명된 파라미터를 소개한다. 비록 이 클래스에 의해 제공되는 다른 특징이지만 여전히 JDBC 3.0에서 필요하다.

이것은 Oracle데이터베이스에서 사용되는 sysdate()함수를 호출하는 프로그램 예제이다. 저장 프로시저 기능을 사용하기 위해 당신은 StoredProcedure 를 확장한 클래스를 생성해야만 한다. 여기엔 입력 파라미터가 없지만 SqlOutParameter 클래스를 사용한 date처럼 선언된 출력 파라미터는 있다. execute() 메소드는 key로써 파라미터이름을 사용한 각각의 선언된 출력 파라미터를 위한 항목을 가지는 map을 반환한다.

import java.sql.Types;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.sql.DataSource;

import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.datasource.*;
import org.springframework.jdbc.object.StoredProcedure;

public class TestStoredProcedure {

    public static void main(String[] args)  {
        TestStoredProcedure t = new TestStoredProcedure();
        t.test();
        System.out.println("Done!");
    }
    
    void test() {
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName("oracle.jdbc.driver.OracleDriver");
        ds.setUrl("jdbc:oracle:thin:@localhost:1521:mydb");
        ds.setUsername("scott");
        ds.setPassword("tiger");

        MyStoredProcedure sproc = new MyStoredProcedure(ds);
        Map res = sproc.execute();
        printMap(res);
    }

    private class MyStoredProcedure extends StoredProcedure {
        public static final String SQL = "sysdate";

        public MyStoredProcedure(DataSource ds) {
            setDataSource(ds);
            setFunction(true);
            setSql(SQL);
            declareParameter(new SqlOutParameter("date", Types.DATE));
            compile();
        }

        public Map execute() {
            Map out = execute(new HashMap());
            return out;
        }

    }

    private static void printMap(Map r) {
        Iterator i = r.entrySet().iterator();
        while (i.hasNext()) {
            System.out.println((String) i.next().toString());  
        }
    }
}

10.4.5. SqlFunction

결과의 하나의 레코드를 반환하는 쿼리를 위한 SQL "함수" 랩퍼. 디폴트 행위는 int를 반환하는 것이지만 추가적인 반환 타입 파라미터를 가진 메소드를 사용해서 오버라이드 할수 있다. 이것은 JdbcTemplatequeryForXxx 메소드를 사용하는것이 유사하다. SqlFunction 이 가진 장점은 JdbcTemplate 을 생성할 필요가 없다는 것이다. 이것은 상태(scenes)뒤에서 행해진다.

이 클래스는"select user()" 나 "select sysdate from dual" 와 같은 쿼리를 사용해서 하나의 결과를 반환하는 SQL함수들을 호출하는 것을 사용하는 경향이 있다. 이것은 좀더 복잡한 저장 프로시저를 호출하거나 저장 프로시저나 저장 함수를 호출하기 위한 CallableStatement 를 사용하는 경향은 아니다. 이러한 타입의 처리를 위해 StoredProcedureSqlCall 을 사용하라.

이것은 하위 클래스에 알반적으로 필요하지 않은 명확한 클래스이다. 이 패키지를 사용하는 코드는 이 타입의 객체를 생성하고 SQL과 파라미터를 선언하고 함수를 수행하기 위해 반복적으로 선호하는 run메소드를 호출 할수 있다. 이것은 테이블로 부터 레코드의 카운트를 가져오는 예제이다.

    public int countRows() {
        SqlFunction sf = new SqlFunction(dataSource, "select count(*) from mytable");
        sf.compile();
        return sf.run();
    }

Chapter 11. 객체-관계 연결자(O/R Mappers)를 이용한 데이터 접근

11.1. 소개

Spring은 자원관리측면에서 Hibernate, JDO, iBATIS SQL Maps, DAO구현 지원, 트랜잭션 전략등의 통합을 제공한다. Hibernate를 위해서 많은 IoC편리 기능과 많은 전형적인 Hibernate통합 이슈를 할당하는 첫번째 클래스가 있다. 여기의 모든것은 Spring의 일반적인 트랜잭션와 DAO exception구조를 따른다.

Spring은 데이터접근 애플리케이션을 생성하기 위해 당신이 선택한 O/R mapping레이어를 사용할때 중요한 지원을 추가한다. 그중에 첫번째는 당신은 O/R맵핑을 위해서 Spring에서 제공하는것을 사용해서 시작해야 한다는것을 알아야만 한다. 당신은 모든것을 해야 할 필요는 없다. 확장하는것과는 상관없이 당신은 다시보기 위해서 초대되었고 Spring접근에 영향을 끼친다. 유사한 하부조직을 만드는 노력과 위험을 가지는데 대해 결정을 먼저해야 한다. 대부분의 O/R맵핑지원은 라이브러리 스타일내에서 사용되는 기술과는 상관없이 모든것은 재사용가능한 자바빈처럼 디자인 되었다. ApplicationContext나 BeanFactory내부에서의 사용은 설정과 배치의 쉬움이라는 면에서 추가적인 이득을 제공한다. 이 섹션의 예제들은 Applicationcontext내에서 설정이 됨을 보여준다.

다음은 O/R맵핑 애플리케이션을 생성하기 위해 Spring을 사용함으로써 발생하는 몇몇 장점들이다.

  • 업체에 종속적인 락(lock-in)방식을 피할수 있고 mix-and-match구현방식을 허락한다. Hibernate는 강력하고 확장이 용이하며 오픈소스이고 free하다. 이것은 여전히 자신만의 API를 사용한다. 더군다나 누구는 iBATIS가 좀더 가볍다는 것에 대해서 논쟁을 벌일수도 있다. 복잡한 O/R맵핑 전략을 요구하지 않는 애플리케이션내에서의 사용은 매우 환상적이다. 주어진 선택에서 이것은 기능과 성능 그리고 다른 어떠한 이유로 다른 구현으로 교체해야 할 경우에 언제나 표준적이고 추상적인 API들을 사용해서 주요한 애플리케이션 기능을 구현하도록 바랄것이다. 예를 들면 Spring의 Hibernate트랜잭션과 exception의 추상화는 데이터접근 기능을 구현하고 있는 맵퍼/DAO객체내에서 쉽게 교환할수 있도록 하는 IoC접근으로 Hibernate의 어떠한 성능적 손실없이 당신의 애플리케이션내에서 모든 Hibernate관련 코드를 쉽게 분리하도록 한다. DAO와 함께 처리되는 높은 단계의 서비스 코드는 그 구현에 대해서 어떤것도 알필요가 없다. 이 접근은 mix-and-match접근으로 방해가 되지 않은 방식내에서 의도적으로 데이터접근을 쉽게 구현하도록 만든다는 추가적인 이익을 가져다 준다. 잠재적으로 기존코드를 계속적으로 사용하게 하는것과 각각의 기술의 강력함을 그대로 유지시킨다는 큰 이익을 제공하기도 한다.

  • 테스트의 용이함(ease) Spring의 IoC접근은 Hibernate세션 팩토리(session factories)의 구현과 위치, DataSource, 트랜잭션 관리자 그리고 맵퍼객체 구현을 쉽게 교체할수 있게 만든다. 이것은 격리내에서 각각의 영속성과 관련된 코드를 쉽게 격리시키고 테스트 할수 있게 만든다.

  • 일반적인 자원 관리 Spring애플리케이션 컨텍스트는 Hibernate SessionFactories, JDBC datasource, iBATIS SQLMaps설정 객체, 그리고 다른 관련 자원의 위치와 설정을 다룰수 있다. 이것은 그런 값들을 쉽게 관리하고 변경할수 있게 만든다. Spring은 효율적이고 쉽고 안전하게 Hibernate Sessions을 다룰수 있는 기능을 제공한다. Hibernate를 사용하는 관련코드는 효율적이고 적합한 트랜잭션 관리를 위해 Hibernate Session객체를 사용할 필요가 있다. Spring은 각각의 선언과 AOP메소드 접근, 명시, 자바코드단계에서 탬플릿 래퍼 클래스를 사용해서 현재 쓰레드에 투몀하게 session을 생성하고 바인딩하는것을 쉽게 만든다. 게다가 Spring은 Hibernate포럼에 반복적으로 올라오는 많은 사용상의 이슈를 쉽게 풀어놓았다.

  • Exception wrapping Spring은 선택한 O/R맵핑 툴로부터 exception을 제어할수 있다. 선호하고 체크된 exception으로 부터 추상화된 런타임exception으로 변형할수 있다. 이것은 당신에게 대부분의 복수할수 없고 선호하는 단게에서만 발생하는 짜증나는 반복적인 catches/throws구문과 exception선언 없이 영속성관련 exception을 다룰수 있도록 한다. 당신은 당신이 필요한 어느곳에서든지 exception을 잡아서 처리할수 있다. JDBC exception들 또한 당신이 일관적인 프로그래밍 모델내에서 JDBC를 가지고 몇몇 기능을 수행할수 있다는것을 의미하는 같은 구조로 변환이 가능하다는것을 기억하라.

  • 통합된 트랜잭션 관리 Spring은 선언, AOP스타일의 메소드 인터셉터, 또는 자바코드 단계에서 명백한 'template'래퍼 클래스를 가지고 O/R맵핑 코드를 만들수 있다. 이런 경우에 트랜잭션의미는 당신을 위해서 다루어지고 exception이 관리되는 경우에 명백하게 트랜잭션이 다루어진다. 밑에 논의되는 것처럼 당신은 Hibernate관련 코드에 영향이 없이 다양한 트랜잭션 관리자를 사용하거나 교체할수 있게 되는 장점또한 가지게 된다. 그리고 추가되는 장점은 JDBC관련 코드가 O/R맵핑을 사용하는 코드와 완벽하게 트랜잭션적으로 통합이 될수 있다. 이것은 예를 들면 Hibernate나 iBATIS내에서 구현되지 않은 기능을 다룰 경우에 유용하다.

11.2. Hibernate

11.2.1. 자원 관리

전형적인 비지니스 애플리케이션은 종종 반복적인 자원 관리 코드가 소스를 뒤죽박죽 만든다. 많은 프로젝트를 이런일을 위해서 자신만의 솔루션을 만들기를 시도한다. 때때로 프로그래밍의 편의성을 위한 명백한 실패의 제어를 희생하기도 한다. Spring은 template을 통한 IoC 즉 callback인터페이스와 함께 하부구조 클래스, 또는 AOP인터셉터 적용등으로 명백한 자원 관리를 위한 간단한 해결법을 제공한다. 하부구조는 명백한 자원 핸들링을 하고 체크되지 않은 하부구조 exception구조를 특정 API exception의 적합하게 변환한다. Spring은 어떠한 데이터 접근 전략에도 적용가능한 DAO exception구조를 소개한다. JDBC를 사용하기 위해서는 JdbcTemplate클래스가 connection핸들링을 위해 이전 섹션에서 언급되었고 SQLException이 데이터베이스에 종속적인 SQL에러코드를 의미있는 exception클래스로 해석하는것을 포함하는 DataAccessException구조로 변환된다. 이것은 각각의 Spring트랜잭션 관리자를 통해서 JTA와 JDBC트랜잭션을 지원한다. Spring은 JdbcTemplate와 유사한 HibernateTemplate/JdoTemplate, HibernateInterceptor/JdoInterceptor 그리고 Hibernate/JDO트랜잭션 관리자로 구성된 Hibernate와 JDO지원을 제공한다. 이것의 커다란 목표는 어떠한 데이터 접근와 트랜잭션 기술을 가지고 깔끔한 애플리케이션 계층화가 애플리케이션 객체의 느슨한 커플링된 상태에서 가능하도록 하는것이다. 데이터 접근과 트랜잭션 전략에서 더이상 비지니스 객체의 의존성 문제가 없고 더 이상 하드코딩형 자원탐색이 없으며, 더 이상 hard-to-replace싱글톤과 고객 서비스 등록자가 없는것이다. 애플리케이션 객체를 묶는 간단하고 일관적인 접근은 가능한 한 컨테이너 의존성으로 부터 재사용가능하고 free하게 유지시켜준다. 모든 개별적인 데이터 접근 기능은 그들 자신에게는 재사용가능하지만 Spring을 알 필요가 없는 XML기반의 설정과 상호간에 참조되는 자바빈 인스턴스를 제공하는 Spring의 애플리케이션 컨텍스트 개념과 함께 잘 통합된다. 전형적인 Spring애플리케이션에서 많은 중요한 객체(데이터 접근 탬플릿, 탬플릿을 사용하는 데이터 접근 객체, 트랜잭션 관리자, 데이터 접근객체와 트랜잭션 관리자를 사용하는 비지니스 객체, 웹의 화면 해설자(resolvers), 비지니스 객체를 사용하는 웹 컨트롤러 등등)는 자바빈이다.

11.2.2. 애플리케이션 컨텍스트내에서 자원 정의

하드코딩형 자원 탐색을 위한 애플리케이션 생성을 피하기 위해서 Spring은 애플리케이션 컨텍스트내에 빈즈처럼 JDBC DataSource나 Hibernate SessionFactory처럼 자원 정의를 하게 한다. 애플리케이션 객체는 빈참조를 통해 미리 선언된 인스턴스에 참조를 받은 자원에 접근하기 위해 필요한 것이다. 다음의 XML애플리케이션 컨텍스트 선언으로 부터 발췌는 JDBC DataSource와 Hibernate SessionFactory를 설정하는 방법을 보여준다.

<beans>

    <bean id="myDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName">
            <value>java:comp/env/jdbc/myds</value>
        </property>
    </bean>

   <bean id="mySessionFactory" class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
       <property name="mappingResources">
           <list>
               <value>product.hbm.xml</value>
           </list>
       </property>
       <property name="hibernateProperties">
           <props>
               <prop key="hibernate.dialect">net.sf.hibernate.dialect.MySQLDialect</prop>
           </props>
       </property>
       <property name="dataSource">
           <ref bean="myDataSource"/>
       </property>
   </bean>

   ...

</beans>

JNDI를 사용하는 DataSource에서 Jakarta Commons DBCP BasicDataSource처럼 로컬에 정의된 것으로 바꾸는것은 설정의 문제라는것을 기억하라.

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName">
        <value>org.hsqldb.jdbcDriver</value>
    </property>
    <property name="url">
        <value>jdbc:hsqldb:hsql://localhost:9001</value>
    </property>
    <property name="username">
        <value>sa</value>
    </property>
    <property name="password">
        <value></value>
    </property>
</bean>

당신은 또한 JNDI를 사용하는 SessionFactory를 사용할수도 있지만 전형적으로 EJB컨텍스트 외부에서는 필요하지 않다.

11.2.3. Inversion of Control: Template and Callback

탬플릿팅을 위한 기본적인 프로그래밍 모델은 어떤 데이터접근 객체나 비지니스 객체의 부분이 될수 있는 메소드를 위해 다음처럼 볼수 있다. 펼쳐진 모든 객체의 구현에서 제한은 없다. 이것은 Hibernate의 SessionFactory을 제공할 필요가 있다. 이것은 어디서든 나중에 얻을수 있지만 간단한 setSessionFactory 빈 속성 setter을 통해 Spring애플리케이션 컨텍스트로 부터 빈처럼 참조할것이다. 다음의 작은 조각(snippets)은 Spring애플리케이션 컨텍스트내에서 DAO선언을 보여준다. 위에서 선언된 SessionFactory를 참조하고 있고 DAO메소드 구현을 위한 에제이다.

<beans>

    <bean id="myProductDao" class="product.ProductDaoImpl">
        <property name="sessionFactory">
            <ref bean="mySessionFactory"/>
        </property>
    </bean>

    ...

</beans>
public class ProductDaoImpl implements ProductDao {

    private SessionFactory sessionFactory;

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    public List loadProductsByCategory(final String category) {
        HibernateTemplate hibernateTemplate =
            new HibernateTemplate(this.sessionFactory);

        return (List) hibernateTemplate.execute(
            new HibernateCallback() {
                public Object doInHibernate(Session session) throws HibernateException {
                    List result = session.find(
                        "from test.Product product where product.category=?",
                        category, Hibernate.STRING);
                    // do some further stuff with the result list
                    return result;
                }
            }
        );
    }
}

callback구현은 어떤 Hibernate데이터 접근을 위해 사용되는데 영향을 끼칠수 있다. HibernateTemplateSession들이 명백하게 열고 닫고 트랜잭션내에서 자동적으로 함께하는것을 확실시한다. 이 탬플릿 인스턴스는 쓰레드에 안전하고(thread-safe) 재사용가능하다. 그들은 주위 클래스의 인스턴스 변수처럼 유지될수 있다. 하나의 검색, 로드, saveOrUpdate또는 삭제 호출처럼 간단한 한단계의 작업은 HibernateTemplate이 대안적으로 편리한 한라인 callback구현처럼 대체될수 있는 메소드를 제공한다. 게다가 Spring은 SessionFactory를 받기위한 setSessionFactory메소드를 제공하는 편리한 HibernateDaoSupport base클래스를 제공한다. 그리고 하위클래스에 의해 사용되기 위한 getSessionFactorygetHibernateTemplate를 제공한다. 이것은 전형적인 요구사항을 위해 매우 간단한 DAO구현을 허락한다.

public class ProductDaoImpl extends HibernateDaoSupport implements ProductDao {

    public List loadProductsByCategory(String category) {
        return getHibernateTemplate().find(
            "from test.Product product where product.category=?", category,
            Hibernate.STRING);
    }
}

11.2.4. 탬플릿 대신에 AOP인터셉터 적용하기.

HibernateTemplate 을 사용하는것에 대한 대안으로는 위임하는 try/catch블럭내에서 Hibernate코드와 함께 callback구현과 애플리케이션 컨텍스트내의 각각의 인터셉터 설정을 대체하는 Spring의 AOP HibernateInterceptor를 사용하는 것이다. 다음의 조각(snippets)은 각각의 DAO, 인터셉터 그리고 Spring애플리케이션 컨텍스트내의 프록시 선언을 보여주고 DAO메소드 구현의 예를 보여준다.

<beans>

    ...

    <bean id="myHibernateInterceptor" 
        class="org.springframework.orm.hibernate.HibernateInterceptor">
        <property name="sessionFactory">
            <ref bean="mySessionFactory"/>
        </property>
    </bean>

    <bean id="myProductDaoTarget" class="product.ProductDaoImpl">
        <property name="sessionFactory">
            <ref bean="mySessionFactory"/>
        </property>
    </bean>

    <bean id="myProductDao" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="proxyInterfaces">
            <value>product.ProductDao</value>
        </property>
        <property name="interceptorNames">
            <list>
                <value>myHibernateInterceptor</value>
                <value>myProductDaoTarget</value>
            </list>
        </property>
    </bean>

    ...

</beans>
public class ProductDaoImpl extends HibernateDaoSupport implements ProductDao {

    public List loadProductsByCategory(final String category) throws MyException {
        Session session = SessionFactoryUtils.getSession(getSessionFactory(), false);
        try {
            List result = session.find(
                "from test.Product product where product.category=?",
                category, Hibernate.STRING);
            if (result == null) {
                throw new MyException("invalid search result");
            }
            return result;
        }
        catch (HibernateException ex) {
            throw SessionFactoryUtils.convertHibernateAccessException(ex);
        }
    }
}

이 메소드는 먼저 쓰레드 범위의 세션을 열고 메소드 호출 후에는 이것을 닫는 것을 위해 단지 HibernateInterceptor와 작업을 수행한다. getSession의 false플래그 Session이 벌써 존재한다는 것을 확신하는것이다. 반면에 SessionFactoryUtils는 아무것도 발견되지 않는다면 새로운 Session을 생성할 것이다. 만약에 쓰레드에 바운드하는 SessionHolder이 존재한다면 예를 들면 HibernateTransactionManager트랜잭션에 의해 SessionFactoryUtils이 자동적으로 어떠한 경우에든 일부를 가져온다. HibernateTemplate은 내부적으로 SessionFactoryUtils를 사용한다. 이것은 모두 같은 하부구조이다. HibernateInterceptor의 가장 큰 장점은 HibernateTemplate이 callback내에서 체크되지 않은 exception에 제한되는 동안 데이터접근 코드내에서 던져지기 위한 어떤 체크된 애플리케이션 exception을 허락한다는 것이다. 어떤것은 종종 개별적인 체크와 callback후에 애플리케이션의 exception의 발생을 미룰수도 있다. 인터셉터의 중요한 결점은 컨텍스트에서 특별한 셋팅이 필요하다는 것이다. HibernateTemplate의 편리한 메소드는 많은 경우를 위해 좀더 간단한 의미를 제공한다.

11.2.5. 프로그램의 트랜잭션 구분(Demarcation)

그런 하위 레벨의 데이터 접근 서비스의 가장 상위에서 트랜잭션은 애플리케이션의 더 놓은 레벨내에서 구분될수 있다. 여기서는 또한 비지니스 객체의 구현에서 어떠한 제한도 없다. 이것은 단지 Spring의 PlatformTransactionManager만을 필요로 한다. 나중에 어디서부터든지 올수(호출할수?) 있지만 마치 productDAOsetProductDao메소드를 통해서 생성이 되듯이 setTransactionManager메소드를 통해 빈 참조처럼 될수도 있다. 다음의 조각(snippets)은 트랜잭션 관리자와 Spring 애플리케이션 컨텍스트내에서 비지니스 객체 선언을 보여준다. 그리고 비지니스 메소드의 구현을 위한 예제를 보여준다.

<beans>

    ...

    <bean id="myTransactionManager" 
        class="org.springframework.orm.hibernate.HibernateTransactionManager">
        <property name="sessionFactory">
            <ref bean="mySessionFactory"/>
        </property>
    </bean>

    <bean id="myProductService" class="product.ProductServiceImpl">
        <property name="transactionManager">
            <ref bean="myTransactionManager"/>
        </property>
        <property name="productDao">
            <ref bean="myProductDao"/>
        </property>
    </bean>

</beans>
public class ProductServiceImpl implements ProductService {

    private PlatformTransactionManager transactionManager;
    private ProductDao productDao;

    public void setTransactionManager(PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    public void setProductDao(ProductDao productDao) {
        this.productDao = productDao;
    }

    public void increasePriceOfAllProductsInCategory(final String category) {
        TransactionTemplate transactionTemplate = new TransactionTemplate(this.transactionManager);
        transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        transactionTemplate.execute(
            new TransactionCallbackWithoutResult() {
                public void doInTransactionWithoutResult(TransactionStatus status) {
                    List productsToChange = productDAO.loadProductsByCategory(category);
                    ...
                }
            }
        );
    }
}

11.2.6. 선언적인 트랜잭션 구분

대신에 누구는 애플리케이션 컨텍스트내에서 인터셉터 설정과 함께 트랜잭션 구분 코드를 대신할수 있는 Spring의 AOP TransactionInterceptor를 사용할수도 있다. 이것은 당신에게 비지니스 객체를 각각의 비지니스 메소드내에 반복적인 트랜잭션 구분코드의 free상태로 유지시키도록 허락한다. 게다가 전달행위와 격리레벨같은 트랜잭션 구문은 설정파일내에서 변경될수도 있다. 그리고 비지니스 객체 구현에는 어떠한 영향도 끼치지 않는다.

<beans>

    ...

    <bean id="myTransactionManager" 
        class="org.springframework.orm.hibernate.HibernateTransactionManager">
        <property name="sessionFactory">
            <ref bean="mySessionFactory"/>
        </property>
    </bean>

    <bean id="myTransactionInterceptor" 
        class="org.springframework.transaction.interceptor.TransactionInterceptor">
        <property name="transactionManager">
            <ref bean="myTransactionManager"/>
        </property>
        <property name="transactionAttributeSource">
            <value>
                product.ProductService.increasePrice*=PROPAGATION_REQUIRED
                product.ProductService.someOtherBusinessMethod=PROPAGATION_MANDATORY
            </value>
        </property>
    </bean>

    <bean id="myProductServiceTarget" class="product.ProductServiceImpl">
        <property name="productDao">
            <ref bean="myProductDao"/>
        </property>
    </bean>

    <bean id="myProductService" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="proxyInterfaces">
            <value>product.ProductService</value>
        </property>
        <property name="interceptorNames">
            <list>
                <value>myTransactionInterceptor</value>
                <value>myProductServiceTarget</value>
            </list>
        </property>
    </bean>

</beans>
public class ProductServiceImpl implements ProductService {

    private ProductDao productDao;

    public void setProductDao(ProductDao productDao) {
        this.productDao = productDao;
    }

    public void increasePriceOfAllProductsInCategory(final String category) {
        List productsToChange = this.productDAO.loadProductsByCategory(category);
        ...
    }

    ...

}

HibernateInterceptor와 함께 TransactionInterceptorTransactionTemplate이 callback내에서 체크되지 않은 exception에 제한적인 동안 callback코드내에 던져진 체크된 애플리케이션 exception을 허락한다. TransactionTemplate는 체크되지 않은 애플리케이션 exception의 경우이거나 애플리케이션에 의해 rollback-only일 경우에 롤백처리를 한다. TransactionTemplate은 체크되지 않은 애플리케이션 예외일 경우 롤백을 유발하거나 트랜잭션이 애플리케이션(TransactionStatus을 통해)에 의해 롤백만을 수행하도록 되어 있다면 TransactionInterceptor는 디폴트에 의해 같은 방식으로 작동하지만 메소드별로 설정가능한 롤백 정책을 허락한다. 선언적인 트랜잭션 셋팅방법의 편리한 대안은 TransactionProxyFactoryBean이다. 특히 다른 어떠한 AOP인터셉터가 포함되지 않았다면 더욱 그렇다. TransactionProxyFactoryBean은 특정 목표 빈을 위한 트랜잭션 설정과 함께 자신의 프록시 정의를 조합한다. 이것은 하나의 목표 빈에 하나의 프록시 빈을 더하는 설정상의 수고를 제거한다. 게다가 당신은 전통적인 메소드가 정의되는 인터페이스나 클래스를 정의 할 필요가 없다.

<beans>

    ...

    <bean id="myTransactionManager" 
        class="org.springframework.orm.hibernate.HibernateTransactionManager">
        <property name="sessionFactory">
            <ref bean="mySessionFactory"/>
        </property>
    </bean>

    <bean id="myProductServiceTarget" class="product.ProductServiceImpl">
        <property name="productDao">
            <ref bean="myProductDao"/>
        </property>
    </bean>

    <bean id="myProductService" 
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager">
            <ref bean="myTransactionManager"/>
        </property>
        <property name="target">
            <ref bean="myProductServiceTarget"/>
        </property>
        <property name="transactionAttributes">
            <props>
                <prop key="increasePrice*">PROPAGATION_REQUIRED</prop>
                <prop key="someOtherBusinessMethod">PROPAGATION_MANDATORY</prop>
            </props>
        </property>
    </bean>

</beans>

11.2.7. 트랜잭션 관리 전략

TransactionTemplateTransactionInterceptor는 Hibernate애플리케이션을 위한 HibernateTransactionManager(ThreadLocal Session을 사용하는 하나의 Hibernate SessionFactory를 위한)나 JtaTransactionManager(컨테이너의 JTA하위 시스템으로 위임하는) 가 될수 있는 PlatformTransactionManager인스턴스로 실질적인 트랜잭션 핸들링을 위임한다. 당신은 사용자 정의 PlatformTransactionManager구현을 사용할수도 있다. 그래서 근본적인 Hibernate트랜잭션관리로 부터 JTA로의 전환(예를 들면 당신의 애플리케이션의 어떠한 배치작업을 위한 분산된 트랜잭션 요구사항에 직면했을때)은 설장상의 문제가 된다. Spring의 JTA트랜잭션 구현으로 Hibernate 트랜잭션 관리자를 간단하게 대신한다. 트랜잭션 구분과 데이터 접근 코드는 변경없이 작동할 것이다. 그리고 그들은 일반적인 트랜잭션 관리 API들을 사용한다. 다중 Hibernate session factories를 통한 분산된 트랜잭션을 위해 다중 LocalSessionFactoryBean정의와 함께 트랜잭션 전략처럼 JtaTransactionManager을 간단하게 조합한다. 각각의 DAO들은 그것의 개별적인 빈 프라퍼티로 전달된 하나의 특정 SessionFactory참조를 얻게된다. 모든 근본적인 JDBC데이터 소스는 트랜잭션적인 컨테이너이다. 비지니스 객체는 전략으로 JtaTransactionManager을 사용하는 한 많은 DAO와 특정 고려없는 많은 session factory를 통해 트랜잭션의 경계를 지정할수 있다.

<beans>

    <bean id="myDataSource1" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName">
            <value>java:comp/env/jdbc/myds1</value>
        </property>
    </bean>

    <bean id="myDataSource2" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName">
            <value>java:comp/env/jdbc/myds2</value>
        </property>
    </bean>

    <bean id="mySessionFactory1" class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
        <property name="mappingResources">
            <list>
                <value>product.hbm.xml</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">net.sf.hibernate.dialect.MySQLDialect</prop>
            </props>
        </property>
        <property name="dataSource">
            <ref bean="myDataSource1"/>
        </property>
    </bean>

    <bean id="mySessionFactory2" class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
        <property name="mappingResources">
            <list>
                <value>inventory.hbm.xml</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">net.sf.hibernate.dialect.OracleDialect</prop>
            </props>
        </property>
        <property name="dataSource">
            <ref bean="myDataSource2"/>
        </property>
    </bean>

    <bean id="myTransactionManager" 
        class="org.springframework.transaction.jta.JtaTransactionManager"/>

    <bean id="myProductDao" class="product.ProductDaoImpl">
        <property name="sessionFactory">
            <ref bean="mySessionFactory1"/>
        </property>
    </bean>

    <bean id="myInventoryDao" class="product.InventoryDaoImpl">
        <property name="sessionFactory">
            <ref bean="mySessionFactory2"/>
        </property>
    </bean>

    <bean id="myProductServiceTarget" class="product.ProductServiceImpl">
        <property name="productDao">
            <ref bean="myProductDao"/>
        </property>
        <property name="inventoryDao">
            <ref bean="myInventoryDao"/>
        </property>
    </bean>

    <bean id="myProductService" 
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager">
            <ref bean="myTransactionManager"/>
        </property>
        <property name="target">
            <ref bean="myProductServiceTarget"/>
        </property>
        <property name="transactionAttributes">
            <props>
                <prop key="increasePrice*">PROPAGATION_REQUIRED</prop>
                <prop key="someOtherBusinessMethod">PROPAGATION_MANDATORY</prop>
            </props>
        </property>
    </bean>

</beans>

HibernateTransactionManagerJtaTransactionManager는 트랜잭션 관리자 룩업이나 JCA연결자(트랜잭션을 초기화하기 위해 EJB를 사용하지 않는 한)를 정의하는 컨테이너 없이 Hibernate를 사용하여 적당한 JVM레벨의 캐시 핸들링을 허락한다. 추가적으로 HibernateTransactionManager는 평범한 JDBC접근 코드를 위해 Hibernate의해 사용되는 JDBC connection을 추출할수 있다. 이것은 하나의 데이터베이스에 접근하는 한 JTA없이 완벽한 Hibernate/JDBC혼합 접근으로 높은 레벨의 트랜잭션 구분을 허락한다.

선언적으로 트랜잭션 경계를 구분하기 위한 TransactionProxyFactoryBean을 사용하기 위해서 사용하는 접근법의 대안을 위해서 Section 7.4.1, “BeanNameAutoProxyCreator, 또 다른 선언적 접근방법”를 보라.

11.2.8. 컨테이너 자원 대 로컬 자원

Spring의 자원 관리는 애플리케이션 코드의 한줄의 변경도 없이 JNDI SessionFactory와 JNDI DataSource와 같은 로컬 SessionFactory사이의 간단한 전환를 허락한다. 컨테이너내 자원 정의를 유지하거나 애플리케이션 내 로컬 상태로 유지하더라도 사용되는 트랜잭션 전략의 주요한 문제이다. Spring정의 로컬 SessionFactory에 비교하여 수동으로 등록된 JNDI SessionFactory는 어떠한 이득도 제공하지 않는다. Hibernate의 JCA연결자를 통해서 등록되었다면 특히 EJB내에서 JTA트랜잭션 내 투명하게 참여한 추가된 값이 있다. Spring의 트랜잭션 지원의 중요한 이득은 컨테이너에 전혀 바운드 되지 않는 것이다. JTA가 아닌 다른 전략에 설정하는 것은 단독으로 작동하거나 테스트 환경에서도 잘 작동할것이다. 하나의 데이터베이스 트랜잭션의 전형적인 경우를 위해 특별히 이것은 가볍고 JTA에 강력한 대안이다. 트랜잭션을 다루기 위해 로컬 EJB 비상태 유지 세션빈을 사용할때 당신은 비록 하나의 데이터베이스만을 사용하고 CMT를 통해 선언적인 트랜잭션을 위해 SLSB를 사용하더라도 EJB컨테이너와 JTA에 모두 의존한다. 프로그램적으로 JTA를 사용하는것의 대안도 J2EE환경을 요구한다. JTA는 JTA와 JNDI DataSource의 개념에서 컨테이너 의존성을 포함하지 않는다. Spring을 사용하지 않는 JTA에 의도한 Hibernate트랜잭션을 위해 당신은 적당한 JVM레벨의 캐시를 위해 Hibernate JCA연결자를 사용하거나 JTATransaction이 설정된 추가적인 Hibernate트랜잭션 코드를 사용해야 한다. Spring에 의도한 트랜잭션은 만약 하나의 데이터베이스에 접근한다면 로컬 JDBC DataSource처럼 로컬에 정의된 Hibernate SessionFactory와 잘 작동할수 있다. 그러므로 당신은 분산 트랜잭션 요구사항에 실질적으로 직면했을때 Spring의 JTA트랜잭션 전략으로 물러나야 한다. JCA연결자는 컨테이너 특유의 배치단계를 필요로 하고 명백하게 첫번째 단계에서 JCA지원을 필요로 한다. 이것은 로컬 자원 정의와 Spring이 의도한 트랜잭션과 함께 간단한 웹 애플리케이션을 배치하는것보다 더 괴롭다. 그리고 당신은 종종 컨테이너의 기업용 버전(예를 들면 웹로직 익스프레스 버전은 JCA를 제공하지 않는다.)을 필요로 한다. 로컬 자원과 하나의 데이터베이스를 확장하는 트랜잭션을 가진 Spring애플리케이션은 Tomcat, Resin, 또는 Jetty와 같은 어떠한 J2EE 웹 컨테이너(JTA, JCA, 또는 EJB 없이)내에서도 작동한다. 추가적으로 미들티어같은 것은 데스크탑 애플리케이션이나 테스트 슈트를 쉽게 재사용할수 있다. 모든것을 고려해서 당신이 EJB를 사용하지 않는다면 로컬 SessionFactory 셋팅과 Spring의 HibernateTransactionManagerJtaTransactionManager에 충실하라. 당신은 어떠한 컨테이너 배치의 귀찮음 없이 적당한 트랜잭션적인 JVM레벨의 캐싱과 분산 트랜잭션을 포함한 모든 이득을 가질것이다. JCA연결자를 통한 Hibernate SessionFactory의 JNDI등록은 EJB를 사용하기 위해 단지 값만 추가한다.

11.2.9. 샘플들

Spring배포판의 Petclinic샘플은 DAO구현물과 Hibernate, JDBC, 그리고 아파치 OJB를 위한 애플리케이션 컨텍스트 설정의 대안을 제공한다. Petclinic은 Spring 웹 애플리케이션내 Hibernate의 사용을 묘사하는 샘플 애플리케이션처럼 제공한다. 이것은 다른 트랜잭션 전략으로 선언적인 트랜잭션 구분에 영향을 끼친다.

11.3. JDO

ToDo

11.4. iBATIS

org.springframework.orm.ibatis패키지를 통해 Spring은 iBATIS SqlMaps 1.3.x 과 2.0.x을 지원한다. iBATIS지원은 Hibernate처럼 템플릿 스타일 프로그래밍을 지원하는 면에서 Hibernate지원과 많은 공통점을 가진다. iBATIS지원은 Spring의 예외구조와 함께 작동하고 당신은 Spring이 가지는 모든 IoC특징을 즐기자.

11.4.1. 1.3.x and 2.0 사이의 개요와 차이점

Spring은 iBATIS SqlMaps 1.3 과 2.0 모두를 지원한다. 첫번째 둘 사이의 차이점을 보자.

xml설정 파일이 노드와 속성명에서 조금 변경되었다. 또한 당신이 확장할 필요가 있는 Spring클래스는 몇몇 메소드 명처럼 다르다.

Table 11.1. 1.3 과 2.0을 위한 iBATIS SqlMaps 지원 클래스

특징 1.3.x 2.0
SqlMap의 생성 SqlMapFactoryBean SqlMapClientFactoryBean
템플릿 스타일의 헬퍼 클래스 SqlMapTemplate SqlMapClientTemplate
MappedStatement를 사용하기 콜백 SqlMapCallback SqlMapClientCallback
DAO를 위한 수퍼 클래스 SqlMapDaoSupport SqlMapClientDaoSupport

11.4.2. iBATIS 1.3.x

11.4.2.1. SqlMap을 셋업하기

iBATIS SQLMaps를 사용하는 것은 statement와 result map들을 포함하는 SQLMaps설정파일을 생성하는것을 포함한다. Spring은 SqlMapFactoryBean을 사용하여 이것들을 로드하는것을 처리한다.

		
public class Account {
	private String name;
	private String email;
	
	public String getName() {
		return this.name;
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	public String getEmail() {
		return this.email;
	}
	
	public void setEmail(String email) {
		this.email = email;
	}
}

우리가 이 클래스를 맵핑하길 원한다고 가정하자. 우리는 다음의 SQLMaps를 생성한다. 쿼리를 사용하여 우리는 그들의 이메일 주소를 통해 나중에 사용자를 가져올수 있다. Account.xml:

<sql-map name="Account">
	<result-map name="result" class="examples.Account">
		<property name="name" column="NAME" columnIndex="1"/>
		<property name="email" column="EMAIL" columnIndex="2"/>
	</result-map>
	
	<mapped-statement name="getAccountByEmail" result-map="result">
		select
			  ACCOUNT.NAME,
			  ACCOUNT.EMAIL
		from ACCOUNT
		where ACCOUNT.EMAIL = #value#
	</mapped-statement>
	
	<mapped-statement name="insertAccount">
		insert into ACCOUNT (NAME, EMAIL) values (#name#, #email#)
	</mapped-statement>
</sql-map>

Sql Map을 정의한 후에 우리는 iBATIS를 위한 설정파일(sqlmap-config.xml)을 생성해야만 한다.

<sql-map-config>

	<sql-map resource="example/Account.xml"/>

</sql-map-config>

iBATIS는 클래스패스로 부터 자원을 로드한다. 그래서 클래스패스 어딘가에 Account.xml파일을 추가하라.

Spring을 사용할때 우리는 SqlMapFactoryBean을 사용해서 SQLMaps를 매우 쉽게 셋업할수 있다.

<bean id="sqlMap" class="org.springframework.orm.ibatis.SqlMapFactoryBean">
	<property name="configLocation"><value>WEB-INF/sqlmap-config.xml</value></property>
</bean>

11.4.2.2. SqlMapDaoSupport 사용하기

SqlMapDaoSupport클래스는 HibernateDaoSupportJdbcDaoSupport타입과 유사한 지원 클래스를 제공한다. DAO를 구현하자.

public class SqlMapAccountDao extends SqlMapDaoSupport implements AccountDao {

	public Account getAccount(String email) throws DataAccessException {
		return (Account) getSqlMapTemplate().executeQueryForObject("getAccountByEmail", email);
	}

	public void insertAccount(Account account) throws DataAccessException {
		getSqlMapTemplate().executeUpdate("insertAccount", account);
	}
}

당신이 볼수 있는것처럼 우리는 쿼리를 수행하기 위해 SqlMapTemplate을 사용한다. Spring은 SqlMapFactoryBean을 사용하기 위해 SQLMap을 초기화하고 다음처럼 SqlMapAccountDao를 셋업할때 당신은 다음처럼 셋팅한다.

<!-- for more information about using datasource, have a look at the JDBC chapter -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
	<property name="driverClassName"><value>${jdbc.driverClassName}</value></property>
	<property name="url"><value>${jdbc.url}</value></property>
	<property name="username"><value>${jdbc.username}</value></property>
	<property name="password"><value>${jdbc.password}</value></property>
</bean>

<bean id="accountDao" class="example.SqlMapAccountDao">
	<property name="dataSource"><ref local="dataSource"/></property>
	<property name="sqlMap"><ref local="sqlMap"/></property>
</bean>

11.4.2.3. 트랜잭션 관리

iBATIS를 사용하는 애플리케이션을 위해 선언적인 트랜잭션 관리를 추가하는 것은 쉽다. 당신이 해야할 필요가 있는 한가지는 애플리케이션 컨텍스트에 트랜잭션 관리자를 추가하고 TransactionProxyFactoryBean예제를 위해 선언적으로 당신의 트랜잭션 경계를 셋팅하는 것이다. 이것에 대한 좀더 다양한 정보는 Chapter 7, 트랜잭션 관리에서 찾을수 있다.

TODO elaborate!

11.4.3. iBATIS 2

11.4.3.1. SqlMap 셋업하기

우리는 iBATIS 2를 사용해서 앞의 Account를 맵핑하기를 원한다면 우리는 다음의 SQLMaps Account.xml을 생성할 필요가 있다.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMap PUBLIC
  "-//iBATIS.com//DTD SQL Map 2.0//EN"
  "http://www.ibatis.com/dtd/sql-map-2.dtd">

<sqlMap namespace="Account">

	<resultMap id="result" class="examples.Account">
		<result property="name" column="NAME" columnIndex="1"/>
		<result property="email" column="EMAIL" columnIndex="2"/>
	</resultMap>

	<select id="getAccountByEmail" resultMap="result">
		select
			  ACCOUNT.NAME,
			  ACCOUNT.EMAIL
		from ACCOUNT
		where ACCOUNT.EMAIL = #value#
	</select>

	<insert id="insertAccount">
		insert into ACCOUNT (NAME, EMAIL) values (#name#, #email#)
	</insert>

</sqlMap>

iBATIS 2를 위한 설정파일(sqlmap-config.xml)은 극히 적은 부분만 바꾼다.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMapConfig PUBLIC
  "-//iBATIS.com//DTD SQL Map Config 2.0//EN"
  "http://www.ibatis.com/dtd/sql-map-config-2.dtd">

<sqlMapConfig>

	<sqlMap resource="example/Account.xml"/>

</sqlMapConfig>

iBATIS는 클래스패스로부터 자원을 로드한다는것을 기억하라. 그래서 클래스패스 어딘가에 Account.xml파일을 추가하는것을 확인하라.

We can use the SqlMapClientFactoryBean in the Spring application context :

<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
	<property name="configLocation"><value>WEB-INF/sqlmap-config.xml</value></property>
</bean>

11.4.3.2. SqlMapClientDaoSupport 사용하기

SqlMapClientDaoSupport클래스는 SqlMapDaoSupport와 유사한 지원 클래스를 제공한다. . 우리는 우리의 DAO를 구현하기 위해 이것을 확장한다.

public class SqlMapAccountDao extends SqlMapClientDaoSupport implements AccountDao {

	public Account getAccount(String email) throws DataAccessException {
		Account acc = new Account();
		acc.setEmail();
		return (Account)getSqlMapClientTemplate().queryForObject("getAccountByEmail", email);
	}

	public void insertAccount(Account account) throws DataAccessException {
		getSqlMapClientTemplate().update("insertAccount", account);
	}
}

DAO에서 우리는 애플리케이션 컨텍스트내에서 SqlMapAccountDao를 셋업한후에 쿼리를 수행하기 위해 SqlMapClientTemplate를 사용한다.

<!-- for more information about using datasource, have a look at the JDBC chapter -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
	<property name="driverClassName"><value>${jdbc.driverClassName}</value></property>
	<property name="url"><value>${jdbc.url}</value></property>
	<property name="username"><value>${jdbc.username}</value></property>
	<property name="password"><value>${jdbc.password}</value></property>
</bean>

<bean id="accountDao" class="example.SqlMapAccountDao">
	<property name="dataSource"><ref local="dataSource"/></property>
	<property name="sqlMapClient"><ref local="sqlMapClient"/></property>
</bean>

Chapter 12. 웹 MVC framework

12.1. 웹 MVC framework 소개

Spring의 웹 MVC framework는 설정가능한 핸들러 맵핑들, view분석, 로케일과 파일 업로드를 위한 지원같은 테마분석과 함께 핸들러에 요청을 할당하는 DispatcherServlet을 기반으로 디자인되었다. 디폴트 핸들러는 ModelAndView handleRequest(request,response)메소드를 제공하는 매우 간단한 컨트롤러 인터페이스이다. 이것은 이미 애플리케이션 컨트롤러를 위해 사용될수 있지만 AbstractController, AbstractCommandController그리고 SimpleFormController같은 것들을 포함한 구현 구조를 포함한것을 더 선호할것이다. 애플리케이션 컨트롤러들은 전형적으로 이것들의 하위 클래스가 된다. 당신이 선호하는 빈 클래스를 선택하도록 하라. 만약에 당신이 form을 가지지 않는다면 당신은 FormController가 필요없다. 이것이 Struts와 가장 큰 차이점이다.

당신은 커맨드나 form객체와 같은 어떤 객체를 사용할 수 있다. 여기엔 인터페이스를 구현하거나 base클래스로부터 가져올 필요가 없다. Spring의 데이터 바인딩은 매우 유연성이 좋다. 예를 들면 이것은 시스템 에러가 아닌 애플리케이션에 의해서 평가될수 있는 유효성 에러와 같은 타입 불일치를 처리할수 있다. 그래서 당신은 당신의 폼객체내에서 유효하지 않은 서브밋을 다룰수 있게 되거나 문자열 프라퍼티로 형변환하기 위해 문자열같은 비지니스 객체의 프라퍼티를 반복시킬 필요가 없다. 대신 이것은 종종 당신의 비지니스 객체에 직접적으로 바인드 하는것이 더 바람직하다. 이것은 액션의 모든 타입을 위한 ActionActionForm처럼 요구되는 기본 클래스가 도처에 빌드되는 Struts와의 가장 큰 차이점이다.

WebWork와 비교해서 Spring은 좀더 차별적인 객체 역할을 가진다. 이것은 컨트롤러, 옵션적인 커맨드나 폼 객체, 그리고 view에 전달되는 모델의 개념을 지원한다. 모델은 커맨드나 폼객체를 일반적으로 포함하지만 또한 임의의 참조 데이터도 포함한다. 대신에 WebWork 액션은 그러한 모든 역할을 하나의 객체로 조합한다. WebWork는 당신에게 당신의 폼 일부처럼 존재하는 비지니스 객체를 사용하도록 하지만 그것들을 만듬으로써 개별적인 Action클래스의 빈 프라퍼티가 된다. 마지막으로 요청을 다루는 같음 Action인스턴스는 view안의 평가와 폼 활성화를 위해서 사용된다. 게다가 참조 데이터는 Action의 빈 프라퍼티처럼 모델화 될 필요가 있다. 하나의 객체를 위해 어쩌면 너무 많은 역할이 있다.

Spring의 view분석은 매우 유연하다. 컨트롤러 구현물은 ModelAndView처럼 null을 반환하는 응답에 직접적으로 view를 작성할수 있다. 일반적인 경우에 ModelAndView인스턴스는 view이름과 빈 이름과 관련 객체(참조 데이터를 포함하는 커맨드또는 폼 같은)를 포함하는 모델 Map으루 구성된다. View이름 분석은 빈 이름, 프라퍼티 파일, 당신 자신의 ViewResolver구현물을 통해 쉽게 설정가능한 사항이다. 추상 모델 Map은 어떠한 귀찮은 작업 없이 view기술의 완벽한 추상화를 허락한다. 어떠한 표현자(renderer)는 JSP, Velocity 또는 다른 어떠한 표현 기술이든지 직접적으로 통합될수 있다. 모델 Map은 간단하게 JSP요청 속성또는 Velocity템플릿 모델과 같은 선호하는 형태로 변형된다.

12.1.1. 다른 MVC구현물의 플러그인 가능성

몇몇 프로젝트가 다른 MVC구현물을 사용하는것을 선호하는데는 여러가지 이유가 있다. 많은 팀은 업무적인 능력과 도구로 그들의 존재하는 투자물에 영향을 끼치도록 기대한다. 추가적으로 Struts프레임워크를 위해 유효한 지식과 경험의 많은 부분이 있다. 게다가 당신이 Struts의 구조적인 돌풍과 함께 한다면 이것은 웹 레이어를 위한 가치있는 선택이 될수 있다. 같은 것은 WebWork와 다른 웹 MVC프레임워크에 적용한다.

만약 당신이 Spring의 웹 MVC를 사용하길 원하지 않지만 Spring이 제공하는 다른 솔루션에 영향을 끼치는 경향이라면 당신은 당신이 선택한 웹 MVC프레임워크와 Spring을 쉽게 통합할수 있다. 이것의 ContextLoaderListener을 통해 Spring 루트 애플리케이션 컨텍스트를 간단하게 시작하고 Struts또는 WebWork액션내로 부터 이것의 ServletContext속성을 통해 접근한다. 어떠한 "plugins"도 포함되지 않았고 전용 통합이 필요하지 않다는 것에 주의하라. 웹 레이어의 관점에서 당신은 라이브러리처럼 간단하게 항목점(entry point)처럼 루트 애플리케이션 컨텍스트 인스턴스와 함께 Spring을 사용할 것이다.

당신의 등록된 모든 빈즈와 Spring의 모든 서비스는 Spring의 웹 MVC이 없더라도 쉽게 될수 있다. Spring은 이러한 시나리오로 Struts나 WebWork와 경쟁하지 않는다. 이것은 빈 설정으로 부터 데이터 접근과 트랜잭션 관리에서 순수한 웹 MVC프레임워크가 하지 않는 많은 면을 담당한다. 그래서 만약 당신이 (예를 들어 JDBC나 Hibernate로 트랜잭션 추상화) 사용하길 원한다면 Spring미들티어 그리고/또는 데이터 접근 티어를 사용해서 당신의 애플리케이션을 가치있게 할수 있다.

12.1.2. Spring MVC의 특징

Spring의 웹 모듈은 다음을 포함하는 유일한 웹 지원의 가치를 제공한다.

  • 역할의 분명한 분리 - 컨트롤러, 유효성 체커, 커맨드 객체, 폼 객체, 모델 객체, DispatcherServlet, 핸들러 맵핑, view분석자, 등등. 각각의 역할은 객체를 특수화된 객체에 의해 충족될수 있다.

  • 프레임워크와 자바빈즈같은 애플리케이션 클래스의 강력하고 일관적인 설정, 웹 컨트롤러에서 비지니스 객체와 유효성 체커와 같은 컨텍스트를 통한 쉬운 참조를 포함한다.

  • 적합성, 침범적이지 않은, 모든것을 위해 하나의 컨트롤러로 부터 유도되는 대신에 주어진 시나리오를 위해 필요한(plain, 커맨드, 폼, 마법사, 다중 액션, 또는 사용자지정의 것) 컨트롤러 하위 클래스가 무엇이든지 사용하라.

  • 재사용가능한 비지니스 코드 - 중복을 위해 필요한 것이 없다. 당신은 특정 프레임워크 기본 클래스를 확장하기 위해 그것들을 반영하는 대신에 커맨드 폼객체처럼 존재하는 비지니스 객체를 사용할수 있다.

  • 사용자 지정 가능한 바인딩과 유효성 체크 - 수동 파싱과 비지니스 객체로 변환하는 오직 문자열만을 사용하는 폼 객체 대신에 잘못된 값, 지역화된 날짜 그리고 숫자 바인딩, 등등 을 유지하는 애플리케이션 레벨의 유효성 에러처럼 타입 부적합

  • 사용자 지정가능한 핸들러 맵핑과 view분석 - 핸들러 맵핑과 view분석 전략은 간단한 URL기반의 설정에서 정교한, 목적이 내포된 분석 전략까지 범위에 둔다. 이것은 특정 기술을 위임하는 몇몇 웹 MVC프레임워크 보다 좀더 유연하다.

  • 유연한 모델 이전 - 모델은 어떠한 view기술과 함께 쉬운 통합을 지원하는 이름/값 Map을 통해 이전한다.

  • 사용자 지정 가능한 로케일과 테마 분석, Spring 태그 라이브러리를 사용하거나 사용하지 않은 JSP를 위한 지원, JSTL을 위한 지원, 추가적인 연결자를 위한 필요성없이 Velocity를 위한 지원

  • 어떠한 가격적인 면에서 HTML생성을 피하는 간단하지만 강력한 태그 라이브러리, 마크업의 개념에서 최대한의 유연성을 허락한다.

12.2. DispatcherServlet

Spring의 웹 MVC프레임워크는 많은 다른 웹 MVC프레임워크와 비슷하고 요청 기반의 웹 MVC프레임워크이고 컨트롤러에 요청을 할당하는 서블릿에 의해 디자인되었고 웹 애플리케이션의 배치를 용이하게 하는 다른 기능을 제공한다. Spring의 DispatcherServlet은 어쨌든 그것보다 더 많은 기능을 수행한다. 이것은 Spring ApplicationContext와 완벽하게 통합되고 당신이 Spring이 가지는 다른 기능을 사용하도록 허락한다.

흔한 서블릿처럼 DispatcherServlet은 당신 웹 애플리케이션의 web.xml내에 선언된다. 당신이 다루는 DispatcherServlet을 원하는 요청은 web.xml파일내 URL맵핑을 사용해서 맵핑될것이다.

<web-app>
    ...
    <servlet>
        <servlet-name>example</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>example</servlet-name>
        <url-pattern>*.form</url-pattern>
    </servlet-mapping>
</web-app>

위 예제에서 .form으로 끝나는 모든 요청은 DispatcherServlet에 의해 다루어질것이다. DispatcherServlet은 지금 설정될 필요가 있다. Section 3.11, “ApplicationContext에 대한 소개”에서 설명되는 것처럼, Spring내 ApplicationContexts는 범위화 될수 있다. 웹 MVC프레임워크내에서 각각의 DispatcherServlet은 DispatcherServlet설정 빈즈를 포함하는 자신만의 WebApplicationContext를 가진다. DispatcherServlet에 의해 사용되는 디폴트 BeanFactory는 XmlBeanFactory이고 DispatcherServlet은 당신의 웹 애플리케이션 WEB-INF디렉토리내 [servlet-name]-servlet.xml라고 불리는 파일을 룩업하여초기화 될것이다. DispatcherServlet에 의해 사용되는 디폴트 값은 서블릿 초기화 파라미터를 사용함으로써 변경될수 있다. (좀더 많은 정보를 위해서 아래를 보라.)

WebApplicationContext는 웹 애플리케이션을 위해 필요한 몇몇 추가적인 특징을 가지는 보통의 ApplicationContext이다. 이것은 테마(Section 12.7, “테마(themes) 사용하기”를 보라)를 분석하는 기능면에서 대개의 ApplicationContext와 다르고 관련된 서블릿이 무엇인지 (ServletContext로의 링크를 가짐으로써) 안다. WebApplicationContext은 ServletContext내에 바운드 되고 당신이 언제나 WebApplicationContext을 룩업할수 있는 RequestContextUtils을 사용하는것이 이 경우에 필요하다.

Spring DispatcherServlet은 요청을 처리하고 선호하는 view들을 표현할수 있도록 하기 위해 이것을 사용하는 두개의 특별한 빈즈를 가진다. Spring프레임워크에 포함되고 WebApplicationContext내에 설정될수 있는 그러한 빈즈는 꼭 설정될 다른 빈즈들과 같다. 그러한 각각의 빈즈들은 밑에서 좀더 상세하게 설명된다. 지금 우리는 그것들을 설명할것이다. 그것들이 존재하고 DispatcherServlet에 대해서 계속적으로 얘기할수 있도록 하자. 대부분의 빈즈를 위해 디폴트는 당신이 그것들을 설정할 걱정을 하지 않도록 제공된다.

Table 12.1. WebApplicationContext내 특별한 빈즈들

표현 설명
핸들러 맵핑 (Section 12.4, “Handler mappings”)은 그것들이 어떠한 기준(예를 들면 컨트롤러와 함께 명시된 URL에 적합할때)에 적합할때 수행되어야 할 선처리자, 후처리자 그리고 컨트롤러의 리스트이다.
컨트롤러 (Section 12.3, “컨트롤러”)는 MVC 3가지 요소의 부분처럼 실질적인 기능(또는 적어도 기능에 접근하는)을 제공하는 빈즈이다.
view 분석자 (Section 12.5, “view와 view결정하기”)는 DispatcherServlet에 의해 사용되는 view를 위한 view이름을 분석하는 능력
로케일 분석자 (Section 12.6, “로케일 사용하기.”)는 국제화된 view를 제공할수 있도록 하기 위해 클라이언트가 사용하는 로케일을 분석하는 능력
테마 분석자 (Section 12.7, “테마(themes) 사용하기”)는 당신의 웹 애플리케이션이 사용할수 있는 테마(예를 들면 개인화된 레이아웃을 제공하기 위한)를 분석하는 능력.
멀티파트 분석자 (Section 12.8, “Spring의 multipart (파일업로드) 지원”)는 HTML폼으로 부터 파일 업로드 처리를 위한 기능을 제공한다.
핸들러 예외 분석자 (Section 12.9, “예외 다루기”)는 view에 예외를 맵핑하거나 좀더 복잡한 예외 핸들링 코드를 구현하는 기능을 제공한다.

DispatcherServlet이 사용하기 위해 셋업되고 특정 DispatcherServlet을 위해 요청이 접수되면 이것을 처리하도록 시작된다. 밑에서 서술된 리스트는 DispatcherServlet에 의해 핸들링 된다면 요청을 완벽하게 처리한다.

  1. WebApplicationContext는 사용하기 위한 프로세스에서 컨트롤러와 다른 요소를 위해 순서대로 속성처럼 요청을 찾고 바운드 한다. 이것은 DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE키 하위 디폴트에 의해 바운드된다.

  2. 로케일 분석자는 요청을 처리할때(view를 표현하고, 데이터를 준비하는 등등) 사용하기 위한 로케일을 프로세스가 분석하도록 요청에 바운드된다. 만약 당신이 분석자를 사용하지 않는다면 이것은 어떠한 영향도 끼치지 않는다. 그래서 당신이 로케일 분석이 필요하지 않다면 그것을 사용하지 않아도 된다.

  3. 테마 분석자는 view가 사용하기 위한 테마가 무엇인지 결정하는 요청에 바운드된다. 테마 분석자는 사용하지 않는다면 어떠한 영향도 끼치지 않는다. 그래서 당신이 테마를 사용할 필요가 없다면 당신은 그것을 무시할수 있다.

  4. 만약 멀티파트 분석자가 명시되었다면 요청은 멀티파트를 위해서 그리고 그들이 발견된다면 조사될것이다. 이것은 프로세스내에서 다른 요소에 의해 좀더 처리되기 위해 MultipartHttpServletRequest내에 포장된다. (멀티파트 핸들링에 대해서 더 많은 정보를 위해서 Section 12.8.2, “MultipartResolver 사용하기”를 보라.).

  5. 선호하는 핸들러는 검색되기 위한것이다. 만약 핸들러가 찾아졌다면 핸들러(선처리자, 후처리자, 컨트롤러)가 속한 수행 묶음이 모델을 준비하기 위해 수행될것이다.

  6. 모델이 반환된다면 WebApplicationContext에서 설정된 view분석자를 사용해서 view는 표현된다. 만약 어떠한 모델도 반환되지 않는다면(선 또는 후처리자가 요청을 가로챌수 있는, 예를 들면 보안적인 이유로) 이미 처리된 요청이후 표현되는 view는 없다.

요청 처리도중 던져질수 있는 예외는 WebApplicationContext내 선언된 어떠한 핸들러예외 분석자에 의해 처리된다. 이러한 예외 분석자를 사용함으로써 당신은 이러한 예외가 던져지는 경우 사용자 정의 행위를 정의할수 있다.

Spring DispatcherServlet은 역시 서블릿 API에 의해 특성화된 last-modification-date를 반환하기 위한 지원을 가진다. 특정 요청을 위해 가장 마지막에 변경된 날짜를 알아내는 프로세스는 매우 간단하다. DispatcherServlet은 먼저 선호하는 핸들러 맵핑을 룩업할것이고 핸들러가 LastModified 인터페이스를 구현한것을 찾을지 테스트할것이다. 만약 찾게된다면 long getLastModified(request)이 클라이언트에 반환된다.

당신은 web.xml파일이나 서블릿 초기화 파라미터에 컨텍스트 파라미터를 추가함으로써 Spring의 DispatcherServlet을 사용자 정의화 할수 있다. 가능한 사항을 밑에서 보여준다.

Table 12.2. DispatcherServlet 초기화 파라미터

파라미터 설명
contextClass 서블릿에 의해 사용되는 컨텍스트를 초기화하기 위해 사용될 WebApplicationContext을 구현하는 클래스. 만약 이 파라미터가 명시되지 않는다면 XmlWebApplicationContext이 사용될 것이다.
contextConfigLocation 발견될수 있는 컨텍스트의 위치를 표시하기 위한 컨텍스트 인스턴스(contextClass에 의해 정의되는)로 전달되는 문자열. 이 문자열은 잠재적으로 다중(다중 컨텍스트의 경우 두개로 명시된 빈의 경우 후자가 상위 서열을 가진다.) 컨텍스트를 지원하기 위해 다중(콤마를 분리자로 사용하여) 문자열로 나누어진다.
namespace WebApplicationContext의 명명공간(namespace). 디폴트 값은 [server-name]-servlet이다.

12.3. 컨트롤러

컨트롤러의 개념은 MVC디자인 패턴의 일부이다. 컨트롤러는 애플리케이션 행위를 명시하거나 적어도 애플리케이션 행위에 대한 접근을 제공한다. 컨트롤러는 사용자 입력을 해석하고 사용자 입력을 view에 의해 사용자에게 표현될 실용적인 모델로 변형된다. Spring은 생성될 굉장히 넓은 범위의 다양한 종류의 컨트롤러를 가능하게 하는 추상화된 방법으로 컨트롤러의 개념을 구현했다. Spring은 폼 컨트롤러, 커맨드 컨트롤러, 마법사 스타일의 로직을 수행하는 컨트롤러 그리고 더 다양한 방법을 포함한다.

Spring의 컨트롤러 구조의 기본은 밑에 있는 org.springframework.web.servlet.mvc.Controller인터페이스이다.

public interface Controller {

    /**
     * Process the request and return a ModelAndView object which the DispatcherServlet
     * will render.
     */
    ModelAndView handleRequest(
        HttpServletRequest request,
        HttpServletResponse response)
    throws Exception;
}

당신이 볼수 있는것처럼 Controller인터페이스는 요청을 처리하고 선호하는 모델및 view로 반환하는 능력을 가지는 하나의 메소드를 요구한다. 이러한 3개의 개념은 Spring MVC구현물(-ModelAndViewController)을 위한 기본이다. Controller인터페이스가 추상적인 동안 Spring은 당신이 필요한 많은 기능을 포함한 많은 컨트롤러를 제공한다. Controller인터페이스는 모든 컨트롤러의 요구되는 가장 공통적인 기능(-요청을 처리하고 모델및 view를 반환하는)을 명시한다.

12.3.1. AbstractController 와 WebContentGenerator

물론 컨트롤러 인터페이스로는 충분하지 않다. 기본적인 구조를 제공하기 위해서 Spring의 모든 컨트롤러는 캐시지원과 예를 들면 mimetype셋팅을 제공하는 클래스인 AbstractController로부터 상속되었다.

Table 12.3. AbstractController에 의해 제공되는 특징

특징 설명
supportedMethods 이 컨트롤러가 받아들이는 방식이 무엇인지 표시. 언제나 이것은 GETPOST로 셋팅되지만 당신은 지원하길 원하는 방식를 반영하기 위해 변경할수 있다. 만약 요청이 컨트롤러에 의해 지원되지 않는 방식으로 들어왔다면 클라이언트는 이것을 정보화(ServletException)을 이용해서) 할것이다.
requiresSession 컨트롤러가 작업을 하기위해 세션을 필요로 하는지 하지 않는지를 표시. 이 기능은 모든 컨트롤러에 의해 제공된다. 만일 컨트롤러가 요청을 받을때 세션이 존재하지 않는다면 사용자는 ServletException을 사용해서 정보화한다.
synchronizeSession 당신이 사용자의 세션에서 동기화되기 위해 이 컨트롤러에 의해 핸들링하길 원한다면 이것을 사용하라. 좀더 상세화되기 위해 컨트롤러를 확장하는 것은 당신이 이 변수를 정의한다면 동기화되기 위해 handleRequestInternal메소드를 오버라이드 할것이다.
cacheSeconds 당신이 HTTP응답에서 캐시를 직업적으로 생성할 컨트롤러를 원한다면 여기에 양수값을 명시하라. 디폴트는 직접적으로 캐시를 포함하지 않을것이기 때문에 -1로 셋팅된다.
useExpiresHeader HTTP 1.0호환 "Expires"헤더를 명시하기 위해 당신의 컨트롤러를 부분적으로 개량하라. 디폴트는 true라는 값으로 셋팅함으로써 당신은 이것을 변경하지 않을것이다.
useCacheHeader HTTP 1.1호환 "Cache-Control"헤더를 명시하기 위해 당신의 컨트롤러를 부분적으로 개량하라. 디폴트는 true라는 값으로 당신은 이것을 변경하지 않을것이다.

마지막 두개의 프라퍼티는 AbstractController의 수퍼클래스이지만 완성도를 위해 여기에 포함된 WebContentGenerator의 실질적인 부분이다.

당신의 컨트롤러(당신을 위해 작업을 이미 수행하는 많은 다른 컨트롤러때문에 추천되지는 않는)를 위한 기본 클래스처럼 AbstractController를 사용할때 당신은 단지 당신의 로직을 구현하고 ModelAndView객체를 반환하는 handleRequestInternal(HttpServletRequest, HttpServletResponse)메소드만 오버라이드 해야 한다. 이것은 웹 애플리케이션 컨텍스트내 클래스와 선언을 구성하는 짧은 예제이다.

package samples;

public class SampleController extends AbstractController {

    public ModelAndView handleRequestInternal(
        HttpServletRequest request,
        HttpServletResponse response)
    throws Exception {
        ModelAndView mav = new ModelAndView("foo", new HashMap());
    }
}

<bean id="sampleController" class="samples.SampleController">
    <property name="cacheSeconds"><value>120</value</property>
</bean>

위 클래스와 웹 애플리케이션 컨텍스트내 선언은 매우 간단히 작동중인 컨트롤러를 얻기 위해 당신이 핸들러 맵핑(Section 12.4, “Handler mappings”을 보라)을 셋업할 필요가 있는 모든것이다. 이 컨트롤러는 다시 체크하기 전에 2분동안 캐시하는것을 클라이언트에게 알리는 직접적인 캐시를 생성할것이다. 이 컨트롤러는 index(view에 대한 좀더 상세한 정보를 위해서는 Section 12.5, “view와 view결정하기” 을 보라.)라는 이름의 직접 작성된(흠.. 별로 좋지 않은) 코드의 view를 반환할것이다.

12.3.2. 간단한 다른 컨트롤러

비록 당신이 AbstractController을 확장할수 있다고 하더라도 Spring은 간단한 MVC애플리케이션내 공통적으로 사용될 기능을 제공하는 명확한 많은 구현물을 제공한다. ParameterizableViewController는 웹 애플리케이션 컨텍스트내 반환될 view이름을 명시할수 있다(하드코딩된 view이름은 필요없다.)는 사실을 제외하면 위 예제와 기본적으로 같다.

FileNameViewController는 URL을 조사하고 요청으로 부터 파일이름(http://www.springframework.org/index.html의 파일이름은 index이다.)을 가져온다. 그리고 view이름으로 그것을 사용한다. 그것을 위한 더이상의 것은 없다.

12.3.3. MultiActionController

Spring은 모두 기능적으로 그룹화되는 한개의 컨트롤러로 다중 액션을 합치도록 하는 다중액션(multi-action) 컨트롤러를 제공한다. 다중액션 컨트롤러는 별도의 패키지인 org.springframework.web.servlet.mvc.multiaction로 되어 있다. 그리고 메소드이름으로 요청을 맵핑하는 능력을 가지고 올바른 메소드 이름을 호출한다. 다중액션 컨트롤러를 사용하는 것은 하나의 컨트롤러로 많은 공통적인 기능을 가질때 특별히 편리하다. 하지만 컨트롤러에 대해 다중 엔트리 포인트를 가지길 원한다. 예를 들면 행위의 일부를 고치기 위해

Table 12.4. MultiActionController에 의해 제공되는 특징

특징 설명
delegate MultiActionController을 위한 두가지 사용 시나리오가 있다. 당신이 MultiActionController를 세분화하고 하위 클래스에서 MethodNameResolver에 의해 분석될 메소드를 정의하거나 분석자(Resolver)에 의해 분석될 메소드가 호출되는 위임(delegate)객체를 명시한다. 만약 당신이 이 시나리오를 선택한다면 당신은 협력자(collaborator)처럼 이 설정 파라미터를 사용해서 위임을 정의할 것이다.
methodNameResolver 아무튼 MultiActionController는 들어온 요청에 기반한 호출할 메소드를 분석할 필요가 있을것이다. 당신은 설정 파라미터를 사용하는 능력이 있는 분석자(resolver)를 명시할수 있다.

다중액션 컨트롤러를 위해 명시한 메소드는 다음의 시그너처에 따를 필요가 있다.

// actionName can be replaced by any methodname
ModelAndView actionName(HttpServletRequest, HttpServletResponse);

메소드 오버로딩은 MultiActionController을 혼동시키기 때문에 허락되지 않는다. 게다가 당신은 명시한 메소드에 의해 던져질 예외 핸들링 능력을 가지는 exception handlers를 명시할수 있다. 예외 핸들러 메소드는 ModelAndView객체를 반환할 필요가 있다. 다른 액션 메소드도 다음의 시그너처에 따를 필요가 있다.

// anyMeaningfulName can be replaced by any methodname
ModelAndView anyMeaningfulName(HttpServletRequest, HttpServletResponse, ExceptionClass);

ExceptionClassjava.lang.Exception 이나 java.lang.RuntimeException의 하위 클래스만큼 어떠한 예외도 될수 있다.

MethodNameResolver는 들어온 요청에 기반하여 메소드 이름을 분석하기 위해 추측된다. 당신의 배치(disposal)에 3개의 분석자가 있지만 물론 당신은 원한다면 당신 스스로 그것들보다 좀더 다양하게 구현할수 있을것이다.

  • ParameterMethodNameResolver - 요청 파라미터를 분석하고 메소드이름(http://www.sf.net/index.view?testParam=testIt는 호출될 testIt(HttpServletRequest, HttpServletResponse) 메소드로 결과를 낼것이다.)처럼 그것을 사용하는 능력. paramName 설정 파라미터는 조사된 파라미터를 명시한다.

  • InternalPathMethodNameResolver - 경로로 부터 파일이름을 가져오고 메소드 이름처럼 그것을 사용한다. (http://www.sf.net/testing.view는 호출될 testing(HttpServletRequest, HttpServletResponse)메소드내 결과를 낼것이다.)

  • PropertiesMethodNameResolver - 요청 URL을 메소드이름에 맵핑되는 사용자 정의 프라퍼티 객체를 사용한다. 프라퍼티가 /index/welcome.html=doIt을 포함하고 /index/welcome.html로 요청이 들어올때 doIt(HttpServletRequest, HttpServletResponse)메소드는 호출된다. 이 메소드 이름 분석자는 PathMatcher과 함께 작동한다. (???를 보라.) 프라퍼티가 /**/welcom?.html을 포함한다면 이것또한 작동할것이다.

여기에 두개의 예제가 있다. 첫번째 예제는 ParameterMethodNameResolver를 보여주고 포함된 파라미터 메소드를 가지는 url로 요청을 받을 프라퍼티를 위임한다. 그리고 retrieveIndex에 셋팅한다.

<bean id="paramResolver" class="org....mvc.multiaction.ParameterMethodNameResolver">
    <property name="paramName"><value>method</value></property>
</bean>

<bean id="paramMultiController" class="org....mvc.multiaction.MultiActionController">
    <property name="methodNameResolver"><ref bean="paramResolver"/></property>
    <property name="delegate"><ref bean="sampleDelegate"/></property>
</bean>

<bean id="sampleDelegate" class="samples.SampleDelegate"/>

## together with

public class SampleDelegate {

    public ModelAndView retrieveIndex(
        HttpServletRequest req,
        HttpServletResponse resp) {

        return new ModelAndView("index", "date", new Long(System.currentTimeMillis()));
    }
}

위에서 보여진 위임을 사용할때 우리는 명시한 메소드에 두개의 URL로 매치시키기 위한 PropertiesMethodNameResolver을 사용할수 있다.

<bean id="propsResolver" class="org....mvc.multiaction.PropertiesMethodNameResolver">
    <property name="mappings">
        <props>
            <prop key="/index/welcome.html">retrieveIndex</prop>
            <prop key="/**/notwelcome.html">retrieveIndex</prop>
            <prop key="/*/user?.html">retrieveIndex</prop>
        </props>
    </property>
</bean>

<bean id="paramMultiController" class="org....mvc.multiaction.MultiActionController">
    <property name="methodNameResolver"><ref bean="propsResolver"/></property>
    <property name="delegate"><ref bean="sampleDelegate"/></property>
</bean>

12.3.4. CommandControllers

Spring의 CommandControllers는 Spring MVC패키지의 필수적인 부분이다. 커맨드 컨트롤러는 데이터 객체와 상호작동하기 위한 방법을 제공한다. HttpServletRequest로 부터 명시된 데이터 객체를 파라미터를 동적으로 바인드한다. 그것들은 Struts의 ActionForm과 유사한 역할을 수행하지만 Spring내에서 당신의 데이터 객체는 프레임워크 특정적인 인터페이스를 구현할 필요는 없다. 당신이 그것들로 무엇을 할수 있는지 개략적으로 살펴보기 위해 사용가능한 커맨드 컨트롤러가 어떤것이 있는지 조사해보자.

  • AbstractCommandController - 당신 자신의 컨트롤러를 생성하기 위해 당신이 사용할수 있는 커맨드 컨트롤러는 당신이 명시하는 데이터 객체로 요청 파라미터를 바인드하는 능력을 가진다. 이 클래스는 폼 기능을 제공하지 않는다. 이것은 어쨌든 유효성체크 기능을 제공하고 요청으로 부터 파라미터를 채우는 커맨드 객체가 무엇인지 컨트롤러내 명시하자.

  • AbstractFormController - 추상 컨트롤러는 폼 서브밋 지원을 제공한다. 이 컨트롤러를 사용하면 당신은 폼을 만들고 당신이 컨트롤러내에서 가져온 커맨드 객체를 사용하여 그것들을 형상화 할수 있다. 사용자가 폼을 채우고 난 뒤 AbstractFormController는 필드를 바인드하고 유효성 체크를 하며 선호하는 액션을 가지기 위해 컨트롤러에 반환된 객체를 다룬다. 지원되는 기능 : 타당치 않은 폼 서브밋, 유효성체크, 그리고 일반적인 폼 워크플로우. 당신은 view가 폼 표현이나 성공을 위해 사용되는지 알아내기 위한 메소드를 구현한다. 만약 당신이 폼이 필요하다면 이 컨트롤러를 사용하라. 하지만 애플리케이션 컨텍스트내 사용자를 보여주기 위해 당신이 사용하는 view를 명시하길 원하지 않는다.

  • SimpleFormController - 유사한 커맨드 객체를 가진 폼을 생성할때 명백한 FormController는 좀더 많은 지원을 제공한다. SimpleFormController는 당신이 커맨드 객체, 폼을 위한 view이름, 폼 서브밋이 성공했을때 당신이 사용자에게 보여주기를 원하는 페이지를 위한 view이름을 명시하도록 한다.

  • AbstractWizardFormController - 클래스 이름이 제시하는것처럼 이것은 당신의 WizardController가 이것을 확장해야만 하는 추상 클래스이다. 이것은 당신이 validatePage(), processFinish 그리고 processCancel를 구현해야만 한다는 것을 의미한다.

    당신은 아마 적어도 setPages()setCommandName()를 호출하는 계약자(contractor)를 쓰길 원할것이다. 이 형태자(former)는 인자를 문자열 타입 배열현태로 가진다. 이 배열은 당신의 마법사를 이루는 view의 목록이다. 나중에 인자를 당신의 view내로부터 커맨드 객체를 참조하기 위해 사용될 문자열처럼 가진다.

    AbstractFormController의 어떠한 인스턴스처럼 당신은 당신의 폼으로 부터 데이터를 활성화할 자바빈인 커맨드 객체를 사용하기 위해 요구된다. 당신은 커맨드 객체의 클래스를 가진 생성자로부터 setCommandClass()을 호출하거나 formBackingObject()메소드를 구현하는 두가지 방법중에 하나로 이것을 할수 있다.

    AbstractWizardFormController는 오버라이드할 많은 수의 메소드를 가진다. 물론 당신이 가장 유용하다고 볼수 있는 것은 당신이 Map의 폼내 당신의 view로 모델 데이터를 전달하기 위해 사용할수 있는 referenceData; 만약 당신의 마법사가 순서대로 페이지를 변경하거나 동적으로페이지를 생략할 필요가 있다면 getTargetPage; 만약 당신이 내장된 바인딩과 유효성 체크 워크 플로우를 오버라이드하기를 원한다면 onBindAndValidate이다.

    마지막으로 이것은 현재 페이지에 유효성 체크가 실패한다면 마법사내에서 뒤로나 앞으로 움직이는 것을 사용자에게 허락하는 getTargetPage로 부터 호출할수 있는 setAllowDirtyBacksetAllowDirtyForward를 가리킬 가치가 있다.

    메소드의 모든 목록을 위해 AbstractWizardFormController를 위한 JavaDoc를 보라. Spring배포판내 포함된 jPetStore내 마법사의 예제가 구현(org.springframework.samples.jpetstore.web.spring.OrderFormController)되어 있다.

12.4. Handler mappings

핸들러 맵핑을 사용하면 당신은 선호하는 핸들러로 들어온 웹 요청을 맵핑할수 있다. 여기에 당신이 특별히 사용할수 있는 몇몇 핸들러 맵핑이 있다. 예를 들면 SimpleUrlHandlerMappingBeanNameUrlHandlerMapping이다. 하지만 먼저 HandlerMapping의 일반적인 개념을 알아보자.

기능적으로 기본 HandlerMapping은 들어온 요청에 매치하는 핸들러를 포함하고 요청에 적용되는 핸들러 입터셉터의 목록을 포함할수도 있는 HandlerExecutionChain의 전달을 제공한다. 요청이 들어왔을때 DispatcherServlet는 요청을 조사하도록 하는 핸들러 맵핑을 위해 이것을 다룰것이고 선호하는 HandlerExecutionChain을 생성할것이다. 그 다음 DispatcherServlet은 핸들러와 체인내 인터셉터를 수행할것이다.

임의로 인터셉터(실질적인 핸들러가 수행되기 전이나 후에 수행되는)를 포함할수 있는 설정가능한 핸들러 맵핑의 개념은 극히 강력하다. 많은 지원 기능은 사용자 정의 HandlerMapping에 내장될수 있다. 핸들러를 선택한 사용자 정의 핸들러 맵핑의 생각은 들어오는 요청의 URL에 기반할뿐 아니라 요청과 함께 속하는 세션의 특정 상태에도 기반을 둔다.

이 섹션은 Spring의 가장 공통적으로 사용되는 핸들러 맵핑 두가지를 서술한다. 그 둘은 AbstractHandlerMapping을 확장하고 다음의 프라퍼티 값을 공유한다.

  • interceptors: 사용하기 위한 인터셉터의 목록. HandlerInterceptorSection 12.4.3, “HandlerInterceptors 추가하기”에서 논의된다.

  • defaultHandler: 핸들러 맵핑이 적합한 핸들러를 매치시키는 결과를 내지 못할때 사용하기 위한 디폴트 핸들러.

  • order: order프라퍼티(org.springframework.core.Ordered인터페이스를 보라)의 값에 기반한다. Spring은 컨텍스트내에서 사용가능한 모든 핸들러 맵핑을 분류하고 첫번째 매치되는 핸들러를 적용한다.

  • alwaysUseFullPath: 이 프라퍼티가 true로 셋팅된다면 Spring은 적당한 핸들러를 찾기 위해 현재 서블릿 컨텍스트내 완전한 경로(full path)를 사용할것이다. 만약 이 프라퍼티가 false(이 값이 디폴트 값이다.)로 셋팅된다면 현재 서블릿 맵핑내 경로가 사용될것이다. 예를 들면 서블릿이 /testing/*을 사용해서 맵핑되어 있고 alwaysUseFullPath프라퍼티가 true로 셋팅되어 있다면 /testing/viewPage.html이 사용될것이다. 만약 프라퍼티가 false라면 /viewPage.html가 사용될것이다.

  • urlPathHelper: 이 프라퍼티를 사용하면 당신은 URL을 조사할때 사용되는 UrlPathHelper를 부분적으로 수정할수 있다. 일반적으로 당신은 디폴트 값을 변경하지 말아야 할것이다.

  • urlDecode: 이 프라퍼티를 위한 디폴트 값은 false이다. HttpServletRequest는 번역(decode)되지 않은 요청 URL과 URI를 반환한다. 만약 HandlerMapping이 적당한 핸들러를 찾기 위해 그것들을 사용하기 전에 그것들이 번역되길 원한다면 이것을 true로 셋팅해야 한다.(이것은 JDK1.4를 요구한다는것에 주의하라.). 번역 방식은 요청에 의해 명시된 인코딩이나 디폴트인 ISO-8859-1 인코딩 스키마를 사용한다.

  • lazyInitHandlers: singleton핸들러(프로토타입 핸들러는 언제나 늦은 초기화를 수행한다.)의 늦은(lazy)초기화를 허락한다. 디폴트 값은 false이다.

(주의: 마지막의 4개의 프라퍼티는 org.springframework.web.servlet.handler.AbstractUrlHandlerMapping의 하위클래스에서만 사용가능하다.).

12.4.1. BeanNameUrlHandlerMapping

매우 간단하지만 굉장히 강력한 핸들러 맵핑은 들어오는 HTTP요청을 웹 애플리케이션 컨텍스트내 명시된 빈즈의 이름으로 맵핑하는 BeanNameUrlHandlerMapping이다. 우리는 사용자가 계정을 추가하는것이 가능하길 원하고 우리는 벌써 적당한 FormController(커맨드와 FormControllers의 좀더 상세한 정보를 위해서 Section 12.3.4, “CommandControllers”을 보라)과 폼을 표현하는 JSP view(또는 Velocity템플릿)가 제공된다고 얘기해보자. BeanNameUrlHandlerMapping을 사용할때 우리는 다음처럼 적당한 FormController를 위해 URL http://samples.com/editaccount.form을 HTTP요청에 맵핑할수 있다.

<beans>
    <bean id="handlerMapping"
          class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>

    <bean name="/editaccount.form"
          class="org.springframework.web.servlet.mvc.SimpleFormController">
        <property name="formView"><value>account</value></property>
        <property name="successView"><value>account-created</value></property>
        <property name="commandName"><value>Account</value></property>
        <property name="commandClass"><value>samples.Account</value></property>
    </bean>
<beans>    

URL /editaccount.form을 위한 들어온 모든 요청은 위 소스 목록의 FormController에 의해 다루어질것이다. 물론 우리는 .form으로 끝나는 모든 요청을 통하기 위해 web.xml에 servlet-mapping을 정확하게 명시해야 한다.

<web-app>
    ...
    <servlet>
        <servlet-name>sample</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

   <!-- Maps the sample dispatcher to /*.form -->
    <servlet-mapping>
        <servlet-name>sample</servlet-name>
        <url-pattern>*.form</url-pattern>
    </servlet-mapping>
    ...
</web-app>

주의: 만약 당신이 BeanNameUrlHandlerMapping을 사용하길 원한다면 당신은 웹 애플리케이션 컨텍스트내 이것을 반드시 명시할 필요가 없다(위에서 표시된것처럼). 디폴트에 의해 어떠한 핸들러 맵핑도 컨텍스트내에서 발견되지 않는다면 DispatcherServlet은 당신을 위해 BeanNameUrlHandlerMapping을 생성한다.

12.4.2. SimpleUrlHandlerMapping

한층 나아가서 그리고 좀더 강력한 핸들러 맵핑은 SimpleUrlHandlerMapping이다. 이 맵핑은 애플리케이션 컨텍스트내에서 설정가능하고 Ant스타일의 경로 매치 능력을 가진다. (org.springframework.util.PathMatcher를 위한 JavaDoc를 보라.). 여기에 예제가 있다.

<web-app>
    ...
    <servlet>
        <servlet-name>sample</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!-- Maps the sample dispatcher to /*.form -->
    <servlet-mapping>
        <servlet-name>sample</servlet-name>
        <url-pattern>*.form</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>sample</servlet-name>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>
    ...
</web-app>

같은 디스패처(dispatcher) 서블릿에 의해 다루어지기 위해 .html과 .form으로 끝나는 모든 요청을 할당한다.

<beans>
    <bean id="handlerMapping"
          class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <prop key="/*/account.form">editAccountFormController</prop>
                <prop key="/*/editaccount.form">editAccountFormController</prop>
                <prop key="/ex/view*.html">someViewController</prop>
                <prop key="/**/help.html">helpController</prop>
            </props>
        </property>
    </bean>

    <bean id="someViewController"
          class="org.springframework.web.servlet.mvc.UrlFilenameViewController"/>

    <bean id="editAccountFormController"
          class="org.springframework.web.servlet.mvc.SimpleFormController">
        <property name="formView"><value>account</value></property>
        <property name="successView"><value>account-created</value></property>
        <property name="commandName"><value>Account</value></property>
        <property name="commandClass"><value>samples.Account</value></property>
    </bean>
<beans>

이 핸들러 맵핑은 어떤 디렉토리내 help.html을 UrlFilenameViewController(Section 12.3, “컨트롤러”에서 찾을수 있는 컨트롤러에 대해서 추가적인)인 helpController로 요청을 보낸다. ex디렉토리내에서 view로 시작하고 .html로 끝나는 자원을 위한 요청은 someViewController로 경로가 지정될것이다. 둘 이상의 맵핑은 editAccountFormController을 위해 명시된다.

12.4.3. HandlerInterceptors 추가하기

Spring은 핸들러 맵핑 기법은 예를 들어 구매자를 체크하는 어떠한 요청을 위한 특정 기능을 적용하길 원할때 매우 유용할수있는 핸들러 인터셉터의 개념을 가진다.

핸들러 맵핑내 위치하는 인터셉터는 org.springframework.web.servlet패키지로부터 HandlerInterceptor를 구현해야만 한다. 이 인터페이스는 3개의 메소드를 선언한다. 하나는 실질적인 핸들러가 수행되기 전에 호출될것이고 하나는 핸들러가 수행된 후에 호출될것이다. 나머지 하나는 완전한 요청이 종료된후에 호출된다. 이 3가지 메소드는 선처리와 후처리의 모든 종류를 처리하기 위한 충분한 유연성을 제공할것이다.

preHandle 메소드는 bolean값을 반환한다. 당신은 작업을 중간에 종료하거나(break) 수행묶음(chain)의 처리를 계속하기 위해 이 메소드를 사용할수 있다. 이 메소드가 true를 반환할때 핸들러 수행묶음은 계속될것이다. false를 반환할때는 DispatcherServlet이 요청을 처리할 인터셉터(그리고 예를 들면 적당한 view를 표현하는)를 추정하고 수행묶음내 다른 인터셉터와 실질적인 핸들러를 계속적으로 수행하지 않는다.

아래의 예제는 모든 요청을 가로채고 오전 9시와 오후 6시가 아니라면 사용자를 특정 페이지로 경로를 변경시키는 인터셉터를 제공한다.

<beans>
    <bean id="handlerMapping"
          class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="interceptors">
            <list>
                <ref bean="officeHoursInterceptor"/>
            </list>
        </property>
        <property name="mappings">
            <props>
                <prop key="/*.form">editAccountFormController</prop>
                <prop key="/*.view">editAccountFormController</prop>
            </props>
        </property>
    </bean>

    <bean id="officeHoursInterceptor"
          class="samples.TimeBasedAccessInterceptor">
        <property name="openingTime"><value>9</value></property>
        <property name="closingTime"><value>18</value></property>
    </bean>
<beans>

package samples;

public class TimeBasedAccessInterceptor extends HandlerInterceptorAdapter {

    private int openingTime;
    private int closingTime;
    public void setOpeningTime(int openingTime) {
        this.openingTime = openingTime;
    }
    public void setClosingTime(int closingTime) {
        this.closingTime = closingTime;
    }
    public boolean preHandle(
            HttpServletRequest request,
            HttpServletResponse response,
            Object handler)
    throws Exception {
        Calendar cal = Calendar.getInstance();
        int hour = cal.get(HOUR_OF_DAY);
        if (openingTime <= hour < closingTime) {
            return true;
        } else {
            response.sendRedirect("http://host.com/outsideOfficeHours.html");
            return false;
        }
    }
}

들어오는 어떠한 요청은 TimeBasedAccessInterceptor에 의해 차단당할것이고 현재 시각이 사무시간(office hours)밖이라면 사용자는 정적 html파일로 전환될것이다. 다시 말하면 예를 들어 그는 사무시간(office hours)내에서만 웹사이트에 접근할수 있다.

당신이 볼수 있는 것처럼 Spring은 당신이 HandlerInterceptor를 확장하기 쉽도록 만드는 어댑터(adapter)를 가진다.

12.5. view와 view결정하기

웹 애플리케이션을 위한 모든 MVC프레임워크는 view를 결정하는 방법을 제공한다. Spring은 특정 view기술을 위해 당신이 기록할 필요없이 브라우저내에서 모델을 표현할수 있도록 만들어주는 view결정자(resolvers)를 제공한다. 특별히 Spring은 JSP, Velocity 템플릿, XSLT view를 사용가능하도록 한다. 예를 들면 Chapter 13, 통합 뷰 기술들는 다양한 view기술과의 통합에 대한 상세사항을 가진다.

Spring이 view를 다루는 방법을 위한 중요한 두개의 인터페이스는 ViewResolverView이다. ViewResolver는 view이름과 실제 view사이의 맵핑을 제공한다. View인터페이스는 준비된 요청을 할당하고 요청을 view기술중 하나에게 처리하도록 넘겨버린다.

12.5.1. ViewResolvers

Section 12.3, “컨트롤러”에서 논의된 것처럼 Spring 웹 MVC프레임워크내 모든 컨트롤러는 ModelAndView인스턴스를 반환한다. Spring내 view는 view이름에 의해 할당되고 view결정자에 의해 결정된다. Spring은 다수의 view결정자를 가진다. 우리는 그것들의 대부분의 목록을 볼것이며 두개의 예제를 제공한다.

Table 12.5. View 결정자(Resolvers)

ViewResolver 설명
AbstractCachingViewResolver 캐싱 view를 다루는 추상 view결정자. 종종 view를 사용되기 전에 준비작업이 필요하다. 이 view결정자를 확장하는 것은 view의 캐싱을 제공한다.
XmlViewResolver Spring의 bean팩토리 처럼 같은 DTD를 가진 XML내 쓰여진 설정파일을 가져오는 ViewResolver의 구현물. 디폴트 설정 파일은 /WEB-INF/views.xml이다.
ResourceBundleViewResolver 번들 basename에 의해 명시된 ResourceBundle내 bean정의를 사용하는 ViewResolver의 구현물. 번들은 대개 클래스패스내 위치한 프라퍼티파일내 명시된다. 디폴트 파일명은 views.properties이다.
UrlBasedViewResolver 추가적인 맵핑 정의 없이 URL로 상징적인 view이름의 직접적인 결정을 허락하는 ViewResolver의 간단한 구현물. 이것은 당신의 상징적인 이름이 임의의 맵핑의 필요성이 없는 직접적인 방법으로 당신의 view자원의 이름을 대응한다면 적당하다.
InternalResourceViewResolver InternalResourceView(예를 들면 서블릿과 JSP)와 JstlView, TilesView와 같은 하위 클래스를 지원하는 UrlBasedViewResolver의 편리한 하위 클래스. 이 결정자에 의해 생성되는 모든 view를 위한 view클래스는 setViewClass를 통해 정의될수 있다. 상세사항을 위해 UrlBasedViewResolver's의 JavaDoc를 보라.
VelocityViewResolver / FreeMarkerViewResolver 직접적인 VelocityView(예를 들면 Velocity템플릿) 또는 FreeMarkerView와 그것들의 사용자 지정 하위 클래스를 지원하는 UrlBasedViewResolver의 편리한 하위 클래스.

예제처럼 view기술로 JSP를 사용할때 당신은 UrlBasedViewResolver을 사용할수 있다. 이 view결정자는 view이름을 URL로 번역하고 view를 표현하기 위해 요청을 RequestDispatcher로 보낸다.

<bean id="viewResolver"
      class="org.springframework.web.servlet.view.UrlBasedViewResolver">
    <property name="prefix"><value>/WEB-INF/jsp/</value></property>
    <property name="suffix"><value>.jsp</value></property>
</bean>

view이름처럼 test를 반환할때 이 view결정자는 /WEB-INF/jsp/test.jsp로 요청을 보낼 RequestDispatcher로 요청을 보낼것이다.

웹 애플리케이션내 서로 다른 view기술을 혼합해서 사용할때 당신은 ResourceBundleViewResolver을 사용할수 있다.

<bean id="viewResolver"
      class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
    <property name="basename"><value>views</value></property>
    <property name="defaultParentView"><value>parentView</value></property>
</bean>

ResourceBundleViewResolver는 basename에 의해 구별되는 ResourceBundle을 추정하고 각각의 view를 위해 이것은 결정하기 위한 제안된다. 이것은 view 클래스처럼 프라퍼티 [viewname].class의 값과 view url처럼 프라퍼티 [viewname].url의 값을 사용한다. 당신이 볼수 있는 것처럼 확장순서대로 프라퍼티 파일내 모든 view로부터 당신은 부모(parent) view를 구별할수 있다. 예들 들면 이 방법으로 당신은 디폴트 view클래스를 정의할수 있다.

캐싱쪽 노트(note) - 결정될수 있는 AbstractCachingViewResolver 캐시 view인스턴스의 하위클래스. 이것은 어떤 view기술을 사용할때 성능을 향상시킨다. cache프라퍼티를 false로 셋팅함으로써 캐시를 끌수도 있다. 게다가 당신이 수행시 어떤 view를 재생할수 있는 요구사항(예를 들면 Velocity템플릿이 변경될 때)을 가진다면 당신은 removeFromCache(String viewName, Locale loc)메소드를 사용할수 있다.

12.5.2. ViewResolvers 묶기(Chaining)

Spring은 하나의 view결정자보다 많은 수의 결정자를 지원한다. 이것은 당신에게 결정자를 묶는것을 가능하게 한다. 예를 들면 어떤 상황에서 특정 view를 오버라이드한다. view결정자를 묶는것은 당신의 애플리케이션 컨텍스트에 하나 이상의 결정자를 추가하는것처럼 상당히 일관적이다. 만약 필요하다면 order를 정의하기 위해 order프라퍼티를 셋팅하라. 더 큰 order프라퍼티는 나중에 view결정자가 묶음내 위치시킬것이다.

다음의 예제에서 view결정자의 묶음은 두개의 결정자인 InternalResourceViewResolver(두번째에 위치하고 묶음내 마지막 결정자)와 엑셀 view(InternalResourceViewResolver에 의해 지원되지 않는)를 정의하기 위한 XmlViewResolver로 이루어진다.

<bean id="jspViewResolver"
		class="org.springframework.web.servlet.view.InternalResourceViewResolver">
	<property name="order"><value>2</value></property>
	<property name="viewClass">
		<value>org.springframework.web.servlet.view.JstlView</value>
	</property>
	<property name="prefix"><value>/WEB-INF/jsp/</value></property>
	<property name="suffix"><value>.jsp</value></property>
</bean>

<bean id="excelViewResolver">
		class="org.springframework.web.servlet.view.XmlViewResolver">
	<property name="order"><value>1</value></property>
	<property name="location"><value>/WEB-INF/views.xml</value></property>
</bean>

### views.xml

<beans>
	<bean name="report" class="org.springframework.example.ReportExcelView"/>
</beans>

만약 특정 view결정자가 view결과를 내지 않을때 Spring은 다른 view결정자가 설정되었다면 보기 위해 컨텍스트를 조사할것이다. 만약 추가적인 view결정자가 있다면 이것은 그것들을 조사하기 위해 지속될것이다. 만약 그렇지 않다면 예외를 던질것이다.

당신은 염두해 두고 있는것을 유지해야한다. view결정자의 규칙은 view결정자가 찾을수 없는 view를 표시하기 위해 null을 반환할수 있다는 것을 말한다. 어쨋든 모든 view결정자기 이것을 하지는 않는다. 이것은 결정자가 view가 존재하는지 존재하지 않는지 검사할수 없을때와 같은 몇몇 경우에 야기된다. 예를 들면 InternalResourceViewResolver는 내부적으로 RequestDispatcher를 사용하고 디스패치(dispatching)는 JSP가 존재할때 설정하는 유일한 방법이다. 이것은 한번에 한하여 수행될수 있다. VelocityViewResolver와 몇몇 다른것들을 위해 같은것이 묶인다. 만약 당신이 존재하지 않는 view를 보고하지 않은 view결정자를 처리한다면 view결정자를 위한 JavaDoc를 체크하라. 이 결과처럼 마지막이 아닌 다른 어느위치내 묶음안에 InternalResourceViewResolver을 두는것은 InternalResourceViewResolver언제나 view를 반환하기 때문에 완전하게 조사되지 않는 묶음이라는 결과를 만들어낼것이다.

12.6. 로케일 사용하기.

Spring구조의 대부분은 Spring 웹 MVC프레임워크가 하는것처럼 국제화를 지원한다. DispatcherServlet은 당신에게 클라이언트 로케일을 사용하여 메시지를 자동적으로 결정하도록 한다. 이것은 LocaleResolver객체를 사용해서 수행한다.

요청이 들어올때 DispatcherServlet는 로케일 결정자를 찾고 로케일 결정자가 찾아진다면 로케일을 셋팅하기 위해 그 결정자를 사용한다. RequestContext.getLocale()메소드를 사용하여 당신은 로케일 결정자에 의해 결정된 로케일을 언제나 가져올수 있다.

자동적인 로케일 전환외에도 당신은 핸들러 맵핑에 인터셉터(핸들러 맵핑 인터셉터의 좀더 다양한 정보를 위해 Section 12.4.3, “HandlerInterceptors 추가하기”를 보라)를 첨부할수 있다. 특정 상황하에 로케일을 변경하는 것은 요청의 파라미터에 기반한다.

로케일 결정자와 인터셉터는 org.springframework.web.servlet.i18n패키지내 모두 명시되고 일반적인 방법으로 당신의 애플리케이션 컨텍스트내 설정된다. 여기에 Spring에 포함된 로케일 결정자의 선택된 일부가 있다.

12.6.1. AcceptHeaderLocaleResolver

이 로케일 결정자는 클라이언트의 브라우저에 의해 보내어진 요청내 accept-language헤더를 조사한다. 언제나 이 헤더 필드는 클라이언트의 OS시스템의 로케일을 포함한다.

12.6.2. CookieLocaleResolver

이 로케일 결정자는 로케일이 정의되었다면 보기 위해 클라이언트내 존재하는 쿠키를 조사한다. 만약 쿠키가 존재한다면 그 특정 로케일을 사용한다. 로케일 결정자의 프라퍼티를 사용하여 당신은 최대생명(maximum age)만큼 쿠키의 이름을 명시할수 있다.

<bean id="localeResolver">
    <property name="cookieName"><value>clientlanguage</value></property>
    <!-- in seconds. If set to -1, the cookie is not persisted (deleted when browser shuts down) -->
    <property name="cookieMaxAge"><value>100000</value></property>
</bean>

이것은 CookieLocaleResolver를 명시하는것의 예제이다.

Table 12.6. WebApplicationContext내 특별한 빈즈

프라퍼티 디폴트 값 설명
cookieName classname + LOCALE 쿠키의 이름
cookieMaxAge Integer.MAX_INT 쿠키가 클라이언트에 일관적으로 머무를 최대시간. 만약 -1이 정의된다면 쿠키는 저장되지 않는다. 이것은 단지 클라이언트가 브라우저는 닫을때 까지만 사용가능하다.
cookiePath / 이 파라미터를 사용하여 당신은 당신 사이트의 특정부분을 위해 쿠키의 가시성(visibility)에 제한을 둘수 있다. cookiePath가 정의되었을때 쿠키는 오직 그 경로와 그 하위경로에서만 볼수 있을것이다.

12.6.3. SessionLocaleResolver

SessionLocaleResolver는 당신에게 사용자의 요청이 속한 세션으로 부터 로케일을 가져오도록 허락한다.

12.6.4. LocaleChangeInterceptor

당신은 LocaleChangeInterceptor을 사용해서 로케일을 변경할수 있다. 이 인터셉터는 하나의 핸들러 맵핑(Section 12.4, “Handler mappings”을 보라.)에 추가될 필요가 있다. 이것은 요청내 파라미터를 찾아내고 로케일(이것은 컨텍스트내 존재하는 LocaleResolver의 setLocale()을 호출한다.)을 변경한다.

<bean id="localeChangeInterceptor"
      class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
    <property name="paramName"><value>siteLanguage</value></property>
</bean>

<bean id="localeResolver"
      class="org.springframework.web.servlet.i18n.CookieLocaleResolver"/>

<bean id="urlMapping"
      class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="interceptors">
        <list>
            <ref local="localeChangeInterceptor"/>
        </list>
    </property>
    <property name="mappings">
        <props>
            <prop key="/**/*.view">someController</prop>
        </props>
    </property>
</bean>

siteLanguage라는 이름의 파라미터를 포함하는 모든 *.view형태의 자원 호출은 지금 로케일을 변경할것이다. 그래서 http://www.sf.net/home.view?siteLanguage=nl의 호출은 사이트 언어를 네델란드어로 변경할것이다.

12.7. 테마(themes) 사용하기

견본절

12.8. Spring의 multipart (파일업로드) 지원

12.8.1. 소개

Spring은 웹 애플리케이션내 파일업로드를 다루기 위한 멀티파트(multipart)지원을 내장한다. 멀티파트 지원을 위한 디자인은 org.springframework.web.multipart패키지내 명시된 플러그인 가능한 MultipartResovler객체로 할수 있다. 특별히 Spring은 Commons FileUpload(http://jakarta.apache.org/commons/fileupload)와 COS FileUpload (http://www.servlets.com/cos)을 사용하기 위해 MultipartResolver를 제공한다. 파일을 업로드하는 지원되는 방법은 이 장의 끝에 서술될것이다.

디폴트에 의해 멀티파트 핸들링은 몇몇 개발자들이 그들 스스로 멀티파트를 다루길 원하는 것처럼 Spring에 의해 수행되지 않을것이다. 당신은 웹 애플리케이션 컨텍스트에 멀티파트 결정자를 추가함으로써 당신 스스로 이것을 가능하게 할수 있다. 당신이 그렇게 한 후에 각각의 요청은 그것이 멀티파트를 포함하는지 보기 위해 조사할것이다. 만약 멀티파트가 찾아지지 않는다면 요청은 기대되는 것처럼 계속될것이다. 어쨌든 멀티파트가 요청내에 발견된다면 당신의 컨텍스트내 명시된 MultipartResolver가 사용될것이다. 그리고 나서 요청내 멀티파트 속성은 다른 속성처럼 처리될것이다.

12.8.2. MultipartResolver 사용하기

다음의 예제는 CommonsMultipartResolver를 사용하는 방법을 보여준다.

<bean id="multipartResolver"
    class="org.springframework.web.multipart.commons.CommonsMultipartResolver">

    <!-- one of the properties available; the maximum file size in bytes -->
    <property name="maxUploadSize">
        <value>100000</value>
    </property>
</bean>

이것은 CosMultipartResolver을 사용하는 예제이다.

<bean id="multipartResolver"
    class="org.springframework.web.multipart.cos.CosMultipartResolver">

    <!-- one of the properties available; the maximum file size in bytes -->
    <property name="maxUploadSize">
        <value>100000</value>
    </property>
</bean>

물론 당신은 작업을 수행하는 멀티파트 결정자를 위해 클래스패스내 적당한 jar파일을 복사해 넣을 필요가 있다. CommonsMultipartResolver의 경우 당신은 commons-fileupload.jar을 사용할 필요가 있다. CosMultipartResolver의 경우 cos.jar를 사용한다.

지금 당신은 멀티파트 요청을 다루기 위해 Spring을 셋업하는 방법을 보았다. 이것을 실제로 사용하기 위해 어떻게 해야 하는지에 대해 얘기해보자. Spring DispatcherServlet가 멀티파트 요청을 탐지했을때 이것은 당신의 컨텍스트내 선언된 결정자를 활성화시키고 요청을 처리한다. 기본적으로 하는 것은 멀티파트를 위한 지원을 가진 MultipartHttpServletRequest로 최근의 HttpServletRequest을 포장해 넣는것이다. MultipartHttpServletRequest을 사용하면 당신은 이 요청에 의해 포함된 멀티파트에 대한 정보를 얻을수 있고 당신 컨트롤러내 스스로 멀티파트를 얻을수 있다.

12.8.3. 폼에서 파일업로드를 다루기.

MultipartResolver가 그 작업을 마친후 요청은 다른것처럼 처리될것이다. 이것을 사용하기 위해 당신은 파일 업로드 기능을 가진 폼을 생성하고 Spring이 폼의 필드에 바인드 하도록 하자. 다른 프라퍼티처럼 그것은 자동적으로 당신이 ServletRequestDatabinder로 사용자 지정 편집기(editor)를 틍록하기 위해 당신 빈즈에 바이너리 데이터를 두기 위한 문자열이나 원시타입으로 형변화되지 않는다. 파일을 다루고 빈에 결과를 셋팅하기 위해 사용가능한 두개의 편집기가 있다. StringMultipartEditor는 파일을 문자열로 형변환(사용자 정의 문자셋을 사용하여)하는 능력을 가진다. 그리고 ByteArrayMultipartEditor는 파일을 바이트 배열로 형변환한다. 그것들은 CustomDateEditor가 하는것처럼 작동한다.

그리고 웹사이트내 폼을 사용하여 파일을 업로드하기 위해 결정자를 선언하라. 컨트롤러를 위한 url맵핑은 빈과 컨트롤러 자신을 처리할것이다.

<beans>

    ...

    <bean id="multipartResolver"
        class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>

    <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <prop key="/upload.form">fileUploadController</prop>
            </props>
        </property>
    </bean>

    <bean id="fileUploadController" class="examples.FileUploadController">
        <property name="commandClass"><value>examples.FileUploadBean</value></property>
        <property name="formView"><value>fileuploadform</value></property>
        <property name="successView"><value>confirmation</value></property>
    </bean>

</beans>

그후 컨트롤러와 파일 프라퍼티를 가지는 실질적인 빈을 생성하라.

// snippet from FileUploadController
public class FileUploadController extends SimpleFormController {

    protected ModelAndView onSubmit(
        HttpServletRequest request,
        HttpServletResponse response,
        Object command,
        BindException errors)
        throws ServletException, IOException {

        // cast the bean
        FileUploadBean bean = (FileUploadBean)command;

        // let's see if there's content there
        byte[] file = bean.getFile();
        if (file == null) {
            // hmm, that's strange, the user did not upload anything
        }

        // well, let's do nothing with the bean for now and return:
        return super.onSubmit(request, response, command, errors);
    }

    protected void initBinder(
        HttpServletRequest request,
        ServletRequestDataBinder binder)
        throws ServletException {
        // to actually be able to convert Multipart instance to byte[]
        // we have to register a custom editor (in this case the
        // ByteArrayMultipartEditor
        binder.registerCustomEditor(byte[].class, new ByteArrayMultipartFileEditor());
        // now Spring knows how to handle multipart object and convert them
    }

}

// snippet from FileUploadBean
public class FileUploadBean {
    private byte[] file;

    public void setFile(byte[] file) {
        this.file = file;
    }

    public byte[] getFile() {
        return file;
    }
}

당신이 볼수 있는 것처럼 FileUploadBean은 파일 데이터를 가지는 byte[]타입의 프라퍼티를 가진다. 컨트롤러는 Spring이 빈에 의해 명시된 프라퍼티를 위한 찾을수 있는 멀티파트 객체 결정자가 변환하는 방식을 알도록 사용자 지정 편집기를 등록한다. 이 예제에서 빈의 byte[] 프라퍼티로 하는것은 아무것도 없다. 하지만 실제로 당신은 당신이 무엇을 원하든지(데이터베이스에 저장을 하거나 누군가에게 메일을 보내더라도) 할수 있다.

하지만 우리는 아직 끝나지 않았다. 실질적으로 사용자가 무언가를 업로드 하기 위해서 우리는 폼을 생성해야만 한다.

<html>
    <head>
        <title>Upload a file please</title>
    </head>
    <body>
        <h1>Please upload a file</h1>
        <form method="post" action="upload.form" enctype="multipart/form-data">
            <input type="file" name="file"/>
            <input type="submit"/>
        </form>
    </body>
</html>

당신이 볼수 있는 것처럼 우리는 빈의 프라퍼티가 byte[]의 데이터를 가진후 명명된 필드를 생성할것이다. 게다가 우리는 멀티파트 필드를 인코딩하는 방법을 아는 브라우저를 위해 필요한 인코딩 속성을 추가한다(이것을 절대 잊지 마라.). 지금 우리는 해야할 모든것을 했다.

12.9. 예외 다루기

Spring은 당신의 요청이 적합한 컨트롤러에 의해 다루어지는 동안 발생하는 기대되지 않는 예외의 고통을 쉽게 하기 위해 HandlerExceptionResolvers제공한다. HandlerExceptionResolvers는 웹 애플리케이션 서술자인 web.xml내 당신이 명시할수 있는 예외 맵핑과 다소 비슷하다. 어쨌든 그들은 예외를 다루기 위한 좀더 유연한 방법을 제공한다. 그들은 예외가 던져질때 어떠한 핸들러가 수행되는지에 대한 정보를 제공한다. 게디가 예외를 다루는 프로그램적인 방법은 당신에게 요청이 다른 URL로 포워딩되기 전에 적절하게 응답하기 위한 방법을 위해 많은 옵션을 제공한다.(서블릿이 예외 맵핑을 명시하는것을 사용할때처럼 같은 결과를 낸다.)

더욱이 HandlerExceptionResolver을 구현하는 것은 오직 resolveException(Exception, Handler)메소드를 구현하고 ModelAndView를 반환하는 문제이다. 당신은 아마도 SimpleMappingExceptionResolver을 사용할지도 모른다. 이 결정자는 당신에게 던져지고 view이름에 그것을 맵핑하는 어떠한 예외의 클래스명을 가져오도록 할것이다. 이것은 서블릿 API로 부터 예외를 맵핑하는 기능과 기능적으로 유사하다. 하지만 이것은 또한 다른 핸들러로부터 잘 정제된 예외의 맵핑을 구현하는것이 가능하다.

Chapter 13. 통합 뷰 기술들

13.1. 소개

Spring의 탁월한 영역중의 하나는 MVC framework의 나머지 부분으로 부터 뷰 기술들을 분리하는 것이다. 예를 들어, JSP가 존재하는 곳에서 Velocity 또는 XSLT 사용을 결심하는 것은 근본적으로 구성(configuration)의 문제이다. 이 장에서는 Spring이 작업하는 주요한 뷰 기술들은 다루고, 새로운 기술들을 추가하는 간단한 방법을 알아본다. 이 장은 MVC framework과 연관된 일반적인 뷰의 기본적인 방법을 이미 다룬 Section 12.5, “view와 view결정하기”와 유사할 것이다.

13.2. JSP & JSTL

Spring은 JSP와 JSTL뷰를 위해 특별히 연관된 해결책을 제공한다. JSP 또는 JSTL 사용은 WebApplicationContext에서 정의된 일반적인 뷰해결자(viewresolver)를 사용한다. 더욱이, JSP들을 쓸 필요가 있을때 실제로 뷰에서 표현할 것이다(render). 이 부분은 JSP 개발을 쉽게 할 수 있게 제공되는 몇몇 추가적인 기능들에서 설명한다.

13.2.1. 뷰 해결자(View resolvers)

Spring에 통합된 다른 뷰 기술처럼 뷰 해결자가 필요로하는 JSP들을 위해 여러분들의 목적(views)을 해결할 것이다. 대부분 보통 JSP들은 InternalResourceViewResolverResourceBundleViewResolver를 개발할 때 뷰 해결자를 사용했다. 이 둘은 WebApplicationContext에 선언되어있다:

# The ResourceBundleViewResolver:
<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
    <property name="basename"><value>views</value></property>
</bean>

# And a sample properties file is uses (views.properties in WEB-INF/classes):
welcome.class=org.springframework.web.servlet.view.JstlView
welcome.url=/WEB-INF/jsp/welcome.jsp

productList.class=org.springframework.web.servlet.view.JstlView
productList.url=/WEB-INF/jsp/productlist.jsp

보는바와 같이, ResourceBundleViewResolver은 1)클래스와 2)URL을 대응시키기위해 뷰이름들을 정의하는 설정파일이 필요하다. ResourceBundleViewResolver와 함께 오직 해결자가 사용하는 뷰들의 다른 형태를 혼합할 수 있다.

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="viewClass"><value>org.springframework.web.servlet.view.JstlView</value></property>
    <property name="prefix"><value>/WEB-INF/jsp/</value></property>
    <property name="suffix"><value>.jsp</value></property>
</bean>

InternalResourceBundleViewResolver는 위에서 설명한것처럼 JSP를 사용하기 위해 구성할 수 있다. 가장 좋은 실행은, WEB-INF 디렉토리 아래의 디렉토리안에 JSP 파일들을 위치시키는것을 강력하게 권장한다. 그래서 클라이언트들에 의해 직접적으로 접근할수 없게 한다.

13.2.2. 'Plain-old' JSPs 대(versus) JSTL

자바 표준 테그 라이브러리를 사용할때, 특별한 뷰 클래스, JstlView을 사용해야하고, JSTL는 i18N기능들을 작업하기전에 몇몇의 표현이 필요하다.

13.2.3. 추가적인 태그들을 쉽게 쓸수 있는 개발

스트링은 이전 챕터들에서 설명한것처럼 객체들을 명령하기위한 request 파라미터들의 데이터 바인딩을 제공한다. 데이터 바인딩 기능들의 조합에서 JSP 페이지들 개발을 쉽게 할 수 있게 하기 위해서, Spring은 더 쉽게 만들어진 몇몇 태그들을 제공한다. 모든 Spring 태그들은 html에서 벗어나는(escaping) 기능들을 가질 수 있거나 문자열들의 벗어나는 기능들을 가지지 않을 수 도 있다.

태그 라이브러리 서술자(TLD)는 자기 자신의 배치(distribution)안에 spring.jar와 같이 포함한다. 개별적인 태그에관한 더많은 정보은 온라인에서 찾을 수 있다: http://www.springframework.org/docs/taglib/index.html.

13.3. Tiles

Spring을 사용하는 웹 애플리케이션 안에서 -다른 뷰 기술들과 같이- Tiles는 통합 가능하다. 다음은 대체로 타일을 사용하는 방법을 설명한다.

13.3.1. 의존물들(Dependencies)

Tiles을 사용할 수 있게 하기 위해서 여러분의 프로젝트 안에 포함된 연관된 추가적인 의존물들을 가진다. 다음은 여러분들이 필요로하는 의존물들의 목록이다.

  • struts version 1.1

  • commons-beanutils

  • commons-digester

  • commons-logging

  • commons-lang

의존물들은 Spring 구분(distribution)안에 모두 이용할 수 있다.

13.3.2. Tiles를 통합하는 방법

Tiles를 사용 할 수 있게 하기 위해서, 정의들이 포함된 파일을 사용해서 구성해야한다 (정의들에 대한 기본적인 정보와 다른 Tiles 개념들, http://jakarta.apache.org/struts에서 볼 수 있다). Spring 안에서 TilesConfigurer가 사용되어진다. 다음에 보는 것은 ApplicationContext 구성 예의 일부분이다:

<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles.TilesConfigurer">
    <property name="factoryClass">
        <value>org.apache.struts.tiles.xmlDefinition.I18nFactorySet</value>
    </property>
    <property name="definitions">
        <list>
            <value>/WEB-INF/defs/general.xml</value>
            <value>/WEB-INF/defs/widgets.xml</value>
            <value>/WEB-INF/defs/administrator.xml</value>
            <value>/WEB-INF/defs/customer.xml</value>
            <value>/WEB-INF/defs/templates.xml</value>
        </list>
    </property>
</bean>

여러분이 볼수 있는 바와 같이, WEB-INF/defs 디렉토리에 위치한, 정의들을 포함한 5개의 파일들이 있다. WebApplicationContext의 초기와에서 파일들은 로드(loaded)될 것이고 factoryClass-설정 에의해 정의된 definitionsfactory은 초기화된다. 이를 마친 후, 정의 파일들에 포함된 tiles은 Spring 웹 애플리케이션 내에 뷰로써 사용되어질 수 있다. 뷰들을 사용가능하게 하기 위해서 Spring과 함께 사용되어지는 다른 기술처럼 ViewResolver를 가진다. 아래의 InternalResourceViewResolverResourceBundleViewResolver의 두 가능성을 발견할 수 있다.

13.3.2.1. InternalResourceViewResolver

InternalResourceViewResolver는 각 뷰가 가지고있는 것을 분석하기위해(resolve) viewClass에서 주어진 것을 증명한다. The InternalResourceViewResolver instantiates the given viewClass for each view it has to resolve.

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="requestContextAttribute"><value>requestContext</value></property>
    <property name="viewClass">
        <value>org.springframework.web.servlet.view.tiles.TilesView</value>
    </property>
</bean>

13.3.2.2. ResourceBundleViewResolver

ResourceBundleViewResolver는 해결자(resolver)가 사용할 수 있는 뷰이름들과 뷰클래스들을 포함한 설정 파일들을 제공한다 :

<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
    <property name="basename"><value>views</value></property>
</bean>
    ...
    welcomeView.class=org.springframework.web.servlet.view.tiles.TilesView
    welcomeView.url=welcome (<b>this is the name of a definition</b>)
        
    vetsView.class=org.springframework.web.servlet.view.tiles.TilesView
    vetsView.url=vetsView (<b>again, this is the name of a definition</b>)
        
    findOwnersForm.class=org.springframework.web.servlet.view.JstlView
    findOwnersForm.url=/WEB-INF/jsp/findOwners.jsp
    ...

여러분이 볼수 있는 바와 같이, ResourceBundleViewResolver을 사용할때는, 다른 뷰 기술들을 사용하여 뷰를 혼합시킬 수 있다.

13.4. Velocity & FreeMarker

VelocityFreeMarker는 두 templating 언어이다. 이 둘은 Spring MVC 애플리케이션 내의 뷰 기술들과 같이 사용되어질수 있다. 언어들은 아주 유사하고 유사한 필요에 도움이 된다. 그래서 이번 섹션에서 함께 생각해 본다. 두 언어들 사이에 의미론상으로 구문론상의 차이점은 FreeMarker 웹사이트에서 보아라.

13.4.1. 의존물들(Dependencies)

여러분의 웹 애플리케이션은 Velocity 또는 FreeMarker 각각 작업을 하기 위해서 velocity-1.x.x.jar 또는 freemarker-2.x.jar를 포함시켜야 할 것이다. 그리고 commons-collections.jar 또한 Velocity를 이용하는데 필요할 것이다. 전형적으로 J2EE 서버에 의해 발견한 보증된 곳인 WEB-INF/lib 폴더에 포함되고 애플리케이션의 클래스 패스에 추가되어진다. 또한 WEB-INF/lib 폴더 안에 spring.jar가 이미 있다고 가정한다. 최신의 안정된 velocity, freemarker 그리고 commons collections jars는 Spring 프레임워크 안에 제공되어있고 관련된 /lib/ 하위-디렉토리들로부터 복사할 수 있다. 만약 Spring의 Velocity 뷰 안의 dateToolAttribute또는 numberToolAttribute를 사용하여 만든다면, 또한 velocity-tools-generic-1.x.jar를 포함시켜야 할 것이다.

13.4.2. 컨텍스트 구성(Context configuration)

알맞은 구성은 아래에 보는바와 같이 관련된 구성자가 *-servlet.xml를 정의를 추가하여 초기화하는 것이다.

<!--
  This bean sets up the Velocity environment for us based on a root path for templates.
  Optionally, a properties file can be specified for more control over the Velocity
  environment, but the defaults are pretty sane for file based template loading.
-->
<bean 
  id="velocityConfig" 
  class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
  <property name="resourceLoaderPath"><value>/WEB-INF/velocity/</value></property>          
</bean>

<!--
  View resolvers can also be configured with ResourceBundles or XML files.  If you need
  different view resolving based on Locale, you have to use the resource bundle resolver.
-->
<bean 
  id="viewResolver" 
  class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
  <property name="cache"><value>true</value></property>
  <property name="prefix"><value></value></property>
  <property name="suffix"><value>.vm</value></property>
</bean>
<!-- freemarker config -->
<bean 
  id="freemarkerConfig" 
  class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
  <property name="templateLoaderPath"><value>/WEB-INF/freemarker/</value></property> 
</bean>

<!--
  View resolvers can also be configured with ResourceBundles or XML files.  If you need
  different view resolving based on Locale, you have to use the resource bundle resolver.
-->
<bean 
  id="viewResolver" 
  class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
  <property name="cache"><value>true</value></property>
  <property name="prefix"><value></value></property>
  <property name="suffix"><value>.ftl</value></property>
</bean>

NB: 애플리케이션 컨텍스트 정의 파일에 VelocityConfigurationFactoryBean 또는 FreeMarkerConfigurationFactoryBean을 web-apps에 추가시키지 말아라.

13.4.3. 생성 템플릿들(Creating templates)

템플릿들은 Section 13.4.2, “컨텍스트 구성(Context configuration)”에서 보여준 *Configurer에 의해 명세화한 디렉토리안에 저장이 필요하다. 이문서는 두언어를 위해 생성 템플릿의 세부사항을 포함시키지 않는다 - 관련된 웹사이트에서 정보를 볼 수 있다. 만약 중요부분의 뷰 해결자들(resolvers)을 사용한다면, 논리적 뷰 이름을 JSP에대해 InternalResourceViewResolver 비슷한 형태인 템플릿 파일 이름과 관련시켜서 설명한다. 그래서 만약 제어자가 "welcome"이라는 뷰이름을 포함한 ModelAndView 객체를 되돌린다면 해결자는 /WEB-INF/freemarker/welcome.ftl 또는 /WEB-INF/velocity/welcome.vm 의 적합한템플릿을 찾을 것이다.

13.4.4. 진보한 구성(Advanced configuration)

위의 중요한 기본 구성들은 대부분 애플리케이션 요구사항에 적합할 것이다. 그러나 추가적인 구성선택들은 색다르거나 진보한 요구사항을 지시할때 이용할 수 있다.

13.4.4.1. velocity.properties

이 파일은 완전히 선택적이다. 그러나 명세화한다면, velocity 자체 구성을 하기 위해서 Velocity 런타임을 통과한 값을 포함한다. 단지 진보한 구성을 요구하는것, 만약 이 파일이 필요하다면 VelocityConfigurer 위치에서 위의 정의와 같이 명세화하라.

<bean 
  id="velocityConfig" 
  class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
  <property name="configLocation">
    <value>/WEB-INF/velocity.properties</value>
  </property>
</bean>

대신에, 다음 inline properties와 "configLocation" 설정을 교체함에따라 Velocity 구성 bean을 위해 bean 정의에 직접적으로 velocity properties를 명세화할 수 있다.

<bean 
  id="velocityConfig" 
  class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
  <property name="velocityProperties">
    <props>
      <prop key="resource.loader">file</prop>
      <prop key="file.resource.loader.class">
        org.apache.velocity.runtime.resource.loader.FileResourceLoader
      </prop>
      <prop key="file.resource.loader.path">${webapp.root}/WEB-INF/velocity</prop>
      <prop key="file.resource.loader.cache">false</prop>
    </props>
  </property>
</bean>

Velocity의 Spring 설정을 위해 API 문서을 참조하거나, velocity.properties 파일 자체의 예들과 정의들을 위한 Velocity 문서를 참조하라.

13.4.4.2. FreeMarker

FreeMarker 'Settings' 과 'SharedVariables' 는 FreeMarkerConfigurer bean의 적당한 bean프라퍼티를 셋팅하여 Spring에 의해 관리되는 FreeMarker Configuration 객체로 직접적으로 전달될수 있다. freemarkerSettings 프라퍼티는 java.util.Properties객체를 요구하고 freemarkerVariables 프라퍼티는 java.util.Map를 요구한다.

<bean 
  id="freemarkerConfig" 
  class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
  <property name="templateLoaderPath"><value>/WEB-INF/freemarker/</value></property> 
  <property name="freemarkerVariables">
    <map>
      <entry key="xml_escape"><ref local="fmXmlEscape"/></entry>
    </map>
  </property>
</bean>

<bean id="fmXmlEscape" class="freemarker.template.utility.XmlEscape"/>

Configuration객체에 적용할 셋팅과 변수의 상세사항을 위해서 FreeMarker문서를 보라.

13.4.5. 바인드(Bind) 지원과 폼(form) 핸들링

Spring은 JSP에서 사용하기 위해서 <spring:bind>를 포함한 태그라이브러리를 제공한다. 이 태그는 주로 폼지원 객체로 부터 값을 표시하거나 웹티어나 비지니스티어내 Validator로 부터 실패한 유효성체크의 결과를 보여주기 위해서 폼을 가능하게 한다. 1.1버전에서 부터, Spring은 자체적인 폼 input 요소를 생성하기 위한 추가적인 편리한 매크로를 가지고 Velocity 와 FreeMarker 모두에 대해 같은 기능을 지원한다.

13.4.5.1. 바인드(bind) 매크로

매크로의 표준 세트는 두가지 언어(Velocity 와 FreeMarker)를 위한 spring.jar 파일내 유지된다. 그래서 그것들은 적합하게 설정된 애플리케이션을 위해 언제나 사용가능하다. 어쨌든 그것들은 당신의 view가 bean프라퍼티인 exposeSpringMacroHelperstrue로 셋팅될때만 사용될수 있다. 당신의 view가 값을 상속할 모든 경우에 같은 프라퍼티는 VelocityViewResolverFreeMarkerViewResolver에서 셋팅될수 있다. 이 프라퍼티는 Spring 매크로의 장점을 가지기를 원하는 곳을 제외하고HTML 폼 핸들링의 어떠한 양상을 위해 요구되지 않는다. 아래는 이러한 view의 정확한 설정을 보여주는 view.properties파일의 예제이다.

personFormV.class=org.springframework.web.servlet.view.velocity.VelocityView
personFormV.url=personForm.vm
personFormV.exposeSpringMacroHelpers=true
personFormF.class=org.springframework.web.servlet.view.freemarker.FreeMarkerView
personFormF.url=personForm.ftl
personFormF.exposeSpringMacroHelpers=true

Spring 라이브러리내 정의된 몇몇 매크로는 내부적(개인적)으로 검토되지만 매크로 정의내 존재하는 범위는 모든 매크로를 호출 코드와 사용자 템플릿을 위해 볼수 있도록 만드는 것은 없다. 다음 부분은 당신에게 템플릿에서 직접적으로 호출될 필요가 있는 매크로에만 집중한다. 만약 당신이 매크로 코드를 직접적으로 보길 원한다면 파일은 spring.vm / spring.ftl 라고 불리고 org.springframework.web.servlet.view.velocityorg.springframework.web.servlet.view.freemarker 패키지내 존재한다.

13.4.5.2. 간단한 바인딩

Spring 폼 컨트롤러를 위한 'formView' 처럼 작동하는 html폼(vm / ftl 템플릿)내에서, 당신은 필드값으로 바인드하는 것과 유사한 코드를 사용하고 JSP와 유사한 형태로 각각의 input필드를 위한 에러메시지를 표시할수 있다. command객체의 이름은 디폴트에 의해 "command" 이지만 폼 컨트롤러의 'commandName' bean프라퍼티를 셋팅하여 당신의 MVC설정에 오버라이드될수 있다는 것을 알라. 예제 코드는 먼저 설정된 personFormVpersonFormF view를 위해 밑에서 보여진다.

<!-- velocity macros are automatically available -->
<html>
...
<form action="" method="POST">
  Name: 
  #springBind( "command.name" )
  <input type="text" 
    name="${status.expression}" 
    value="$!status.value" /><br>
  #foreach($error in $status.errorMessages) <b>$error</b> <br> #end
  <br>
  ... 
  <input type="submit" value="submit"/>
</form>
...
</html>
<!-- freemarker macros have to be imported into a namespace.  We strongly
recommend sticking to 'spring' -->
<#import "spring.ftl" as spring />
<html>
...
<form action="" method="POST">
  Name: 
  <@spring.bind "command.name" /> 
  <input type="text" 
    name="${spring.status.expression}" 
    value="${spring.status.value?default("")}" /><br>
  <#list spring.status.errorMessages as error> <b>${error}</b> <br> </#list>
  <br>
  ... 
  <input type="submit" value="submit"/>
</form>
...
</html>

#springBind / <@spring.bind> 는 문장(period)과 당신이 바인드하고자 하는 command객체 필드의 이름에 의해 당신의 command객체(FormController프라퍼티를 변경하지 않는다면 이것은 'command'가 될것이다.)의 이름을 구성하는 'path' 인자를 요구한다. 내포된 필드는 "command.address.street" 처럼 사용될수 있다. bind 매크로는 web.xml내 ServletContext 파라미터인 defaultHtmlEscape에 의해 명시된 디폴트 HTML 회피(escaping) 행위를 가정한다.

#springBindEscaped / <@spring.bindEscaped> 라고 불리는 매크로의 선택적인 폼은 2개의 인자를 가지고 HTML회피가 상태 에러 메시지나 값에서 사용될지에 대해 명시한다. 요구되는 값은 true나 false이다. 추가적인 폼 핸들링 매크로는 HTML회피와 사용이 가능한 매크로의 사용을 단순화한다. 그것들은 다음 부분에서 설명된다.

13.4.5.3. 폼 input 생성 매크로

두가지 언어를 위한 추가적인 편리한 매크로는 바인딩과 폼 생성(유효성 체크 에러 표시를 포함하여)을 단순화한다. 폼 input필드를 생성하기 위한 매크로를 사용하는 것은 결코 필요하지 않다. 그것들은 간단한 HTML과 혼합되거나 대응될수 있거나 먼저 강조된 Spring 바인드 매크로에 직접적으로 호출한다.

사용가능한 매크로의 다음 테이블은 VTL과 FTL정의와 파라미터 목록을 보여준다.

Table 13.1. 매크로 정의 테이블

매크로 VTL 정의 FTL 정의
message (코드 파라미터에 기반한 자원 번들로부터 문자열 출력) #springMessage($code) <@spring.message code/>
messageText (코드 파라미터에 기반한 자원 번들로부터 문자열 출력, 디폴트 파라미터의 값으로 되돌아 감) #springMessageText($code $default) <@spring.messageText code, default/>
url (애플리케이션 컨텍스트 root를 가진 상대적인 URL을 접두사로 붙이는) #springUrl($relativeUrl) <@spring.url relativeUrl/>
formInput (사용자 입력을 모으기 위한 표준적인 input필드) #springFormInput($path $attributes) <@spring.formInput path, attributes, fieldType/>
formHiddenInput * (비-사용자 입력을 서브릿하기 위한 hidden input필드) #springFormHiddenInput($path $attributes) <@spring.formHiddenInput path, attributes/>
formPasswordInput * (비밀번호를 모으기 위한 표준적인 input 필드. 이 타입의 필드로 활성화될 값은 없다는것을 알라.) #springFormPasswordInput($path $attributes) <@spring.formPasswordInput path, attributes/>
formTextarea (long값을 모으기 위한 큰 텍스트 필드, freeform형태의 텍스트 input) #springFormTextarea($path $attributes) <@spring.formTextarea path, attributes/>
formSingleSelect (선택되기 위한 하나의 필수값을 허용하는 선택사항의 drop down 박스) #springFormSingleSelect( $path $options $attributes) <@spring.formSingleSelect path, options, attributes/>
formMultiSelect (하나 이상의 값을 선택하기 위한 사용자를 허용하는 선택사항의 리스트 박스) #springFormMultiSelect($path $options $attributes) <@spring.formMultiSelect path, options, attributes/>
formRadioButtons (사용가능한 선택사항으로 부터 만들수 있는 하나의 selection을 허용하는 radio버튼의 세트) #springFormRadioButtons($path $options $separator $attributes) <@spring.formRadioButtons path, options separator, attributes/>
formCheckboxes (선택되기 위한 하나 이상의 값을 허용하는 checkbox의 세트) #springFormCheckboxes($path $options $separator $attributes) <@spring.formCheckboxes path, options, separator, attributes/>
showErrors (연결된 필드를 위한 유효성 체크 에러의 간단한 표시) #springShowErrors($separator $classOrStyle) <@spring.showErrors separator, classOrStyle/>

* FTL (FreeMarker) 에서, 두개의 매크로는 당신이 'hidden' 이나 fieldType 파라미터를 위한 값처럼 'password' 을 명시하는 일반적인 formInput 매크로를 사용할수 있는것처럼 실질적으로 필수가 아니다.

위 매크로중 어느것을 위한 파라미터는 일관적인 수단을 가진다.

  • path: 바인드 하기 위한 필드의 이름(이를테면 "command.name")

  • options: 모든 사용가능한 값의 map은 input필드내 선택될수 있다. 값을 표시하기 위한 map의 key는 form으로 부터 게시될것이고 command객체로 연결된다. key에 대응되어 저장되는 map객체는 사용자를 위한 폼에 표시되는 라벨이고 form에 의해 게시되는 관련값들과는 다르다. map은 언제나 컨트롤러에 의한 참조데이터처럼 제공된다. map구현물은 필수행위에 의존되어 사용될수 있다. 엄격하게 정렬된 map을 위해 적당한 비교자를 가진 TreeMap과 같은 SortedMap은 사용될수 있고 입력순으로 값을 반환해야만 하는 임의의 map을 위해 commons-collections의 LinkedHashMap 이나 LinkedMap를 사용하자.

  • separator: 다중 옵션이 신중한(discreet) 요소(radio버튼이나 checkbox)처럼 사용가능한 곳. 순차적인 문자는 목록에서 각각 분리되기 위해 사용된다. (이를테면 "<br>").

  • attributes: HTML 태그 자체에 포함되는 임의의 태그나 텍스트의 추가적인 문자열. 이 문자열은 매크로에 의해 문자그대로 울린다. 예를 들어, textarea필드에서 당신은 'rows="5" cols="60"' 처럼 속성을 제공하거나 'style="border:1px solid silver"' 처럼 스타일 정보를 전달할수 있다.

  • classOrStyle: showErrors 매크로를 위해, 태그를 확장하는 CSS클래스의 이름은 사용할 각각의 에러를 포장한다. 아무런 정보도 없다면(또는 값이 공백이라면) 에러는 <b></b> 태그내 포장될것이다.

매크로의 예제는 몇몇 FTL과 VTL는 아래에서 간단하게 설명된다. 두가지 언어사이의 사용상의 차이점은 이 노트에서 설명된다.

13.4.5.3.1. input 필드
<!-- the Name field example from above using form macros in VTL -->
...
    Name:
    #springFormInput("command.name" "")<br>
    #springShowErrors("<br>" "")<br>

formInput 매크로는 path파라미터(command.name)와 위 예제에서 빈 추가적인 attribute속성을 가져온다. 다른 폼 생성 매크로와 함께 매크로는 path파라미터에 함축적으로 Spring 바인드를 수행한다. 바인딩은 새로운 바인드가 발생해서 showErrors 매크로가 다시는 path파라미터를 전달할 필요가 없을때까지 유효하다.

showErrors 매크로는 separator(분리자 - 문자들은 주어진 필드에 다중 에러를 분리하기 위해 사용될것이다.) 파라미터를 가지고 두번째 파라미터를 받아들인다. 이번은 클래스명과 style속성이다. FreeMarker는 Velocity와는 달리 attribute속성을 위한 디폴트값을 명시하는것이 가능하다. 그리고 위 두개의 매크로 호출은 다음의 FTL처럼 표시될수 있다.

<@spring.formInput "command.name"/>
<@spring.showErrors "<br>"/>

출력은 name필드를 생성하는 form일부를 밑에서 보여준다. 그리고 form이 필드내 어떤값도 가지지 않고 서브밋된 후에 유효성체크 에러를 표시한다. 유효성체크는 Spring의 Validation프레임워크를 통해 발생한다.

생성된 HTML은 다음처럼 보일것이다.

Name:
  <input type="text" name="name" value=""     
>
<br>
  <b>required</b>
<br>
<br>

formTextarea매크로는 formInput매크로와 같은 방법으로 작동하고 같은 파라미터 목록을 받아들인다. 공통적으로 두번째 파라미터(속성)는 스타일정보를 전달하거나 textarea를 위한 rows와 cols를 전달하기 위해 사용될것이다.

13.4.5.3.2. selection 필드

4개의 selection 필드 매크로는 HTML form내 공통 UI값 selection input를 생성하기 위해 사용될수 있다.

  • formSingleSelect

  • formMultiSelect

  • formRadioButtons

  • formCheckboxes

4가지 매크로 각각은 form필드를 위한 값과 그 값에 관련된 라벨을 포함하는 옵션의 map을 받아들인다. 값과 라벨은 같을수 있다.

FTL내 radio버튼의 예제는 밑에 있다. form지원 객체는 이 필드를 위한 'London'의 디폴트 값을 명시하고 유효성체크가 필요하지 않다. form이 표현될때 선택하는 city의 전체 목록이 'cityMap'이라는 이름하의 모델내 참조 데이터처럼 제공된다.

...
  Town:
  <@spring.formRadioButtons "command.address.town", cityMap, "" /><br><br>

이것은 분리자 ""를 사용하여 cityMap내 각각의 값중 하나인 radio버튼을 표현한다. 추가적인 속성은 제공되지 않는다(매크로를 위한 마지막 파라미터는 없다). cityMap은 map내 각각의 키(key)-값(value)쌍을 위한 같은 문자열을 사용한다. map의 키는 form이 실질적으로 전송된 요청 파라미터처럼 서브밋하는 것이다. map의 값은 사용자가 보는 라벨이다. 위 예제에서 form지원 객체내 주어진 3개의 잘 알려진 city이 목록과 디폴트 값이다.

Town:
<input type="radio" name="address.town" value="London"
   
>
London
<input type="radio" name="address.town" value="Paris"
  checked="checked" 
>
Paris
<input type="radio" name="address.town" value="New York"
   
>
New York

만약 당신의 애플리케이션이 내부 코드에 의해 city를 다루는것을 기대한다면 예를 들어, 코드의 map은 아래의 예제처럼 적합한 key를 가지고 생성될것이다.

protected Map referenceData(HttpServletRequest request) throws Exception {
  Map cityMap = new LinkedHashMap();
  cityMap.put("LDN", "London");
  cityMap.put("PRS", "Paris");
  cityMap.put("NYC", "New York");
  
  Map m = new HashMap();
  m.put("cityMap", cityMap);
  return m;
}

코드는 radio값이 적절한 코드지만 사용자가 좀더 사용자에게 친숙한 city이름을 볼수 있는 출력을 생성할것이다.

Town:
<input type="radio" name="address.town" value="LDN"
   
>
London
<input type="radio" name="address.town" value="PRS"
  checked="checked" 
>
Paris
<input type="radio" name="address.town" value="NYC"
   
>
New York

13.4.5.4. HTML회피를 오버라이드하고 XHTML호환 태그를 만든다.

위 form매크로의 디폴트 사용은 HTML 4.01호환 HTML태그의 결과를 보일것이고 Spring의 바인트 지원이 사용하는것처럼 web.xml내 정의된 HTML회피를 위한 디폴트 값을 사용한다. XHTML호환 태그를 만들거나 디폴트 HTML회피 값을 오버라이드하기 위해 당신은 템플릿(또는 당신의 템플릿을 볼수 있는 모델내)에 두개의 변수를 명시할수 있다. 템플릿내 그것들을 명시하는 장점은 form내 다른 필드를 위해 다른 행위를 제공하기 위한 템플릿 처리로 그것들이 나중에 다른 값으로 변경될수 있다는 것이다.

당신의 태그를 위한 XHTML호환으로 변경하기 위해 xhtmlCompliant라는 이름의 모델/컨텍스트 변수를 'true'의 값을 명시하라.

## for Velocity..
#set($springXhtmlCompliant = true)

<#-- for FreeMarker -->
<#assign xhtmlCompliant = true in spring>

Spring매크로에 의해 생성되는 태그는 직접적으로 처리된 후 XHTML호환이 될것이다.

유사한 방법으로 HTML회피는 필드마다 명시될수 있다.

<#-- until this point, default HTML escaping is used -->

<#assign htmlEscape = true in spring>
<#-- next field will use HTML escaping -->
<@spring.formInput "command.name" />

<#assign htmlEscape = false in spring>
<#-- all future fields will be bound with HTML escaping off -->

13.5. XSLT

XSLT는 XML을 위한 변형언어이고 웹 애플리케이션내 view기술처럼 인기있다. 당신의 애플리케이션이 당연히 XML을 다루거나 당신의 모델이 XML로 쉽게 변활될수 있다면 XSLT는 view기술로 좋은 선택이 될수 있다. 다음 부분은 모델 데이터처럼 XML문서를 생성하는 방법을 보여주고 Spring애플리케이션내 XSLT로 변형한다.

13.5.1. 나의 첫번째 단어

이 예제는 Controller내 단어 목록을 생성하고 그것들을 모델 map으로 추가하는 사소한 Spring애플리케이션이다. map은 XSLT view의 view이름과 함께 반환된다. Spring Controller들의 상세사항을 위해 Section 12.3, “컨트롤러”을 보라. XSLT view는 단어의 목록에서 변형될 준비가 된 간단한 XML문서로 바뀔것이다.

13.5.1.1. Bean 정의

설정은 간단한 Spring애플리케이션을 위한 표준이다. dispatcher 서블릿 설정파일은 URL맵핑과 하나의 컨트롤러 bean을 가지는 ViewResolver를 위한 참조를 포함한다.

<bean id="homeController"class="xslt.HomeController"/> 

그것은 우리의 단어 생성 'logic'을 구현한다.

13.5.1.2. 표준적인 MVC 컨트롤러 코드

컨트롤러 로직은 정의된 핸들러 메소드와 함께 AbstractController의 하위클래스로 캡슐화된다.

protected ModelAndView handleRequestInternal(
    HttpServletRequest req,
    HttpServletResponse resp)
    throws Exception {
        
    Map map = new HashMap();
    List wordList = new ArrayList();
        
    wordList.add("hello");
    wordList.add("world");
       
    map.put("wordList", wordList);
      
    return new ModelAndView("home", map);
} 

지금까지 우리는 XSLT가 명시하는것을 아무것도 하지 않았다. 모델 데이터는 당신이 다른 Spring MVC애플리케이션을 하는것처럼 같은 방법으로 생성된다. 지금 애플리케이션의 설정에 의존하여 단어의 목록은 JSP/JSTL에 의해 요청 속성을 추가하여 표시될수 있거나 VelocityContext에 객체를 구가하여 Velocity에 의해 다루어질수 있다. XSLT가 그것들을 표현하기 위해, 그것들은 XML문서로 변환된다. 자동적으로 'domify' 객체 그래프가 될 사용가능한 소프트웨어 패키지가 있다. 당신은 선택한 방법으로 모델에서 DOM을 생성하는 완벽한 유연성을 가진다. 이것은 domification처리를 관리하는 툴을 사용할때 위험한 모델 데이터의 구조에서 너무 큰 부분으로 작동하는 XML의 변형을 방지한다.

13.5.1.3. 모델 데이터를 XML로 변환하기

우리의 단어 목록이나 다른 모델 데이터로부터 DOM문서를 생성하기 위해 우리는 org.springframework.web.servlet.view.xslt.AbstractXsltView의 하위클래스를 만든다. 우리는 추상 메소드인 createDomNode()을 구현해야만 한다. 이 메소드에 전달되는 첫번째 파라미터는 모델 map이다. 우리의 단어 애플리케이션내 HomePage클래스의 완벽한 목록이다. 이것은 W3C Node를 요구하는 것으로 변환하기 전에 XML문서를 빌드하기 위한 JDOM을 사용한다. 하지만 이것은 W3C API보다 다루기 쉬운 JDOM(그리고 Dom4J) API를 알기 때문에 간단한다.

package xslt;

// imports omitted for brevity

public class HomePage extends AbstractXsltView {

    protected Node createDomNode( 
        Map model, String rootName, HttpServletRequest req, HttpServletResponse res
    ) throws Exception {
        
        org.jdom.Document doc = new org.jdom.Document();
        Element root = new Element(rootName);
        doc.setRootElement(root);

        List words = (List) model.get("wordList");
        for (Iterator it = words.iterator(); it.hasNext();) {
            String nextWord = (String) it.next();
            Element e = new Element("word");
            e.setText(nextWord);
            root.addContent(e);
        }

        // convert JDOM doc to a W3C Node and return
        return new DOMOutputter().output( doc );
    }

}
13.5.1.3.1. stylesheet 파라미터 추가하기

일련의 파라미터 이름/값 쌍은 선택적으로 변형객체로 추가될 하위클래스에 의해 정의될수 있다. 파라미터 이름은 파라미터를 명시하기 위한 <xsl:param name="myParam">defaultValue</xsl:param>로 선언되는 XSLT템플릿으로 정의되는 것들에 매치되어야만 한다. AbstractXsltView의 getParameters() 메소드를 오버라이드하고 이름/값 쌍의 Map을 반환한다. 만약 파라미터가 현재 요청으로부터 정보를 가져올 필요가 있다면 당신은 getParameters(HttpServletRequest request) 메소드를 대신 오버라이드(1.1버전부터)할수 있다.

13.5.1.3.2. 날짜와 화폐단위 포맷팅

JSTL 과 Velocity와는 달리, XSLT는 로케일에 기반한 화폐단위와 날짜 포멧팅을 위한 지원이 상대적으로 빈약하다. 그 사실을 인정해서 Spring은 그부분에 대한 지원을 위해 createDomNode() 메소드로 부터 사용할수 있는 헬퍼 클래스를 제공한다. org.springframework.web.servlet.view.xslt.FormatHelper를 위해 JavaDoc를 보라.

13.5.1.4. view프라퍼티 정의하기

views.properties 파일(또는 위 Velocity예제에서 처럼 XML기반한 view해설자(resolver)를 사용할때 동등한 xml정의)은 'My First Words'인 하나의 view를 가진 애플리케이션을 위한 것처럼 보인다.

home.class=xslt.HomePage
home.stylesheetLocation=/WEB-INF/xsl/home.xslt
home.root=words

여기서, 당신은 view가 첫번째 프라퍼티인 '.class'내 모델 domification을 다루는 것에 의해 쓰여진 HomePage클래스와 묶이는 방법을 볼수 있다. stylesheetLocation 프라퍼티는 HTML파일로의 변형이 되는 XML을 다루는 XSLT파일을 가리키고 마지막 프라퍼티인 '.root'는 XML문서의 root처럼 사용될 이름이다. 이것은 createDomNode 메소드를 위한 두번째 파라미터로 위 HomePage 클래스에 전달된다.

13.5.1.5. 문서 변형

마지막으로, 우리는 위 문서를 변형하기 위해 사용되는 XSLT코드를 가진다. views.properties 파일에서 강조된것처럼 이것은 home.xslt로 불리고 WEB-INF/xsl하위의 war파일내 있다.

<?xml version="1.0"?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text/html" omit-xml-declaration="yes"/>

    <xsl:template match="/">
        <html>
            <head><title>Hello!</title></head>
            <body>

                <h1>My First Words</h1>
                <xsl:for-each select="wordList/word">
                    <xsl:value-of select="."/><br />
                </xsl:for-each> 

            </body>
        </html>
    </xsl:template>

</xsl:stylesheet>

13.5.2. 요약

언급된 파일의 요약과 WAR파일내 그것들의 위치는 아래의 단순화된 WAR구조에서 보여진다.

ProjectRoot
  |
  +- WebContent
      |
      +- WEB-INF
          |
          +- classes
          |    |
          |    +- xslt
          |    |   |
          |    |   +- HomePageController.class 
          |    |   +- HomePage.class
          |    |
          |    +- views.properties
          |
          +- lib
          |   |
          |   +- spring.jar
          |
          +- xsl
          |   |
          |   +- home.xslt
          |
          +- frontcontroller-servlet.xml

당신은 XML파서와 XSLT엔진이 클래스패스에서 사용가능한지를 확인할 필요가 있다. JDK 1.4는 디폴트로 그것들을 제공한다. 그리고 대부분의 J2EE컨테이너는 디폴트에 의해 그것들을 사용가능하게 만들것이다. 하지만 이것은 인식되는 에러의 가능한 원인이다.

13.6. 문서 views (PDF/Excel)

13.6.1. 소개

HTML페이지를 반환하는 것은 사용자에게 모델 출력을 보여주기 위해 언제나 가장 좋은 방법은 아니다. 그리고 Spring은 모델 데이터로부터 동적으로 PDF문서나 Excel 스프레드시트를 생성하는것을 쉽게 만든다. 문서는 view이고 서버로부터 응답시 클라이언트 PC가 스프레드시트나 PDF뷰어 애플리케이션을 실행할수 있도록 하는 올바른 컨텐츠타입을 가지고 나올것이다.

Excel 뷰를 사용하기 위해, 당신은 클래스패스내 'poi' 라이브러리를 추가할 필요가 있다. 그리고 PDF생성을 위해 iText.jar를 추가할 필요가 있다. 둘다 Spring 배포물에 포함되어 있다.

13.6.2. 설정 그리고 셋업

문서 기반 view는 XSLT view와 대부분 동일한 형태로 다루어진다. 그리고 다음의 부분은 XSLT예제에서 사용된 같은 컨트롤러가 PDF문서나 Excel 스프레드시트(Open Office에서 볼수 있거나 변경이 가능한)처럼 같은 모델을 표시하기 위해 호출되는 방법을 보여주어서 이전의 것을 빌드한다.

13.6.2.1. 문서 view정의

첫번째, views.properties파일(또는 xml파일형태의 프라퍼티 파일)을 수정하자. 그리고 두가지 문서 타입을 위해 간단한 view정의를 추가하자. 전체 파일은 이전에 XSLT view에서 보여진 것과 비슷하게 보일것이다.

home.class=xslt.HomePage
home.stylesheetLocation=/WEB-INF/xsl/home.xslt
home.root=words

xl.class=excel.HomePage

pdf.class=pdf.HomePage

만약 당신의 모델 데이터를 추가하기 위해 템플릿 스프레드시트로 시작하길 원한다면 view정의내 'url' 프라퍼티로 위치를 명시하라.

13.6.2.2. 컨트롤러 코드

컨트롤러 코드에서 우리는 사용하기 위한 view의 이름을 변경하는것보다 이전의 XSLT예제로부터 같은것을 사용할것이다. 물론, 당신은 능숙할수 있고 URL파라미터나 몇몇 다른 로직에 기반하여 이것을 선택할수 있다. 이것은 Spring이 컨트롤러로부터 view를 디커플링하는데 매우 좋다는것을 증명한다.

13.6.2.3. Excel view를 위한 하위클래스 만들기

XSLT에제를 위해 했던것처럼, 우리는 출력문서를 생성하는 사용자정의 행위를 구현하기 위한 적합한 추상 클래스의 하위클래스를 만들것이다. Excel을 위해, 이것은 org.springframework.web.servlet.view.document.AbstractExcelView의 하위클래스를 생성하고 buildExcelDocument를 구현한다.

새로운 스프레드시트의 첫번째 칼럼의 연속적인 row내 모델 map으로 부터 단어목록을 보여주는 Excel view을 위한 완벽한 목록이다.

package excel;

// imports omitted for brevity

public class HomePage extends AbstractExcelView {

    protected void buildExcelDocument(
        Map model,
        HSSFWorkbook wb,
        HttpServletRequest req,
        HttpServletResponse resp)
        throws Exception {
    
        HSSFSheet sheet;
        HSSFRow sheetRow;
        HSSFCell cell;

        // Go to the first sheet
        // getSheetAt: only if wb is created from an existing document
        //sheet = wb.getSheetAt( 0 );
        sheet = wb.createSheet("Spring");
        sheet.setDefaultColumnWidth((short)12);

        // write a text at A1
        cell = getCell( sheet, 0, 0 );
        setText(cell,"Spring-Excel test");

        List words = (List ) model.get("wordList");
        for (int i=0; i < words.size(); i++) {
            cell = getCell( sheet, 2+i, 0 );
            setText(cell, (String) words.get(i));

        }
    }
}

만약 당신이 지금 view(새로운 ModelAndView("xl", map);를 반환하는)의 이름처럼 xl을 반환하는 컨트롤러를 수정하고 다시 애플리케이션을 실행한다면, 이전처럼 같은 페이지를 요청할때 자동적으로 Excel 스프레드시트가 생성되거나 다운로드되는것을 알게된다.

13.6.2.4. PDF view를 위한 하위클래스 만들기

단어 목록의 PDF버전은 좀더 간단하다. 이 시점에, 클래스는 org.springframework.web.servlet.view.document.AbstractPdfView를 확장하고 다음처럼 buildPdfDocument() 메소드를 구현한다.

package pdf;

// imports omitted for brevity

public class PDFPage extends AbstractPdfView {

    protected void buildPdfDocument(
        Map model,
        Document doc