스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

164

Post on 22-Jul-2016

305 views

Category:

Documents


8 download

DESCRIPTION

게리 막, 조시 롱, 다니엘 루비오 지음 | 고종봉, 백기선, 유윤선 옮김 | 오픈소스 & 웹 시리즈 _ 029 | ISBN: 9788992939768 | 45,000원 | 2011년 03월 30일 발행 | 1192쪽

TRANSCRIPT

Page 1: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서
Page 2: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서
Page 3: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

스프링 3레시피

Page 4: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

iv

•목 차•

01장 스프링 소개 11-1. 스프링 IoC 컨테이너 초기화 .....................................................................1

과제_1 / 해결책_2 / 과제 풀이_3

1-2. 스프링 IoC 컨테이너에 빈 구성하기 ........................................................4

과제_4 / 해결책_5 / 과제 풀이_5

1-3. 생성자를 호출해 빈 생성하기 ................................................................ 16

과제_16 / 해결책_16 / 과제 풀이_16

1-4. 모호한 생성자 문제 해결하기 ................................................................ 19

과제_19 / 해결책_20 / 과제 풀이_20

1-5. 빈 레퍼런스 지정하기 ............................................................................. 23

과제_23 / 해결책_23 / 과제 풀이_23

1-6. 컬렉션 엘리먼트에 데이터 타입 지정하기 ............................................ 28

과제_28 / 해결책_28 / 과제 풀이_28

1-7. 스프링 FactoryBean을 이용한 빈 생성 ................................................. 31

과제_31 / 해결책_31 / 과제 풀이_31

1-8. 팩터리 빈과 유틸리티 스키마로 컬렉션 정의하기 ............................... 33

과제_33 / 해결책_33 / 과제 풀이_34

1-9. 의존성 검사 기능으로 프로퍼티 검사하기 ............................................ 36

과제_36 / 해결책_36 / 과제 풀이_37

1-10. @Required 애노테이션으로 프로퍼티 검사하기 ............................... 38

과제_38 / 해결책_39 / 과제 풀이_39

1-11. XML 설정을 이용한 빈 자동 연결 ....................................................... 41

과제_41 / 해결책_41 / 과제 풀이_42

Page 5: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

v

1-12. @Autowired와 @Resource로 빈 자동 연결하기 ................................ 46

과제_46 / 해결책_46 / 과제 풀이_46

1-13. 빈 설정 상속하기 ................................................................................... 53

과제_53 / 해결책_54 / 과제 풀이_54

1-14. 클래스패스 컴포넌트 스캔하기 ........................................................... 57

과제_57 / 해결책_57 / 과제 풀이_57

정리 ................................................................................................................... 64

02장 고급 스프링 IoC 컨테이너 672-1. 스태틱 팩터리 메서드 호출을 통한 빈 생성 .......................................... 67

과제_67 / 해결책_67 / 과제 풀이_68

2-2. 인스턴스 팩터리 메서드 호출을 통한 빈 생성 ...................................... 69

과제_69 / 해결책_69 / 과제 풀이_69

2-3. 스태틱 필드를 통한 빈 선언 ................................................................... 71

과제_71 / 해결책_71 / 과제 풀이_71

2-4. 객체 프로퍼티를 통한 빈 선언 ............................................................... 73

과제_73 / 해결책_73 / 과제 풀이_73

2-5. 스프링 표현 언어(SpEL) 사용하기 ........................................................ 75

과제_75 / 해결책_75 / 과제 풀이_75

2-6. 빈 범위 설정 ............................................................................................. 81

과제_81 / 해결책_81 / 과제 풀이_81

2-7. 빈 초기화 및 소멸 과정 재정의 .............................................................. 84

과제_84 / 해결책_84 / 과제 풀이_85

Page 6: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

vi

2-8. Java Con�g를 활용한 XML 설정 단축 .................................................. 89

과제_89 / 해결책_90 / 과제 풀이_90

2-9. 빈에 컨테이너 정보 알려주기 ................................................................ 94

과제_94 / 해결책_94 / 과제 풀이_95

2-10. 외부 자원 읽어들이기 ........................................................................... 96

과제_96 / 해결책_96 / 과제 풀이_96

2-11. 빈 후처리기 작성 ................................................................................... 99

과제_99 / 해결책_99 / 과제 풀이_100

2-12. 빈 설정의 외부화 ................................................................................. 104

과제_104 / 해결책_104 / 과제 풀이_105

2-13. 텍스트 메시지 해석 ............................................................................. 106

과제_106 / 해결책_106 / 과제 풀이_106

2-14. 애플리케이션 이벤트와의 상호작용 ................................................. 108

과제_108 / 해결책_109 / 과제 풀이_109

2-15. 스프링에 프로퍼티 에디터 등록 ........................................................ 112

과제_112 / 해결책_112 / 과제 풀이_112

2-16. 커스텀 프로퍼티 에디터 만들기 ........................................................ 116

과제_116 / 해결책_116 / 과제 풀이_116

2-17. TaskExecutor를 사용한 동시성 처리 ................................................ 117

과제_117 / 해결책_118 / 과제 풀이_118

정리 ................................................................................................................. 128

03장 스프링 AOP와 AspectJ 지원 1313-1. 스프링 AspectJ 애노테이션 지원 기능 사용 ....................................... 132

과제_132 / 해결책_132 / 과제 풀이_132

3-2. AspectJ 애노테이션을 통한 애스펙트 선언 ........................................ 135

과제_135 / 해결책_135 / 과제 풀이_135

3-3. 조인 포인트의 상세 정보 참조 ............................................................. 142

과제_142 / 해결책_142 / 과제 풀이_142

Page 7: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

vii

3-4. 애스펙트 우선순위 지정 ....................................................................... 143

과제_143 / 해결책_143 / 과제 풀이_144

3-5. 포인트컷 정의 재사용 ........................................................................... 146

과제_146 / 해결책_146 / 과제 풀이_146

3-6. AspectJ 포인트컷 표현식 작성 ............................................................. 148

과제_148 / 해결책_148 / 과제 풀이_149

3-7. 인트로덕션을 활용해 빈에 행위 추가하기 .......................................... 154

과제_154 / 해결책_154 / 과제 풀이_154

3-8. 인트로덕션을 활용해 빈에 상태 추가하기 .......................................... 157

과제_157 / 해결책_158 / 과제 풀이_158

3-9. XML 기반 설정을 활용한 애스펙트 선언 ........................................... 160

과제_160 / 해결책_160 / 과제 풀이_160

3-10. AspectJ 애스펙트의 로드 시점 위빙 .................................................. 164

과제_164 / 해결책_164 / 과제 풀이_164

3-11. 스프링에서의 AspectJ 애스펙트 설정 ............................................... 170

과제_170 / 해결책_170 / 과제 풀이_171

3-12. 도메인 객체에 스프링 빈 주입하기 ................................................... 172

과제_172 / 해결책_172 / 과제 풀이_172

정리 ................................................................................................................. 176

04장 스프링 스크립팅 1794-1. 스크립트 언어를 사용한 빈 구현 ......................................................... 180

과제_180 / 해결책_180 / 과제 풀이_180

4-2. 스크립트에 대한 스프링 빈 주입 ......................................................... 185

과제_185 / 해결책_185 / 과제 풀이_185

4-3. 스크립트 빈 리프레시 ........................................................................... 188

과제_188 / 해결책_188 / 과제 풀이_189

4-4. 인라인 스크립트 소스 정의 .................................................................. 189

과제_189 / 해결책_190 / 과제 풀이_190

정리 ................................................................................................................. 192

Page 8: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

viii

05장 스프링 시큐리티 1955-1. URL 접근 보호 ....................................................................................... 196

과제_196 / 해결책_196 / 과제 풀이_197

5-2. 웹 애플리케이션 로그인 ....................................................................... 208

과제_208 / 해결책_208 / 과제 풀이_209

5-3. 사용자 인증 ............................................................................................ 213

과제_213 / 해결책_214 / 과제 풀이_214

5-4. 접근 제어 결정 ....................................................................................... 226

과제_226 / 해결책_226 / 과제 풀이_227

5-5. 메서드 호출 보호 ................................................................................... 230

과제_230 / 해결책_230 / 과제 풀이_231

5-6. 뷰에서의 보안 처리 ............................................................................... 233

과제_233 / 해결책_234 / 과제 풀이_234

5-7. 도메인 객체 보안 처리 .......................................................................... 236

과제_236 / 해결책_236 / 과제 풀이_237

정리 ................................................................................................................. 248

06장 스프링과 다른 웹 프레임워크의 연동 2516-1. 일반 웹 애플리케이션에서의 스프링 접근 .......................................... 252

과제_252 / 해결책_252 / 과제 풀이_252

6-2. 서블릿과 필터에서의 스프링 사용 ...................................................... 257

과제_257 / 해결책_258 / 과제 풀이_258

6-3. 스프링과 스트러츠 1.x 연동 ................................................................. 264

과제_264 / 해결책_264 / 과제 풀이_265

6-4. 스프링과 JSF 연동 ................................................................................. 272

과제_272 / 해결책_272 / 과제 풀이_272

6-5. 스프링과 DWR의 연동 ......................................................................... 279

과제_279 / 해결책_279 / 과제 풀이_279

정리 ................................................................................................................. 284

Page 9: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

ix

07장 스프링 웹 플로우 2877-1. 스프링 웹 플로우를 활용한 스프링 UI 플로우 관리 .......................... 287

과제_287 / 해결책_288 / 과제 풀이_288

7-2. 다양한 스테이트 타입을 사용한 웹 플로우 모델링 ............................ 297

과제_297 / 해결책_297 / 과제 풀이_297

7-3. 웹 플로우 보호 ....................................................................................... 310

과제_310 / 해결책_310 / 과제 풀이_311

7-4. 웹 플로우에서의 객체 영속성 .............................................................. 313

과제_313 / 해결책_313 / 과제 풀이_314

7-5. 스프링 웹 플로우와 JSF의 연동............................................................ 321

과제_321 / 해결책_322 / 과제 풀이_322

7-6. 스프링 웹 플로우와 리치페이스 연계.................................................. 331

과제_331 / 해결책_331 / 과제 풀이_331

정리 ................................................................................................................. 336

08장 스프링 MVC 3398-1. 스프링 MVC를 활용한 간단한 웹 애플리케이션 개발 ...................... 340

과제_340 / 해결책_340 / 과제 풀이_342

8-2. @RequestMapping을 활용한 요청 매핑 ............................................. 356

과제_356 / 해결책_356 / 과제 풀이_356

8-3. 핸들러 인터셉터를 활용한 요청 가로채기 .......................................... 361

과제_361 / 해결책_361 / 과제 풀이_361

8-4. 사용자 로캘 리졸브 ............................................................................... 366

과제_366 / 해결책_366 / 과제 풀이_366

8-5. 로캘에 민감한 텍스트 메시지의 외부화 .............................................. 369

과제_369 / 해결책_369 / 과제 풀이_369

8-6. 이름을 통한 뷰 리졸브 .......................................................................... 370

과제_370 / 해결책_370 / 과제 풀이_371

Page 10: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

x

8-7. 뷰와 콘텐츠에 대한 판단 ...................................................................... 374

과제_374 / 해결책_374 / 과제 풀이_375

8-8. 예외의 뷰 매핑 ....................................................................................... 378

과제_378 / 해결책_378 / 과제 풀이_378

8-9. @Value를 이용한 컨트롤러에서의 값 대입 ........................................ 380

과제_380 / 해결책_380 / 과제 풀이_381

8-10. 컨트롤러를 이용한 폼 처리 ................................................................ 383

과제_383 / 해결책_383 / 과제 풀이_383

8-11. 마법사 폼 컨트롤러를 이용한 멀티페이지 폼 처리 .......................... 401

과제_401 / 해결책_401 / 과제 풀이_402

8-12. 애노테이션(JSR-303)을 사용한 빈 밸리데이션 ............................... 413

과제_413 / 해결책_413 / 과제 풀이_414

8-13. 엑셀과 PDF 뷰 생성 ............................................................................ 417

과제_417 / 해결책_417 / 과제 풀이_417

정리 ................................................................................................................. 424

09장 스프링 REST 4279-1. 스프링을 사용한 REST 서비스 발행 ................................................... 427

과제_427 / 해결책_428 / 과제 풀이_428

9-2. 스프링을 이용한 REST 서비스 접근 ................................................... 434

과제_434 / 해결책_434 / 과제 풀이_434

9-3. RSS와 Atom 피드 발행 ......................................................................... 440

과제_440 / 해결책_440 / 과제 풀이_441

9-4. REST 서비스를 통한 JSON 발행 ......................................................... 451

과제_451 / 해결책_451 / 과제 풀이_452

9-5. 정교한 XML 응답을 반환하는 REST 서비스에 접근하기 ................. 455

과제_455 / 해결책_455 / 과제 풀이_455

정리 ................................................................................................................. 467

Page 11: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

xi

10장 스프링과 플렉스 47110-1. 플렉스 시작하기 .................................................................................. 473

과제_473 / 해결책_474 / 과제 풀이_474

10-2. 샌드박스를 벗어난 외부 연동 ............................................................ 481

과제_481 / 해결책_481 / 과제 풀이_481

10-3. 애플리케이션에 대한 스프링 BlazeDS 지원 기능 추가 ................... 496

문제_496 / 해결책_496 / 과제 풀이_496

10-4. BlazeDS/스프링을 통한 서비스 노출 ................................................ 502

과제_502 / 해결책_502 / 과제 풀이_503

10-5. 서버 사이드 객체와의 연동 ................................................................ 510

과제_510 / 해결책_510 / 과제 풀이_510

10-6. BlazeDS 및 스프링을 활용한 메시지 관련 서비스 처리 .................. 514

과제_514 / 해결책_514 / 과제 풀이_515

10-7. 액션스크립트 클라이언트에서의 의존성 주입 ................................. 530

과제_530 / 해결책_530 / 과제 풀이_530

정리 ................................................................................................................. 536

11장 그레일즈 53911-1. 그레일즈 다운로드 및 설치 ................................................................ 539

과제_539 / 해결책_540 / 과제 풀이_540

11-2. 그레일즈 애플리케이션의 생성 ......................................................... 541

과제_541 / 해결책_541 / 과제 풀이_541

11-3. 그레일즈 플러그인 .............................................................................. 547

과제_547 / 해결책_548 / 과제 풀이_548

11-4. 그레일즈 환경에서의 개발, 배포, 테스트 ......................................... 550

과제_550 / 해결책_550 / 과제 풀이_551

11-5. 애플리케이션의 도메인 클래스 정의 ................................................ 553

과제_553 / 해결책_553 / 과제 풀이_554

11-6. 애플리케이션의 도메인 클래스와 관련한

CRUD 컨트롤러 및 뷰 작성 ............................................................... 556

Page 12: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

xii

과제_556 / 해결책_557 / 과제 풀이_557

11-7. 국제화(i18n) 메시지 프로퍼티 .......................................................... 561

과제_561 / 해결책_561 / 과제 풀이_561

11-8. 영구 저장 시스템 변경 ........................................................................ 565

과제_565 / 해결책_565 / 과제 풀이_565

11-9. 로깅 ....................................................................................................... 568

과제_568 / 해결책_568 / 과제 풀이_568

11-10. 단위 테스트와 통합 테스트 실행 ..................................................... 572

과제_572 / 해결책_572 / 과제 풀이_572

11-11. 커스텀 레이아웃과 템플릿 사용 ...................................................... 579

과제_579 / 해결책_579 / 과제 풀이_579

11-12. GORM 쿼리의 사용 .......................................................................... 583

과제_583 / 해결책_583 / 과제 풀이_583

11-13. 커스텀 태그 생성 ............................................................................... 585

과제_585 / 해결책_585 / 과제 풀이_585

정리 ................................................................................................................. 588

12장 스프링 루 59112-1. 스프링 루 개발 환경 설정 ................................................................... 594

과제_594 / 해결책_594 / 과제 풀이_594

12-2. 첫 번째 스프링 루 프로젝트 생성 ...................................................... 598

과제_598 / 해결책_598 / 과제 풀이_598

12-3. 기존 프로젝트를 스프링소스 툴 스위트로 불러오기 ....................... 604

과제_604 / 해결책_604 / 과제 풀이_605

12-4. 더 좋은 애플리케이션의 신속한 개발 ............................................... 606

과제_606 / 해결책_607 / 과제 풀이 _607

12-5. 프로젝트에서 스프링 루 제거 ............................................................ 615

과제_615 / 해결책_615 / 과제 풀이_615

정리 ................................................................................................................. 617

Page 13: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

xiii

13장 스프링 테스트 61913-1. JUnit과 TestNG를 활용한 테스트 작성 ............................................ 620

과제_620 / 해결책_620 / 과제 풀이_620

13-2. 단위 테스트와 통합 테스트 작성 ....................................................... 627

과제_627 / 해결책_627 / 과제 풀이_628

13-3. 스프링 MVC 컨트롤러의 단위 테스트 .............................................. 638

과제_638 / 해결책_638 / 과제 풀이_638

13-4. 통합 테스트에서의 애플리케이션 컨텍스트 관리 ............................ 640

과제_640 / 해결책_640 / 과제 풀이_642

13-5. 통합 테스트에 테스트 픽스처 주입 ................................................... 648

과제_648 / 해결책_648 / 과제 풀이_649

13-6. 통합 테스트에서의 트랜잭션 관리 .................................................... 653

과제_653 / 해결책_653 / 과제 풀이_654

13-7. 통합 테스트에서의 데이터베이스 접근 ............................................ 660

과제_660 / 해결책_661 / 과제 풀이_661

13-8. 스프링의 일반 테스트 애노테이션 활용 ........................................... 665

과제_665 / 해결책_665 / 과제 풀이_666

정리 ................................................................................................................. 668

14장 스프링 포틀릿 MVC 프레임워크 67114-1. 스프링 포틀릿 MVC를 사용한 간단한 포틀릿 개발 ........................ 672

과제_672 / 해결책_672 / 과제 풀이_673

14-2. 포틀릿 요청의 핸들러 매핑 ................................................................ 682

과제_682 / 해결책_682 / 과제 풀이_683

14-3. 간단한 폼 컨트롤러를 사용한 포틀릿 폼 처리 .................................. 691

과제_691 / 해결책_691 / 과제 풀이_692

정리 ................................................................................................................. 701

Page 14: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

xiv

15장 데이터 접근 705JDBC를 직접 사용할 때의 문제점 ............................................................... 706

애플리케이션 데이터베이스 구성_707 /

데이터 접근 객체(DAO) 디자인 패턴의 이해_708 /

JDBC를 활용한 DAO 구현_709 /

스프링에서의 데이터 소스 설정_711 /

과제 풀이_713 / DAO 실행_715 /

ORM 같은 다른 대안 소개_716

15-1. JDBC 템플릿을 이용한 데이터베이스 업데이트 ............................. 716

과제_716 / 해결책_716 / 과제 풀이_717

15-2. JDBC 템플릿을 활용한 데이터베이스 조회 ..................................... 722

과제_722 / 해결책_723 / 과제 풀이_723

15-3. JDBC 템플릿 생성 간소화 .................................................................. 729

과제_729 / 해결책_729 / 과제 풀이_729

15-4. 자바 1.5 버전에서의 SimpleJdbcTemplate 활용 .............................. 732

과제_732 / 해결책_732 / 과제 풀이_732

15-5. JDBC 템플릿에서 네임드 매개변수 사용 ......................................... 736

과제_736 / 해결책_736 / 과제 풀이_736

15-6. 스프링 JDBC 프레임워크에서 예외 처리 ......................................... 739

과제_739 / 해결책_739 / 과제 풀이_740

15-7. ORM 프레임워크를 직접 사용할 때의 문제점 ................................. 744

과제_744 / 해결책_745 / 과제 풀이_745 /

하이버네이트 XML 매핑과 하이버네이트 API를 활용한 객체 영속화_747 /

JPA 애노테이션과 하이버네이트 API를 활용한 객체 영속화_751 /

하이버네이트를 엔진으로 사용한 JPA로 객체 영속화하기_753

15-8. 스프링에서 ORM 리소스 팩터리 설정하기...................................... 758

과제_758 / 해결책_758 / 과제 풀이_758

15-9. 스프링 ORM 템플릿을 활용한 객체 영속화..................................... 765

과제_765 / 해결책_765 / 과제 풀이_766

Page 15: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

xv

15-10. 하이버네이트의 컨텍스트 세션으로 객체영속화하기 ................... 772

과제_772 / 해결책_772 / 과제 풀이_772

15-11. JPA 컨텍스트 주입을 통한 객체 영속화 .......................................... 776

과제_776 / 해결책_776 / 과제 풀이_776

정리 ................................................................................................................. 780

16장 트랜잭션 관리 78316-1. 트랜잭션 관리와 관련된 문제 ............................................................ 784

16-2. 트랜잭션 관리자 구현체 선택 ............................................................ 792

과제_792 / 해결책_792 / 과제 풀이_792

16-3. 트랜잭션 매니저 API를 통한 프로그램 방식의 트랜잭션 관리 ...... 794

과제_794 / 해결책_794 / 과제 풀이_794

16-4. 트랜잭션 템플릿을 통한 프로그램 방식의 트랜잭션 관리 .............. 796

과제_796 / 해결책_796 / 과제 풀이_797

16-5. 트랜잭션 어드바이스를 통한 선언적인 방식의 트랜잭션 관리 ...... 800

과제_800 / 해결책_800 / 과제 풀이_800

16-6. @Transactional 애노테이션을 통한 선언적인 방식의

트랜잭션 관리 ...................................................................................... 803

과제_803 / 해결책_803 / 과제 풀이_803

16-7. 트랜잭션 전달 속성 설정 .................................................................... 805

과제_805 / 해결책_805 / 과제풀이_806

16-8. 트랜잭션 격리 속성 설정 .................................................................... 811

과제_811 / 해결책_811 / 과제 풀이_812

16-9. 롤백 트랜잭션 속성 설정 .................................................................... 820

과제_820 / 해결책_820 / 과제 풀이_820

16-10. 트랜잭션 타임아웃 및 읽기전용 속성 설정 .................................... 821

과제_821 / 해결책_822 / 과제 풀이_822

16-11. 로드 시점 위빙을 활용한 트랜잭션 관리 ........................................ 823

과제_823 / 해결책_823 / 과제 풀이_823

정리 ................................................................................................................. 828

Page 16: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

xvi

17장 EJB, 스프링 리모팅, 웹 서비스 83117-1. RMI를 통한 서비스 노출 및 호출 ...................................................... 831

과제_831 / 해결책_832 / 과제 풀이_832

17-2. 스프링 EJB 2.x 컴포넌트 작성 ........................................................... 836

과제_836 / 해결책_836 / 과제 풀이_837

17-3. 스프링에서 기존 EJB 2.x 컴포넌트에 접근하기 ............................... 844

과제_844 / 해결책_844 / 과제 풀이_845

17-4. 스프링에서 EJB 3.0 컴포넌트 작성하기 ............................................ 849

과제_849 / 해결책_849 / 과제 풀이_849

17-5. 스프링에서 EJB 3.0 컴포넌트에 접근하기 ........................................ 851

과제_851 / 해결책_851 / 과제 풀이_851

17-6. HTTP를 통한 서비스 노출과 호출 .................................................... 853

과제_853 / 해결책_854 / 과제 풀이_854

17-7. SOAP 웹 서비스 개발 방식 선택 ....................................................... 858

과제_858 / 해결책_858 / 과제 풀이_858

17-8. JAX-WS를 활용한 후계약 SOAP 웹 서비스 노출과 호출 ............... 861

과제_861 / 해결책_861 / 과제 풀이_861

17-9. 웹 서비스 계약 정의 ............................................................................ 868

과제_868 / 해결책_868 / 과제 풀이_869

17-10. Spring-WS를 이용한 웹 서비스 구현 .............................................. 873

과제_873 / 해결책_873 / 과제 풀이_874

17-11. Spring-WS를 이용한 웹 서비스 호출 .............................................. 880

과제_880 / 해결책_880 / 과제 풀이_881

17-12. XML 마샬링을 활용한 웹 서비스 개발 ........................................... 885

과제_885 / 해결책_885 / 과제 풀이_886

17-13. 애노테이션을 활용한 서비스 엔드포인트 생성.............................. 891

과제_891 / 해결책_891 / 과제 풀이_891

정리 ................................................................................................................. 893

Page 17: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

xvii

18장 엔터프라이즈 환경의 스프링 89518-1. 스프링 빈을 JMX MBean으로 익스포트하기 .................................. 896

과제_896 / 해결책_896 / 과제 풀이_896

18-2. JMX 통지 발행과 리스닝 .................................................................... 910

과제_910 / 해결책_910 / 과제 풀이_910

18-3. 스프링에서 원격 JMX MBean에 접근하기 ...................................... 912

과제_912 / 해결책_912 / 과제 풀이_912

18-4. 스프링 E-mail 지원 기능을 활용한 E-mail 발송 .............................. 916

과제_916 / 해결책_916 / 과제 풀이_916

18-5. 스프링 Quartz 지원 기능을 이용한 스케줄링 .................................. 925

과제_925 / 해결책_925 / 과제 풀이_925

18-6. 스프링 3.0의 스케줄링 네임스페이스를 이용한 스케줄링 .............. 930

과제_930 / 해결책_931 / 과제 풀이_931

정리 ................................................................................................................. 935

19장 메시징 93719-1. 스프링으로 JMS 메시지 주고받기 ..................................................... 938

과제_938 / 해결책_939 / 과제 풀이_939

19-2. JMS 메시지 변환 ................................................................................. 952

과제_952 / 해결책_952 / 과제 풀이_952

19-3. JMS 트랜잭션 관리 ............................................................................. 955

과제_955 / 해결책_955 / 과제 풀이_955

19-4. 스프링에서 메시지 기반 POJO 작성하기 ......................................... 957

과제_957 / 해결책_957 / 과제 풀이_957

19-5. 연결 생성 .............................................................................................. 964

과제_964 / 해결책_964 / 과제 풀이_964

정리 ................................................................................................................. 965

Page 18: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

xviii

20장 스프링 인티그레이션 96720-1. 한 시스템과 다른 시스템 간의 EAI 연동 .......................................... 968

과제_968 / 해결책_969 / 과제 풀이_969

20-2. JMS를 이용한 두 시스템 간의 연동 ................................................... 972

과제_972 / 해결책_972 / 과제 풀이_972

20-3. 스프링 인티그레이션 메시지에서 컨텍스트 정보 조회하기 ........... 976

과제_976 / 해결책_977 / 과제 풀이_977

20-4. 파일 시스템을 이용한 시스템 연동 ................................................... 979

과제_979 / 해결책_980 / 과제 풀이_980

20-5. 메시지 타입 변환 ................................................................................. 982

과제_982 / 해결책_983 / 과제 풀이_983

20-6. 스프링 인티그레이션을 이용한 에러 처리........................................ 986

과제_986 / 해결책_986 / 과제 풀이_986

20-7. Splitter와 Aggregator를 이용한 연동 분기 제어 .............................. 989

과제_989 / 해결책_990 / 과제 풀이_990

20-8. Router를 이용한 조건적 라우팅 ........................................................ 994

과제_994 / 해결책_994 / 과제 풀이_994

20-9. 외부 시스템과 버스 간의 어댑터 ....................................................... 995

과제_995 / 해결책_995 / 과제 풀이_995

20-10. 스프링 배치를 이용한 이벤트 스테이징 ....................................... 1007

과제_1007 / 해결책_1008 / 과제 풀이_1008

20-11. 게이트웨이 활용 .............................................................................. 1009

과제_1009 / 해결책_1009 / 과제 풀이_1009

정리 ............................................................................................................... 1016

21장 스프링 배치 1019런타임 메타데이터 모델 ............................................................................. 1021

21-1. 스프링 배치 인프라 준비 .................................................................. 1022

과제_1022 / 해결책_1023 / 과제 풀이_1023

Page 19: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

xix

21-2. 읽기와 쓰기 (연산 없이) ................................................................... 1026

과제_1026 / 해결책_1026 / 과제 풀이_1026

21-3. 커스텀 ItemWriter와 ItemReader 작성 .......................................... 1032

과제_1032 / 해결책_1032 / 과제 풀이_1032

21-4. 출력 전에 입력 데이터 처리하기 ..................................................... 1035

과제_1035 / 해결책_1035 / 과제 풀이_1035

21-5. 트랜잭션을 통한 더 나은 삶 ............................................................. 1039

과제_1039 / 해결책_1039 / 과제 풀이_1039

21-6. 재시도 ................................................................................................. 1041

과제_1041 / 해결책_1041 / 과제 풀이_1041

21-7. 단계 실행 제어하기 ........................................................................... 1045

과제_1045 / 해결책_1045 / 과제 풀이_1045

21-8. 작업 실행하기 .................................................................................... 1050

과제_1050 / 해결책_1050 / 과제 풀이_1050

21-9. 작업 매개변수화하기 ........................................................................ 1055

과제_1055 / 해결책_1055 / 과제 풀이_1055

정리 ............................................................................................................... 1057

22장 스프링과 그리드 105922-1. 테라코타를 이용한 객체 상태 클러스터링...................................... 1062

과제_1062 / 해결책_1062 / 과제 풀이_1062

22-2. 그리드에서 실행하기 ........................................................................ 1074

과제_1074 / 해결책_1074 / 과제 풀이_1074

22-3. 메서드 로드 밸런싱하기 ................................................................... 1076

과제_1076 / 해결책_1076 / 과제 풀이_1076

22-4. 병렬처리 ............................................................................................. 1080

과제_1080 / 해결책_1080 / 과제 풀이_1080

22-5. 그리드게인 배포하기 ........................................................................ 1083

과제_1083 / 해결책 _1083 / 과제 풀이_1083

정리 ............................................................................................................... 1089

Page 20: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

xx

23장 jBPM과 스프링 1091소프트웨어 프로세스 .................................................................................. 1093

23-1. 워크플로우 모델 이해하기 ............................................................... 1095

과제_1095 / 해결책_1095 / 과제 풀이_1096

23-2. jBPM 설치 .......................................................................................... 1097

과제_1097 / 해결책_1097 / 과제 풀이_1097

23-3. jBPM 4와 스프링 연동하기 .............................................................. 1100

과제_1100 / 해결책_1100 / 과제 풀이_1100

23-4. 스프링으로 서비스 만들기 ............................................................... 1108

과제_1108 / 해결책_1108 / 과제 풀이_1108

23-5. 비즈니스 프로세스 만들기 ............................................................... 1112

과제_1112 / 해결책_1112 / 과제 풀이_1112

24장 OSGi와 스프링 111924-1. OSGi 시작하기 .................................................................................. 1121

과제_1121 / 해결책_1121 / 과제 풀이_1121

24-2. 스프링 다이내믹 모듈 사용하기 ...................................................... 1128

과제_1128 / 해결책_1129 / 과제 풀이_1129

24-3. 스프링 다이내믹 모듈로 서비스 익스포트하기 .............................. 1133

과제_1133 / 해결책_1133 / 과제 풀이_1133

24-4. OSGi 레지스트리에서 특정 서비스 찾아내기 ................................ 1137

과제_1137 / 해결책_1137 / 과제 풀이_1138

24-5. 다중 인터페이스를 구현한 서비스 발행 ......................................... 1140

과제_1140 / 해결책_1140 / 과제 풀이_1140

24-6. 스프링 다이내믹 모듈 커스터마이징하기 ...................................... 1141

과제_1141 / 해결책_1141 / 과제 풀이_1142

24-7. 스프링소스 dm 서버 사용하기 ........................................................ 1144

과제_1144 / 해결책_1144 / 과제 풀이_1144

24-8. 스프링소스의 도구 지원 ................................................................... 1146

과제_1146 / 해결책_1146 / 과제 풀이_1146

Page 21: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

xxi

나의 부모님, 클락 롱과 캐슬린 맥도널드께

- 조시 롱

나의 가족에게

- 다니엘 루비오

Page 22: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

xxii

•옮긴이의 글•

여러분은 지금, 어렵게만 느껴졌던 스프링 프레임워크를 맛있게 요리해줄 요리책(Recipe)을 만

났습니다. 이 책은 스프링으로 할 수 있는 수많은 요리들을 메뉴별로 분류하고, 요리 과제와 요리

방법을 풍부한 예제와 사례를 들어 설명하고 있습니다. 이 책을 통해 여러분은 스프링으로 만들

수 있는 여러 가지 요리들을 하나씩 따라 해 보면서 좀 더 쉽고 자연스럽게 스프링의 세계에 다가

서게 될 것입니다.

저는 스프링을 접하게 된 것이 매우 행운이라고 생각합니다.

스프링은 그 이름에서 의도하는 것처럼 개발자들에게 봄날이 오길 꿈꾸며 역동적으로 발전하

고 있습니다. 스프링은 역제어(Inversion of Control) 구조를 통해 개발자로 하여금 클래스를 유

연하게 설계하도록 유도하며, 의존 관계의 주입(Dependency Injection)을 도와줍니다. 그리고

컨테이너를 통해 객체(Bean)의 생명주기(Life Cycle)를 관리하고 필요한 모든 관계들을 종합적

으로 제공해주는 프레임워크 역할을 수행합니다. 그리고 엔터프라이즈 애플리케이션 개발에 필

요한 여러 가지 기능들도 라이브러리 형태로 제공합니다. 수많은 개발자들이 엔터프라이즈 애플

리케이션을 개발하기 위해 반드시 해결해야 하는 과제의 공통된 모범 답안을 제공함으로써 개

발자들의 반복적이고 소모적인 노동을 줄여줍니다. 이 밖에도 여러 유용한 이점들을 제공해주

며 개발 생산성을 향상시키는 데 도움을 줍니다.

개발자들의 뛰어난 파트너인 스프링을 통해 엔터프라이즈 애플리케이션 개발에 봄날이 오기

를 기대해봅니다.

이 책은 스프링에 관한 상당히 많은 내용들을 담고 있습니다. 원서의 2판이 출간되면서 1판에

비해 분량도 거의 2배로 늘어났습니다. 이 책은 스프링으로 할 수 있는 거의 대부분의 내용들을

다루고 있으며, 그중에는 국내에서 잘 쓰이지 않는 기술도 있습니다. 하지만 이 책이 쓰여진 (거대

IT 시장인) 미국에서 거론되는 기술들은 나중에 국내에서도 접할 가능성이 높은 유용한 기술이

므로 관심을 가지고 알아두면 충분히 도움되리라 생각합니다.

Page 23: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

xxiii

원서를 번역하면서 가장 어려웠던 점은 개발 환경과 언어적으로 태생이 다른 기술 용어와 개

념을 우리말로 완벽히 일대일 대응시키기가 어려웠다는 것입니다. 발원지의 문화권에서 느끼고

이해하고 있는 문체와 기술 용어들을 국내 실정에 맞게 번역하기 위해 역자끼리 많은 고민을 해

야 했습니다. 그리고 원문이 전달하려는 내용을 최대한 살리면서 국내 독자들이 쉽게 이해할 수

있게 번역하려고 노력했습니다.

드디어 이 책이 잉태되어 세상에 나오게 되는 것을 보면서 가장 생각나는 사람은 역시, 최근에

새로운 생명을 잉태해 선물해 준 사랑하는 아내, 그리고 그 소중한 생명인 우진이, 우진이를 정성

으로 기르고 돌봐주신 어머니입니다. 그리고 모든 가족과 동료, 친구들에게 사랑과 감사의 인사

를 함께 전하고 싶습니다.

또한 독자에게 좋은 내용을 전달하기 위해 번역에 무한한 열정을 쏟아주신 공동 역자 윤선

님, 다양한 번역 경험을 토대로 여러 조언을 아끼지 않았던 공동 역자 기선군, 자신의 작품을 다

루는 것과 같이 애착을 가지고 내용의 품질을 높이기 위해 힘써주신 대엽님께 특별한 감사의 말

씀을 전합니다. 그리고 이렇게 좋은 기회를 주시고 배려로 후원해 주신 위키북스에 뜻깊은 감사

의 말씀을 전합니다.

마지막으로 시작부터 끝까지 모든 것을 주관해 주시는 하나님께 감사와 영광을 돌려 드리며,

여러분의 가정에도 평안과 기쁨이 넘치기를 기원합니다.

- 고종봉

Page 24: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

xxiv

•저자 소개•

■ 게리 막 (Gary Mak)

게리 막은 Meta-Archit Software Technology Limited의 설립자이

자 핵심 컨설턴트이며, 7년 넘게 엔터프라이즈 자바 플랫폼 기반 애플

리케이션 개발과 기술 아키텍트를 담당하고 있다. Spring Recipes: A

Problem-Solution Approach와 Pro SpringSource dm Server를 집필

했다.

게리는 그동안 여러 자바 기반 소프트웨어 프로젝트를 진행해 왔으

며 대부분 애플리케이션 프레임워크, 시스템 인프라와 소프트웨어 툴

을 개발했다. 게리는 소프트웨어 프로젝트에서 복잡한 부분을 설계하고 구현하는 것을 좋아한

다. 게리는 컴퓨터 사이언스 석사 학위를 가지고 있으며 객체지향 기술, 관점지향 기술, 디자인 패

턴, 소프트웨어 재사용, 도메인 주도 개발에 관심을 가지고 있다.

게리는 스프링, 하이버네이트, JPA, JSF, Portlet, Ajax, OSGi 같은 기술을 사용해서 엔터프라

이즈 애플리케이션을 개발하는 데 전문가다. 스프링 1.0 버전일 때인 5년 전부터 프로젝트에서

스프링 프레임워크를 사용해왔다. 게리는 엔터프라이즈 자바, 스프링, 하이버네이트, 웹 서비스,

애자일 개발 과정 강사로 활동 중이다. 강의 교재로 스프링과 하이버네이트 튜토리얼을 작성했는

데, 그 중 일부를 공개했으며 자바 커뮤니티에서 인기를 얻고 있다. 여가 시간에는 테니스를 즐기

거나 테니스 경기를 관람한다.

Page 25: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

xxv

■ 조시 롱 (Josh Long)

조시 롱은 스프링소스 Spring Developer Advocate의 일원으로 (아키

텍트 역할을 하면서) 엔지니어로서 수십 년간의 경력을 지니고 있으며

커뮤니티에서 왕성한 기여를 하고 있다. 그는 스프링 인티그레이션 프

로젝트를 비롯한 오픈소스 프로젝트의 기여자이자 커미터로 활약하고

있다. JCP의 일원이자 유명한 기술 포털인 InfoQ.com의 편집자이기도

한 조시는 국내외 컨퍼런스의 주요 발표자로서 비즈니스 프로세스 관

리와 엔터프라이즈 애플리케이션 연동 및 아키텍처 패턴에 이르기까지

다양한 주제를 다룬다. 그는 확장성, BPM, 그리드 처리, 모바일 컴퓨팅

등 일종의 ‘스마트’ 시스템에 관심을 가지고 있다.

스프링소스 Spring Developer Advocate에서 그의 역할은 스프링 플랫폼을 기반으로 커뮤니

티를 성장시키고 풍족하게 하는 것이다.

조시는 아내 리첼(Richelle)과 햇살 좋은 남부 캘리포니아에 살고 있다. www.joshlong.com에

블로그를 운영하고 있으며 이메일([email protected])로 연락할 수 있다.

■ 다니엘 루비오(Daniel Rubio)

다니엘 루비오는 엔터프라이즈와 웹 기술 분야에서 10년 넘게 컨설턴

트로 활약해오고 있다. 그동안 자바, 파이썬, CORBA, 닷넷 기술을 기

반으로 금융업과 제조업에 비용효율적인 솔루션을 제공해왔다. 최근

에는 설정 대신 규약을 우선시하는 웹 프레임워크인 스프링, 그레일즈,

루, 장고에 관심이 있으며 엔터프라이즈 환경에서 그러한 기술의 성능

과 확장성을 살펴보는 데 집중하고 있다.

또한 그는 Oracle Technology Network, DZone 그리고 자신의 블로

그인 www.WebForefront.com에서 다양한 주제로 글을 쓰고 있다.

Page 26: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

xxvi

•기술 감수자 소개•

■ 마뉴엘 조단 엘라(Manuel Jordan Elera)

마뉴엘 조단 엘라는 프리랜서 자바 개발자다. 그는 자바 기반 유명 프레임워크인 스프링과 하이

버네이트 등을 사용해서 고객이 필요로 하는 개인 시스템을 설계하고 개발해 왔다. 마뉴엘은 프

로젝트에서 더 좋은 결과를 내기 위해 새로운 프레임워크를 익히길 좋아한다.

마뉴엘은 시스템 엔지니어링 학위를 가지고 있으며, 산타 마리아의 Universidad Católica와 페

루의 Universidad Alas Peruanas에서 교수로 역임하고 있다. 약간의 여가 시간에는 성경 읽기를

좋아하며 기타로 작곡을 한다. 마뉴엘은 스프링 커뮤니티 포럼에서 dr_pompeii로 알려져 있다.

마뉴엘의 근황은 블로그인 http://manueljordan.wordpress.com/에서 확인할 수 있다.

■ 마리오 그레이(Mario Gray)

마리오 그레이는 시스템 연동, 시스템 관리, 게임 프로그래밍, 사용성 좋은 엔터프라이즈 아키텍

처에 수십 년에 달하는 경력을 보유하고 있다. 그는 더 나은 비즈니스가 가능하게끔 여러 기술을

적용하는 것을 경계한다. 그는 최신 오픈소스, 엔터프라이즈 자바 프레임워크와 툴을 사용해서

수많은 CRM, 메시지 기반 시스템과 사용성 좋은 웹 애플리케이션을 개발해 왔다. 마리오는 비

즈니스를 더 잘 뒷받침할 수 있게 오픈소스 프레임워크를 성공적으로 적용한 경험이 있다.

마리오는 아내 후미코(Fumiko)와 딸 마카니(Makani)와 함께 애리조나의 챈들러 시에 살고

있다. 야외 레크리에이션 스포츠를 즐기며, 운동을 좋아하고, 일 이외에는 주로 가족과 함께 하

는 활동을 즐긴다. www.sudoinit5.com라는 블로그를 운영하며 [email protected]으로

연락할 수 있다.

Page 27: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

xxvii

■ 그렉 턴퀴스트(Greg Turnquist)

그렉 턴퀴스트는 테스트 광 스크립트 중독자로 항상 작업에 적절한 도구를 찾아 다닌다. 그는

1997년부터 소프트웨어 개발자로 일하고 있으며, 매우 중요한 무정지 시스템을 비롯해 다양한

시스템을 다룬 경험이 있다. 2006년에는 스프링 파이썬 프로젝트를 만들어서 스프링의 개념을

파이썬 플랫폼에 도입했다. 2010년에는 스프링소스 팀의 일원이 되었다. 그는 스프링 파이썬 1.1

을 만들었고 스프링원의 발표자이기도 하다. Auburn University를 석사로 졸업했으며 가족과

함께 미국에서 살고 있다.

Page 28: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

xxviii

•감사의 글•

거짓말하고 싶지 않다. 이 책에서 감사의 글 부분은 나에게 굉장히 두려운 부분이다. 내가 할 수

있는 말이라고는 이 책을 만드는 데 엄청난 기여가 있었다는 것이다. 최선을 다하겠지만, 혹시라

도 뭔가를 빼먹었더라도 용서하기 바란다. 진심으로 사과하겠다.

나의 아내 리첼의 무한한 인내심, 배려, 지혜, 도움, 사랑에 감사한다. 나의 부모님을 비롯해 장

인 어른과 장모님의 감화, 기품, 지원, 사랑에 감사드린다.

공동 저자인 게리 막과 다니엘 루비오 역시 나 혼자 작업한 책을 읽었을 때보다 더 멋진 책으

로 거듭나게 하는 데 도움을 줘서 고맙다.

엄청난 편집자인 Steve Anglin, Laurin Becker, Tom Welsh, Manuel Jordan, Mario Gray,

Greg Turnquist에게 감사한다. 그들은 내가 이 책을 쓰는 동안 전문가적 기질로 계속해서 품질

을 높여줬다. 특히 Greg Turnquist가 이 책에 많은 도움을 줬다. 그는 책을 쓰는 과정 내내 핵심

적인 피드백을 제공해줬다. 그는 스프링소스 엔지니어이자 스프링 프레임워크를 파이썬에서 사

용할 수 있게 파이썬으로 포팅하는 프로젝트인 스프링 파이썬 프로젝트의 리더다. 그는 조만간

매닝에서 스프링 파이썬 인 액션이라는 아주 아주 흥미로운 책을 출간할 것이다.

스프링 인티그레이션 프로젝트의 리더인 마크 피셔에게 감사하다. 나는 스프링 인티그레이션

프로젝트를 좋아하는데, 코어 프레임워크와 마찬가지로 매우 복잡한 문제 도메인을 멋지게 해결

하기 때문이다. 불가능한 문제는 어려운 문제가 되고, 어려운 문제는 쉬워진다. 프레임워크를 만

든 그에게 감사하며 그의 인내, 열정, 그리고 내가 프로젝트 참여할 수 있게 도와준 것에 감사하

다. 그의 친절함에도 감사하다.

나는 InfoQ.com의 편집자다. 이 책은 완전히 예측 불가능한 일정, 촉박한 마감과 고된 작업을

통해 만들어졌다. InfoQ.com 팀은 나의 산만한 일정에도 자비롭게 대해줬다. 기술 저널리즘의

수준을 높이고 무한한 지원과 염감을 준 Floyd Marinescu, Charles Humble, Ryan Slobojan,

InfoQ.com에 감사하다.

마지막으로, 샵질라(Shopzilla)의 동료들에게 감사드린다. 이 책을 쓰는 지금, 나는샵질라에

서 스프링소스로 이직 중이다. 샵질라는 자부심을 가지고 일할 정도로, 한마디로 완전 멋진 회

사다. 매일 새로운 것을 배우고 이전 직장에서 느낄 수 없던 동료애를 느낄 수 있었다. 엔지니어링

문화가 강하기 때문에 엔지니어가 일하기에는 제일 좋은 곳이다. 결과 역시 놀랍다. 그곳에 근무

Page 29: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

xxix

하던 동안 난 운 좋게도 놀라운 사람들과 함께 일할 수 있었다. 목록을 간추리기 힘들었다. Tim

Morrow, Rodney Barlow, Andy Chan, Rob Roland, Paul Snively, Phil Dixon, Jody Mulkey의

조언, 우정, 지원에 감사하다.

–조시 롱

우선, 공동 저자에게 감사하다. 게리 막은 이 책의 1판을 썼으며, 그 책에 믿을 만한 내용을 담아

스프링과 자바 분야의 베스트셀러가 됐다. 조시 롱은 스프링 엔터프라이즈 레시피 책의 선두를

맡았으며, 그로 인해 이 책은 강력히 통합됐고 개선된 스프링 레시피의 후속판이 될 수 있었다.

다음으로 Apress에 있는 모든 분께 감사하다. Steve Anglin, Matt Moodie, Tom Welsh를

비롯해서 이 책이 나올 수 있게 개요부터 모든 페이지를 살펴본 모든 분들께 감사하다. Laurin

Becker는 프로젝트 관리자 역할을 맡아 모든 것을 총괄하고, 모두가 제 시간에 작업할 수 있게

해줬다. 그리고 당연히, 이 책에 책임이 있는 모든 Apress 직원들과, 차마 직접 연락할 기회가 없

었던 편집자, 교정자, 표지 디자이너 등 모두에게 감사하다.

기술적인 면에서 내용이 적확한지 확인하는 데 도움을 준 Manuel Jordan에게 특히 감사하다.

그는 기술 검토 과정에 참여하고 있던 Mario Gray와 함께 이 책의 기술 검수자로 활동해줬다.

또한 Arjen Poutsma, Juergen Hoeller, Alef Arendsen, Graeme Rocher 등 스프링과 그레일

즈를 만든 많은 사람들의 수 많은 기사, 문서, 블로그를 읽지 않았다면 이 책은 존재할 수 없었을

것이다. 물론 나의 사고 방식에 영향을 준, 이 분야에서 활발히 활동 중인 분들께도 감사하지만

지면 관계상 이름을 다 적을 수 없어 아쉽다.

마지막으로, 내 가족과 친구에게 감사하다. 내가 이미 책 쓰는 프로젝트에 참여해 그들을 당황

스럽게 했기 때문에 이 분야에 문외한인 그들에게 이 책의 목적을 설명하는 것은 쉽게 말해 그들

의 인내심을 테스트하는 것이었다. 하지만 그런 시선 이면에 계속해서 내가 정진할 수 있게 가끔

씩 중요한 질문을 해주었다. 따라서 나에게 질문해준 모두에게 감사하다. 직접 얼굴을 맞대고 사

기를 고양시켜줌으로써 이 책이 나오는 데 상당한 역할을 했다고 생각한다.

―다니엘 루비오

Page 30: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

xxx

서문

스프링 프레임워크는 성장하고 있다. 모든 것은 늘 선택의 문제였다. 자바 EE는 여러 대안과 더 나

은 해결책을 뒤로하고 일부 기술에만 집중하고 있다. 스프링 프레임워크가 등장했을 때 자바 EE

는 최선이라고 여겨질 만큼의 아키텍처를 나타낸다고 보기 어려웠다. 스프링은 자바 EE를 단순화

하는 방법을 강구했기 때문에 엄청난 추종자를 확보했다. 매번 배포할 때마다 새 기능을 추가해

여러 해결책을 사용할 수 있게 했으며 그러한 기능을 간편하게 사용할 수 있게 만들어줬다.

2.0 이후부터 스프링 프레임워크는 여러 플랫폼을 다루기 시작했다. 프레임워크는 항상 그래

왔듯이 기존 플랫폼 위에서 서비스를 제공할 수 있었지만, 가능한 한 기저 플랫폼에 느슨하게 의

존하게 해줬다. 자바 EE는 여전히 주요한 준거점이지만 유일한 것은 아니다. OSGi(모듈형 아키텍

처에 있어 장래성이 있는 기술)는 현재 스프링소스의 큰 전략 중 하나다. 또한 스프링 프레임워크

는 구글 앱 엔진에서도 사용할 수 있다. 애노테이션 중심의 프레임워크와 XML 스키마를 도입해

스프링소스는 효율적으로 특정 문제 영역의 도메인을 사실상 DSL(domain-speci�c language)

로 모델링할 수 있게 해준다. 스프링 프레임워크 기반의 프레임워크가 생겨 인티그레이션, 배치

처리, 플렉스와 플래시 연동, GWT, OSGi 등 많은 것을 다루고 있다.

스프링 레시피 원본을 갱신할 시점이 오자, 효율적으로 핵심 스프링 프레임워크만 다룬 책이

나온 지 오래됐다는 사실을 금방 알 수 있었다. 앞서 언급한 스프링소스 포트폴리오는 여러 프레

임워크로 구성돼 있으며, 각각이 경쟁 중인 다른 제품과 비교해 봤을 때 훨씬 더 많은 가능성을

지녔다. 이 책에서는 다양한 프레임워크를 살펴볼 것이다. 그런 기술이 필요하지 않다면 프로젝트

에서 사용하지 않아도 된다. 하지만 필요하다면 무엇을 사용할 수 있는지 알아두는 것이 좋겠다.

대상 독자

이 책은 아키텍처를 단순화하고 싶고 자바 EE 플랫폼 영역 밖의 문제를 해결하고자 하는 자바

개발자를 위한 책이다. 프로젝트에서 이미 스프링을 사용해서 개발 중이라면 미처 알지 못했던

새로운 기술을 다룬 더 심도 있는 내용을 확인할 수 있을 것이다. 프레임워크를 처음 사용하는

분이라면 바로 이 책으로 시작할 수 있을 것이다.

Page 31: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

xxxi

이 책은 독자가 자바와 IDE에 익숙하다고 가정한다. 자바를 클라이언트 애플리케이션에서 사

용하는 것도 가능하지만 자바의 거대한 커뮤니티는 엔터프라이즈 영역에도 존재하며, 이 분야의

기술이 가장 많은 이익을 가져다 주기도 한다는 사실을 알게 될 것이다. 따라서 서블릿 API 같은

기본적인 엔터프라이즈 프로그래밍 개념에도 익숙할 것으로 가정하겠다.

이 책의 구성

1장, ‘스프링 소개’에서는 스프링 프레임워크에 대한 일반적인 개요를 제시한다. 스프링을 어떻

게 설정하고, 스프링이 무엇이며, 어떻게 사용하는지 다룬다.

2장, ‘고급 스프링 IoC 컨테이너’에서는 1장에서 다룬 것과 비교해서 널리 사용되고 있지는 않

지만 컨테이너를 십분 활용하기 위한 핵심적인 개념을 다룬다.

3장, ‘스프링 AOP와 AspectJ 지원’에서는 AspectJ를 활용한 스프링의 관점지향 프로그래밍 기

능을 다룬다. 이 기술은 스프링 프레임워크가 제공하는 여러 다른 서비스의 기반이다.

4장, ‘스프링 스크립팅’에서는 그루비, 빈쉘, 제이루비 같은 스크립트 언어를 스프링 프레임워크

와 함께 사용하는 방법을 다룬다.

5장, ‘스프링 시큐리티’에서는 애플리케이션을 더 잘 보호하는 데 도움을 주는 (기존에 Acegi라

고 부르던) 스프링 시큐리티 프로젝트를 소개한다.

6장, ‘스프링과 다른 웹 프레임워크의 연동’에서는 스프링이 제공하는 핵심 웹 단 기능을 소개

한다. 여기서는 스프링이 웹 단에서 제공하는 모든 기술의 기반을 다룬다.

7장, ‘스프링 웹 플로우’에서는 웹 단의 UI 플로우를 만들 수 있는 스프링 웹 플로우를 소개한다.

8장, ‘스프링 MVC’에서는 스프링 웹 MVC 프레임워크를 사용한 웹 기반 애플리케이션 개발을

다룬다.

9장, ‘스프링 REST’에서는 스프링의 RESTful 웹 서비스 기능을 소개한다.

Page 32: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

xxxii

10장, ‘스프링과 플렉스’에서는 스프링 BlazeDS를 사용해서 리치 인터넷 애플리케이션(RIA)과

스프링 빈을 연동하는 방법을 소개한다. 더불어 액션스크립트로 플래시 애플리케이션을 작성

할 때도 자바 스프링 개발자가 사용하는 것과 동일한 컨테이너 서비스와 컨벤션을 사용할 수

있게 해주는 스프링 액션스크립트도 소개한다.

11장, ‘그레일즈’에서는 그루비 코드로 작성하고 구성하는 방식으로 생산성을 높일 수 있는 그

레일즈 프레임워크를 다룬다.

12장, ‘스프링 루’에서는 스프링소스의 새로운 핵심 프레임워크로서 자바 개발자에게 더 강력

한 위력을 선사하는 스프링 루를 다룬다.

13장, ‘스프링 테스트’에서는 스프링 프레임워크를 사용한 단위 테스트를 다룬다.

14장, ‘스프링 포틀릿 MVC 프레임워크’에서는 스프링 MVC 포틀릿 프레임워크를 사용해서 애

플리케이션을 개발하고 포틀릿 컨테이너의 장점을 증진시키는 방법을 다룬다.

15장, ‘데이터 접근’에서는 스프링을 사용해서 JDBC, 하이버네이트, JPA 같은 API를 활용해

데이터를 저장하는 방법을 다룬다.

16장, ‘트랜잭션 관리’에서는 스프링의 일관된 트랜잭션 관리 기능의 기반 개념을 소개한다.

17장, ‘EJB, 스프링 리모팅, 웹 서비스’에서는 스프링 웹 서비스 프로젝트를 비롯한 다양한 RPC

기술을 소개한다.

18장, ‘엔터프라이즈 환경의 스프링’에서는 JMX 지원, 스케줄링, 이메일 지원과 같은 스프링 플

랫폼이 제공하는 편의성 기능을 소개한다.

19장, ‘메시징’에서는 스프링을 사용해서 메시지지향 미들웨어를 사용하고 스프링 추상화로

단순화하는 방법을 다룬다.

20장, ‘스프링 인티그레이션’에서는 스프링 인티그레이션 프레임워크를 사용해서 개별적인 서

비스와 데이터를 연동하는 방법을 다룬다.

21장, ‘스프링 배치’에서는 메인프레임 영역에서 일반적으로 고려하는 솔루션의 모델링 방법을

제공하는 스프링 배치를 다룬다.

22장, ‘스프링과 그리드’에서는 분산된 상태와 그리드 처리를 통해 스프링을 확장하는 다양한

방법을 살펴본다.

Page 33: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

xxxiii

23장, ‘jBPM과 스프링’에서는 비즈니스 프로세스 관리 개념을 소개하고 제이보스의 유명 프레

임워크인 jBPM과 스프링 프레임워크를 연동하는 방법을 살펴본다.

24장, ‘OSGi와 스프링’에서는 스프링 프레임워크가 제공하는 견고한 OSGi 지원을 살펴본다.

이 책의 조판 관례

코드 예제에서 주의 깊게 살펴봤으면 하는 부분은 굵은 글씨체로 표기했다. 굵은 글씨체가 이전

예제에서 변경된 코드를 나타내는 것이 아니라는 데 주의하기 바란다.

페이지 너비에 비해 코드 줄이 너무 길다 싶으면 코드 계속 문자를 넣어 줄을 나눴다. 코드를

입력할 때는 나눈 줄을 공백 없이 한 줄로 이어 붙이길 바란다.

준비

자바 프로그래밍 언어는 플랫폼 독립적이라서 어떤 운영체제든 자유롭게 선택할 수 있다. 하지

만 이 책의 어떤 예제는 특정 플랫폼에만 해당하는 경로를 사용한다. 예제를 실습할 때는 해당

경로를 자신이 사용하고 있는 형태로 바꾸기 바란다.

이 책을 가장 잘 활용하려면 JDK 버전 1.5 이상을 설치하기 바란다. 개발을 편하게 하려면

자바 IDE를 설치하는 것이 좋다. 이 책에서 사용하는 예제 코드는 메이븐 기반이다. 이클립스

를 사용하고 있다면 m2Eclipse 플러그인을 설치해서 예제 코드를 이클립스에서 열 수 있고,

m2Eclipse가 메이븐 메타데이터를 사용해 CLASSPATH와 의존성을 자동으로 채워줄 것이다.

이클립스를 사용하고 있다면 아마도 이클립스에서 스프링 프레임워크를 개발할 때 생산성을

더 높여주는 스프링소스의 스프링소스 툴 스위트(STS)를 더 선호할지도 모르겠다. 넷빈즈를 사

용하고 있거나 인텔리제이 IDEA를 사용하고 있다면 별다른 것을 설정할 필요가 없다. 둘 모두

이미 메이븐 지원 기능을 내장하고 있다.

스프링 프레임워크 버전 3.0.3부터 프레임워크를 사용하는 데 필요한 의존성이 아무것도 없으

므로 이 책에서는 메이븐을 사용한다. 의존성을 관리할 때는 손쉽게 메이븐(또는 앤트와 아이

비) 같은 도구를 사용할 것을 권장한다. 메이븐에 익숙하지 않다면 12장(‘스프링 루’)으로 넘어가

서 아파치 메이븐을 포함해 스프링 루 환경을 설정하는 방법을 살펴보자.

Page 34: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

xxxiv

예제 코드

이 책의 소스 코드는 Apress 웹사이트(www.apress.com)의 소스 코드/다운로드 영역에서 내려

받을 수 있다. 소스 코드는 장 단위로 묶여 있고, 각 장의 코드는 하나 이상의 예제로 구성돼 있다.

또, 위키북스 홈페이지 www.wikibook.co.kr에서 <스프링 3 레시피> 표지를 클릭하고 ‘소스

코드’ 메뉴를 찾아 클릭하면 내려 받을 수 있다.

연락처

언제든지 책 내용과 관련된 질문이나 피드백을 보내주면 좋겠다. 조시 롱은 josh@joshlong.

com(또는 그의 웹사이트인 w w w.josh long.com)을 통해 연락할 수 있다. 게리 막은

[email protected](또는 그의 웹사이트인 www.metarchit.com)으로 연락할 수 있

다. 다니엘 루비오는 그의 웹사이트인 www.webforefront.com을 통해 연락할 수 있다.

번역서에 대한 질문은 위키북스 홈페이지 www.wikibook.co.kr의 게시판으로 문의 바란다.

Page 35: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

xxxv

Page 36: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

Spri

ng R

ecip

es

Page 37: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

1

01스프링 소개

이번 장에서는 코어 컨테이너인 스프링과 컨테이너가 제공하는 주요 편의 기능을 간략하게 소개

하고 애노테이션 기반의 지원 기능과 스프링 XML 설정 형식을 학습한다.

여기서는 이 책의 나머지 장에서 소개할 개념들을 이해하는 데 필요한 여러 지식을 다루며, 스

프링 IoC 컨테이너의 기본적인 컴포넌트 구성에 대해 배운다. 스프링 프레임워크의 심장과도 같

은 IoC 컨테이너는 높은 수준의 적응성과 설정 가능성을 갖추게끔 설계돼 있다. IoC 컨테이너

는 컴포넌트 구성을 최대한 단순하게 만들어주는 편의 기능을 제공함으로써 컴포넌트가 스프링

IoC 컨테이너에서 실행되게끔 손쉽게 설정할 수 있다.

스프링에서는 컴포넌트를 ‘빈(bean)’이라고도 하며, 이것은 썬(Sun)사에서 정의한 자바빈

(JavaBeans) 명세와는 다른 개념이다. 스프링 IoC 컨테이너에 정의된 빈은 꼭 자바빈이어야 할 필

요는 없으며, 어떠한 POJO(Plain Old Java Object)라도 가능하다. POJO라는 용어는 (특정 인터페

이스를 구현하거나 특정 부모 클래스를 상속받는 등의) 특별한 요구사항을 따르지 않은 평범한

자바 객체를 의미한다. 이 용어는 다른 복잡한 컴포넌트 모델(예를 들면, EJB 3.1 명세 이전의 컴

포넌트)의 무거운 컴포넌트와 구별되는 가벼운 자바 컴포넌트들을 지칭하는 데 사용된다.

이 장을 마칠 때쯤이면, 스프링 IoC 컨테이너를 이용해서 완전한 자바 애플리케이션을 만들

수 있게 되고, 아울러 기존 자바 애플리케이션을 되돌아보면 분명 스프링 IoC 컨테이너를 이용

해 상당히 단순화하고 개선할 수 있는 여지가 보일 것이다.

1-1. 스프링 IoC 컨테이너 초기화

◈ 과제

설정 정보를 읽어들여 빈 인스턴스를 생성하려면 스프링 IoC 컨테이너를 초기화해야 한다. 그런

다음 IoC 컨테이너로부터 사용할 빈 인스턴스를 얻는다.

Page 38: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

2 l 스프링 3 레시피

◈ 해결책

스프링은 두 종류의 IoC 컨테이너 구현체를 제공한다. 하나는 기본적인 빈 팩터리(bean factory)

다. 나머지 하나는 좀더 발전된 형태의 애플리케이션 컨텍스트(application context)이며, 빈 팩터

리를 확장하여 호환성을 가진다. 하지만 두 종류의 IoC 컨테이너에 사용되는 빈 설정 파일은 동

일하다.

애플리케이션 컨텍스트는 빈 팩터리의 기본적인 기능에 대한 호환성을 유지하면서 좀더 발전

된 기능들을 제공한다. 그래서 애플릿이나 휴대용 기기와 같이 자원 사용이 제한적인 경우를 제

외하고 대부분의 애플리케이션에서는 애플리케이션 컨텍스트를 사용하길 권장한다.

빈 팩터리와 애플리케이션 컨텍스트의 인터페이스는 각각 BeanFactory와 Application

Context다. ApplicationContext 인터페이스는 BeanFactory와의 호환성을 갖춘 하위 인터페이

스(subinterface)다.

알아두기

이 장과 이후 장에 나오는 스프링 코드를 컴파일하고 실행하려면 스프링 프레임워크와 관련

의존 라이브러리들을 클래스패스에 위치시켜야 한다. 이러한 작업을 위해 권장하는 방식은 아

파치 메이븐(Apache Maven)이나 아파치 앤트(Ant), 아이비(Ivy)와 같은 빌드 관리 솔루션

을 사용하는 것이다. 메이븐을 사용할 때는 아래에 나열된 의존 라이브러리들을 메이븐 프로

젝트에 추가한다. 여기서는 ${spring.version}과 같은 형식으로 버전을 참조했으며, 해당 라

이브러리의 버전으로 변경해서 사용한다. 이 책에서는 3.0.2.RELEASE 버전을 기준으로 코

드를 작성하고 컴파일했다.

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-aop</artifactId>

<version>${spring.version}</version>

</dependency>

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-web</artifactId>

<version>${spring.version}</version>

</dependency>

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-context-support</artifactId>

<version>${spring.version}</version>

</dependency>

Page 39: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

01 스프링 소개 l 3

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-beans</artifactId>

<version>${spring.version}</version>

</dependency>

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-context</artifactId>

<version>${spring.version}</version>

</dependency>

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-core</artifactId>

<version>${spring.version}</version>

</dependency>

◈ 과제 풀이

애플리케이션 컨텍스트 초기화하기

ApplicationContext는 인터페이스에 불과하므로 이 인터페이스를 구현한 구현체를 인스턴스화

해야 한다. ClassPathXmlApplicationContext 구현체는 클래스패스에서 XML 설정 파일을 읽

어들여 애플리케이션 컨텍스트를 구성한다. 또한 여러 개의 설정 파일을 지정해서 사용할 수도

있다.

ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

ClassPathXmlApplicationContext 말고도 스프링에서는 여러 가지 ApplicationContext 구

현체를 제공한다. FileSystemXmlApplicationContext는 파일시스템으로부터 XML 설정 파일

을 읽어들이는 데 사용되며, XmlWebApplicationContext와 XmlPortletApplicationContext

는 웹과 포털 애플리케이션에서만 사용할 수 있다.

IoC 컨테이너에서 빈 얻기

빈 팩터리나 애플리케이션 컨텍스트에 선언된 빈을 얻으려면 getBean() 메서드를 호출하면서 고

유한 빈 이름을 전달하면 된다. getBean() 메서드의 반환 타입은 java.lang.Object이므로 사용하

기 전에 실제 타입으로 형변환(cast)해야 한다.

Page 40: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

4 l 스프링 3 레시피

SequenceGenerator generator =

(SequenceGenerator) context.getBean("sequenceGenerator");

지금까지 생성자로 객체를 생성하는 것처럼 쉽게 빈을 사용할 수 있다. 시퀀스(sequence) 생성

기 애플리케이션을 실행하기 위한 소스 코드는 다음과 같다.

package com.apress.springrecipes.sequence;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

public static void main(String[] args) {

ApplicationContext context =

new ClassPathXmlApplicationContext("beans.xml");

SequenceGenerator generator =

(SequenceGenerator) context.getBean("sequenceGenerator");

System.out.println(generator.getSequence());

System.out.println(generator.getSequence());

}

}

설정을 제대로 했다면 다음과 같은 시퀀스 값이 로깅 메시지로 출력되는 것을 볼 수 있다.

30100000A

30100001A

1-2. 스프링 IoC 컨테이너에 빈 구성하기

◈ 과제

스프링은 애플리케이션을 구성하는 빈을 관리하기 위한 강력한 IoC 컨테이너를 제공한다. 컨테

이너에서 제공하는 유용한 기능들을 활용하려면 작성한 빈들이 스프링 IoC 컨테이너에서 실행

되도록 구성해야 한다.

Page 41: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

01 스프링 소개 l 5

◈ 해결책

작성한 빈은 XML 파일이나 프로퍼티 파일, 애노테이션 또는 API 등의 방법으로 스프링 IoC 컨

테이너에 구성할 수 있다.

스프링은 하나 이상의 설정 파일에 빈을 구성할 수 있게 해준다. 애플리케이션이 단순하다면

모든 빈 설정을 설정 파일 하나로 모아도 된다. 하지만 수많은 빈이 존재하는 규모가 큰 애플리케

이션에서는 빈의 기능(예: 컨트롤러, DAO, JMS)에 따라 여러 개의 설정 파일로 빈 설정을 나눠

야 할 것이다. 컨텍스트 서비스에 주어진 아키텍처 계층에 따라 구분하는 것도 좋은 방법이다.

◈ 과제 풀이

시퀀스를 생성하는 애플리케이션을 개발할 예정이라고 가정하자. 이 애플리케이션은 다양한 목

적으로 여러 종류의 시퀀스를 생성하며, 각 시퀀스에는 접두어, 접미어, 초기값이 있다. 따라서

애플리케이션에서는 다양한 시퀀스 생성기 인스턴스를 생성하고 관리해야 한다.

빈 클래스 작성

앞의 요구사항에 따라 접두어, 접미어, 초기값을 가진 SequenceGenerator 클래스를 만든다.

이때 접두어, 접미어, 초기값은 세터(setter) 메서드와 생성자를 통해 주입할 수 있다. private

필드인 counter는 시퀀스 생성기의 현재 값을 저장하는 용도다. 시퀀스 생성기 인스턴스의

getSequence() 메서드를 호출할 때마다 접두어와 접미어가 결합된 시퀀스가 반환된다. 이 메서

드를 synchronized로 선언해 다중 스레드에 안전하게 만든다.

package com.apress.springrecipes.sequence;

public class SequenceGenerator {

private String prefix;

private String suffix;

private int initial;

private int counter;

public SequenceGenerator() {}

public SequenceGenerator(String prefix, String suffix, int initial) {

this.prefix = prefix;

this.suffix = suffix;

this.initial = initial;

}

Page 42: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

6 l 스프링 3 레시피

public void setPrefix(String prefix) {

this.prefix = prefix;

}

public void setSuffix(String suffix) {

this.suffix = suffix;

}

public void setInitial(int initial) {

this.initial = initial;

}

public synchronized String getSequence() {

StringBuffer buffer = new StringBuffer();

buffer.append(prefix);

buffer.append(initial + counter++);

buffer.append(suffix);

return buffer.toString();

}

}

보다시피 SequenceGenerator 클래스는 게터(getter)/세터(setter) 메서드나 생성자로 값을 설정

할 수 있으며, 컨테이너에서 접두어, 접미어, 초기값을 설정하면 생성자 주입이나 세터 주입으로

호출된다.

빈 설정 파일 작성하기

XML을 통해 스프링 IoC 컨테이너에 빈을 선언하려면 우선 beans.xml과 같이 적절한 이름으

로 XML 빈 설정 파일을 만들고 IDE에서 테스트하기 쉽게 이 파일을 최상위 클래스패스에 둬

야 한다.

스프링 설정 XML에서는 tx, jndi, jee 등 다양한 스키마의 커스텀 태그를 사용해서 빈 설정을

더 단순하고 명확하게 만들 수 있다. 다음은 가장 간단한 XML 설정 예제다.

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

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

...

</beans>

Page 43: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

01 스프링 소개 l 7

빈 설정 파일에 빈 선언하기

각 빈에는 고유한 이름 또는 ID와 전체 클래스명을 제공해서 스프링 IoC 컨테이너가 초기화할

수 있게 한다. 간단한 타입(String이나 기본primitive 타입)의 빈 프로퍼티는 <value> 엘리먼트로

지정할 수 있으며, 스프링이 프로퍼티가 선언된 타입으로 값을 변환해준다. 세터 주입을 통해 프

로퍼티를 설정하려면 <property> 엘리먼트의 name 속성에 프로퍼티명을 지정하면 된다. 빈에

는 각 <property>에 대응되는 세터 메서드가 있어야 한다.

<bean name="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

<property name="prefix">

<value>30</value>

</property>

<property name="suffix">

<value>A</value>

</property>

<property name="initial">

<value>100000</value>

</property>

</bean>

<constructor-arg> 엘리먼트를 사용해 생성자 주입 방식으로 빈 프로퍼티를 설정해도 된다.

생성자의 인자는 위치(순서)를 기반으로 하므로 <constructor-arg>에는 name 속성이 없다.

<bean name="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

<constructor-arg>

<value>30</value>

</constructor-arg>

<constructor-arg>

<value>A</value>

</constructor-arg>

<constructor-arg>

<value>100000</value>

</constructor-arg>

</bean>

스프링 IoC 컨테이너에서 각 빈의 이름은 유일해야 하지만 컨텍스트를 하나 이상 읽는 경우에

는 빈을 오버라이드하기 위해 중복된 이름을 사용하는 것도 가능하다. 빈의 이름은 <bean> 엘

리먼트의 name 속성으로 정의할 수 있다. 실제로는 id 속성을 통해 빈을 구별하는 방식을 권장

하는데, 표준 XML에서 id 속성은 XML 문서 내에서 엘리먼트를 식별할 용도로 사용된다. 아울

러 XML 방식을 이용할 경우 XML을 지원하는 텍스트 편집기를 사용하고 있다면 설계 시 빈의

유일성도 검증할 수 있다.

Page 44: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

8 l 스프링 3 레시피

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

...

</bean>

XML 문법은 XML의 id 속성에 특수문자의 사용을 제한하고 있는데, 보통은 빈 이름으로 이

러한 특수문자를 사용하지 않을 것이다. 아울러 스프링은 빈의 name 속성에 여러 개의 이름을

콤마로 구분해서 지정하는 것을 허용하지만 id 속성에서는 콤마가 허용되지 않으므로 이렇게 할

수 없다.

사실 빈에 빈 이름이나 빈 ID가 꼭 있어야 하는 것은 아니다. 아무런 이름이 지정되지 않은 빈

을 익명(anonymous) 빈이라고 하는데, 보통 이러한 빈을 생성하는 경우는 스프링 컨테이너와 자

체적인 연계만 수행하거나, 타입을 통해 주입하는 경우, 또는 빈 선언 안에 또 다른 빈을 중첩해

서 선언하는 경우다.

빈 프로퍼티 숏컷 정의

스프링은 단순 타입의 프로퍼티 값을 숏컷으로 지정할 수 있도록 지원한다. <property> 엘리먼

트에서 <value> 엘리먼트를 사용하는 대신 <property> 엘리먼트 안에 value 속성을 두면 된다.

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

<property name="prefix" value="30" />

<property name="suffix" value="A" />

<property name="initial" value="100000" />

</bean>

이러한 숏컷(Shortcut) 정의는 생성자 매개변수에서도 사용 가능하다.

<bean name="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

<constructor-arg value="30" />

<constructor-arg value="A" />

<constructor-arg value="100000" />

</bean>

스프링 2.0부터는 프로퍼티 값을 지정하는 또 다른 편리한 숏컷이 추가됐다. 바로 p 스키마를

이용해 빈 프로퍼티를 <bean> 엘리먼트의 속성으로 정의하는 것이다. 이렇게 하면 XML 설정을

상당히 줄일 수 있다.

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

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:p="http://www.springframework.org/schema/p"

Page 45: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

01 스프링 소개 l 9

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator"

p:prefix="30" p:suffix="A" p:initial="100000" />

</beans>

빈에 사용할 컬렉션 구성하기

List, Set, Map은 자바의 컬렉션을 대표하는 세 가지 핵심 인터페이스다. 각 컬렉션 타입에 대해

자바에서는 다양한 기능과 특징을 지닌 구현체를 제공하여 선택적으로 사용할 수 있다. 이러한

컬렉션 타입들을 스프링에서는 <list>, <set>, <map>과 같은 내장 XML 태그를 통해 손쉽게 설

정할 수 있다.

앞에서 작성한 시퀀스 생성기에서 하나 이상의 접미어를 사용한다고 가정해보자. 각 접미어는

하이픈으로 구분하고, 임의의 데이터 타입을 지닌 접미어를 문자열로 변환해 시퀀스 뒤에 붙일

것이다.

List, Array, Set

먼저 java.util.List 컬렉션에 접미어를 담아보자. list는 순차적이고 인덱스로 접근이 가능한 컬렉

션이며, list의 요소에는 인덱스나 for-each 순환문으로 접근할 수 있다.

package com.apress.springrecipes.sequence;

...

public class SequenceGenerator {

...

private List<Object> suffixes;

public void setSuffixes(List<Object> suffixes) {

this.suffixes = suffixes;

}

public synchronized String getSequence() {

StringBuffer buffer = new StringBuffer();

...

for (Object suffix : suffixes) {

buffer.append("-");

buffer.append(suffix);

}

return buffer.toString();

}

}

Page 46: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

10 l 스프링 3 레시피

빈 설정에 java.util.List 인터페이스 프로퍼티를 설정하려면 <list> 태그를 지정하고 엘리먼트

들을 포함하면 된다. <list> 태그 내에서는 <value>로 간단한 상수값을, <ref>로 빈 레퍼런스를,

<bean>으로 내부 빈 정의를, 그리고 <null>로 null 값을 지정할 수 있다. 컬렉션 내에 다시 다른

컬렉션 엘리먼트를 사용할 수도 있다.

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

<property name="initial" value="100000" />

<property name="suffixes">

<list>

<value>A</value>

<bean class="java.net.URL">

<constructor-arg value="http" />

<constructor-arg value="www.apress.com" />

<constructor-arg value="/" />

</bean>

<null />

</list>

</property>

</bean>

개념상 array는 list와 매우 유사하며, 순차적이고 인덱스를 지원하는 컬렉션이다. 하지만

array의 길이는 고정돼 있어 동적으로 확장이 불가능하다는 것이 주된 차이점이다. 자바 컬렉션

프레임워크에서 array와 list는 Arrays.asList()와 List.toArray() 메서드로 상호 변환할 수 있다.

시퀀스 생성기에서는 Object[] 배열(array)을 사용해 접미어를 저장하고 인덱스나 for-each 순환

문으로 해당 접미어에 접근할 수 있다.

package com.apress.springrecipes.sequence;

...

public class SequenceGenerator {

...

private Object[] suffixes;

public void setSuffixes(Object[] suffixes) {

this.suffixes = suffixes;

}

...

}

빈 설정 파일에서는 <list> 태그로 배열을 정의하면 된다.

Page 47: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

01 스프링 소개 l 11

또 하나 자주 사용되는 컬렉션 타입은 set이다. java.util.List 인터페이스와 java.util.Set 인터페

이스 둘 다 동일하게 java.util.Collection 인터페이스를 상속하지만, set은 list와 달리 순차적이

지 않고 인덱싱도 되어 있지 않으며, 유일한(unique) 객체만 저장할 수 있다. 이것은 set 내에는 동

일한 엘리먼트가 중복해서 포함될 수 없다는 의미이며, 동일한 엘리먼트가 set에 한번 더 들어가

면 기존 엘리먼트를 교체(replace)하게 된다. 엘리먼트의 동일성(equality)은 equals() 메서드로 판

단한다.

package com.apress.springrecipes.sequence;

...

public class SequenceGenerator {

...

private Set<Object> suffixes;

public void setSuffixes(Set<Object> suffixes) {

this.suffixes = suffixes;

}

...

}

java.util.Set 타입의 프로퍼티를 정의하려면, <set> 태그를 사용해 list와 동일한 방법으로 엘

리먼트를 설정한다.

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

...

<property name="suffixes">

<set>

<value>A</value>

<bean class="java.net.URL">

<constructor-arg value="http" />

<constructor-arg value="www.apress.com" />

<constructor-arg value="/" />

</bean>

<null />

</set>

</property>

</bean>

본래 set은 의미상 순서 개념이 없지만, 스프링에서는 java.util.LinkedHashSet을 사용해 엘리

먼트의 순서를 지정할 수 있다. java.util.LinkedHashSet은 java.util.Set 인터페이스의 구현체로

서, 순서가 보존된다.

Page 48: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

12 l 스프링 3 레시피

맵과 프로퍼티

맵(map)은 키(key)와 값(value)의 쌍으로 구성된 엔트리(entry)들을 저장하는 테이블이다. 해당

키를 통해 특정 값을 얻을 수 있으며, for-each 순환문을 통해 맵 엔트리들을 순회(iterate)할 수

있다. 키와 값으로는 어떠한 타입도 가능하며, 키를 비교할 때는 equals() 메서드를 사용한다.

예제에서는 접미어를 키로 가진 java.util.Map 컬렉션을 사용하도록 시퀀스 생성기 코드를 변

경한다.

package com.apress.springrecipes.sequence;

...

public class SequenceGenerator {

...

private Map<Object, Object> suffixes;

public void setSuffixes(Map<Object, Object> suffixes) {

this.suffixes = suffixes;

}

public synchronized String getSequence() {

StringBuffer buffer = new StringBuffer();

...

for (Map.Entry entry : suffixes.entrySet()) {

buffer.append("-");

buffer.append(entry.getKey());

buffer.append("@");

buffer.append(entry.getValue());

}

return buffer.toString();

}

}

스프링에서 맵은 <map> 태그로 정의하고 하위에 여러 개의 <entry> 태그를 가진다. 각 엔트

리는 키와 값을 가지고 있으며, 키는 <key> 태그에 정의한다. 키와 값의 타입 제한은 없으므로

<value>, <ref>, <bean>, <idref>, <null> 엘리먼트들을 사용해 자유롭게 지정할 수 있다. 스프

링에서는 java.util.LinkedHashMap을 사용해 맵 엔트리의 순서를 보존한다.

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

...

<property name="suffixes">

<map>

<entry>

<key>

<value>type</value>

Page 49: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

01 스프링 소개 l 13

</key>

<value>A</value>

</entry>

<entry>

<key>

<value>url</value>

</key>

<bean class="java.net.URL">

<constructor-arg value="http" />

<constructor-arg value="www.apress.com" />

<constructor-arg value"/" />

</bean>

</entry>

</map>

</property>

</bean>

<entry> 태그의 키와 값 속성들도 숏컷으로 지정할 수 있다. 간단한 상수 값일 경우 key와

value 속성으로 설정이 가능하며, 빈 레퍼런스인 경우 key-ref와 value-ref 속성으로 설정할 수

있다.

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

...

<property name="suffixes">

<map>

<entry key="type" value="A" />

<entry key="url">

<bean class="java.net.URL">

<constructor-arg value="http" />

<constructor-arg value="www.apress.com" />

<constructor-arg value="/" />

</bean>

</entry>

</map>

</property>

</bean>

지금까지 소개한 모든 컬렉션 클래스에는 값을 사용해서 프로퍼티를 설정했는데, 가끔은 맵

인스턴스를 널(null) 값으로 설정해야 할 때도 있다. 스프링의 XML 설정 스키마는 명시적으로 이

러한 기능을 지원한다. 다음은 null 값을 엔트리의 값으로 가진 맵을 보여준다.

<property name="nulledMapValue">

<map>

Page 50: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

14 l 스프링 3 레시피

<entry>

<key> <value>null</value> </key>

</entry>

</map>

</property>

java.util.Properties 컬렉션은 맵과 매우 유사하여 java.util.Map 인터페이스를 구현하고 있으

며, 키/값의 쌍으로 된 엔트리를 저장한다. 차이점이 있다면 프로퍼티 컬렉션의 키와 값은 모두

문자열(string) 타입이라는 것이다.

package com.apress.springrecipes.sequence;

...

public class SequenceGenerator {

...

private Properties suffixes;

public void setSuffixes(Properties suffixes) {

this.suffixes = suffixes;

}

...

}

스프링에서 java.util.Properties 컬렉션을 정의하려면 <props> 태그를 사용한 다음 그 아래에

여러 개의 <prop> 태그를 사용하면 된다. 각 <prop> 태그에는 key 속성이 있으며, 값은 태그 내

부에 지정한다.

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

...

<property name="suffixes">

<props>

<prop key="type">A</prop>

<prop key="url">http://www.apress.com/</prop>

<prop key="null">null</prop>

</props>

</property>

</bean>

부모 빈의 컬렉션과 병합하기

상속을 써서 빈을 정의하고, merge 속성을 true로 설정하면 자식 빈의 컬렉션은 부모 빈의 컬렉

션과 병합된다. <list> 컬렉션에서는 순서 유지를 위해 자식 엘리먼트가 부모 엘리먼트 뒤에 붙는

다. 그러므로 다음의 시퀀스 생성기는 접미어로 A, B, A, C를 갖게 된다.

Page 51: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

01 스프링 소개 l 15

<beans ...>

<bean id="baseSequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

<property name="prefixGenerator" ref="datePrefixGenerator" />

<property name="initial" value="100000" />

<property name="suffixes">

<list>

<value>A</value>

<value>B</value>

</list>

</property>

</bean>

<bean id="sequenceGenerator" parent="baseSequenceGenerator">

<property name="suffixes">

<list merge="true">

<value>A</value>

<value>C</value>

</list>

</property>

</bean>

...

</beans>

<set>과 <map> 컬렉션에서는 자식 엘리먼트와 부모 엘리먼트의 값이 동일하면 자식 엘리먼

트가 부모 엘리먼트를 덮어쓴다. 그러므로 다음의 시퀀스 생성기는 접미어로 A, B, C를 갖게 될

것이다.

<beans ...>

<bean id="baseSequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

<property name="prefixGenerator" ref="datePrefixGenerator" />

<property name="initial" value="100000" />

<property name="suffixes">

<set>

<value>A</value>

<value>B</value>

</set>

</property>

</bean>

<bean id="sequenceGenerator" parent="baseSequenceGenerator">

<property name="suffixes">

<set merge="true">

<value>A</value>

<value>C</value>

</set>

Page 52: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

16 l 스프링 3 레시피

</property>

</bean>

...

</beans>

1-3. 생성자를 호출해 빈 생성하기

◈ 과제

스프링 IoC 컨테이너에서 생성자를 호출해 빈을 생성하고자 하며, 생성자 호출 방식은 빈을 생성

하는 가장 일반적이고 직접적인 방식이다. 또한 이 방식은 자바에서 new 연산자로 객체를 생성

하는 것에 해당한다.

◈ 해결책

일반적으로 빈에 class 속성을 지정할 경우 이것은 스프링 IoC 컨테이너가 생성자 호출로 빈을

생성하도록 요청하는 것에 해당한다.

◈ 과제 풀이

온라인상에서 제품을 판매하는 쇼핑몰 애플리케이션을 개발한다고 가정해 보자. 우선 제품의

이름과 가격 같은 프로퍼티를 지닌 Product 클래스를 작성한다. 쇼핑몰에는 여러 종류의 제품

들이 있으므로 Product 클래스를 추상 클래스로 만들어 다른 하위 제품들이 상속하게 한다.

package com.apress.springrecipes.shop;

public abstract class Product {

private String name;

private double price;

public Product() {}

public Product(String name, double price) {

this.name = name;

this.price = price;

}

// 게터 및 세터

...

Page 53: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

01 스프링 소개 l 17

public String toString() {

return name + " " + price;

}

}

그리고 Battery와 Disc를 하위 제품으로 작성한다. 각 제품은 자체적인 프로퍼티를 가진다.

package com.apress.springrecipes.shop;

public class Battery extends Product {

private boolean rechargeable;

public Battery() {

super();

}

public Battery(String name, double price) {

super(name, price);

}

// 게터 및 세터

...

}

package com.apress.springrecipes.shop;

public class Disc extends Product {

private int capacity;

public Disc() {

super();

}

public Disc(String name, double price) {

super(name, price);

}

// 게터 및 세터

...

}

제품들을 스프링 IoC 컨테이너에 정의하기 위해 다음과 같이 빈 설정 파일을 작성한다.

Page 54: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

18 l 스프링 3 레시피

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

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<bean id="aaa" class="com.apress.springrecipes.shop.Battery">

<property name="name" value="AAA" />

<property name="price" value="2.5" />

<property name="rechargeable" value="true" />

</bean>

<bean id="cdrw" class="com.apress.springrecipes.shop.Disc">

<property name="name" value="CD-RW" />

<property name="price" value="1.5" />

<property name="capacity" value="700" />

</bean>

</beans>

<constructor-arg> 엘리먼트가 지정돼 있지 않으면 인자가 없는 기본 생성자가 호출된다. 그리

고 각 <property> 엘리먼트에 대해 스프링이 세터 메서드를 통해 값을 주입할 것이다. 앞의 빈 설

정은 다음 코드와 동일하다.

Product aaa = new Battery();

aaa.setName("AAA");

aaa.setPrice(2.5);

aaa.setRechargeable(true);

Product cdrw = new Disc();

cdrw.setName("CD-RW");

cdrw.setPrice(1.5);

cdrw.setCapacity(700);

반면 <constructor-arg> 엘리먼트를 하나 이상 지정하면 스프링이 인자에 대응하는 가장 적

합한 생성자를 호출할 것이다.

<beans ...>

<bean id="aaa" class="com.apress.springrecipes.shop.Battery">

<constructor-arg value="AAA" />

<constructor-arg value="2.5" />

<property name="rechargeable" value="true" />

</bean>

<bean id="cdrw" class="com.apress.springrecipes.shop.Disc">

<constructor-arg value="CD-RW" />

Page 55: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

01 스프링 소개 l 19

<constructor-arg value="1.5" />

<property name="capacity" value="700" />

</bean>

</beans>

Product 클래스와 해당 클래스의 하위 클래스에는 모호한 생성자가 없으므로 앞의 빈 설정은

다음 코드와 동일하다.

Product aaa = new Battery("AAA", 2.5);

aaa.setRechargeable(true);

Product cdrw = new Disc("CD-RW", 1.5);

cdrw.setCapacity(700);

스프링 IoC 컨테이너에서 제품을 얻는 테스트를 위해 다음과 같이 Main 클래스를 작성한다.

package com.apress.springrecipes.shop;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

public static void main(String[] args) throws Exception {

ApplicationContext context =

new ClassPathXmlApplicationContext("beans.xml");

Product aaa = (Product) context.getBean("aaa");

Product cdrw = (Product) context.getBean("cdrw");

System.out.println(aaa);

System.out.println(cdrw);

}

}

1-4. 모호한 생성자 문제 해결하기

◈ 과제

빈에 하나 이상의 생성자 매개변수를 지정할 경우 스프링은 빈 클래스에서 적절한 생성자를 찾

아 지정한 매개변수로 빈 인스턴스를 생성하려고 시도한다. 하지만 지정한 매개변수가 적용될 수

있는 생성자가 하나 이상이면 호출해야 할 생성자를 선택하기가 모호해질 수 있다. 이런 경우에

는 기대하는 생성자를 스프링이 호출하지 못할 수도 있다.

Page 56: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

20 l 스프링 3 레시피

◈ 해결책

<constructor-arg> 엘리먼트에 type과 index 속성을 지정하면 스프링이 원하는 생성자를 찾는

데 도움을 줄 수 있다.

◈ 과제 풀이

SequenceGenerator 클래스에 pre�x와 su�x를 인자로 받는 새로운 생성자를 추가해보자.

package com.apress.springrecipes.sequence;

public class SequenceGenerator {

...

public SequenceGenerator(String prefix, String suffix) {

this.prefix = prefix;

this.suffix = suffix;

}

}

빈 정의에 <constructor-arg> 엘리먼트를 사용해 하나 이상의 생성자 매개변수를 지정할 수

있다. 스프링은 해당 클래스에서 적절한 생성자를 찾아 매개변수를 전달해 빈의 인스턴스를 생

성하려고 할 것이다. 생성자 인자는 위치를 기반으로 하기 때문에 <constructor-arg>에 name

속성이 없다는 것을 기억한다.

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

<constructor-arg value="30" />

<constructor-arg value="A" />

<property name="initial" value="100000" />

</bean>

두 개의 인자를 필요로 하는 생성자는 하나밖에 없으므로 스프링은 이러한 두 개의 인자를 전

달받는 생성자를 쉽게 찾을 것이다. SequenceGenerator에 pre�x와 initial을 인자로 하는 또 다

른 생성자를 추가한다고 가정해보자.

package com.apress.springrecipes.sequence;

public class SequenceGenerator {

...

public SequenceGenerator(String prefix, String suffix) {

this.prefix = prefix;

this.suffix = suffix;

}

Page 57: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

01 스프링 소개 l 21

public SequenceGenerator(String prefix, int initial) {

this.prefix = prefix;

this.initial = initial;

}

}

이 생성자를 호출하기 위해 다음과 같은 빈 정의를 만들어서 pre�x와 initial 값을 전달한다.

맨 아래의 su�x는 세터 메서드를 통해 주입된다.

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

<constructor-arg value="30" />

<constructor-arg value="100000" />

<property name="suffix" value="A" />

</bean>

애플리케이션을 실행해보면 다음과 같은 결과가 출력된다.

300A

301A

결과가 예상과 다르게 나타난 이유는 두 번째 생성자가 아닌 pre�x와 su�x를 인자로 하는 첫

번째 생성자가 호출됐기 때문이다. 이것은 스프링이 인자를 해석할 때 타입 변환에 대한 언급이

없으면 기본적으로 String 타입으로 변환하므로 첫 번째 생성자가 가장 적합하다고 판단했기 때

문이다. 인자의 타입을 지정하려면 <constructor-arg>에 type 속성을 지정하면 된다.

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

<constructor-arg type="java.lang.String" value="30" />

<constructor-arg type="int" value="100000" />

<property name="suffix" value="A" />

</bean>

다시 SequenceGenerator에 initial과 su�x를 인자로 하는 생성자를 하나 더 추가하고 그에

따라 빈 정의를 수정한다.

package com.apress.springrecipes.sequence;

public class SequenceGenerator {

...

public SequenceGenerator(String prefix, String suffix) {

this.prefix = prefix;

Page 58: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

22 l 스프링 3 레시피

this.suffix = suffix;

}

public SequenceGenerator(String prefix, int initial) {

this.prefix = prefix;

this.initial = initial;

}

public SequenceGenerator(int initial, String suffix) {

this.initial = initial;

this.suffix = suffix;

}

}

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

<constructor-arg type="int" value="100000" />

<constructor-arg type="java.lang.String" value="A" />

<property name="prefix" value="30" />

</bean>

애플리케이션을 다시 실행해 보면 정상적인 결과가 나타나거나 다음과 같이 예상치 못한 결과

가 나타날 수도 있다.

30100000null

30100001null

이처럼 결과가 분명하지 않은 이유는 스프링이 내부적으로 인자를 가지고 각 생성자의 적합

성을 판단하기 때문이다. 그런데 이러한 판단 과정에서 XML에 인자가 나타나는 순서는 고려되

지 않는다. 다시 말해, 스프링의 관점에서는 두 번째와 세 번째 생성자가 동등하게 취급되며, 먼

저 발견된 것이 우선적으로 선택된다. 좀더 엄밀하게 살펴보면 자바 리플렉션 API에서 Class.

getDeclaredConstructors() 메서드를 통해 반환되는 생성자들은 선언된 순서와 다르게 임의적

인 순서로 나타난다. 이러한 이유로 적절한 생성자를 선택하는 데 모호함이 생긴다.

이러한 문제를 피하려면 <constructor-arg>의 index 속성으로 매개변수의 인덱스를 명시적

으로 지정해야 한다. type과 index 속성을 둘 다 설정하면 스프링이 빈에 적절한 생성자를 찾게

될 것이다.

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

<constructor-arg type="int" index="0" value="100000" />

Page 59: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

01 스프링 소개 l 23

<constructor-arg type="java.lang.String" index="1" value="A" />

<property name="prefix" value="30" />

</bean>

하지만 생성자에서 절대 모호한 경우가 생기지 않는다고 확신한다면 type과 index 속성을 사

용하지 않아도 된다.

1-5. 빈 레퍼런스 지정하기

◈ 과제

애플리케이션의 기능을 완성하기 위해 애플리케이션을 구성하는 각 빈이 서로 협력해야 할 때가

있다. 빈이 다른 빈을 참조하려면 빈 설정 파일에 빈 레퍼런스를 지정해야 한다.

◈ 해결책

빈 레퍼런스는 빈 설정 파일에서 빈 프로퍼티나 생성자 매개변수에 <ref> 엘리먼트를 사용해 지

정할 수 있다. 이것은 <value> 엘리먼트를 사용해 단순한 값을 지정하는 것만큼이나 쉽다. 프로

퍼티나 생성자 매개변수 내부에 빈을 직접 정의해 내부(inner) 빈을 정의할 수도 있다.

◈ 과제 풀이

시퀀스 생성기의 접두어를 문자열로 받아들이는 것은 추가로 발생할 수 있는 요구사항들을 수

용할 수 있을 만큼 유연하지 못하다. 대신 접두어를 프로그래밍 로직으로 재정의할 수 있게 만드

는 편이 더 나을 것이다. 따라서 이러한 접두어 생성 오퍼레이션은 Pre�xGenerator 인터페이스

로 정의할 수 있다.

package com.apress.springrecipes.sequence;

public interface PrefixGenerator {

public String getPrefix();

}

접두어 생성 방식 가운데 하나는 특정 패턴을 이용해 현재 시스템 날짜를 형식화하는 것이다.

Pre�xGenerator 인터페이스를 구현하는 DatePre�xGenerator 클래스를 작성해보자.

package com.apress.springrecipes.sequence;

...

Page 60: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

24 l 스프링 3 레시피

public class DatePrefixGenerator implements PrefixGenerator {

private DateFormat formatter;

public void setPattern(String pattern) {

this.formatter = new SimpleDateFormat(pattern);

}

public String getPrefix() {

return formatter.format(new Date());

}

}

이 생성기의 패턴은 setPattern() 세터 메서드를 통해 주입된 다음 날짜를 형식화하는

java.text.DateFormat 객체를 생성하는 데 사용될 것이다. DateFormat 객체가 생성될 때 말

고는 패턴 문자열이 다시 사용될 일은 없으므로 private 필드로 저장할 필요가 없다. 이제

DatePre�xGenerator 타입의 빈이 날짜 형식화를 위한 임의 패턴 문자열을 받아들이도록 선언

할 수 있다.

<bean id="datePrefixGenerator"

class="com.apress.springrecipes.sequence.DatePrefixGenerator">

<property name="pattern" value="yyyyMMdd" />

</bean>

세터 메서드로 빈 레퍼런스 지정하기

이러한 접두어 생성기 방식을 적용하려면 SequenceGenerator 클래스에서는 단순한 문자열 접

두어 대신 Pre�xGenerator 타입의 객체를 받아들여야 한다. 이때 세터 주입을 사용해서 접두어

생성기를 전달받을 수도 있다. 아울러 pre�x 프로퍼티를 삭제하고, 컴파일 에러를 일으키는 세

터 메서드와 생성자도 삭제한다.

package com.apress.springrecipes.sequence;

public class SequenceGenerator {

...

private PrefixGenerator prefixGenerator;

public void setPrefixGenerator(PrefixGenerator prefixGenerator) {

this.prefixGenerator = prefixGenerator;

}

public synchronized String getSequence() {

Page 61: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

01 스프링 소개 l 25

StringBuffer buffer = new StringBuffer();

buffer.append(prefixGenerator.getPrefix());

buffer.append(initial + counter++);

buffer.append(suffix);

return buffer.toString();

}

}

그러고 나면 SequenceGenerator 빈은 내부에 <ref> 엘리먼트를 정의해 해당 pre�xGenerator

프로퍼티로 datePre�xGenerator 빈을 참조할 수 있다.

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

<property name="initial" value="100000" />

<property name="suffix" value="A" />

<property name="prefixGenerator">

<ref bean="datePrefixGenerator" />

</property>

</bean>

<ref> 엘리먼트의 bean 속성에 있는 빈 이름은 IoC 컨테이너에 있는 빈이라면 뭐든 참조할 수

있다. 심지어 빈이 같은 XML 설정 파일에 정의돼 있지 않더라도 말이다. 같은 XML 파일에 있는

빈을 참조하는 경우에는 XML ID 참조 방식에 따라 local 속성을 사용할 수 있다. 그러면 XML

에디터가 같은 XML 파일에 해당 ID를 가진 빈이 존재하는지 검증(참조 무결성 검증)할 수 있다.

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

...

<property name="prefixGenerator">

<ref local="datePrefixGenerator" />

</property>

</bean>

<property> 엘리먼트의 ref 속성으로 빈 참조를 바로 지정하는 방법도 있다.

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

...

<property name="prefixGenerator" ref="datePrefixGenerator" />

</bean>

하지만 이 경우에는 XML 에디터가 참조 무결성을 검증하지는 못할 것이다. 실제로 이 방법은

<ref> 엘리먼트의 bean 속성을 지정한 것과 같은 효과를 가진다.

Page 62: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

26 l 스프링 3 레시피

스프링 2.x에서는 빈 레퍼런스를 지정하는 또 다른 편리한 방법을 제공한다. 바로 p 스키마를

사용해 <bean> 엘리먼트의 속성에 빈 레퍼런스를 지정하는 것이며, 이렇게 하면 XML 설정을

상당량 줄일 수 있다.

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

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:p="http://www.springframework.org/schema/p"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator"

p:suffix="A" p:initial="1000000"

p:prefixGenerator-ref="datePrefixGenerator" />

</beans>

빈 레퍼런스와 단순한 프로퍼티 값을 구분하기 위해 프로퍼티 이름에는 –ref 접미어를 추가해

야 한다.

생성자 인자에 빈 레퍼런스 지정하기

빈 레퍼런스는 생성자 주입에도 적용할 수 있다. 예를 들어, Pre�xGenerator 객체를 인자로 받는

생성자를 추가할 수 있다.

package com.apress.springrecipes.sequence;

public class SequenceGenerator {

...

private PrefixGenerator prefixGenerator;

public SequenceGenerator(PrefixGenerator prefixGenerator) {

this.prefixGenerator = prefixGenerator;

}

}

<constructor-arg> 엘리먼트에서는 <property> 엘리먼트에서 했던 것처럼 <ref>를 사용해

빈 레퍼런스를 만들 수 있다.

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

<constructor-arg>

<ref local="datePrefixGenerator" />

</constructor-arg>

<property name="initial" value="100000" />

Page 63: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

01 스프링 소개 l 27

<property name="suffix" value="A" />

</bean>

빈 레퍼런스를 지정하는 숏컷은 <constructor-arg>에서도 사용할 수 있다.

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

<constructor-arg ref="datePrefixGenerator" />

...

</bean>

내부 빈 선언하기

하나의 빈 인스턴스가 특정 프로퍼티에서만 사용된다면 해당 빈을 내부 빈으로 선언할 수 있다.

내부 빈 선언은 <property>나 <constructor-arg>로 감싼 형태이며, id나 name과 같은 속성은

필요하지 않다. 이렇게 선언한 빈은 익명(anonymous) 빈이 되어 다른 곳에서는 사용하지 못한

다. 실제로 내부 빈에 id나 name 속성을 지정하더라도 아무런 효과가 없을 것이다.

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

<property name="initial" value="100000" />

<property name="suffix" value="A" />

<property name="prefixGenerator">

<bean class="com.apress.springrecipes.sequence.DatePrefixGenerator">

<property name="pattern" value="yyyyMMdd" />

</bean>

</property>

</bean>

내부 빈은 생성자 인자에도 선언할 수 있다.

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

<constructor-arg>

<bean class="com.apress.springrecipes.sequence.DatePrefixGenerator">

<property name="pattern" value="yyyyMMdd" />

</bean>

</constructor-arg>

<property name="initial" value="100000" />

<property name="suffix" value="A" />

</bean>

Page 64: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

28 l 스프링 3 레시피

1-6. 컬렉션 엘리먼트에 데이터 타입 지정하기

◈ 과제

기본적으로 스프링은 컬렉션의 모든 엘리먼트들을 문자열 형태로 다루지만 문자열을 사용하지

않으려면 컬렉션 엘리먼트에 대한 데이터 타입을 지정해줄 수 있다.

◈ 해결책

각 컬렉션 엘리먼트의 <value> 태그에 type 속성을 사용해 데이터 타입을 지정할 수 있으며, 컬

렉션 태그의 value-type 속성으로 모든 엘리먼트의 데이터 타입을 지정할 수도 있다. 자바 1.5 이

상에서는 타입에 안전한(type-safe) 컬렉션을 정의할 수 있어서 스프링이 컬렉션의 타입 정보를

읽을 수 있다.

◈ 과제 풀이

시퀀스 생성기의 접미어들을 정수(integer) 값의 목록으로 입력받는다고 가정해보자. 각 번호는

java.text.DecimalFormat 인스턴스에 의해 네 자리로 바뀐다.

package com.apress.springrecipes.sequence;

...

public class SequenceGenerator {

...

private List<Object> suffixes;

public void setSuffixes(List<Object> suffixes) {

this.suffixes = suffixes;

}

public synchronized String getSequence() {

StringBuffer buffer = new StringBuffer();

...

DecimalFormat formatter = new DecimalFormat("0000");

for (Object suffix : suffixes) {

buffer.append("-");

buffer.append(formatter.format((Integer) suffix));

}

return buffer.toString();

}

}

Page 65: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

01 스프링 소개 l 29

그리고 빈 설정 파일에서 시퀀스 생성기에 접미어를 여러 개 정의한다.

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

<property name="prefixGenerator" ref="datePrefixGenerator" />

<property name="initial" value="100000" />

<property name="suffixes">

<list>

<value>5</value>

<value>10</value>

<value>20</value>

</list>

</property>

</bean>

하지만 애플리케이션을 실행해보면 접미어가 String 타입이므로 integer로 변환할 수 없음을

나타내는 ClassCastException이 발생할 것이다. 스프링은 컬렉션의 엘리먼트들을 기본적으로

문자열로 다루기 때문에 <value> 태그의 type 속성을 사용해 엘리먼트의 타입을 지정해줘야

한다.

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

...

<property name="suffixes">

<list>

<value type="int">5</value>

<value type="int">10</value>

<value type="int">20</value>

</list>

</property>

</bean>

또는 컬렉션 태그의 value-type 속성을 사용해 컬렉션의 모든 엘리먼트들에 대한 타입을 지정

할 수도 있다.

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

...

<property name="suffixes">

<list value-type="int">

<value>5</value>

<value>10</value>

<value>20</value>

</list>

Page 66: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

30 l 스프링 3 레시피

</property>

</bean>

자바 1.5 이상에서는 정수만 저장하도록 타입에 안전한 컬렉션으로 su�x 목록을 정의할 수

있다.

package com.apress.springrecipes.sequence;

...

public class SequenceGenerator {

...

private List<Integer> suffixes;

public void setSuffixes(List<Integer> suffixes) {

this.suffixes = suffixes;

}

public synchronized String getSequence() {

StringBuffer buffer = new StringBuffer();

...

DecimalFormat formatter = new DecimalFormat("0000");

for (int suffix : suffixes) {

buffer.append("-");

buffer.append(formatter.format(suffix));

}

return buffer.toString();

}

}

타입에 안전한 방식으로 컬렉션을 정의해 두면 스프링이 리플렉션으로 컬렉션의 타입 정보를

읽어들일 수 있으므로 더는 <list>에 value-type 속성을 지정할 필요가 없다.

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

...

<property name="suffixes">

<list>

<value>5</value>

<value>10</value>

<value>20</value>

</list>

</property>

</bean>

Page 67: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

01 스프링 소개 l 31

1-7. 스프링 FactoryBean을 이용한 빈 생성

◈ 과제

스프링의 팩터리 빈을 사용해 스프링 IoC 컨테이너에서 빈을 생성하려고 한다. 팩터리 빈은 IoC

컨테이너에서 다른 빈을 생성하는 팩터리 역할을 하는 빈이다. 개념상 팩터리 빈은 팩터리 메서

드와 매우 유사하지만 빈 생성 과정에서 스프링 IoC 컨테이너에 의해 인식될 수 있는 스프링에

특화된(speci�c) 빈이다.

◈ 해결책

팩터리 빈은 기본적으로 FactoryBean 인터페이스를 구현해야 한다. 편의를 위해 스프링에서는

상속해서 사용할 수 있는 AbstractFactoryBean 추상 템플릿 클래스를 제공한다. 팩터리 빈은

프레임워크의 편의 기능들을 구현하는 데 종종 사용되며, 몇 가지 예는 다음과 같다.

▶ JNDI에서 객체(데이터소스 등)를 탐색할 때 JndiObjectFactoryBean을 사용할 수 있다.

▶ 빈에 대한 프록시를 생성하기 위해 기존의 스프링 AOP를 사용할 때 ProxyFactoryBean

을 사용할 수 있다.

▶ IoC 컨테이너에서 하이버네이트 세션 팩터리를 생성할 때 LocalSessionFactoryBean을

사용할 수 있다.

하지만 프레임워크 사용자가 커스텀 팩터리 빈을 작성할 일은 거의 없다. 왜냐하면 커스텀 팩

터리 빈은 스프링 IoC 컨테이너 밖에서는 사용할 수 없는 프레임워크에 종속된 빈이기 때문이다.

실제로 팩터리 빈과 동등한 역할을 하는 팩터리 메서드를 주로 구현해 사용한다.

◈ 과제 풀이

커스텀 팩터리 빈을 작성할 일이 별로 없더라도 예제를 통해 내부 메커니즘을 이해하는 것은 도

움이 될 수 있다. 예제로 가격 할인이 적용된 제품을 만드는 팩터리 빈을 작성한다. 이때 product

프로퍼티와 discount 프로퍼티를 입력받아 제품에 할인된 가격을 적용하고 그것을 빈으로 생성

해 반환한다.

package com.apress.springrecipes.shop;

import org.springframework.beans.factory.config.AbstractFactoryBean;

public class DiscountFactoryBean extends AbstractFactoryBean {

Page 68: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

32 l 스프링 3 레시피

private Product product;

private double discount;

public void setProduct(Product product) {

this.product = product;

}

public void setDiscount(double discount) {

this.discount = discount;

}

public Class getObjectType() {

return product.getClass();

}

protected Object createInstance() throws Exception {

product.setPrice(product.getPrice() * (1 - discount));

return product;

}

}

AbstractFactoryBean 클래스를 상속함으로써 팩터리 빈에서는 간단히 대상 빈 인스턴스를

생성하는 createInstance() 메서드만 오버라이드하면 된다. 그리고 자동 연결(auto-wiring) 기능

이 적절히 동작하게끔 getObjectType() 메서드에서 대상 빈의 타입을 반환한다.

다음으로 제품 인스턴스를 DiscountFactoryBean에서 선언한다. FactoryBean 인터페이스를

구현한 빈을 호출할 때마다 스프링 IoC 컨테이너는 이 팩터리 빈을 사용해 대상 빈을 생성하고

반환해줄 것이다. 팩터리 빈 인스턴스 자체가 필요하다면 빈 이름 앞에 &를 지정하면 된다.

<beans ...>

<bean id="aaa"

class="com.apress.springrecipes.shop.DiscountFactoryBean">

<property name="product">

<bean class="com.apress.springrecipes.shop.Battery">

<constructor-arg value="AAA" />

<constructor-arg value="2.5" />

</bean>

</property>

<property name="discount" value="0.2" />

</bean>

<bean id="cdrw"

class="com.apress.springrecipes.shop.DiscountFactoryBean">

Page 69: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

01 스프링 소개 l 33

<property name="product">

<bean class="com.apress.springrecipes.shop.Disc">

<constructor-arg value="CD-RW" />

<constructor-arg value="1.5" />

</bean>

</property>

<property name="discount" value="0.1" />

</bean>

</beans>

앞의 팩터리 빈 설정의 동작은 다음 코드와 유사하다.

DiscountFactoryBean aaa = new DiscountFactoryBean();

aaa.setProduct(new Battery("AAA", 2.5));

aaa.setDiscount(0.2);

Product aaa = (Product) aaa.createInstance();

DiscountFactoryBean cdrw = new DiscountFactoryBean();

cdrw.setProduct(new Disc("CD-RW", 1.5));

cdrw.setDiscount(0.1);

Product cdrw = (Product) cdrw.createInstance();

1-8. 팩터리 빈과 유틸리티 스키마로 컬렉션 정의하기

◈ 과제

기본적인 컬렉션 태그를 사용해 컬렉션을 정의할 경우 LinkedList, TreeSet, TreeMap과 같은 특

정 컬렉션의 구현 클래스는 지정할 수가 없다. 게다가 컬렉션은 다른 빈에서 참조할 수 있게 독립

형 빈으로 선언하는 식으로 다른 빈과 공유할 수 없다.

◈ 해결책

스프링에서는 기본 컬렉션 태그로는 부족한 기능적 한계를 극복할 수 있는 몇 가지 방법을 제공

한다. 첫 번째 방법은 ListFactoryBean, SetFactoryBean, MapFactoryBean과 같은 컬렉션 팩터

리 빈을 사용하는 것이다. 팩터리 빈은 다른 빈을 생성하는 데 사용하는 특별한 종류의 스프링

빈이다. 두 번째 방법은 스프링 2.x에서 도입된 util 스키마의 <util:list>, <util:set>, <util:map>

같은 컬렉션 태그를 사용하는 것이다.

Page 70: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

34 l 스프링 3 레시피

◈ 과제 풀이

컬렉션 구현 클래스 지정하기

컬렉션을 정의하고 그것의 대상 클래스를 지정하기 위해 컬렉션 팩터리를 사용할 수 있다. 예를

들어, SetFactoryBean에 targetSetClass 프로퍼티를 지정하면 스프링이 이 컬렉션에 지정된 클

래스의 인스턴스를 생성한다.

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

<property name="prefixGenerator" ref="datePrefixGenerator" />

<property name="initial" value="100000" />

<property name="suffixes">

<bean class="org.springframework.beans.factory.config.SetFactoryBean">

<property name="targetSetClass">

<value>java.util.TreeSet</value>

</property>

<property name="sourceSet">

<set>

<value>5</value>

<value>10</value>

<value>20</value>

</set>

</property>

</bean>

</property>

</bean>

아니면 util 스키마에 있는 컬렉션 태그를 사용해 컬렉션을 정의하고 그것의 대상 클래스를 지

정할 수도 있다(이를테면, <util:set>의 set-class 속성을 이용해), 하지만 해당 스키마를 사용하

려면 반드시 <beans> 최상위 엘리먼트에 util 스키마 정의를 추가해야 한다.

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

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:util="http://www.springframework.org/schema/util"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

http://www.springframework.org/schema/util

http://www.springframework.org/schema/util/spring-util-3.0.xsd">

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

...

<property name="suffixes">

Page 71: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

01 스프링 소개 l 35

<util:set set-class="java.util.TreeSet">

<value>5</value>

<value>10</value>

<value>20</value>

</util:set>

</property>

</bean>

...

</beans>

독립형 컬렉션 정의하기

컬렉션 팩터리 빈의 또 다른 이점은 독립적으로 정의된 빈이 다른 빈에 의해 참조되는 것처럼 컬

렉션을 독립적으로 정의할 수 있게 해준다는 것이다. 예를 들면, SetFactoryBean을 이용하면 독

립적인 set을 정의할 수 있다.

<beans ...>

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

...

<property name="suffixes">

<ref local="suffixes" />

</property>

</bean>

<bean id="suffixes"

class="org.springframework.beans.factory.config.SetFactoryBean">

<property name="sourceSet">

<set>

<value>5</value>

<value>10</value>

<value>20</value>

</set>

</property>

</bean>

...

</beans>

또는 util 스키마에 있는 <util:set> 태그를 사용해 독립적인 set을 정의할 수도 있다.

<beans ...>

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

...

<property name="suffixes">

Page 72: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

36 l 스프링 3 레시피

<ref local="suffixes" />

</property>

</bean>

<util:set id="suffixes">

<value>5</value>

<value>10</value>

<value>20</value>

</util:set>

...

</beans>

1-9. 의존성 검사 기능으로 프로퍼티 검사하기

◈ 과제

규모가 큰 애플리케이션에서는 IoC 컨테이너에 선언된 빈의 개수가 수백에서 수천 개가 될 수도

있으며, 빈의 의존 관계도 매우 복잡해질 수 있다. 세터 주입의 단점 중 하나는 프로퍼티가 주입

됐는지를 확인할 수 없다는 것이므로 모든 필요한 프로퍼티가 제대로 설정됐는지 검사하기가 매

우 어려워진다.

◈ 해결책

스프링의 의존성 검사(dependency checking) 기능은 특정 타입의 프로퍼티가 모두 빈에 설정됐

는지를 검사해준다. 의존성 검사 기능을 사용하려면 <bean>의 dependency-check 속성에 의존

성 검사 모드를 지정하면 된다. 다만 한 가지 유의할 점은 의존성 검사 기능은 프로퍼티가 설정

됐는지만 검사할 수 있으며, 그 값이 null이 아닌지는 검사할 수 없다는 것이다. 표 1-1은 스프링

에서 지원하는 모든 의존성 검사 모드를 보여준다.

표 1-1. 스프링에서 지원하는 의존성 검사 모드

모드 설명

none* 아무런 의존성 검사도 수행하지 않는다. 설정되지 않은 프로퍼티가 있을 수 있다.

simple 단순한 타입(기본 타입 또는 컬렉션 타입)의 프로퍼티 가운데 설정되지 않은 것이 있을

경우 UnsatisfiedDependencyException을 던진다.

objects 객체 타입(단순 타입 외)의 프로퍼티 가운데 설정되지 않은 것이 있을 경우

UnsatisfiedDependencyException을 던진다.

all 모든 타입의 프로퍼티 가운데 설정되지 않은 것이 있을 경우

UnsatisfiedDependencyException을 던진다.

* 기본 모드는 none이지만 <beans> 최상위 엘리먼트의 default-dependency-check 속성을 설정해 변경할 수 있다. <beans>에 지

정한 기본 모드도 빈 자체에 모드를 다시 지정하면 오버라이드된다. <beans>에 속성을 지정하면 IoC 컨테이너에 있는 모든 빈에

대한 기본 의존성 검사 모드가 변경되므로 주의해서 설정해야 한다.

Page 73: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

01 스프링 소개 l 37

◈ 과제 풀이

단순 타입의 프로퍼티 검사하기

시퀀스 생성기에 su�x 프로퍼티가 설정되지 않았다고 가정해보자. 그러면 시퀀스 생성기는 접미

어가 null 문자열로 된 시퀀스를 생성할 것이다. 특히 복잡한 빈에서는 이런 종류의 문제를 디버

깅하기가 어렵고, 더욱이 스프링은 특정 타입의 프로퍼티가 설정됐는지만 검사할 수 있다. 스프

링에서 단순 타입의 프로퍼티에 대해 의존성 검사를 수행하려면 <bean>의 dependency-check

속성에 simple을 설정한다.

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator"

dependency-check="simple">

<property name="initial" value="100000" />

<property name="prefixGenerator" ref="datePrefixGenerator" />

</bean>

해당하는 타입의 프로퍼티 중 하나라도 설정이 되지 않으면 설정되지 않은 프로퍼티를 나타내

는 Unsatis�edDependencyException이 발생할 것이다.

Exception in thread "main"

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating

bean with name 'sequenceGenerator' defined in class path resource [beans.xml]:

Unsatisfied dependency expressed through bean property 'suffix': Set this property

value or disable dependency checking for this bean.

객체 타입의 프로퍼티 검사하기

접미어 생성기가 설정되지 않았을 경우 접미어 생성기를 요청하면 NullPointerException이

발생할 것이다. 객체 타입(단순 타입 외)의 빈 프로퍼티들에 대한 의존성을 검사하게 하려면

dependency-check 속성을 objects로 변경한다.

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator"

dependency-check="objects">

<property name="initial" value="100000" />

<property name="suffix" value="A" />

</bean>

애플리케이션을 실행하면 스프링이 pre�xGenerator 프로퍼티가 설정되지 않았다고 알려줄

것이다.

Page 74: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

38 l 스프링 3 레시피

Exception in thread "main"

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating

bean with name 'sequenceGenerator' defined in class path resource [beans.xml]:

Unsatisfied dependency expressed through bean property 'prefixGenerator': Set this

property value or disable dependency checking for this bean.

모든 타입의 프로퍼티 검사하기

타입에 관계 없이 모든 빈 프로퍼티들을 검사하고 싶다면 dependency-check 속성을 all로 변경

한다.

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator"

dependency-check="all">

<property name="initial" value="100000" />

</bean>

의존성 검사와 생성자 주입

스프링의 의존성 검사 기능은 프로퍼티가 세터 메서드를 통해 주입될 때만 검사가 가능하다. 그

러므로 생성자에 접두어 생성기를 주입하면 마찬가지로 Unsatis�edDependencyException이

발생할 것이다.

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator"

dependency-check="all">

<constructor-arg ref="datePrefixGenerator" />

<property name="initial" value="100000" />

<property name="suffix" value="A" />

</bean>

1-10. @Required 애노테이션으로 프로퍼티 검사하기

◈ 과제

스프링의 의존성 검사 기능은 모든 프로퍼티를 대상으로 검사하며, 유연하게 특정 프로퍼티만

검사하지는 못한다. 대부분의 경우 특정 프로퍼티가 설정됐는지만 검사하지, 모든 프로퍼티를

검사하지는 않을 것이다.

Page 75: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

01 스프링 소개 l 39

◈ 해결책

RequiredAnnotationBeanPostProcessor는 @Required 애노테이션을 가진 빈 프로퍼티가 설

정됐는지를 검사하는 스프링의 빈 후처리기(post processor)다. 빈 후처리기는 각 빈이 초기화

되기 이전에 추가적인 작업을 수행해주는 특별한 종류의 스프링 빈이다. 프로퍼티 검사를 위해

RequiredAnnotationBeanPostProcessor를 사용하려면 스프링 IoC 컨테이너에 등록해줘야 한

다. RequiredAnnotationBeanPostProcessor는 프로퍼티가 설정됐는지만 검사할 수 있으며, 그

값이 null이 아닌지는 검사할 수 없다는 점을 기억하자.

◈ 과제 풀이

시퀀스 생성기에서 pre�xGenerator와 su�x 프로퍼티가 모두 필요하다고 가정해보자. 이 프로

퍼티의 세터 메서드에 @Required 애노테이션을 적용할 수 있다.

package com.apress.springrecipes.sequence;

import org.springframework.beans.factory.annotation.Required;

public class SequenceGenerator {

private PrefixGenerator prefixGenerator;

private String suffix;

...

@Required

public void setPrefixGenerator(PrefixGenerator prefixGenerator) {

this.prefixGenerator = prefixGenerator;

}

@Required

public void setSuffix(String suffix) {

this.suffix = suffix;

}

...

}

스프링이 모든 시퀀스 생성기 인스턴스에 이 프로퍼티들이 설정됐는지 검사하게 하려면 Requ

iredAnnotationBeanPostProcessor 인스턴스를 IoC 컨테이너에 등록해야 한다. 빈 팩터리를 사

용하는 경우라면 API를 통해 빈 후처리기를 등록해야 하며, 그밖의 경우에는 애플리케이션 컨

텍스트에 빈 후처리기의 인스턴스를 선언하기만 하면 된다.

<bean class="org.springframework.beans.factory.annotation.

RequiredAnnotationBeanPostProcessor" />

Page 76: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

40 l 스프링 3 레시피

스프링 2.5 이상을 사용한다면 간단히 빈 설정 파일에 <context:annotation-con�g> 엘리먼트

를 추가해 RequiredAnnotationBeanPostProcessor 인스턴스가 자동으로 등록되게 할 수 있다.

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

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:context="http://www.springframework.org/schema/context"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context-3.0.xsd">

<context:annotation-config />

...

</beans>

@Required 애노테이션을 가진 프로퍼티에 값이 설정되지 않았다면 빈 후처리기에 의해

BeanInitializationException가 발생할 것이다.

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error

creating bean with name 'sequenceGenerator' defined in class path resource [beans.xml]:

Initialization of bean failed; nested exception is org.springframework.beans.factory.

BeanInitializationException: Property 'prefixGenerator' is required for bean

'sequenceGenerator'

RequiredAnnotationBeanPostProcessor는 @Required 애노테이션뿐 아니라 커스텀 애노테

이션이 적용된 프로퍼티도 검사할 수 있다. 예를 들면, 다음과 같은 애노테이션 타입으로도 작성

할 수 있다.

package com.apress.springrecipes.sequence;

...

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

public @interface Mandatory {

}

그리고 이 애노테이션은 프로퍼티의 세터 메서드에도 적용할 수 있다.

package com.apress.springrecipes.sequence;

public class SequenceGenerator {

private PrefixGenerator prefixGenerator;

private String suffix;

Page 77: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

01 스프링 소개 l 41

...

@Mandatory

public void setPrefixGenerator(PrefixGenerator prefixGenerator) {

this.prefixGenerator = prefixGenerator;

}

@Mandatory

public void setSuffix(String suffix) {

this.suffix = suffix;

}

...

}

이러한 애노테이션 타입이 적용된 프로퍼티를 검사하려면 애노테이션을 RequiredAnnotatio

nBeanPostProcessor의 requiredAnnotationType 프로퍼티에 지정해야 한다.

<bean class="org.springframework.beans.factory.annotation.

RequiredAnnotationBeanPostProcessor">

<property name="requiredAnnotationType">

<value>com.apress.springrecipes.sequence.Mandatory</value>

</property>

</bean>

1-11. XML 설정을 이용한 빈 자동 연결

◈ 과제

한 빈에서 다른 빈을 사용해야 할 경우 빈의 레퍼런스를 명시적으로 지정해 연결(wire)할 수 있

다. 하지만 컨테이너가 빈들을 자동으로 연결해줄 수 있다면 직접 연결을 설정하는 경우보다 문

제가 발생할 여지가 줄어들 것이다.

◈ 해결책

스프링 IoC 컨테이너는 빈들을 자동으로 연결해주며, <bean>의 autowire 속성에 auto-wiring

모드만 지정해주면 된다. 표 1-2는 스프링이 지원하는 자동 연결 모드를 나타낸다.

표 1-2. 스프링에서 지원하는 자동 연결 모드

모드 설명

no* 자동 연결을 수행하지 않음. 의존성을 명시적으로 지정해야 한다.

byName 빈의 프로퍼티들을 동일한 이름을 가진 빈과 연결함.

Page 78: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

42 l 스프링 3 레시피

모드 설명

byType 빈의 프로퍼티들을 타입이 호환되는 빈과 연결함. 타입이 적합한 빈이 여러 개 발견

된 경우에는 UnsatisfiedDependencyException이 발생함.

constructor 생성자의 각 인자에 대해 우선 타입이 호환되는 빈을 찾고 인자가 가장 부합하는

생성자를 선택함. 모호하여 결정할 수 없는 경우에는 UnsatisfiedDependency

Exception이 발생함.

autodetect 아무런 인자가 없는 기본 생성자가 있으면 타입에 의한 자동 연결을 하며, 그렇지

않은 경우 생성자에 의해 자동 연결함.

* 기본값으로 no 모드가 지정되며, 최상위 <beans> 엘리먼트의 default-autowire 속성값에 설정해서 모드를 변경할 수 있다. 하위

에서 다시 지정할 경우에는 기본 모드가 오버라이드된다.

자동 연결은 매우 강력한 기능이지만 빈 설정의 가독성을 떨어뜨리는 문제를 초래한다. 왜냐

하면 자동 연결은 런타임(runtime)에 스프링에 의해 수행되므로 빈 설정 파일에서 빈이 어떻게

생성되는지 확인하기 어렵기 때문이다. 실제로 애플리케이션에서 컴포넌트 간의 의존성이 그리

복잡하지 않은 경우에만 자동 연결을 사용할 것을 권장한다.

◈ 과제 풀이

타입에 의한 자동 연결

시퀀스 생성기 빈의 autowire 속성을 byType으로 지정하고 pre�xGenerator 프로퍼티를 지정하

지 않은 채로 두면 스프링이 Pre�xGenerator 타입에 호환되는 빈을 찾아서 연결하려고 할 것이

다. 이 예제에서는 datePre�xGenerator 빈이 자동으로 연결될 것이다.

<beans ...>

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator"

autowire="byType">

<property name="initial" value="100000" />

<property name="suffix" value="A" />

</bean>

<bean id="datePrefixGenerator"

class="com.apress.springrecipes.sequence.DatePrefixGenerator">

<property name="pattern" value="yyyyMMdd" />

</bean>

</beans>

타입에 의한 자동 연결의 가장 큰 문제점은 IoC 컨테이너에 해당 타입과 호환되는 빈이 여러

개 존재할 수 있다는 것이다. 이러한 경우 스프링에서는 해당 프로퍼티에 어떤 빈이 적합한지

어떤 빈의 프로퍼티에 적당한지 결정하지 못해서 자동 연결을 할 수 없게 된다. 다음과 같이 현

Page 79: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

01 스프링 소개 l 43

재의 연도(year)를 접두어로 사용하는 또 다른 접두어 생성기를 만들면 자동 연결이 되지 않을

것이다.

<beans ...>

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator"

autowire="byType">

<property name="initial" value="100000" />

<property name="suffix" value="A" />

</bean>

<bean id="datePrefixGenerator"

class="com.apress.springrecipes.sequence.DatePrefixGenerator">

<property name="pattern" value="yyyyMMdd" />

</bean>

<bean id="yearPrefixGenerator"

class="com.apress.springrecipes.sequence.DatePrefixGenerator">

<property name="pattern" value="yyyy" />

</bean>

</beans>

자동 연결 시 해당하는 빈을 여러 개 발견하면 스프링이 Unsatis�edDependencyException을

던질 것이다.

Exception in thread "main"

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating

bean with name 'sequenceGenerator' defined in class path resource [beans.xml]:

Unsatisfied dependency expressed through bean property 'prefixGenerator': No unique

bean of type [com.apress.springrecipes.sequence.PrefixGenerator]

is defined: expected single matching bean but found 2: [datePrefixGenerator,

yearPrefixGenerator]

이름에 의한 자동 연결

또 다른 자동 연결 모드는 byName이며, 이것은 타입에 의한 자동 연결에서 종종 발생하는 문제

를 해결할 수 있다. 동작 방식은 byType과 매우 비슷하지만 스프링이 연결을 수행할 때 적합한

타입이 아닌 프로퍼티와 이름이 같은 빈을 사용한다. 컨테이너에서 사용되는 빈 이름이 모두 유

일하다면 자동 연결에서 모호함 때문에 문제가 발생하는 경우는 없을 것이다.

<beans ...>

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator"

Page 80: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

44 l 스프링 3 레시피

autowire="byName">

<property name="initial" value="100000" />

<property name="suffix" value="A" />

</bean>

<bean id="prefixGenerator"

class="com.apress.springrecipes.sequence.DatePrefixGenerator">

<property name="pattern" value="yyyyMMdd" />

</bean>

</beans>

하지만 이름으로 자동 연결하는 방법이 모든 경우에 적합하지는 않다. 때로는 대상 빈의 이름

과 프로퍼티 이름을 동일하게 지을 수 없는 경우도 있다. 실제로 나머지 빈들은 자동 연결 방식을

사용하면서 의존 관계가 모호한 빈은 명시적으로 지정할 필요가 있다. 다시 말해, 자동 연결과

명시적 방식을 혼합해서 사용하는 것이다.

생성자에 의한 자동 연결

constructor 모드의 자동 연결은 byType과 비슷하게 동작하지만 좀 더 복잡하다. 빈의 생성자가

하나인 경우 스프링은 각 생성자 인자에 호환되는 빈을 찾아서 연결을 시도한다. 하지만 빈의 생

성자가 여러 개인 경우에는 절차가 복잡해진다. 우선 스프링이 각 생성자에 사용되는 인자와 일

치하는 빈들을 찾으려고 하며, 인자가 가장 부합하는 생성자를 선택하게 된다.

시퀀스 생성기가 기본 생성자 하나와 Pre�xGenerator를 인자로 받는 생성자를 하나 가지고

있다고 가정해보자.

package com.apress.springenterpriserecipes.sequence;

public class SequenceGenerator {

public SequenceGenerator() {}

public SequenceGenerator(PrefixGenerator prefixGenerator) {

this.prefixGenerator = prefixGenerator;

}

...

}

이 경우 두 번째 생성자가 선택될 것이다. 왜냐하면 스프링이 Pre�xGenerator와 호환되는 빈

을 찾았기 때문이다.

<beans ...>

<bean id="sequenceGenerator"

Page 81: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

01 스프링 소개 l 45

class="com.apress.springrecipes.sequence.SequenceGenerator"

autowire="constructor">

<property name="initial" value="100000" />

<property name="suffix" value="A" />

</bean>

<bean id="datePrefixGenerator"

class="com.apress.springrecipes.sequence.DatePrefixGenerator">

<property name="pattern" value="yyyyMMdd" />

</bean>

</beans>

하지만 클래스에서 여러 개의 생성자로 인해 인자 대응이 모호해지면 스프링이 생성자를 결정

하기가 어려워질 것이다. 그러므로 생성자 자동 연결 모드를 사용할 경우에는 모호성을 피할 수

있게 상당히 주의해야 한다.

자동 탐지에 의한 자동 연결

autodetect 모드의 자동 연결은 스프링이 byType과 constructor의 중간 방식으로 자동 연결을

결정하게 한다. 기본 생성자만 가지고 있는 빈을 발견하면 byType 방식이 선택될 것이다. 그렇

지 않은 경우에는 constructor 방식이 선택될 것이다. 시퀀스 생성기는 기본 생성자만 정의하고

있으므로 byType 방식이 선택될 것이다. 다시 말해 세터 메서드를 통해 접두어 생성기가 주입

될 것이다.

<beans ...>

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator"

autowire="autodetect">

<property name="initial" value="100000" />

<property name="suffix" value="A" />

</bean>

<bean id="datePrefixGenerator"

class="com.apress.springrecipes.sequence.DatePrefixGenerator">

<property name="pattern" value="yyyyMMdd" />

</bean>

</beans>

자동 연결과 의존성 검사

앞에서 봤다시피 자동 연결의 대상이 되는 빈이 여러 개 발견되면 UnsatisfiedDependency

Exception이 발생한다. 반대로, 자동 연결이 byName이나 byType 모드로 설정됐을 때 일치하는

Page 82: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

46 l 스프링 3 레시피

빈이 하나도 발견되지 않으면 프로퍼티가 설정되지 않아 NullPointerException이 발생하거나 초

기화되지 않을 것이다. 이처럼 자동 연결이 아무런 빈도 연결 하지 않은 경우에 이를 알려주려면

dependency-check 속성에 objects나 all을 사용한다.

이렇게 하면 자동 연결이 동작하지 않은 경우에 Unsatis�edDependencyException이 발생할

것이다. objects는 스프링이 동일한 빈 팩터리 내에서 협력 관계에 있는 빈을 전혀 찾지 못한 경우

에 에러를 발생시키며, all은 objects의 기능에 더해 빈의 의존성을 표시하는 단순한 프로퍼티 타

입(String 또는 기본 타입)이 전혀 설정되지 않은 경우에 에러를 일으킨다.

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator"

autowire="byName" dependency-check="objects">

<property name="initial" value="100000" />

<property name="suffix" value="A" />

</bean>

1-12. @Autowired와 @Resource로 빈 자동 연결하기

◈ 과제

빈 설정 파일에서 autowire 속성 설정을 통해 자동 연결을 사용하면 빈의 모든 프로퍼티가 연결

될 것이다. 하지만 특정한 프로퍼티만 설정할 수 있을 정도로 유연하지는 않으며, 타입이나 이름

만으로 자동 연결을 수행한다. 자동 연결만으로 요구사항을 만족시킬 수 없는 경우에는 명시적

으로 빈을 연결해야 한다.

◈ 해결책

스프링 2.5부터는 좀 더 향상된 자동 연결 기능을 제공한다. JSR-250(자바 플랫폼의 공통 애노테

이션)에 정의된 @Autowired 애노테이션이나 @Resource 애노테이션을 세터 메서드나 생성자,

필드뿐 아니라 임의의 메서드에까지 지정해서 특정 프로퍼티에 대해 자동 연결을 수행할 수 있

다. 이것은 autowire 속성을 설정하는 방법 외에도 자동 연결을 위한 또 다른 방법이 있음을 의

미한다. 하지만 이러한 애노테이션 기반의 방식은 자바 1.5 이상에서만 사용할 수 있다.

◈ 과제 풀이

@Autowired나 @Resource를 사용해 스프링에서 빈 프로퍼티를 자동 연결하려면 먼저 IoC 컨

테이너에 AutowiredAnnotationBeanPostProcessor 인스턴스를 등록해야 한다. 빈 팩터리를

Page 83: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

01 스프링 소개 l 47

사용하는 경우라면 API를 통해 빈 후 처리기(post processor)를 등록해야 한다. 그 밖의 경우에는

애플리케이션 컨텍스트에 간단히 인스턴스를 선언하기만 하면 된다.

<bean class="org.springframework.beans.factory.annotation.

AutowiredAnnotationBeanPostProcessor" />

또는 간단히 빈 설정 파일에 <context:annotation-con�g> 엘리먼트를 포함시키면 Autowired

AnnotationBeanPostProcessor 인스턴스가 자동으로 등록될 것이다.

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

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:context="http://www.springframework.org/schema/context"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context-3.0.xsd">

<context:annotation-config />

...

</beans>

적합한 타입을 가진 하나의 빈 자동 연결하기

@Autowired 애노테이션은 스프링이 특정 프로퍼티를 자동 연결할 수 있게 해준다. 가령

pre�xGenerator 프로퍼티의 세터 메서드에 @Autowired 애노테이션을 적용할 수도 있다. 그러

면 스프링이 Pre�xGenerator에 타입이 호환되는 빈을 찾아서 연결한다.

package com.apress.springrecipes.sequence;

import org.springframework.beans.factory.annotation.Autowired;

public class SequenceGenerator {

...

@Autowired

public void setPrefixGenerator(PrefixGenerator prefixGenerator) {

this.prefixGenerator = prefixGenerator;

}

}

IoC 컨테이너에 Pre�xGenerator와 타입이 일치하는 빈이 있으면 pre�xGenerator 프로퍼티

에 자동으로 설정된다.

<beans ...>

...

Page 84: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

48 l 스프링 3 레시피

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

<property name="initial" value="100000" />

<property name="suffix" value="A" />

</bean>

<bean id="datePrefixGenerator"

class="com.apress.springrecipes.sequence.DatePrefixGenerator">

<property name="pattern" value="yyyyMMdd" />

</bean>

</beans>

기본적으로 @Autowired가 지정된 프로퍼티는 모두 반드시 설정되어야 한다. 스프링이 연결

을 위해 일치하는 빈을 찾지 못하면 예외가 발생할 것이다. 프로퍼티 설정을 선택적으로 하려면

@Autowired의 required 속성을 false로 설정한다. 그러고 나면 스프링이 호환되는 빈을 찾지 못

할 경우 프로퍼티를 설정되지 않은 상태로 남겨둘 것이다.

package com.apress.springrecipes.sequence;

import org.springframework.beans.factory.annotation.Autowired;

public class SequenceGenerator {

...

@Autowired(required = false)

public void setPrefixGenerator(PrefixGenerator prefixGenerator) {

this.prefixGenerator = prefixGenerator;

}

}

@Autowired 애노테이션은 세터 메서드뿐 아니라 생성자에도 적용할 수 있다. 생성자에 적용

하면 스프링은 각 생성자 인자와 호환되는 빈을 찾으려고 할 것이다.

package com.apress.springrecipes.sequence;

import org.springframework.beans.factory.annotation.Autowired;

public class SequenceGenerator {

...

@Autowired

public SequenceGenerator(PrefixGenerator prefixGenerator) {

this.prefixGenerator = prefixGenerator;

}

}

Page 85: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

01 스프링 소개 l 49

@Autowired 애노테이션은 public으로 선언되지 않은 필드에도 적용할 수 있다. 이렇게 하면

이 필드를 사용하는 세터 메서드나 생성자에 애노테이션을 생략할 수 있다. 스프링은 리플렉션

을 사용해 일치하는 빈을 주입할 것이다. 다만 public으로 선언되지 않은 필드에 @Autowired 애

노테이션을 사용하면 단위 테스트가 어려운 코드가 되어 테스트 용이성을 떨어뜨릴 수 있다. (목

(mock) 객체를 사용하는 것처럼 상태를 다룰 수 있는 블랙박스 테스팅 방법이 없기 때문이다)

package com.apress.springrecipes.sequence;

import org.springframework.beans.factory.annotation.Autowired;

public class SequenceGenerator {

@Autowired

private PrefixGenerator prefixGenerator;

...

}

임의의 이름이나 인자를 가진 메서드에도 @Autowired 애노테이션을 적용할 수 있다. 그러면

스프링이 각 메서드의 인자와 일치하는 빈을 찾아서 연결할 것이다.

package com.apress.springrecipes.sequence;

import org.springframework.beans.factory.annotation.Autowired;

public class SequenceGenerator {

...

@Autowired

public void inject(PrefixGenerator prefixGenerator) {

this.prefixGenerator = prefixGenerator;

}

}

적합한 타입을 가진 모든 빈 자동 연결하기

@Autowired 애노테이션은 배열 형태의 프로퍼티에도 적용할 수 있으며, 스프링에서 일치하는

빈을 모두 자동 연결해 줄 것이다. 예를 들어, Pre�xGenerator[] 프로퍼티에 @Autowired 애노

테이션을 적용하면 스프링이 Pre�xGenerator와 일치하는 모든 빈을 한 번에 자동 연결해 줄 것

이다.

package com.apress.springrecipes.sequence;

import org.springframework.beans.factory.annotation.Autowired;

Page 86: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

50 l 스프링 3 레시피

public class SequenceGenerator {

@Autowired

private PrefixGenerator[] prefixGenerators;

...

}

Pre�xGenerator와 일치하는 빈이 IoC 컨테이너에 여러 개 있다면 pre�xGenerators 배열에

자동으로 추가될 것이다.

<beans ...>

...

<bean id="datePrefixGenerator"

class="com.apress.springrecipes.sequence.DatePrefixGenerator">

<property name="pattern" value="yyyyMMdd" />

</bean>

<bean id="yearPrefixGenerator"

class="com.apress.springrecipes.sequence.DatePrefixGenerator">

<property name="pattern" value="yyyy" />

</bean>

</beans>

이와 비슷한 방법으로 타입에 안전한 컬렉션에도 @Autowired 애노테이션을 적용할 수 있다.

스프링은 이 컬렉션의 타입 정보를 읽어들여 타입이 일치하는 모든 빈을 자동 연결해 줄 것이다.

package com.apress.springrecipes.sequence;

import org.springframework.beans.factory.annotation.Autowired;

public class SequenceGenerator {

@Autowired

private List<PrefixGenerator> prefixGenerators;

...

}

@Autowired 애노테이션이 문자열(string)을 키(key)로 하는, 타입에 안전한 java.util.Map에

적용됐다면 스프링은 타입과 일치하는 빈들을 이름을 키로 해서 모두 맵에 등록해줄 것이다.

package com.apress.springrecipes.sequence;

import org.springframework.beans.factory.annotation.Autowired;

Page 87: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

01 스프링 소개 l 51

public class SequenceGenerator {

@Autowired

private Map<String, PrefixGenerator> prefixGenerators;

...

}

Qualifier로 지정한 타입 자동 연결하기

기본적으로 타입에 의한 자동 연결은 IoC 컨테이너 내에 일치하는 빈이 여러 개 존재할 경우 수

행되지 않는다. 하지만 이름에 @Quali�er 애노테이션이 적용된 빈은 자동 연결의 후보로 지정

할 수 있다.

package com.apress.springrecipes.sequence;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.beans.factory.annotation.Qualifier;

public class SequenceGenerator {

@Autowired

@Qualifier("datePrefixGenerator")

private PrefixGenerator prefixGenerator;

...

}

이렇게 하면 스프링이 IoC 컨테이너에서 해당 이름을 가진 빈을 찾아서 프로퍼티에 연결할 것

이다.

<bean id="datePrefixGenerator"

class="com.apress.springrecipes.sequence.DatePrefixGenerator">

<property name="pattern" value="yyyyMMdd" />

</bean>

@Quali�er 애노테이션은 메서드 인자에도 적용할 수 있다.

package com.apress.springrecipes.sequence;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.beans.factory.annotation.Qualifier;

public class SequenceGenerator {

...

@Autowired

public void inject(

Page 88: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

52 l 스프링 3 레시피

@Qualifier("datePrefixGenerator") PrefixGenerator prefixGenerator) {

this.prefixGenerator = prefixGenerator;

}

}

자동 연결을 목적으로 커스텀 Quali�er를 작성할 수도 있다. 이러한 애노테이션 타입에는 자

체적으로 @Quali�er 애노테이션이 적용되어야 하며, 애노테이션이 필드나 세터 메서드에 적용

될 때마다 빈의 타입과 설정을 주입하고자 하는 경우에 유용하다.

package com.apress.springrecipes.sequence;

import java.lang.annotation.Target;

import java.lang.annotation.Retention;

import java.lang.annotation.ElementType;

import java.lang.annotation.RetentionPolicy;

import org.springframework.beans.factory.annotation.Qualifier;

@Retention(RetentionPolicy.RUNTIME)

@Target({ElementType.FIELD, ElementType.PARAMETER })

@Qualifier

public @interface Generator {

String value();

}

그러면 @Autowired가 사용된 빈 프로퍼티에 이 애노테이션을 적용할 수 있으며, 스프링이 이

@Quali�er 애노테이션에 지정된 값으로 자동 연결해준다.

package com.apress.springrecipes.sequence;

import org.springframework.beans.factory.annotation.Autowired;

public class SequenceGenerator {

@Autowired

@Generator("prefix")

private PrefixGenerator prefixGenerator;

...

}

자동 연결하려는 빈 프로퍼티 앞에 이 @Qualif ier를 설정해야 한다. 이 @Qualif ier는

<quali�er> 엘리먼트에 type 속성을 지정해서 추가한다. @Quali�er의 값은 value 속성에 지정

하며, value 속성은 애노테이션의 String value() 속성으로 대응된다.

Page 89: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

01 스프링 소개 l 53

<bean id="datePrefixGenerator"

class="com.apress.springrecipes.sequence.DatePrefixGenerator">

<qualifier type="Generator" value="prefix" />

<property name="pattern" value="yyyyMMdd" />

</bean>

이름에 의한 자동 연결

이름으로 빈 프로퍼티를 자동 연결하려면 JSR-250의 @Resource 애노테이션을 세터 메서드, 생

성자 또는 필드에 적용한다. 기본적으로 스프링은 해당 프로퍼티와 이름이 일치하는 빈을 찾을

것이며, name 속성을 사용해 명시적으로 빈의 이름을 지정할 수도 있다.

알아두기

JSR-250 애노테이션을 사용하려면 JSR 250 의존 라이브러리들을 포함시켜야 한다. 메이븐

을 사용 중이라면 다음을 추가한다.

<dependency>

<groupId>javax.annotation</groupId>

<artifactId>jsr250-api</artifactId>

<version>1.0</version>

</dependency>

package com.apress.springrecipes.sequence;

import javax.annotation.Resource;

public class SequenceGenerator {

@Resource(name = "datePrefixGenerator")

private PrefixGenerator prefixGenerator;

...

}

1-13. 빈 설정 상속하기

◈ 과제

스프링 IoC 컨테이너에 빈 설정을 작성할 경우 <bean> 엘리먼트의 빈 프로퍼티나 속성처럼 여

러 개의 빈에서 공유하는 공통 설정이 있을 수 있다. 종종 이러한 설정을 여러 빈들에 대해 반복

해야 할 때가 있다.

Page 90: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

54 l 스프링 3 레시피

◈ 해결책

스프링에서는 공통 빈 설정을 부모 빈(parent bean) 형태로 분리할 수 있다. 이러한 부모 빈을 상

속하는 빈을 자식 빈(child bean)이라고 한다. 자식 빈은 설정 중복을 피하기 위해 부모 빈으로부

터 <bean> 엘리먼트의 빈 프로퍼티와 속성을 포함한 빈 설정을 상속하며, 필요 시 상속받은 설

정을 오버라이드할 수도 있다.

부모 빈은 설정 템플릿의 역할을 하며 동시에 빈 인스턴스로도 사용될 수 있다. 하지만 부모 빈

을 템플릿으로만 사용하고 인스턴스는 얻을 수 없게 하려면 abstract 속성을 true로 설정해 스프

링이 빈을 인스턴스화하지 못하게 해야 한다.

부모 <bean> 엘리먼트에 정의된 모든 속성이 다 상속되지는 않는다는 점에 유의하자. 예를 들

어, autowire와 dependency-check 속성은 부모로부터 상속되지 않는다. 어떤 속성이 부모로부

터 상속되고 상속되지 않는지를 좀 더 알고 싶다면 빈 상속에 관한 스프링 문서를 참고한다.

◈ 과제 풀이

기존의 시퀀스 생성기와 초기값과 접미어가 같은 새로운 시퀀스 생성기 인스턴스를 추가해야 한

다고 가정해보자.

<beans ...>

<bean id="sequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

<property name="initial" value="100000" />

<property name="suffix" value="A" />

<property name="prefixGenerator" ref="datePrefixGenerator" />

</bean>

<bean id="sequenceGenerator1"

class="com.apress.springrecipes.sequence.SequenceGenerator">

<property name="initial" value="100000" />

<property name="suffix" value="A" />

<property name="prefixGenerator" ref="datePrefixGenerator" />

</bean>

<bean id="datePrefixGenerator"

class="com.apress.springrecipes.sequence.DatePrefixGenerator">

<property name="pattern" value="yyyyMMdd" />

</bean>

</beans>

Page 91: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

01 스프링 소개 l 55

동일한 프로퍼티의 중복 정의를 피하기 위해 이 프로퍼티들을 포함한 부모 시퀀스 생성기 빈

을 정의한다. 그리고 두 개의 시퀀스 생성기가 이 부모 생성기를 상속받아 자동으로 이러한 프로

퍼티들을 가질 수 있게 한다. 자식 빈이 부모 빈과 클래스가 동일하다면 class 속성도 지정할 필

요가 없다.

<beans ...>

<bean id="baseSequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

<property name="initial" value="100000" />

<property name="suffix" value="A" />

<property name="prefixGenerator" ref="datePrefixGenerator" />

</bean>

<bean id="sequenceGenerator" parent="baseSequenceGenerator" />

<bean id="sequenceGenerator1" parent="baseSequenceGenerator" />

...

</beans>

상속한 프로퍼티들은 자식 빈에서 오버라이드할 수 있으며, 예제와 같이 초기값을 다르게 하

는 자식 시퀀스 생성기를 작성할 수 있다.

<beans ...>

<bean id="baseSequenceGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

<property name="initial" value="100000" />

<property name="suffix" value="A" />

<property name="prefixGenerator" ref="datePrefixGenerator" />

</bean>

<bean id="sequenceGenerator2" parent="baseSequenceGenerator">

<property name="initial" value="200000" />

</bean>

...

</beans>

기본적으로 부모 시퀀스 생성기도 빈 인스턴스로 얻어서 사용할 수 있으며, 템플릿으로만 사

용하려면 abstract 속성을 true로 지정한다. 그러면 스프링이 이 빈을 인스턴스화하지 않는다.

<bean id="baseSequenceGenerator" abstract="true"

class="com.apress.springrecipes.sequence.SequenceGenerator">

...

</bean>

Page 92: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

56 l 스프링 3 레시피

특히 부모 빈과 자식 빈의 클래스 상속 구조가 같지 않고, 일부 이름이 같은 프로퍼티만 공유

할 경우에는 부모 빈의 클래스를 생략하고 자식 빈이 자체적인 클래스를 갖게 할 수도 있다. 이러

한 경우에는 부모 빈의 abstract 속성을 반드시 true로 설정해 부모 빈이 인스턴스화되지 않게 해

야 한다. 예를 들어, initial 프로퍼티가 추가된 ReverseGenerator 클래스를 만들어보자.

package com.apress.springrecipes.sequence;

public class ReverseGenerator {

private int initial;

public void setInitial(int initial) {

this.initial = initial;

}

}

SequenceGenerator와 ReverseGenerator는 동일한 부모 클래스를 상속받지 않는다(클래스

상속 구조는 같지 않지만 이름이 같은 initial 프로퍼티를 가진다). 이러한 공통 initial 프로퍼티

를 뽑아내려면 baseGenerator 부모 빈이 class 속성을 갖지 않게 정의한다.

<beans ...>

<bean id="baseGenerator" abstract="true">

<property name="initial" value="100000" />

</bean>

<bean id="baseSequenceGenerator" abstract="true" parent="baseGenerator"

class="com.apress.springrecipes.sequence.SequenceGenerator">

<property name="suffix" value="A" />

<property name="prefixGenerator" ref="datePrefixGenerator" />

</bean>

<bean id="reverseGenerator" parent="baseGenerator"

class="com.apress.springrecipes.sequence.ReverseGenerator" />

<bean id="sequenceGenerator" parent="baseSequenceGenerator" />

<bean id="sequenceGenerator1" parent="baseSequenceGenerator" />

<bean id="sequenceGenerator2" parent="baseSequenceGenerator"/>

...

</beans>

그림 1-1은 이 시퀀스 생성기 빈의 상속 구조를 객체 다이어그램으로 나타낸 것이다.

Page 93: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

01 스프링 소개 l 57

그림 1.1 | 시퀀스 생성기 빈의 상속 구조에 대한 객체 다이어그램

1-14. 클래스패스 컴포넌트 스캔하기

◈ 과제

컴포넌트를 스프링 IoC 컨테이너가 관리하게 하려면 빈 설정 파일에 일일이 선언해줘야 한다. 하

지만 직접적인 설정 대신 스프링이 자동으로 컴포넌트들을 탐지할 수 있다면 작업을 상당수 줄

일 수 있다.

◈ 해결책

스프링은 컴포넌트 스캔(component scan)이라고 하는 강력한 기능을 제공한다. 컴포넌트 스캔

은 클래스패스에서 특정한 스테레오타입(stereotype) 애노테이션들을 스캔하고 탐지한 후 해당

컴포넌트들을 자동으로 인스턴스화한다. 스프링에서 관리되는 컴포넌트를 나타내는 기본적인

애노테이션은 @Component다. 이 밖의 더 구체적인 스테레오타입은 @Repository, @Service,

@Controller가 있으며 각각 퍼시스턴스(persistence), 서비스, 프레젠테이션 레이어의 컴포넌트들

을 나타낸다.

◈ 과제 풀이

시퀀스 생성기 애플리케이션에서 데이터베이스 시퀀스(sequence)를 사용하고, 각 시퀀스 접두

어 및 접미어를 테이블에 저장한다고 가정해보자. 우선 id, prefix, suffix 프로퍼티가 포함된

Sequence 도메인 클래스를 만든다.

Page 94: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

58 l 스프링 3 레시피

package com.apress.springrecipes.sequence;

public class Sequence {

private String id;

private String prefix;

private String suffix;

// 생성자, 게터 및 세터

...

}

그러고 나서 데이터베이스로부터 데이터를 처리하는 책임을 지닌, 데이터 접근 객체(DAO,

Data Access Object)의 인터페이스를 만든다. getSequence() 메서드는 테이블에서 ID를 통해

Sequence 객체를 읽어들이며, getNextValue() 메서드는 특정 데이터베이스의 다음 시퀀스 값을

되돌려준다.

package com.apress.springrecipes.sequence;

public interface SequenceDao {

public Sequence getSequence(String sequenceId);

public int getNextValue(String sequenceId);

}

상용 애플리케이션에서는 DAO 인터페이스를 JDBC나 ORM과 같은 데이터 접근 기술로 구현

해야 하지만 지금은 테스트를 위해 맵으로 시퀀스 인스턴스와 값을 저장하겠다.

package com.apress.springrecipes.sequence;

...

public class SequenceDaoImpl implements SequenceDao {

private Map<String, Sequence> sequences;

private Map<String, Integer> values;

public SequenceDaoImpl() {

sequences = new HashMap<String, Sequence>();

sequences.put("IT", new Sequence("IT", "30", "A"));

values = new HashMap<String, Integer>();

values.put("IT", 100000);

}

public Sequence getSequence(String sequenceId) {

return sequences.get(sequenceId);

Page 95: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

01 스프링 소개 l 59

}

public synchronized int getNextValue(String sequenceId) {

int value = values.get(sequenceId);

values.put(sequenceId, value + 1);

return value;

}

}

시퀀스 생성 서비스를 제공하기 위한 파사드(façade)에 해당하는 서비스 객체도 필요하다. 내

부적으로 이 서비스 객체는 DAO를 사용해 시퀀스 생성을 요청하므로 DAO에 대한 참조도 필

요하다.

package com.apress.springrecipes.sequence;

public class SequenceService {

private SequenceDao sequenceDao;

public void setSequenceDao(SequenceDao sequenceDao) {

this.sequenceDao = sequenceDao;

}

public String generate(String sequenceId) {

Sequence sequence = sequenceDao.getSequence(sequenceId);

int value = sequenceDao.getNextValue(sequenceId);

return sequence.getPrefix() + value + sequence.getSuffix();

}

}

마지막으로 빈 설정 파일에 이 컴포넌트들을 설정해 시퀀스 생성기 애플리케이션에서 동작하

게 한다. 컴포넌트들을 자동 연결해 설정하는 데 드는 시간을 줄일 수 있다.

<beans ...>

<bean id="sequenceService"

class="com.apress.springrecipes.sequence.SequenceService"

autowire="byType" />

<bean id="sequenceDao"

class="com.apress.springrecipes.sequence.SequenceDaoImpl" />

</beans>

그런 다음 아래와 같은 Main 클래스로 앞의 컴포넌트들을 테스트할 수 있다.

Page 96: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

60 l 스프링 3 레시피

package com.apress.springrecipes.sequence;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

public static void main(String[] args) {

ApplicationContext context =

new ClassPathXmlApplicationContext("beans.xml");

SequenceService sequenceService =

(SequenceService) context.getBean("sequenceService");

System.out.println(sequenceService.generate("IT"));

System.out.println(sequenceService.generate("IT"));

}

}

컴포넌트를 자동으로 스캔하기

스프링 2.5부터 제공되는 컴포넌트 스캔 기능은 클래스패스에서 컴포넌트들을 자동으로 스

캔하여 탐지하고 인스턴스화한다. 기본적으로 스프링은 스테레오타입 애노테이션이 적용된

모든 컴포넌트들을 탐지한다. 스프링에서 관리되는 컴포넌트를 나타내는 기본 애노테이션은

@Component다. 이를 SequenceDaoImpl 클래스에 적용해보자.

package com.apress.springrecipes.sequence;

import org.springframework.stereotype.Component;

import java.util.Map;

@Component

public class SequenceDaoImpl implements SequenceDao {

...

}

또한 이 스테레오타입을 SequenceService 클래스에 적용해 스프링에서 탐지되게 한다. 그리고

DAO 필드에 @Autowired 애노테이션을 적용해 스프링에서 타입에 의해 자동 연결되게 한다. 애

노테이션을 필드에 사용한 경우라면 세터 메서드를 만들 필요가 없다.

package com.apress.springrecipes.sequence;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;

Page 97: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

01 스프링 소개 l 61

@Component

public class SequenceService {

@Autowired

private SequenceDao sequenceDao;

...

}

스테레오타입 애노테이션을 컴포넌트 클래스들에 적용했으면 스프링이 스캔할 수 있게

<context:component-scan> XML 엘리먼트를 선언하고 스캔을 위한 패키지명을 이 엘리먼트에

지정해준다. 그러면 지정된 패키지와 하위 패키지를 포함한 경로가 스캔 대상이 된다. 여러 패키

지를 스캔하게 하려면 콤마로 패키지명을 구분할 수 있다.

빈을 사용하는 데는 앞의 스테레오타입이면 충분하다. 스프링은 빈의 이름을 클래스명의 첫

번째 글자가 소문자로 시작하는 낙타 표기법으로 생성할 것이므로 다음과 같이 사용할 수 있다

(이미 애플리케이션 컨텍스트가 <context:component-scan> 엘리먼트를 포함해 인스턴스화되

어 있다고 가정한다).

SequenceService sequenceService = (SequenceService) context.getBean("sequenceService");

<context:component-scan> 엘리먼트에서는 @Autowired 애노테이션을 통해 프로퍼티들을

자동 연결하기 위해 AutowiredAnnotationBeanPostProcessor 인스턴스를 함께 등록하고 있음

을 눈여겨보자.

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

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:context="http://www.springframework.org/schema/context"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context-3.0.xsd">

<context:component-scan base-package="com.apress.springrecipes.sequence" />

</beans>

@Component 애노테이션은 일반적인 용도의 컴포넌트들을 표시하는 기본 스테레오타입

이다. 실제로 여러 레이어의 컴포넌트를 표시하는 구체적인 스테레오타입들이 존재한다. 우선

@Repository 스테레오타입은 퍼시스턴스 레이어의 DAO 컴포넌트를 나타낸다.

package com.apress.springrecipes.sequence;

import org.springframework.stereotype.Repository;

Page 98: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

62 l 스프링 3 레시피

@Repository

public class SequenceDaoImpl implements SequenceDao {

...

}

그리고 @Service 스테레오타입은 서비스 레이어의 서비스 컴포넌트를 나타낸다.

package com.apress.springrecipes.sequence;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

@Service

public class SequenceService {

@Autowired

private SequenceDao sequenceDao;

...

}

또 다른 컴포넌트 스테레오타입인 @Controller는 프레젠테이션 레이어의 컨트롤러 컴포넌트

를 나타낸다.

스캔할 컴포넌트 필터링하기

기본적으로 스프링은 @Component, @Repository, @Service, @Controller 애노테이션과

@Component를 사용해 직접 만든 커스텀 애노테이션이 적용된 클래스들을 모두 탐지한다. 추

가적으로 include/exclude 필터를 사용해 스캔 대상을 조정할 수 있다.

스프링은 네 가지 타입의 필터 표현식을 지원한다. annotation과 assignable 타입은 필터링을

위한 애노테이션 타입과 클래스/인터페이스를 지정할 수 있게 해주며, regex와 aspect 타입은 클

래스에 대응하는 정규표현식과 AspectJ 포인트컷 표현식을 지정할 수 있게 해준다. 그리고 use-

default-�lters 속성으로 기본 필터를 사용하지 않게 할 수 있다.

예를 들어, 아래의 컴포넌트 스캔에서는 Dao 또는 Service라는 단어가 포함된 클래스들을 모

두 포함하고, @Controller 애노테이션으로 지정된 클래스들을 제외한다.

<beans ...>

<context:component-scan base-package="com.apress.springrecipes.sequence">

<context:include-filter type="regex"

expression="com\.apress\.springrecipes\.sequence\..*Dao.*" />

<context:include-filter type="regex"

expression="com\.apress\.springrecipes\.sequence\..*Service.*" />

Page 99: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

01 스프링 소개 l 63

<context:exclude-filter type="annotation"

expression="org.springframework.stereotype.Controller" />

</context:component-scan>

</beans>

Dao와 Service라는 단어를 포함하는 모든 클래스들을 탐지해서 필터에 포함하도록 적용했으

므로 SequenceDaoImpl과 SequenceService 컴포넌트가 스테레오타입 애노테이션이 지정돼 있

지 않음에도 자동으로 탐지될 수 있다.

탐지된 컴포넌트에 이름 부여하기

기본적으로 스프링은 탐지된 컴포넌트의 이름을 클래스 이름의 첫 글자를 소문자로 지정해서

부여한다. 예를 들어, SequenceService 클래스의 이름은 sequenceService로 부여될 것이다. 스

테레오타입 애노테이션의 값을 사용하면 컴포넌트 이름을 명시적으로 부여할 수 있다.

package com.apress.springrecipes.sequence;

...

import org.springframework.stereotype.Service;

@Service("sequenceService")

public class SequenceService {

...

}

package com.apress.springrecipes.sequence;

import org.springframework.stereotype.Repository;

@Repository("sequenceDao")

public class SequenceDaoImpl implements SequenceDao {

...

}

BeanNameGenerator 인터페이스를 구현한 것을 <context:component-scan> 엘리먼트의

name-generator 속성에 지정해 이름 부여 정책을 직접 만들 수도 있다.

Page 100: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

64 l 스프링 3 레시피

정리이번 장에서는 스프링 IoC 컨테이너의 기본적인 빈 설정을 살펴봤다. 스프링은 여러 종류의 빈

설정을 지원하며, 그 중에서 XML 방식이 가장 간단하고 안정적이다. 스프링은 두 종류의 IoC 컨

테이너 구현체를 제공하는데, 기본 구현체는 빈 팩터리이며 좀 더 발전된 형태는 애플리케이션

컨텍스트다. 가능하다면 자원의 제약이 없는 애플리케이션 컨텍스트를 사용한다. 스프링은 빈

프로퍼티를 정의하는 데 세터 주입과 생성자 주입을 지원하며, 빈 프로퍼티로는 단순 값과 컬렉

션, 빈 레퍼런스를 사용할 수 있다.

의존성 검사와 자동 연결은 스프링이 제공하는 매우 유용한 컨테이너 기능이다. 의존성 검사

는 필요한 모든 프로퍼티가 설정됐는지 검사하며, 자동 연결은 타입, 이름, 애노테이션 기반으로

빈들을 자동으로 연결해준다. 이 두 가지 기능을 기존 방식에서는 XML 속성을 사용해서 제공

하고, 새로운 방식에서는 애노테이션과 빈 후처리기를 사용해 훨씬 유연하게 제공한다.

스프링은 공통적인 빈 설정을 부모 빈의 형태로 뽑아내는 빈 상속 기능을 제공한다. 부모 빈은

설정 템플릿으로 사용되거나 빈 인스턴스로 사용될 수 있으며, 동시에 두 가지 역할을 다 할 수

도 있다.

컬렉션은 자바 프로그래밍의 필수 요소이므로 스프링은 빈 설정 파일에서 컬렉션 설정을 손쉽

게 할 수 있게 다양한 컬렉션 태그를 제공한다. 컬렉션을 더욱 상세하게 지정하기 위해 유틸리티

스키마의 컬렉션 태그를 사용하거나 컬렉션 팩터리 빈을 사용할 수도 있으며, 여러 빈에서 공유

할 수 있는 독립적인 컬렉션 빈을 정의할 수도 있다.

마지막으로 스프링은 클래스패스에서 컴포넌트들을 자동으로 탐지할 수 있다. 기본적으로 특

정 스테레오타입의 애노테이션으로 지정된 모든 컴포넌트들을 탐지하며, 필터를 사용해 추가적

으로 컴포넌트를 포함하거나 제외할 수도 있다. 컴포넌트 스캔은 설정 작업을 상당수 줄여주는

매우 유용한 기능이다.

Page 101: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

01 스프링 소개 l 65

Page 102: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

Spri

ng R

ecip

es

Page 103: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

67

02고급 스프링 IoC 컨테이너

이번 장에서는 스프링 애플리케이션을 개발할 때 개발 효율을 높여주는 스프링 IoC 컨테이너의

여러 고급 기능들과 내부 메커니즘을 살펴보겠다. 이러한 기능은 자주 사용하는 기능은 아니지

만 종합적이고 강력한 기능을 제공하는 컨테이너에서는 꼭 필요한 기능이다.

스프링 IoC 컨테이너는 쉽게 커스터마이징하거나 확장할 수 있게 설계돼 있다. 컨테이너에서

요구하는 명세를 만족시키는 플러그인을 등록하면 컨테이너의 기본적인 기능들을 확장하고 구

성요소를 커스터마이징할 수 있다.

이번 장을 마치고 나면 스프링 IoC 컨테이너의 기능에 대부분 익숙해질 것이며, 앞으로 계속

살펴볼 스프링의 여러 주제에 대해 학습할 때도 매우 유용한 기반 지식이 될 것이다.

2-1. 스태틱 팩터리 메서드 호출을 통한 빈 생성

◈ 과제

스태틱(static) 메서드 내에서 다른 객체 인스턴스에 대한 생성 프로세스를 캡슐화하는 스태틱 팩

터리 메서드를 호출해서 스프링 IoC 컨테이너에서 빈을 생성하고 싶을 수 있다. 클라이언트에서

는 생성에 대한 세부사항을 알 필요 없이 간단히 이 메서드만 호출해 객체를 요청할 수 있다.

◈ 해결책

스프링은 factory-method 속성에서 지정한 스태틱 팩터리 메서드를 호출해 빈을 생성해 준다.

Page 104: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

68 l 스프링 3 레시피

◈ 과제 풀이

예를 들어 정의된 Product ID를 통해 Product를 생성하는 createProduct() 스태틱 팩터리 메서

드를 다음과 같이 작성할 수 있다. 이 메서드는 Product ID에 따라 어느 Product 구현 클래스를

인스턴스화할지 결정한다. ID에 해당하는 Product가 없으면 IllegalArgumentException을 발

생시킨다.

package com.apress.springrecipes.shop;

public class ProductCreator {

public static Product createProduct(String productId) {

if ("aaa".equals(productId)) {

return new Battery("AAA", 2.5);

} else if ("cdrw".equals(productId)) {

return new Disc("CD-RW", 1.5);

}

throw new IllegalArgumentException("Unknown product");

}

}

스태틱 팩터리 메서드를 사용해 빈을 생성하려면 팩터리 메서드가 포함된 클래스를 class 속

성에 지정하고 팩터리 메서드의 이름을 factory-method 속성에 지정한다. 그리고 <constructor-

arg> 엘리먼트에 메서드 인자를 전달한다.

<beans ...>

<bean id="aaa" class="com.apress.springrecipes.shop.ProductCreator"

factory-method="createProduct">

<constructor-arg value="aaa" />

</bean>

<bean id="cdrw" class="com.apress.springrecipes.shop.ProductCreator"

factory-method="createProduct">

<constructor-arg value="cdrw" />

</bean>

</beans>

팩터리 메서드에서 예외가 발생하면 스프링이 해당 예외를 BeanCreationException 예외로

감싸(wrap)준다. 앞에서 작성한 빈 설정은 아래 코드와 같은 의미다.

Product aaa = ProductCreator.createProduct("aaa");

Product cdrw = ProductCreator.createProduct("cdrw");

Page 105: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

02 고급 스프링 IoC 컨테이너 l 69

2-2. 인스턴스 팩터리 메서드 호출을 통한 빈 생성

◈ 과제

메서드 내에서 다른 객체 인스턴스에 대한 생성 프로세스를 캡슐화하는 인스턴스 팩터리 메서드

를 호출해서 스프링 IoC 컨테이너에서 빈을 생성하고 싶을 수 있다. 클라이언트에서는 생성에 대

한 세부사항을 몰라도 간단히 이 메서드를 호출해 객체를 요청할 수 있다.

◈ 해결책

스프링은 인스턴스 팩터리 메서드를 호출해 빈을 생성해 준다. 빈 인스턴스를 factory-bean 속성

에 지정하고 팩터리 메서드를 factory-method 속성에 지정한다.

◈ 과제 풀이

예를 들어, 사전에 정의된 Product들을 맵으로 저장해 구성하는 ProductCreator 클래스를 다

음과 같이 작성한다. createProduct() 인스턴스 팩터리 메서드에서 제공된 productId로 맵을 탐

색해 Product를 찾는다. ID와 일치하는 Product가 없을 경우 IllegalArgumentException 예외

가 발생한다.

package com.apress.springrecipes.shop;

...

public class ProductCreator {

private Map<String, Product> products;

public void setProducts(Map<String, Product> products) {

this.products = products;

}

public Product createProduct(String productId) {

Product product = products.get(productId);

if (product != null) {

return product;

}

throw new IllegalArgumentException("Unknown product");

}

}

Page 106: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

70 l 스프링 3 레시피

이 ProductCreator 클래스를 사용해 Product를 생성하려면 IoC 컨테이너에 인스턴스를 선

언하고 Product 맵을 구성한다. Product 맵은 인스턴스의 내부(inner) 빈 형태로 선언할 수 있

다. 인스턴스 팩터리 메서드를 통해 빈을 생성하려면 팩터리 메서드가 포함된 빈을 factory-

bean 속성에 지정하고 팩터리 메서드 이름을 factory-method 속성에 지정하면 된다. 이와 함께

<constructor-arg> 엘리먼트에 메서드 인자를 전달한다.

<beans ...>

<bean id="productCreator"

class="com.apress.springrecipes.shop.ProductCreator">

<property name="products">

<map>

<entry key="aaa">

<bean class="com.apress.springrecipes.shop.Battery">

<property name="name" value="AAA" />

<property name="price" value="2.5" />

</bean>

</entry>

<entry key="cdrw">

<bean class="com.apress.springrecipes.shop.Disc">

<property name="name" value="CD-RW" />

<property name="price" value="1.5" />

</bean>

</entry>

</map>

</property>

</bean>

<bean id="aaa" factory-bean="productCreator"

factory-method="createProduct">

<constructor-arg value="aaa" />

</bean>

<bean id="cdrw" factory-bean="productCreator"

factory-method="createProduct">

<constructor-arg value="cdrw" />

</bean>

</beans>

팩터리 메서드에서 예외가 발생하면 스프링이 해당 예외를 BeanCreationException 예외로

감싼다. 앞에서 작성한 빈 설정은 아래 코드와 같은 의미다.

ProductCreator productCreator = new ProductCreator();

productCreator.setProducts(...);

Page 107: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

02 고급 스프링 IoC 컨테이너 l 71

Product aaa = productCreator.createProduct("aaa");

Product cdrw = productCreator.createProduct("cdrw");

2-3. 스태틱 필드를 통한 빈 선언

◈ 과제

스프링 IoC 컨테이너에서 스태틱 필드를 사용해 빈을 선언하고 싶을 수 있다. 자바에서는 주로

상수 값을 스태틱 필드로 선언한다.

◈ 해결책

스태틱 필드를 사용해 빈을 선언하려면 내장 팩터리 빈인 FieldRetrievingFactoryBean을 사용

하거나 스프링 2.x 이후부터는 <util:constant> 태그를 사용할 수 있다.

◈ 과제 풀이

우선 Product 클래스에 두 개의 Product 상수를 정의한다.

package com.apress.springrecipes.shop;

public abstract class Product {

public static final Product AAA = new Battery("AAA", 2.5);

public static final Product CDRW = new Disc("CD-RW", 1.5);

...

}

스태틱 필드로 빈을 선언하기 위해 FieldRetrievingFactoryBean을 사용해 전체 필드명을

staticField 프로퍼티에 지정한다.

<beans ...>

<bean id="aaa" class="org.springframework.beans.factory.config.

FieldRetrievingFactoryBean">

<property name="staticField">

<value>com.apress.springrecipes.shop.Product.AAA</value>

</property>

</bean>

<bean id="cdrw" class="org.springframework.beans.factory.config.

FieldRetrievingFactoryBean">

<property name="staticField">

Page 108: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

72 l 스프링 3 레시피

<value>com.apress.springrecipes.shop.Product.CDRW</value>

</property>

</bean>

</beans>

앞의 빈 설정은 아래 코드와 같은 의미다.

Product aaa = com.apress.springrecipes.shop.Product.AAA;

Product cdrw = com.apress.springrecipes.shop.Product.CDRW;

필드명을 staticField 프로퍼티에 명시적으로 지정하는 대신 FieldRetrievingFactoryBean의

빈 이름으로 지정해도 된다. 이렇게 하면 설정 코드가 줄어드는 반면 빈 이름이 더 길어져 설정이

장황해진다.

<beans ...>

<bean id="com.apress.springrecipes.shop.Product.AAA"

class="org.springframework.beans.factory.config.

FieldRetrievingFactoryBean" />

<bean id="com.apress.springrecipes.shop.Product.CDRW"

class="org.springframework.beans.factory.config.

FieldRetrievingFactoryBean" />

</beans>

스프링 2.x 이후부터는 <util:constant> 태그를 사용해 스태틱 필드를 선언할 수 있다.

FieldRetrievingFactoryBean을 사용하는 방법보다 더 간편하지만 이 태그를 사용하려면 루트

<beans> 엘리먼트에 util 스키마 정의를 추가해야 한다.

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

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:util="http://www.springframework.org/schema/util"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

http://www.springframework.org/schema/util

http://www.springframework.org/schema/util/spring-util-3.0.xsd">

<util:constant id="aaa"

static-field="com.apress.springrecipes.shop.Product.AAA" />

<util:constant id="cdrw"

static-field="com.apress.springrecipes.shop.Product.CDRW" />

</beans>

Page 109: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

02 고급 스프링 IoC 컨테이너 l 73

2-4. 객체 프로퍼티를 통한 빈 선언

◈ 과제

객체 프로퍼티와 중첩(nested) 프로퍼티(프로퍼티 경로)를 사용해 스프링 IoC 컨테이너에 빈을

선언하고 싶을 수 있다.

◈ 해결책

객체 프로퍼티와 프로퍼티 경로를 사용해 빈을 선언하려면 내장 팩터리 빈인 Property

PathFactoryBean을 사용하거나 스프링 2.x의 <util:property-path> 태그를 사용한다.

◈ 과제 풀이

예를 들어 bestSeller라는 프로퍼티가 포함된 Product 타입의 ProductRanking 클래스를 작성

한다.

package com.apress.springrecipes.shop;

public class ProductRanking {

private Product bestSeller;

public Product getBestSeller() {

return bestSeller;

}

public void setBestSeller(Product bestSeller) {

this.bestSeller = bestSeller;

}

}

다음의 빈 선언에서 bestSeller 프로퍼티는 내부 빈으로 선언돼 있어 빈 이름으로는 가져올 수

없지만 productRanking 빈의 프로퍼티로는 가져올 수 있다. PropertyPathFactoryBean 팩터리

빈을 사용해 객체 프로퍼티나 프로퍼티 경로로 빈을 선언할 수 있다.

<beans ...>

<bean id="productRanking"

class="com.apress.springrecipes.shop.ProductRanking">

<property name="bestSeller">

<bean class="com.apress.springrecipes.shop.Disc">

Page 110: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

74 l 스프링 3 레시피

<property name="name" value="CD-RW" />

<property name="price" value="1.5" />

</bean>

</property>

</bean>

<bean id="bestSeller"

class="org.springframework.beans.factory.config.PropertyPathFactoryBean">

<property name="targetObject" ref="productRanking" />

<property name="propertyPath" value="bestSeller" />

</bean>

</beans>

PropertyPathFactoryBean의 propertyPath 프로퍼티에는 프로퍼티 이름뿐 아니라 프로퍼티

경로를 점(dot)으로 구분해 사용할 수도 있다. 앞의 빈 설정은 아래 코드와 같다.

Product bestSeller = productRanking.getBestSeller();

targetObject와 propertyPath 프로퍼티를 명시적으로 지정하는 것 외에도 PropertyPath

FactoryBean의 빈 이름으로 targetObject와 propertyPath를 합쳐서 지정할 수도 있다. 이렇게

하면 설정 코드가 줄어들지만 빈 이름은 더 길어져 설정이 장황해진다.

<bean id="productRanking.bestSeller"

class="org.springframework.beans.factory.config.PropertyPathFactoryBean" />

스프링 2.x이후부터는 <util:property-path> 태그를 사용해 객체 프로퍼티와 프로퍼티 경로

로 빈을 선언할 수 있다. PropertyPathFactoryBean을 사용하는 방법보다는 간단하지만 태그를

사용하려면 루트 <beans> 엘리먼트에 util 스키마 정의를 추가해줘야 한다.

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

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:util="http://www.springframework.org/schema/util"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

http://www.springframework.org/schema/util

http://www.springframework.org/schema/util/spring-util-3.0.xsd">

...

<util:property-path id="bestSeller" path="productRanking.bestSeller" />

</beans>

IoC 컨테이너에서 빈을 얻어 콘솔에 출력하는 방법으로 이 프로퍼티 경로를 테스트해 볼 수

있다.

Page 111: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

02 고급 스프링 IoC 컨테이너 l 75

package com.apress.springrecipes.shop;

...

public class Main {

public static void main(String[] args) throws Exception {

...

Product bestSeller = (Product) context.getBean("bestSeller");

System.out.println(bestSeller);

}

}

2-5. 스프링 표현 언어(SpEL) 사용하기

◈ 과제

IoC 컨테이너 설정에서 어떠한 조건이나 프로퍼티를 동적으로 평가해서 그 값을 사용하거나, 특

정 커스텀 범위(scope)에 대해 연산을 설계 시점이 아닌 런타임 시점으로 미루고 싶을 수 있다. 아

니면 단순히 애플리케이션에서 사용하기 위한 유용한 표현 언어가 필요한 경우도 있다.

◈ 해결책

스프링 3.0의 스프링 표현 언어(SpEL)는 JSF나 JSP의 통합 EL(Uni�ed EL)이나 객체 그래프 탐색

언어(OGNL, Object Graph Navigation Language)와 같은 기능들을 제공한다. SpEL은 스프링 컨테

이너의 외부에서도 쉽게 사용할 수 있는 기반구조를 제공하며, 컨테이너 내에서도 각종 설정을

더욱 쉽게 만들어준다.

◈ 과제 풀이

현재 엔터프라이즈 소프트웨어 영역에는 온갖 종류의 표현 언어가 있다. 웹워크/스트러츠 2나 태

피스트리 4에서는 OGNL을 사용하고, 최근 버전의 JSP나 JSF에서는 환경에 따라 이용 가능한

각종 표현 언어를 사용한다. JBoss 씸(Seam)에서는 JSF에 포함된 표준 표현 언어(통합 EL)의 상

위 집합 중 이용 가능한 표현 언어를 사용한다.

표현 언어들은 여러 환경에서 다양한 특성을 물려받았는데, 통합 EL을 통해 이용 가능한 것들

이 이런 환경에서 물려받은 특성이다. Spring.NET도 유사한 표현 언어를 가지고 있으며, 이에 대

한 피드백도 상당히 잘되고 있다. 생명주기의 임의 지점(특정 유효범위가 정해진 빈을 초기화하

는 것과 같은)에서 특정 표현식을 평가해야 할 필요성은 이러한 표현 언어의 품질을 제고하는 데

이바지했다.

Page 112: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

76 l 스프링 3 레시피

어떤 표현 언어는 그 자체로 스크립트 언어의 역할을 수행할 수 있을 만큼 강력한 기능을 제공

하기도 한다. SpEL도 이런 표현 언어 중 하나다. SpEL은 애노테이션부터 XML 설정에 이르기까

지 거의 모든 곳에서 사용할 수 있다. 아울러 스프링 툴 스위트에서는 SpEL과 관련한 훌륭한 자

동완성 및 탐색 기능을 제공한다.

표현 언어 문법

스프링 표현 언어는 여러 가지 문법들을 지원한다. 표 2-1에 표현 언어의 다양한 구성요소와 사

용법을 정리했다.

표 2-1. 표현 언어 문법

유형 사용법 예제

리터럴 표현 표현 언어의 사용법 중 가장 간단하며, 결국은

자바 코드를 작성하는 것과 같다. 모든 종류의

숫자를 비롯해 String 문자열도 지원한다.

2342

'Hello Spring Enterprise Recipes'

불린과 관계

연산자

자바 표준 형식에 따라 조건을 평가할 수 있다. T(java.lang.Math).random()> .5

표준 표현식 통합 EL을 사용하는 것과 동일한 방식으로 빈

의 프로퍼티들을 순회해서 반환한다. 자바 빈

형식의 명명 규칙을 사용해서 마침표를 가지고

역참조된 프로퍼티를 구분한다. 오른쪽의 예제

는 getCat().getMate().getName()와 동일한

표현이다.

cat.mate.name

클래스 표현식 T()는 인스턴스 타입이 아닌 클래스 타입을

나타낸다. 오른쪽 예제에서, 첫 번째는 java.

lang.Math에 대한 Class 인스턴스를 나타내

며(java.lang.Math.class와 동일), 두 번째는

해당 타입의 스태틱 메서드를 호출하고 있다.

결과적으로 T(java.lang.Math).random()은

java.lang.Math.random()과 같다.

T(java.lang.Math)

T(java.lang.Math).random()

배열, 리스트,

맵 참조

대괄호와 키(key)를 사용해 리스트, 배열, 맵의

인덱스를 사용할 수 있다. 리스트와 배열의 인

덱스는 숫자이며 맵의 인덱스는 객체다. 첫 번

째 예제는 네 개의 char가 포함된 java.util.List

에 1번째 인덱스를 참조하는 것을 나타내며(결

과는 ‘b’), 두 번째 예제는 맵에서 'OR’이라는

인덱스로 해당 키와 연관된 값을 구하는 것을

보여준다.

T(java.util.Arrays).asList(

'a','b','c','d')[1]

T(SpelExamplesDemo)

.MapOfStatesAndCapitals['OR']

메서드 호출 자바에서처럼 인스턴스의 메서드가 호출된다.

기본적인 JSF, JSP 표현 언어보다 향상된 기능

중 하나다.

'Hello, World'.toLowerCase()

관계 연산자 값의 동일성과 크기를 비교해 결과를 불린으로

반환한다.

23 == person.age

'fala' < 'fido'

Page 113: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

02 고급 스프링 IoC 컨테이너 l 77

유형 사용법 예제

생성자 호출 생성자로 객체를 생성할 수 있다. 예제에서는

간단히 String과 Cat 객체를 생성한다.

new String('Hello Spring

Enterprise Recipes, again!')

new Cat('Felix')

삼항 연산자 삼항 연산자(ternary operator)는 판별식의

결과 중 참인 값을 돌려준다.

T(java.lang.Math).random()>

.5 ? 'She loves me' : 'She

loves me not'

변수 SpEL에서는 변수 값을 설정하고 평가할 수 있

다. 변수는 표현식 파서의 컨텍스트에 의해 사

용할 수 있게 되며, #this와 같은 내부 변수도

존재한다. #this는 컨텍스트의 루트 객체를 참

조한다.

#this.firstName

#customer.email

컬렉션 프로젝

SpEL의 매우 강력한 기능으로, 맵과 컬렉션

에 대한 정교한 조작 기능을 제공한다. 예제는

cats 리스트에 대한 프로젝션(projection)을

생성하며, name 프로퍼티에 값을 가지는 엘리

먼트들을 순회해서 결과를 컬렉션 타입으로 반

환한다. cats는 Cat 객체의 컬렉션이며, 결과

값으로 String 객체의 컬렉션을 반환한다.

cats.![name]

컬렉션 셀렉션 셀렉션(selection)은 컬렉션이나 맵의 각 항목

을 평가해서 평가 수식에 부합하는 엘리먼트

들을 동적으로 필터링해 컬렉션 형태로 반환

한다. 예제에서는 맵의 각 엔트리에 있는 java.

util.Map.Entry.value 프로퍼티 값(이 경우

String)이 소문자 “s”로 시작하는 경우만 남기

고 나머지는 버린다.

mapOfStatesAndCapitals.?

[value.toLowerCase().

startsWith('s')]

템플릿 표현식 문자열 표현의 표현식을 평가해 결과를 반환한

다. 예제에서는 삼항 표현식을 평가해 결과에

따라 ‘good’이나 ‘bad’가 포함된 결과를 생성

한다.

Your fortune is ${T(java

.lang.Math).random()> .5 ?

'good' : 'bad'}

설정에서 표현 언어 사용하기

표현 언어는 XML이나 애노테이션에서 모두 사용할 수 있다. 표현식은 컨텍스트의 초기화 시

점이 아닌 빈 생성 시점에 평가된다. 그러므로 임의의 범위에서 생성된 빈은 적절한 범위에 속

하기 전까지는 구성되지 않는다. 이러한 표현식은 XML이나 애노테이션에서 동일하게 사용할

수 있다.

첫 번째 예제는 systemProperties라는 표현 언어 변수를 주입하고 있으며, systemProperties

는 System.getProperties()로 이용할 수 있는 java.util.Properties 인스턴스를 나타내는 특별한

변수다. 두 번째 예제는 시스템 프로퍼티 자체를 String 변수로 직접 주입하는 것을 보여준다.

@Value("#{ systemProperties }")

private Properties systemProperties;

Page 114: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

78 l 스프링 3 레시피

@Value("#{ systemProperties['user.region'] }")

private String userRegion;

계산된 결과나 메서드를 호출한 결과도 주입할 수 있다. 다음 예제에서는 계산된 결과를 직접

변수에 주입한다.

@Value("#{ T(java.lang.Math).random() * 100.0 }")

private double randomNumber;

다음 예제는 컨텍스트에 emailUtilities라는 또 다른 빈이 구성돼 있다고 가정하고, 그 빈을 자

바 빈 형식의 프로퍼티로 필드에 주입한다.

@Value("#{ emailUtilities.email }")

private String email;

@Value("#{ emailUtilities.password }")

private String password;

@Value("#{ emailUtilities.host}")

private String host;

표현 언어를 사용해 동일한 컨텍스트 내의 다른 빈의 이름을 지정하는 식으로 참조를 주입할

수도 있다.

@Value("#{ emailUtilities }")

private EmailUtilities emailUtilities;

이 경우 컨텍스트에 EmailUtilities 인터페이스 빈이 하나만 있다면 다음과 같이 할 수도 있다.

@Autowired

private EmailUtilities emailUtilities;

동일한 인터페이스의 빈들을 구분하는 데는 여러 메커니즘이 있지만 표현 언어는 단순히 빈

ID만으로 구분한다.

XML 설정에서도 애노테이션과 동일한 방식으로 표현 언어를 사용할 수 있으며, 두 경우 모두

접두어와 접미어인 #{와 }까지도 같다.

<bean class="com.apress.springrecipes.spring3. spel.EmailNotificationEngine"

p:randomNumber="#{ T(java.lang.Math).random() * 100.0 }"

...

/>

Page 115: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

02 고급 스프링 IoC 컨테이너 l 79

스프링 표현 언어 파서 사용하기

SpEL은 주로 스프링 프레임워크에서 제공하는 XML 설정과 애노테이션에서 사용되지만 표현

언어 자체는 사용하는 데 아무런 제약이 없다. 표현 언어 핵심적인 기능은 표현 언어 파서인 org.

springframework.expression.spel.antlr.SpelAntlrExpressionParser에서 제공하며, 아래처럼

직접 인스턴스를 생성할 수 있다.

ExpressionParser parser = new SpelAntlrExpressionParser();

쉽게 짐작할 수 있듯이 ExpressionParser 인터페이스의 구현체를 작성하거나 이 API를 사용

하는 자체적인 인티그레이션을 작성할 수도 있다. SpEL을 사용해 작성된 표현식을 연산하는 중

심에는 ExpressionParser 인터페이스가 있으며, 가장 단순한 평가는 다음과 같은 식으로 이뤄

진다.

Expression exp = parser.parseExpression("'ceci n''est pas une String'" );

String val = exp.getValue(String.class);

예제에서는 String 문자열을 평가해서 결과를 반환하며, 작은 따옴표를 역슬래시가 아닌 작은

따옴표를 하나 더 붙여서 이스케이프 처리하고 있다. getValue() 호출은 매개변수 타입에 기반을

둔 제네릭 형식이므로 형변환할 필요가 없다.

객체에 대한 표현식을 평가하는 것은 흔히 하게 되는 작업 중 하나다. 객체의 프로퍼티와 메서

드에는 인스턴스나 클래스가 직접 필요하지 않으므로 독립적으로 다룰 수 있으며, SpEL 파서는

해당 객체를 루트 객체로 참조한다. 이를테면, SocialNetworkingSiteContext라는 객체는 사이

트의 회원을 순회하고자 할 때 활용할 수 있는 속성들을 포함하고 있다.

SocialNetworkingSiteContext socialNetworkingSiteContext =

new SocialNetworkingSiteContext();

// ... 속성이 초기화됐는지 확인한다 ...

Expression firstNameExpression = parser.parseExpression("loggedInUser.firstName");

StandardEvaluationContext ctx = new StandardEvaluationContext();

ctx.setRootObject(socialNetworkingSiteContext);

String valueOfLoggedInUserFirstName = firstNameExpression.getValue(ctx, String.class);

socialNetworkingSiteContext를 루트로 설정했기 때문에 전체 참조 이름을 지정하지 않고도

자식 프로퍼티들을 열거할 수 있다.

루트 객체를 지정하는 대신 변수 이름을 지정하고 표현식에서 해당 변수에 접근한다고 하

면 SpEL 파서가 표현식에 해당하는 변수 값을 평가해서 제공한다. 다음 예제에서는 social

NetworkingSiteContext 변수를 사용해 값을 제공하며, 표현식 내에서 변수 앞에는 “#”가 붙는다.

Page 116: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

80 l 스프링 3 레시피

StandardEvaluationContext ctx1 = new StandardEvaluationContext();

SocialNetworkingSiteContext socialNetworkingSiteContext =

new SocialNetworkingSiteContext();

Friend myFriend = new Friend() ;

myFriend.setFirstName("Manuel");

socialNetworkingSiteContext.setLoggedInUser(myFriend);

ctx1.setVariable("socialNetworkingSiteContext",socialNetworkingSiteContext);

Expression loggedInUserFirstNameExpression =

parser.parseExpression("#socialNetworkingSiteContext.loggedInUser.firstName");

String loggedInUserFirstName = loggedInUserFirstNameExpression.getValue

(ctx1, String.class);

이와 비슷하게 표현식 안에서 전체 참조 이름을 지정하지 않고도 네임드 함수(named function)

를 이용할 수도 있다.

StandardEvaluationContext ctx1 = new StandardEvaluationContext();

ctx1.registerFunction("empty", StringUtils.class.getDeclaredMethod(

"isEmpty", new Class[] { String.class }));

Expression functionEval = parser.parseExpression(

" #empty(null) ? 'empty' : 'not empty' ");

String result = functionEval.getValue(ctx1, String.class);

표현 언어 파서의 기반구조를 활용하면 String을 템플릿화하는 것도 가능하다. 결과값은

String이지만, String 내의 표현식의 평가 결과를 파서가 대체하게 할 수 있다. 이것은 단순

히 메시지를 준비하는 경우를 비롯해 여러 상황에서 매우 유용하다. org.springframework.

expression.ParserContext 인스턴스를 생성하고, 파서에게 접두어 토큰은 "${"이고 접미어 토큰

은 "}"임을 알려준다. 다음 예제는 "�e millisecond is 1246953975093"을 결과로 반환한다.

ParserContext pc = new ParserContext() {

public String getExpressionPrefix() {

return "${";

}

public String getExpressionSuffix() {

return "}";

}

public boolean isTemplate() {

return true;

}

};

String templatedExample = parser.parseExpression(

"The millisecond is ${ T(System).currentTimeMillis() }.", pc).getValue(String.class);

Page 117: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

02 고급 스프링 IoC 컨테이너 l 81

2-6. 빈 범위 설정

◈ 과제

설정 파일에서 빈을 선언하면 실제로는 빈 인스턴스를 정의하는 게 아니라 빈 생성을 위한 틀

(template)을 정의하는 것이다. getBean() 메서드나 다른 빈의 참조로 빈을 요청하면 스프링이 빈

범위에 따라 어느 빈 인스턴스가 반환돼야 할지 결정한다. 때로는 빈에 기본 범위가 아닌 적절한

범위를 설정해야 할 때도 있다.

◈ 해결책

스프링 2.x 이후부터는 빈 범위를 <bean> 엘리먼트의 scope 속성으로 설정한다. 기본적으로 스

프링은 선언된 각 빈에 대해 IoC 컨테이너상에서 하나의 인스턴스만 생성해 전체 IoC 컨테이너

의 범위에서 공유하며, getBean()을 호출하거나 빈이 참조될 때 해당 빈 인스턴스를 반환한다. 이

러한 범위를 싱글턴(singleton)이라고 하며 모든 빈의 기본 범위에 해당한다. 표 2-2에는 스프링

에서 사용 가능한 모든 빈 범위가 정리돼 있다.

표 2-2. 스프링에서 사용 가능한 빈 범위

범위 설명

싱글턴(Singleton) 스프링 IoC 컨테이너당 하나의 빈 인스턴스를 생성한다

프로토타입(Prototype) 요청할 때마다 매번 새로운 빈 인스턴스를 생성한다

요청(Request) HTTP 요청(request)당 하나의 빈 인스턴스를 생성한다. 웹 애플리케이션 컨텍스

트에서만 사용 가능하다.

세션(Session) HTTP 세션(session)당 하나의 빈 인스턴스를 생성한다. 웹 애플리케이션 컨텍스

트에서만 사용 가능하다.

전역 세션

(GlobalSession)

전체 HTTP 세션당 하나의 빈 인스턴스를 생성한다. 포털 애플리케이션의 컨텍스

트에서만 사용 가능하다.

스프링 1.x에서는 빈 범위로 싱글턴과 프로토타입만 사용할 수 있었으며, singleton="true" 또

는 singleton="false"와 같이 scope 속성 대신 singleton 속성으로 구분했다.

◈ 과제 풀이

빈 범위의 개념을 설명하기 위해 쇼핑몰 애플리케이션의 장바구니를 예로 들겠다. 우선 다음과

같이 ShoppingCart 클래스를 작성한다.

package com.apress.springrecipes.shop;

...

Page 118: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

82 l 스프링 3 레시피

public class ShoppingCart {

private List<Product> items = new ArrayList<Product>();

public void addItem(Product item) {

items.add(item);

}

public List<Product> getItems() {

return items;

}

}

그리고 IoC 컨테이너에 몇 개의 Product 빈과 ShoppingCart 빈을 선언한다.

<beans ...>

<bean id="aaa" class="com.apress.springrecipes.shop.Battery">

<property name="name" value="AAA" />

<property name="price" value="2.5" />

</bean>

<bean id="cdrw" class="com.apress.springrecipes.shop.Disc">

<property name="name" value="CD-RW" />

<property name="price" value="1.5" />

</bean>

<bean id="dvdrw" class="com.apress.springrecipes.shop.Disc">

<property name="name" value="DVD-RW" />

<property name="price" value="3.0" />

</bean>

<bean id="shoppingCart" class="com.apress.springrecipes.shop.ShoppingCart" />

</beans>

장바구니에 Product가 추가되는지를 다음의 Main 클래스로 테스트할 수 있다. 두 명의 고객

이 동시에 쇼핑 중이라고 가정한다. 첫 번째 고객이 getBean() 메서드를 호출해 장바구니를 가져

와서 두 개의 Product를 담는다. 그리고 두 번째 고객이 마찬가지로 getBean() 메서드로 장바구

니를 가져와서 또 다른 Product를 담는다.

package com.apress.springrecipes.shop;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

Page 119: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

02 고급 스프링 IoC 컨테이너 l 83

public class Main {

public static void main(String[] args) {

ApplicationContext context =

new ClassPathXmlApplicationContext("beans.xml");

Product aaa = (Product) context.getBean("aaa");

Product cdrw = (Product) context.getBean("cdrw");

Product dvdrw = (Product) context.getBean("dvdrw");

ShoppingCart cart1 = (ShoppingCart) context.getBean("shoppingCart");

cart1.addItem(aaa);

cart1.addItem(cdrw);

System.out.println("Shopping cart 1 contains " + cart1.getItems());

ShoppingCart cart2 = (ShoppingCart) context.getBean("shoppingCart");

cart2.addItem(dvdrw);

System.out.println("Shopping cart 2 contains " + cart2.getItems());

}

}

앞에서 작성한 빈 정의에서는 두 명의 고객이 동일한 장바구니 인스턴스를 얻게 되는 것을 볼

수 있다.

Shopping cart 1 contains [AAA 2.5, CD-RW 1.5]

Shopping cart 2 contains [AAA 2.5, CD-RW 1.5, DVD-RW 3.0]

이는 스프링의 기본 빈 범위가 싱글턴이라서 IoC 컨테이너당 하나의 장바구니 인스턴스가 생

성되기 때문이다.

<bean id="shoppingCart"

class="com.apress.springrecipes.shop.ShoppingCart"

scope="singleton" />

쇼핑몰 애플리케이션에서는 각 고객이 getBean() 메서드를 호출해서 장바구니 인스턴스

를 요청할 때 서로 다른 인스턴스가 반환돼야 한다. 이 예제에서 shoppingCart 빈의 범위를

prototype으로 변경하면 getBean() 메서드를 호출하거나 다른 빈에 의해 shoppingCart 빈이 참

조될 때마다 스프링에서 새로운 빈 인스턴스를 생성한다.

<bean id="shoppingCart"

class="com.apress.springrecipes.shop.ShoppingCart"

scope="prototype" />

Page 120: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

84 l 스프링 3 레시피

이제 Main 클래스를 다시 실행하면 두 고객이 서로 다른 장바구니 인스턴스를 얻는 것을 볼

수 있다.

Shopping cart 1 contains [AAA 2.5, CD-RW 1.5]

Shopping cart 2 contains [DVD-RW 3.0]

2-7. 빈 초기화 및 소멸 과정 재정의

◈ 과제

실무에서 사용되는 컴포넌트들은 사용하기 전에 갖가지 초기화 작업을 수행한다. 이와 같은 초

기화 작업으로 파일이나 네트워크/데이터베이스에 연결하거나 메모리를 할당하는 등의 일을 수

행한다. 컴포넌트가 생명주기를 마칠 때는 여기에 상응하는 소멸 작업을 수행해야 한다. 따라서

스프링 IoC 컨테이너의 빈 초기화 및 소멸 작업을 커스터마이징하고 싶을 수 있다.

◈ 해결책

스프링 IoC 컨테이너는 빈 등록뿐 아니라 생명주기를 관리하는 역할도 하며, 생명주기상의 특정

지점에서 임의의 작업을 수행할 수 있게 해준다. 임의 작업은 콜백 메서드로 캡슐화되어 스프링

IoC 컨테이너에 의해 적절한 시점에 호출된다. 다음은 스프링 IoC 컨테이너가 빈의 생명주기를

관리하는 절차를 단계별로 나타낸 것이며, IoC 컨테이너의 기능이 추가될 때마다 절차가 늘어날

것이다.

1. 생성자나 팩터리 메서드를 사용해 빈 인스턴스를 생성한다.

2. 빈 프로퍼티에 값과 빈 레퍼런스를 설정한다.

3. 초기화 콜백 메서드를 호출한다.

4. 빈이 사용될 준비를 마친다.

5. 컨테이너가 종료될 때 소멸 콜백 메서드를 호출한다.

스프링이 초기화 콜백 메서드와 소멸 콜백 메서드를 인식하게 하는 방법으로는 세 가지가 있

다. 첫 번째는 InitializingBean과 DisposableBean 생명주기 인터페이스를 구현하고 초기화

와 소멸 작업을 위한 a�erPropertiesSet()과 destroy() 메서드를 구현하는 것이다. 두 번째는 빈

선언에 init-method와 destroy-method 속성을 설정해 콜백 메서드의 이름을 지정하는 것이

Page 121: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

02 고급 스프링 IoC 컨테이너 l 85

다. 스프링 2.5 이후부터는 JSR-250(Common Annotations for the Java Platform)에 정의돼 있는

@PostConstruct와 @PreDestroy 생명주기 애노테이션을 사용해 초기화 및 소멸 콜백 메서드

를 적용할 수 있다. 이 콜백 메서드들을 호출하려면 IoC 컨테이너에 CommonAnnotationBean

PostProcessor 인스턴스가 등록돼 있어야 한다.

◈ 과제 풀이

스프링 IoC 컨테이너에서 빈의 생명주기가 어떻게 관리되는지 이해하기 위해 계산(checkout) 기

능과 관련된 예제를 살펴보자. 아래의 Cashier 클래스는 장바구니의 Product들을 계산하는 데

사용되며, 계산할 때마다 시각과 수량을 텍스트 파일에 기록한다.

package com.apress.springrecipes.shop;

...

public class Cashier {

private String name;

private String path;

private BufferedWriter writer;

public void setName(String name) {

this.name = name;

}

public void setPath(String path) {

this.path = path;

}

public void openFile() throws IOException {

File logFile = new File(path, name + ".txt");

writer = new BufferedWriter(new OutputStreamWriter(

new FileOutputStream(logFile, true)));

}

public void checkout(ShoppingCart cart) throws IOException {

double total = 0;

for (Product product : cart.getItems()) {

total += product.getPrice();

}

writer.write(new Date() + "\t" + total + "\r\n");

writer.flush();

}

public void closeFile() throws IOException {

Page 122: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

86 l 스프링 3 레시피

writer.close();

}

}

Cashier 클래스의 openFile() 메서드는 지정된 시스템 경로에서 Cashier와 이름이 같은 텍

스트 파일을 연다. checkout() 메서드를 호출할 때마다 계산 기록을 텍스트 파일에 추가하고,

마지막에 closeFile() 메서드로 파일을 닫아서 시스템 자원을 반납한다. 따라서 casher1이라는

Cashier 빈을 IoC 컨테이너에 선언하면 이 Cashier의 계산 기록은 c:/cashier/cashier1.txt 파일

에 기록된다. 해당 디렉터리를 사전에 만들어두거나 이미 존재하는 다른 디렉터리를 지정할 수

도 있다.

<beans ...>

...

<bean id="cashier1" class="com.apress.springrecipes.shop.Cashier">

<property name="name" value="cashier1" />

<property name="path" value="c:/cashier" />

</bean>

</beans>

하지만 Main 클래스에서 이 Cashier를 사용해 장바구니를 계산하려고 하면 NullPointer

Exception이 발생할 것이다. 이는 openFile() 메서드를 사전에 호출하는 초기화 작업을 하지 않

았기 때문이다.

package com.apress.springrecipes.shop;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.FileSystemXmlApplicationContext;

public class Main {

public static void main(String[] args) throws Exception {

ApplicationContext context =

new FileSystemXmlApplicationContext("beans.xml");

Cashier cashier1 = (Cashier) context.getBean("cashier1");

cashier1.checkout(cart1);

}

}

Main 클래스에서 초기화 작업으로 openFile() 메서드를 호출하려면 어떻게 해야 할까? 자

바에서는 생성자에서 초기화 작업을 수행한다. 하지만 이 Cashier 클래스의 기본 생성자에서

openFile() 메서드를 호출할 수 있을까? 그렇게 할 수 없다. openFile() 메서드가 열 파일을 결정

하려면 사전에 name과 path 프로퍼티가 설정돼 있어야 하므로 이 방식은 불가능하다.

Page 123: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

02 고급 스프링 IoC 컨테이너 l 87

package com.apress.springrecipes.shop;

...

public class Cashier {

...

public void openFile() throws IOException {

File logFile = new File(path, name + ".txt");

writer = new BufferedWriter(new OutputStreamWriter(

new FileOutputStream(logFile, true)));

}

}

기본 생성자가 호출될 때 이 프로퍼티들은 아직 설정되지 않은 상태이므로 프로퍼티를 인자로

받아들일 수 있는 생성자를 추가하고, 생성자가 끝나는 지점에서 openFile() 메서드를 호출해야

한다. 하지만 그렇게 할 수 없거나 세터 주입을 통한 프로퍼티 설정을 선호할 수도 있다. 실제로

openFile() 메서드를 호출하기에 적절한 시점은 스프링 IoC 컨테이너가 프로퍼티를 모두 설정하

고 난 후다.

InitializingBean과 DisposableBean 인터페이스 구현

스프링은 InitializingBean과 DisposableBean 인터페이스를 구현한 빈에 대해 a�erProperties

Set()과 destroy() 콜백 메서드를 호출해 초기화 및 소멸 작업을 수행한다. 스프링은 빈 생성 과

정에서 빈이 이 인터페이스를 구현한 것을 알아차리고 적절한 시점에 콜백 메서드를 호출한다.

package com.apress.springrecipes.shop;

...

import org.springframework.beans.factory.DisposableBean;

import org.springframework.beans.factory.InitializingBean;

public class Cashier implements InitializingBean, DisposableBean {

...

public void afterPropertiesSet() throws Exception {

openFile();

}

public void destroy() throws Exception {

closeFile();

}

}

이제 Main 클래스를 다시 실행해 보면 계산 기록이 c:/cashier/cashier1.txt 텍스트 파일에 추

가되는 것을 확인할 수 있다. 하지만 이와 같이 스프링에 종속된 인터페이스를 구현하면 빈이 스

프링에 종속되므로 스프링 IoC 컨테이너 외부에서 재사용하지 못한다.

Page 124: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

88 l 스프링 3 레시피

init-method와 destroy-method 속성 설정

초기화와 소멸 콜백 메서드를 호출하는 좀 더 나은 방법은 빈 선언에 init-method와 destroy-

method 속성을 설정하는 것이다.

<bean id="cashier1" class="com.apress.springrecipes.shop.Cashier"

init-method="openFile" destroy-method="closeFile">

<property name="name" value="cashier1" />

<property name="path" value="c:/cashier" />

</bean>

빈 선언에 두 속성을 지정하면 Cashier 클래스는 InitializingBean과 DisposableBean 인터페

이스를 구현하지 않아도 되고 a�erPropertiesSet()과 destroy() 메서드도 제거할 수 있다.

@PostConstruct와 @PreDestroy 애노테이션 적용

스프링 2.5부터는 JSR-250의 생명주기 애노테이션인 @PostConstruct와 @PreDestroy를 적용

해 초기화 및 소멸 콜백 메서드를 호출할 수 있다.

알아두기

JSR-250 애노테이션을 사용하려면 JSR-250 의존 라이브러리를 추가해야 한다. 메이븐을

사용한다면 다음을 추가한다.

<dependency>

<groupId>javax.annotation</groupId>

<artifactId>jsr250-api</artifactId>

<version>1.0</version>

</dependency>

package com.apress.springrecipes.shop;

...

import javax.annotation.PostConstruct;

import javax.annotation.PreDestroy;

public class Cashier {

...

@PostConstruct

public void openFile() throws IOException {

File logFile = new File(path, name + ".txt");

writer = new BufferedWriter(new OutputStreamWriter(

new FileOutputStream(logFile, true)));

}

Page 125: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

02 고급 스프링 IoC 컨테이너 l 89

@PreDestroy

public void closeFile() throws IOException {

writer.close();

}

}

그리고 CommonAnnotationBeanPostProcessor 인스턴스를 IoC 컨테이너에 추가하면 생명

주기 애노테이션에 의해 초기화 및 소멸 콜백 메서드가 호출된다. 이렇게 하면 빈에 init-method

와 destroy-method 속성을 지정하지 않아도 된다.

<beans ...>

...

<bean class="org.springframework.context.annotation.

CommonAnnotationBeanPostProcessor" />

<bean id="cashier1" class="com.apress.springrecipes.shop.Cashier">

<property name="name" value="cashier1" />

<property name="path" value="c:/cashier" />

</bean>

</beans>

빈 설정 파일에 간단히 <context:annotation-con�g> 엘리먼트를 추가하면 CommonAnno

tationBeanPostProcessor 인스턴스가 자동으로 등록된다. 하지만 이 태그를 사용하려면 루트

<beans> 엘리먼트에 context 스키마를 추가해야 한다.

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

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:context="http://www.springframework.org/schema/context"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context-3.0.xsd">

<context:annotation-config />

...

</beans>

2-8. Java Config를 활용한 XML 설정 단축

◈ 과제

DI 컨테이너가 주는 이점을 그대로 활용하면서 설정의 일부를 오버라이드하거나 XML 형식으

로 돼 있던 설정을 리팩터링과 타입 검사가 가능한 자바로 옮기고 싶을 수 있다.

Page 126: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

90 l 스프링 3 레시피

◈ 해결책

Java Con�g를 사용해 과제를 해결할 수 있다. Java Con�g는 구글 주스(Google Guice)가 알려

지기 훨씬 전인 2005년 상반기에 인큐베이션 프로젝트로 시작해 현재는 코어 프레임워크에 포함

됐다.

◈ 과제 풀이

Java Con�g 지원 기능은 매우 강력하며, XML이나 애노테이션을 사용하는 다른 설정 방법과는

근본적으로 다른 방식을 취한다. 특히 Java Con�g는 기존의 방식과도 병행해 사용할 수 있다.

Java Con�g를 사용하는 가장 간단한 방법은 보통의 XML 설정 파일과 함께 사용하는 것이다.

그렇게 하면 스프링이 나머지 부분을 담당해준다.

ClassPathXmlApplicationContext classPathXmlApplicationContext =

new ClassPathXmlApplicationContext("myApplicationContext.xml");

이 파일에 대한 설정은 예상하는 바와 같다.

...

<context:annotation-config />

<context:component-scan base-package="com.my.base.package" />

...

이렇게 설정하면 스프링이 @Configuration 애노테이션이 적용된 클래스를 찾는다. @Con

�guration은 @Component를 따르는 애노테이션이므로 애노테이션 지원 기능으로 이용할 수

있으며, @Autowired를 사용해 빈을 주입하는 등의 기능도 사용할 수 있다. @Configuration

애노테이션을 적용한 클래스를 작성하면 스프링이 애노테이션이 적용된 클래스 내에서 빈 정

의를 찾을 것이다(빈 정의는 @Bean 애노테이션이 적용된 자바 메서드다). 이러한 빈 정의는

ApplicationContext에 제공되어 빈 이름을 사용하는 메서드에 설정되거나 빈 이름을 @Bean

애노테이션에 명시적으로 지정할 수도 있다. 하나의 빈 정의를 가진 설정 클래스는 다음과 같다.

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

@Configuration

public class PersonConfiguration {

@Bean

public Person josh() {

Person josh = new Person();

josh.setName("Josh");

return josh;

Page 127: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

02 고급 스프링 IoC 컨테이너 l 91

}

}

이것은 아래의 XML 애플리케이션 컨텍스트와 동일하다.

<bean id="josh" class="com.apress.springrecipes.

spring3.javaconfig.Person" p:name="Josh" />

이렇게 생성한 빈은 스프링 애플리케이션 컨텍스트에서 일반 빈과 동일하게 사용할 수 있다.

ApplicationContext context = ... ;

Person person = context.getBean("josh", Person.class);

빈에 ID를 지정하려면 @Bean 정의의 id 속성을 사용한다.

@Bean(name="theArtistFormerlyKnownAsJosh")

public Person josh() {

// ...

}

그리고 빈을 다음과 같이 사용할 수도 있다.

ApplicationContext context = ... ;

Person person = context.getBean("theArtistFormerlyKnownAsJosh", Person.class);

‘코드가 다섯 줄이나 늘었는데 대체 뭐가 좋아진 거지?’라고 생각할 수도 있다. 하지만 이렇게

하면 자바 본래의 가독성을 유지하면서 컴파일 과정을 통해 설정이 제대로 됐는지 확인할 수 있

다. XML 방식은 이러한 이점들을 제공하지 못했다.

생명주기 메서드를 지정할 때도 선택의 여지가 많다. 과거 스프링에서는 생명주기 메서드들을

InitializingBean과 DisposableBean 같은 특정 인터페이스의 콜백 메서드를 구현하고, 의존성

이 주입되고 난 직후 호출되거나 컨텍스트에서 제거되어 소멸될 때 각각 호출됐다. 하지만 XML

설정에서 빈의 init-method와 destroy-method 속성을 사용해 초기화 및 소멸 메서드를 직접 지

정할 수도 있다. 스프링 2.5 이후부터는 JSR-250 애노테이션들을 사용해 초기화 및 소멸 메서드

를 지정할 수 있다. Java Con�g에서도 이러한 옵션이 있다.

@Bean 애노테이션을 사용해 생명주기 메서드를 지정하거나 아니면 직접 메서드를 호출할 수

있다. 다음과 같이 initMethod와 destroyMethod 속성을 사용하는 첫 번째 방법은 좀 더 직관적

이다.

@Bean( initMethod = "startLife", destroyMethod = "die")

public Person companyLawyer() {

Person companyLawyer = new Person();

Page 128: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

92 l 스프링 3 레시피

companyLawyer.setName("Alan Crane");

return companyLawyer;

}

초기화를 직접 다루는 것도 어렵지 않다.

@Bean

public Person companyLawyer() {

Person companyLawyer = new Person();

companyLawyer.startLife() ;

companyLawyer.setName("Alan Crane");

return companyLawyer;

}

다른 빈을 참조하는 것도 비슷하며 매우 간단하다.

@Configuration

public class PetConfiguration {

@Bean

public Cat cat(){

return new Cat();

}

@Bean

public Person master(){

Person person = new Person() ;

person.setPet( cat() );

return person;

}

// ...

}

이보다 더 쉬울 수는 없을 것이다. 이제 다른 빈에 대한 참조가 필요하면 자바 애플리케이션에

서처럼 다른 빈의 참조를 얻기만 하면 된다. 그러면 스프링에서 빈이 한 번만 생성되고 관련 범위

가 적용된다.

XML 설정에서 이용 가능한 전반적인 빈 설정 옵션들도 Java Con�g에서 사용할 수 있다.

@Lazy, @Primary, @DependsOn 애노테이션은 XML에서 같은 역할을 하는 설정과 동일하

게 동작한다. @Lazy는 의존성을 모두 구비해야 하거나 애플리케이션 컨텍스트에서 명시적으로

빈을 호출할 때까지 빈 생성을 미룬다. @DependsOn은 어떤 빈의 존재가 다른 빈을 생성하는

데 중요하다면 반드시 해당 빈을 지정한 다른 빈이 생성된 이후에 생성되게 한다. @Primary는

동일한 인터페이스를 구현하는 빈이 여러 개 존재할 때 빈 정의에 이 애노테이션을 가진 하나의

빈을 반환하는 데 사용되며, 컨테이너가 빈의 이름을 통해 접근할 경우에는 필요 없다.

Page 129: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

02 고급 스프링 IoC 컨테이너 l 93

이 애노테이션들도 다른 여느 애노테이션처럼 적용할 빈 설정 메서드 위에 지정하면 된다. 예

제를 보자.

@Bean @Lazy

public NetworkFileProcessor fileProcessor(){ ... }

유지보수성과 재사용성을 높이기 위해 빈 설정을 여러 개의 Con�guration 클래스로 나눌 때

가 있는데, 이 점을 미리 고려해 스프링에서는 다른 빈을 임포트할 수 있게 해준다. XML에서는

임포트 엘리먼트(<import resource="someOtherElement.xml" />)를 사용했으며, JavaCon�g

에서는 @Import 애노테이션을 클래스 수준에 적용하면 된다.

@Configuration

@Import(BusinessConfiguration.class)

public class FamilyConfiguration {

// ...

}

이렇게 하면 BusinessConfiguration에 정의된 빈들을 현재의 범위로 가져올 수 있으며,

@Autowired나 @Value 애노테이션으로 간단히 원하는 빈에 접근할 수 있다. Application

Context를 @Autowired를 사용해 주입한 경우에는 ApplicationContext를 사용해 빈에 접근할

수도 있다. 다음 예제에서는 컨테이너가 AttorneyCon�guration 구성 클래스에 정의된 빈을 임

포트하고 @Value 애노테이션을 사용해 이름을 기준으로 빈을 주입하고 있다. 해당 타입의 인스

턴스가 하나만 존재하는 경우에는 @Autowired를 사용할 수도 있다.

package com.apress.springrecipes.spring3.javaconfig;

import static java.lang.System.*;

import java.util.Arrays;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.context.annotation.Import;

import org.springframework.context.support.ClassPathXmlApplicationContext;

@Configuration

@Import(AttorneyConfiguration.class)

public class LawFirmConfiguration {

@Value("#{denny}")

private Attorney denny;

Page 130: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

94 l 스프링 3 레시피

@Value("#{alan}")

private Attorney alan;

@Value("#{shirley}")

private Attorney shirley;

@Bean

public LawFirm bostonLegal() {

LawFirm lawFirm = new LawFirm();

lawFirm.setLawyers(Arrays.asList(denny, alan, shirley));

lawFirm.setLocation("Boston");

return lawFirm;

}

}

단순한 빈을 정의하는 경우에는 이 기능이 과도한 감이 없지 않다. 예를 들어 스프링에서 단순

히 빈을 생성하는 기능만 사용하고 다른 프로세스는 사용하지 않는다면 XML에는 구성하지 않

고 @Bean 메서드만 작성해도 된다. 취향에 따라 어느 방식을 사용할지 선택하고, 여기서는 애플

리케이션에 객체를 지정하는 데 Java Con�g를 사용하고 나머지 스프링의 여러 FactoryBean 구

현체들은 손쉽게 작업하고 몇 가지 스키마의 기능들을 이용하기 위해 XML에 그대로 남겨뒀다.

2-9. 빈에 컨테이너 정보 알려주기

◈ 과제

잘 설계된 컴포넌트라면 컨테이너와 직접 의존성을 가져서는 안 되겠지만 이따금 빈이 컨테이너

의 자원을 알아야 할 필요가 있다.

◈ 해결책

표 2-3에 정리된 “aware” 인터페이스를 구현하면 스프링 IoC 컨테이너의 자원들을 빈이 알 수

있게 된다. 스프링은 이 인터페이스에 정의된 세터 메서드를 통해 해당하는 자원을 빈에 주입해

준다.

표 2-3. 자주 사용되는 스프링의 Aware 인터페이스

인터페이스 대상 자원

BeanNameAware IoC 컨테이너에서 해당 인스턴스가 구성돼 있는 빈 이름

BeanFactoryAware 현재 빈 팩터리, 컨테이너의 서비스를 호출할 수 있음

ApplicationContextAware* 현재 애플리케이션 컨텍스트, 컨테이너의 서비스를 호출할 수 있음

Page 131: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

02 고급 스프링 IoC 컨테이너 l 95

인터페이스 대상 자원

MessageSourceAware 메시지 소스, 텍스트 메시지를 해석할 수 있음

ApplicationEventPublisherAware 애플리케이션 이벤트 발행자, 애플리케이션 이벤트를 발행할 수 있음

ResourceLoaderAware 리소스 로더, 외부 자원을 읽어들일 수 있음

* 실제로 ApplicationContext 인터페이스는 MessageSource, ApplicationEventPublisher, ResourceLoader 인터페이스를 확장하므로

애플리케이션 컨텍스트만 가지고도 해당 서비스들을 이용할 수 있다. 하지만 가장 좋은 방법은 요구사항에 부합하는 최소한의

인터페이스를 사용하는 것이다.

Aware 인터페이스의 세터 메서드는 스프링에 의해 빈 프로퍼티가 설정되고 난 뒤에 호출되지

만 초기화 콜백 메서드보다는 앞서 호출된다. 이 호출 과정을 설명하면 다음과 같다.

1. 생성자나 팩터리 메서드를 사용해 빈 인스턴스를 생성한다.

2. 빈 프로퍼티에 값과 빈 레퍼런스를 설정한다.

3. Aware 인터페이스에 정의된 세터 메서드를 호출한다.

4. 초기화 콜백 메서드를 호출한다.

5. 빈이 사용될 준비를 마친다.

6. 컨테이너가 종료되면 소멸 콜백 메서드가 호출된다.

빈이 Aware 인터페이스를 구현하면 스프링에 종속되어 스프링 IoC 컨테이너 밖에서는 정상

적으로 동작하지 않을 수도 있다. 그러므로 이러한 스프링의 인터페이스를 구현해야 하는지 여

부는 신중하게 고민해봐야 한다.

◈ 과제 풀이

아래 예제에서는 Cashier 빈이 BeanNameAware 인터페이스를 구현해 IoC 컨테이너에 지정된

자신의 빈 이름을 알아낼 수 있다. 주입된 빈 이름을 Cashier의 이름으로 사용하면 다른 이름이

프로퍼티에 설정되는 문제를 방지할 수 있다.

package com.apress.springrecipes.shop;

...

import org.springframework.beans.factory.BeanNameAware;

public class Cashier implements BeanNameAware {

...

public void setBeanName(String beanName) {

this.name = beanName;

}

}

Page 132: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

96 l 스프링 3 레시피

빈 이름을 Cashier 이름으로 사용하면 빈 선언을 간소화할 수 있으며, 구성 설정의 name 프로

퍼티와 setName() 메서드를 제거할 수 있다.

<bean id="cashier1" class="com.apress.springrecipes.shop.Cashier">

<property name="path" value="c:/cashier" />

</bean>

알아두기

FieldRetrievingFactoryBean과 PropertyPathFactoryBean을 직접 사용해 필드명과

프로퍼티 경로를 빈 이름으로 사용할 수 있다는 것을 기억하는가? 실제로 두 팩터리 빈은

BeanNameAware 인터페이스를 구현하고 있다.

2-10. 외부 자원 읽어들이기

◈ 과제

애플리케이션에서 텍스트, XML, 프로퍼티, 이미지 파일 등의 외부 자원을 파일 시스템, 클래스

패스, URL 등의 갖가지 다른 위치에서 읽어들여야 할 때가 있다. 보통 이럴 때는 다양한 곳의 자

원을 읽어들이기 위해 여러 API를 사용해야 한다.

◈ 해결책

스프링의 리소스 로더는 getResource() 메서드를 사용해 리소스 경로에 있는 외부 자원들을 읽

어들이는 통합 기능을 제공한다. 이러한 경로에 각기 다른 접두어를 지정하면 여러 위치에 있는

자원을 읽어들일 수 있다. 파일 시스템에서 자원을 읽어들이려면 �le 접두어를, 클래스패스에서

읽어들이려면 classpath 접두어를 사용하면 된다. 또 리소스 경로에 URL을 지정할 수도 있다.

Resource는 스프링에서 외부 자원을 나타내는 일반화된 인터페이스다. 스프링은 Resource 인

터페이스에 대한 여러 가지 구현체를 제공한다. 리소스 로더의 getResource() 메서드는 리소스

경로에 따라 어떤 Resource 구현체를 생성할지 결정한다.

◈ 과제 풀이

애플리케이션이 시작될 때 배너를 표시한다고 가정해 보자. 배너는 banner.txt 텍스트 파일에 다

음과 같은 문자로 구성돼 있으며, 이 파일은 현재 애플리케이션 경로에 저장돼 있다.

Page 133: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

02 고급 스프링 IoC 컨테이너 l 97

*************************

* Welcome to My Shop! *

*************************

이제 배너를 읽어들여 콘솔에 출력하는 BannerLoader 클래스를 작성한다. 리소스를 읽어들

이려면 리소스 로더가 필요하므로 ApplicationContextAware 인터페이스나 ResourceLoader

Aware 인터페이스를 구현한다.

package com.apress.springrecipes.shop;

...

import org.springframework.context.ResourceLoaderAware;

import org.springframework.core.io.Resource;

import org.springframework.core.io.ResourceLoader;

public class BannerLoader implements ResourceLoaderAware {

private ResourceLoader resourceLoader;

public void setResourceLoader(ResourceLoader resourceLoader) {

this.resourceLoader = resourceLoader;

}

public void showBanner() throws IOException {

Resource banner = resourceLoader.getResource("file:banner.txt");

InputStream in = banner.getInputStream();

BufferedReader reader = new BufferedReader(new InputStreamReader(in));

while (true) {

String line = reader.readLine();

if (line == null)

break;

System.out.println(line);

}

reader.close();

}

}

애플리케이션 컨텍스트에서 getResource() 메서드를 호출하면 리소스 경로로 지정된 외부 리

소스를 읽어들일 수 있다. 배너 파일은 파일 시스템상에 위치하므로 리소스 경로는 �le 접두어를

사용한다. getInputStream() 메서드를 사용하면 리소스의 입력 스트림을 얻을 수 있다. 그런 다

음 Bu�eredReader로 파일의 내용을 줄 단위로 읽어들여 콘솔에 출력한다.

마지막으로 배너를 표시하기 위해 빈 설정 파일에 BannerLoader 인스턴스를 선언한다. 애플

Page 134: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

98 l 스프링 3 레시피

리케이션을 시작할 때 배너를 보여줄 생각이므로 showBanner() 메서드를 초기화 메서드로 지

정한다.

<bean id="bannerLoader"

class="com.apress.springrecipes.shop.BannerLoader"

init-method="showBanner" />

리소스 접두어

앞에서는 파일 시스템상에 위치하는 리소스를 상대 경로로 읽어들이도록 지정했으며, 다음과

같이 절대 경로를 지정할 수도 있다.

file:c:/shop/banner.txt

리소스를 클래스패스에서 읽어들일 때는 classpath 접두어를 사용한다. 경로 정보를 표시하지

않으면 리소스가 클래스패스의 루트 경로에서 로딩된다.

classpath:banner.txt

리소스가 특정 패키지 안에 있으면 클래스패스에서 절대 경로를 지정해도 된다.

classpath:com/apress/springrecipes/shop/banner.txt

파일 시스템이나 클래스패스뿐 아니라 URL을 지정해서 리소스를 읽어들일 수도 있다.

http://springrecipes.apress.com/shop/banner.txt

리소스 경로에 접두어를 사용하지 않으면 애플리케이션 컨텍스트의 종류에 따라 리소스가 로

딩된다. 예를 들어 애플리케이션 컨텍스트가 FileSystemXmlApplicationContext이면 파일 시

스템에서 리소스를 읽어들이고, ClassPathXmlApplicationContext라면 클래스패스에서 읽어

들인다.

리소스 주입

리소스를 읽어들이기 위해 getResource() 메서드를 명시적으로 호출하는 방법 말고도 세터 메

서드를 사용해 리소스를 주입할 수도 있다.

package com.apress.springrecipes.shop;

...

import org.springframework.core.io.Resource;

public class BannerLoader {

Page 135: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

02 고급 스프링 IoC 컨테이너 l 99

private Resource banner;

public void setBanner(Resource banner) {

this.banner = banner;

}

public void showBanner() throws IOException {

InputStream in = banner.getInputStream();

...

}

}

빈 설정에서는 이 Resource 프로퍼티의 리소스 경로를 지정하기만 하면 된다. 스프링은

Resource 객체가 빈에 주입되기 전에 타입 변환을 위해 사전에 등록돼 있는 ResourceEditor 프

로퍼티 에디터를 사용한다.

<bean id="bannerLoader"

class="com.apress.springrecipes.shop.BannerLoader"

init-method="showBanner">

<property name="banner">

<value>classpath:com/apress/springrecipes/shop/banner.txt</value>

</property>

</bean>

2-11. 빈 후처리기 작성

◈ 과제

빈 인스턴스의 생성 과정 중 추가적인 처리를 위해 직접 플러그인을 작성해 스프링 IoC 컨테이너

에 등록하고 싶을 수 있다.

◈ 해결책

빈 후처리기(bean post processor)를 이용하면 초기화 콜백 메서드가 호출되기 전후로 추가적인

처리를 할 수 있다. 빈 후처리기의 주요 특징은 단일 빈 인스턴스가 아닌 IoC 컨테이너에 있는 모

든 빈에 대해 하나씩 처리를 해준다는 것이다. 일반적으로 빈 후처리기는 빈 프로퍼티의 유효성

검사나 빈 프로퍼티들을 특정 기준값으로 변경할 때 사용된다.

빈 후처리기의 기본 요구사항은 BeanPostProcessor 인터페이스를 구현하는 것이다. postPro

cessBeforeInitialization()과 postProcessA�erInitialization() 메서드를 구현해 모든 빈에 대해

Page 136: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

100 l 스프링 3 레시피

초기화 콜백 메서드가 호출되기 전후에 처리할 내용을 작성한다. 그러면 스프링이 각 빈 인스턴

스를 초기화 콜백 메서드가 호출되기 전후로 이 두 메서드에 전달해준다. 그 과정을 설명하면 다

음과 같다.

1. 생성자나 팩터리 메서드를 사용해 빈 인스턴스를 생성한다.

2. 빈 프로퍼티에 값과 빈 레퍼런스를 설정한다.

3. Aware 인터페이스에 정의된 세터 메서드를 호출한다.

4. 빈 인스턴스를 각 빈 후처리기의 postProcessBeforeInitialization() 메서드에 전달한다.

5. 초기화 콜백 메서드를 호출한다.

6. 빈 인스턴스를 각 빈 후처리기의 postProcessA�erInitialization() 메서드에 전달한다.

7. 빈이 사용될 준비를 마친다.

8. 컨테이너가 종료되면 소멸 콜백 메서드가 호출된다.

빈 팩터리를 IoC 컨테이너로 사용하는 경우에는 addBeanPostProcessor() 메서드를 통해 프

로그램 방식으로만 빈 후처리기를 등록할 수 있다. 하지만 애플리케이션 컨텍스트를 사용할 경

우 빈 설정 파일에 후처리기의 인스턴스만 간단히 선언하면 빈 후처리기가 자동으로 등록된다.

◈ 과제 풀이

FileNotFoundException 예외가 일어나지 않게 Cashier가 로깅 파일을 열기 전에 항상 로깅 경

로가 존재하는지 확인해 보자. 이러한 기능은 파일 시스템에 뭔가를 저장하는 컴포넌트에게는

공통적인 요구사항이므로 재사용 가능하고 일반화된 방식으로 구현하는 것이 좋다. 스프링에서

는 빈 후처리기가 이러한 기능을 구현하기에 제격이다.

빈 후처리기가 어느 빈에서 검사를 해야 하는지 구별할 수 있게 마커(marker) 인터페이스인

StorageCon�g를 작성해서 대상 빈에서 구현하게 한다. 아울러 빈 후처리기에서 경로의 존재 여

부를 검사하려면 path 프로퍼티에 접근해야 하므로 인터페이스에 getPath() 메서드를 추가한다.

package com.apress.springrecipes.shop;

public interface StorageConfig {

public String getPath();

}

다음으로 Cashier 클래스에서 이 인터페이스를 구현하면 빈 후처리기는 이 인터페이스를 구현

한 빈만 검사할 것이다.

Page 137: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

02 고급 스프링 IoC 컨테이너 l 101

package com.apress.springrecipes.shop;

...

public class Cashier implements BeanNameAware, StorageConfig {

...

public String getPath() {

return path;

}

}

이제 경로 검사를 위한 빈 후처리기를 작성할 차례다. 경로 검사를 하기 가장 좋은 시점은 초

기화 메서드에서 파일을 열기 전이므로 검사를 수행하기 위해 postProcessBeforeInitializati

on() 메서드를 구현한다.

package com.apress.springrecipes.shop;

...

import org.springframework.beans.BeansException;

import org.springframework.beans.factory.config.BeanPostProcessor;

public class PathCheckingBeanPostProcessor implements BeanPostProcessor {

public Object postProcessBeforeInitialization(Object bean, String beanName)

throws BeansException {

if (bean instanceof StorageConfig) {

String path = ((StorageConfig) bean).getPath();

File file = new File(path);

if (!file.exists()) {

file.mkdirs();

}

}

return bean;

}

public Object postProcessAfterInitialization(Object bean, String beanName)

throws BeansException {

return bean;

}

}

빈 생성 과정에서 스프링 IoC 컨테이너가 모든 빈 인스턴스를 하나씩 빈 후처리기에 전달할 것

이므로 이 중에서 StorageCon�g 인터페이스를 구현한 빈을 필터링해야 한다. 이 인터페이스를

구현한 빈을 찾아서 getPath() 메서드로 path 프로퍼티를 얻고 파일 시스템에서 해당 경로가 존

재하는지 검사한다. 경로가 존재하지 않으면 File.mkdirs() 메서드로 경로를 생성한다.

Page 138: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

102 l 스프링 3 레시피

postProcessBeforeInitialization()과 postProcessA�erInitialization() 메서드에서는 처리한

빈 인스턴스를 반드시 반환해야 한다. 이는 원래의 빈 인스턴스를 빈 후처리기를 거친 새로운 인

스턴스로 대체할 수도 있음을 의미한다. 그리고 메서드 내에서 아무것도 하지 않더라도 원래의

빈 인스턴스를 반드시 반환해야 한다는 점을 기억하자.

빈 후처리기를 애플리케이션 컨텍스트에 등록하려면 빈 설정 파일에 해당 인스턴스를 선언해

주기만 하면 애플리케이션 컨텍스트가 BeanPostProcessor 인터페이스를 구현한 빈을 찾아 컨

테이너의 다른 모든 빈들을 처리할 수 있게 빈 후처리기로 등록한다.

<beans ...>

...

<bean class="com.apress.springrecipes.shop.PathCheckingBeanPostProcessor" />

<bean id="cashier1" class="com.apress.springrecipes.shop.Cashier"

init-method="openFile" destroy-method="closeFile">

...

</bean>

</beans>

참고로 초기화 콜백 메서드를 init-method 속성으로 지정했거나 InitializingBean 인터페이

스로 구현한 경우라면 작성한 PathCheckingBeanPostProcessor는 초기화 메서드가 호출되기

이전에 정상적으로 잘 동작할 것이다.

하지만 Cashier 빈이 JSR-250의 @PostConstruct와 @PreDestroy 애노테이션에 의존하

고 초기화 메서드 호출을 위한 CommonAnnotationBeanPostProcessor 인스턴스도 등록

돼 있다면 PathCheckingBeanPostProcessor는 정상적으로 동작하지 않을 것이다. 왜냐하면

PathCheckingBeanPostProcessor가 기본적으로 CommonAnnotationBeanPostProcessor의

우선순위보다 낮기 때문에 결국 초기화 메서드가 경로 검사보다 먼저 호출되기 때문이다.

<beans ...>

...

<bean class="org.springframework.context.annotation.

CommonAnnotationBeanPostProcessor" />

<bean class="com.apress.springrecipes.shop.PathCheckingBeanPostProcessor" />

<bean id="cashier1" class="com.apress.springrecipes.shop.Cashier">

...

</bean>

</beans>

Page 139: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

02 고급 스프링 IoC 컨테이너 l 103

빈 후처리기의 처리 순서를 정의하려면 Ordered나 PriorityOrdered 인터페이스를 구현하

고 getOrder() 메서드로 순서를 반환해야 한다. 값이 낮을수록 높은 우선순위로 수행되며,

PriorityOrdered 인터페이스에서 반환하는 값이 항상 Ordered 인터페이스에서 반환하는 값보

다 우선한다.

CommonAnnotationBeanPostProcessor가 PriorityOrdered 인터페이스를 구현하고 있으므

로 PathCheckingBeanPostProcessor가 먼저 수행되게 하려면 반드시 PriorityOrdered 인터페

이스를 구현해야 한다.

package com.apress.springrecipes.shop;

...

import org.springframework.beans.factory.config.BeanPostProcessor;

import org.springframework.core.PriorityOrdered;

public class PathCheckingBeanPostProcessor implements BeanPostProcessor,

PriorityOrdered {

private int order;

public int getOrder() {

return order;

}

public void setOrder(int order) {

this.order = order;

}

...

}

이제 빈 설정 파일에서 PathCheckingBeanPostProcessor의 order 값을 더 낮게 할당해

cashier 빈의 경로 검사를 CommonAnnotationBeanPostProcessor의 초기화 메서드보다

먼저 호출되게 한다. CommonAnnotationBeanPostProcessor의 기본 순서 값은 Ordered.

LOWEST_PRECEDENCE이므로 PathCheckingBeanPostProcessor에는 순서값으로 0을 할

당해야 한다.

<beans ...>

...

<bean class="org.springframework.context.annotation.

CommonAnnotationBeanPostProcessor" />

<bean class="com.apress.springrecipes.shop.PathCheckingBeanPostProcessor">

<property name="order" value="0" />

</bean>

Page 140: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

104 l 스프링 3 레시피

<bean id="cashier1" class="com.apress.springrecipes.shop.Cashier">

<property name="path" value="c:/cashier" />

</bean>

</beans>

0은 PathCheckingBeanPostProcessor의 기본 순서값이므로 설정을 생략해도 된다. 그리고

계속해서 <context:annotation-con�g>를 사용해 CommonAnnotationBeanPostProcessor를

자동으로 동록되게 할 수 있다.

<beans ...>

...

<context:annotation-config />

<bean class="com.apress.springrecipes.shop.PathCheckingBeanPostProcessor" />

</beans>

2-12. 빈 설정의 외부화

◈ 과제

빈 설정 파일을 구성할 때 파일 경로, 서버 주소, 사용자명, 비밀번호 등의 배포와 관련된 내용들

을 함께 섞어놓는 것은 바람직하지 않다. 보통 빈 설정은 애플리케이션 개발자들이 작성하는 반

면 배포와 관련된 내용은 배포 담당자나 시스템 관리자가 담당한다.

◈ 해결책

스프링은 빈 설정의 외부 요소들을 프로퍼티 파일로 분리하기 위한 PropertyPlaceholder

Con�gurer 빈 팩터리 후처리기를 제공한다. 빈 설정 파일에서 ${var}의 형태로 변수를 사용하면

PropertyPlaceholderConfigurer가 프로퍼티 파일로부터 프로퍼티를 읽어들여 변수를 대체

한다.

빈 팩터리 후처리기(bean factory post processor)는 빈 후처리기와는 다르다. 빈 팩터리 후처리기

는 빈 인스턴스가 아닌 IoC 컨테이너(빈 팩터리나 애플리케이션 컨텍스트)를 대상으로 한다. 빈

팩터리 후처리기는 IoC 컨테이너에서 빈 설정을 읽어들인 후 빈 인스턴스가 생성되기 이전에 영

향을 미친다. 빈 팩터리 후처리기의 일반적인 사용법은 빈이 생성되기 전에 빈 설정 내용을 변경

하는 것이다. 스프링은 여러 가지 빈 팩터리 후처리기를 제공하고 있으며, 실무에서 직접 빈 팩터

리 후처리기를 작성할 일은 별로 없다.

Page 141: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

02 고급 스프링 IoC 컨테이너 l 105

◈ 과제 풀이

앞에서는 Cashier의 로깅 경로를 빈 설정 파일에 지정했다. 배포와 관련된 내용을 빈 설정과 함

께 섞어두는 것은 좋지 않으므로 배포와 관련된 내용은 클래스패스 루트의 con�g.properties와

같은 프로퍼티 파일로 분리하는 편이 더 나을 것이다. 그리고 프로퍼티 파일에 로깅 경로를 정의

한다.

cashier.path=c:/cashier

이제 빈 설정 파일에 ${var}의 형태로 변수를 사용한다. 프로퍼티 파일로부터 외부 프로퍼

티를 읽어들여 변수를 치환해 사용하려면 애플리케이션 컨텍스트에 PropertyPlaceholder

Con�gurer 빈 팩터리 후처리기를 등록해야 한다. PropertyPlaceholderCon�gurer의 location

프로퍼티에는 프로퍼티 파일을 하나만 지정하고, locations 프로퍼티에는 여러 개의 프로퍼티 파

일을 지정할 수 있다.

<beans ...>

...

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

<property name="location">

<value>config.properties</value>

</property>

</bean>

<bean id="cashier1" class="com.apress.springrecipes.shop.Cashier">

<property name="path" value="${cashier.path}" />

</bean>

</beans>

빈 팩터리 후처리기로 구현된 PropertyPlaceholderCon�gurer는 빈 설정 파일의 변수를 빈이

생성되기 전에 외부 프로퍼티로부터 읽어와서 대체할 것이다.

스프링 2.5 이후에서는 PropertyPlaceholderCon�gurer도 <context:property-placeholder>

엘리먼트로 간단하게 등록할 수 있다.

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

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:context="http://www.springframework.org/schema/context"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context-3.0.xsd">

Page 142: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

106 l 스프링 3 레시피

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

...

</beans>

2-13. 텍스트 메시지 해석

◈ 과제

애플리케이션에서 국제화(I18N, i로 시작해 n으로 끝나는 중간 18자의 영단어 줄임 표기) 기능

을 지원하려면 여러 다른 로캘(locale, 지역/언어)로 작성된 텍스트 메시지를 해석하는 기능이 필

요하다.

◈ 해결책

스프링의 애플리케이션 컨텍스트는 대상 로캘의 키를 통해 텍스트 메시지를 해석한다. 보

통은 한 로캘에 해당하는 메시지들을 프로퍼티 파일 하나로 분리해 저장하는데, 이러한 프

로퍼티 파일을 리소스 번들(resource bundle)이라고 한다. MessageSource는 이러한 메시지

를 해석하기 위한 메서드를 정의해 놓은 인터페이스다. ApplicationContext 인터페이스에

는 MessageSource 인터페이스를 상속해 텍스트 메시지를 해석하는 기능도 있으며, 이름이

messageSource인 빈에게 메시지 해석을 위임한다. 가장 일반적인 MessageSource 구현체인

ResourceBundleMessageSource는 여러 로캘의 리소스 번들의 메시지를 해석한다.

◈ 과제 풀이

예제로 미국식 영어를 사용하기 위한 messages_en_US.properties와 리소스 번들을 생성한다.

리소스 번들은 루트 클래스패스를 기준으로 로딩된다.

alert.checkout=A shopping cart has been checked out.

MessageSource의 구현체로 ResourceBundleMessageSource를 사용해 리소스 번들로부터

메시지를 해석한다. ResourceBundleMessageSource 빈의 이름을 messageSource로 지정해 애

플리케이션 컨텍스트에서 찾을 수 있게 하며 리소스 번들의 기준 이름(base name)도 지정한다.

<beans ...>

...

<bean id="messageSource"

class="org.springframework.context.support.ResourceBundleMessageSource">

<property name="basename">

Page 143: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

02 고급 스프링 IoC 컨테이너 l 107

<value>messages</value>

</property>

</bean>

</beans>

이 MessageSource 정의의 경우 로캘(미국 영어가 우선되는)의 텍스트 메시지를 찾으려고

할 때 언어와 국가가 모두 일치하는 messages_en_US.properties 리소스 번들을 우선 찾을 것

이다. 해당하는 리소스 번들이 없거나 메시지를 찾을 수 없다면 언어만 일치하는 messages_

en.properties를 차순위로 고려하며, 이 리소스 번들도 존재하지 않으면 모든 로캘에 대해 기본

값인 messages.properties가 최종적으로 선택될 것이다. 리소스 번들 로딩에 대한 추가적인 정보

는 java.util.ResourceBundle 클래스의 자바 문서(javadoc)를 참조하자.

getMessage() 메서드를 사용하면 애플리케이션 컨텍스트가 메시지를 해석할 수 있으며, 첫 번

째 매개변수는 메시지의 키에 해당하고 세 번째는 사용할 로캘이다.

package com.apress.springrecipes.shop;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.FileSystemXmlApplicationContext;

public class Main {

public static void main(String[] args) throws Exception {

ApplicationContext context =

new FileSystemXmlApplicationContext("beans.xml");

...

String alert = context.getMessage("alert.checkout", null, Locale.US);

System.out.println(alert);

}

}

getMessage() 메서드의 두 번째 매개변수는 메시지 매개변수의 배열이다. 텍스트 메시지에서

는 인덱스를 통해 여러 개의 매개변수를 정의할 수 있다.

alert.checkout=A shopping cart costing {0} dollars has been checked out at {1}.

메시지 매개변수를 채우려면 객체 배열을 전달해야 한다. 이 배열의 요소들은 문자열로 변환

된 후 매개변수에 채워진다.

package com.apress.springrecipes.shop;

...

public class Main {

Page 144: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

108 l 스프링 3 레시피

public static void main(String[] args) throws Exception {

...

String alert = context.getMessage("alert.checkout",

new Object[] { 4, new Date() }, Locale.US);

System.out.println(alert);

}

}

Main 클래스에서도 애플리케이션 컨텍스트에 직접 접근할 수 있으므로 텍스트 메시지를 사

용할 수 있다. 하지만 빈에서 텍스트 메시지를 사용하려면 ApplicationContextAware 인터페이

스나 MessageSourceAware 인터페이스를 구현해야 한다. 이제 Main 클래스에서 메시지 해석

로직을 삭제할 수 있게 됐다.

package com.apress.springrecipes.shop;

...

import org.springframework.context.MessageSource;

import org.springframework.context.MessageSourceAware;

public class Cashier implements BeanNameAware, MessageSourceAware,

StorageConfig {

...

private MessageSource messageSource;

public void setMessageSource(MessageSource messageSource) {

this.messageSource = messageSource;

}

public void checkout(ShoppingCart cart) throws IOException {

...

String alert = messageSource.getMessage("alert.checkout",

new Object[] { total, new Date() }, Locale.US);

System.out.println(alert);

}

}

2-14. 애플리케이션 이벤트와의 상호작용

◈ 과제

일반적인 컴포넌트 간의 상호작용 모델에서 메시지 발신자는 메서드를 호출할 수신자의 위치를

알고 있어야 한다. 이러한 경우 발신 컴포넌트는 수신 컴포넌트를 반드시 알아야 한다. 이러한

Page 145: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

02 고급 스프링 IoC 컨테이너 l 109

유형의 상호작용은 직접적이고 간단하지만, 발신 컴포넌트와 수신 컴포넌트가 긴밀하게 결합돼

있다.

IoC 컨테이너를 사용하면 구현체 대신 인터페이스를 사용해 컴포넌트 간에 상호작용할 수 있

다. 이러한 상호작용 모델은 결합도는 낮지만 발신 컴포넌트는 하나의 수신 컴포넌트와 상호작

용할 때만 효율적이다. 발신자가 여러 개의 수신자와 상호작용하려면 일일이 하나씩 호출해줘야

한다.

◈ 해결책

스프링의 애플리케이션 컨텍스트는 이벤트를 기반으로 빈 사이의 상호작용을 지원한다. 이벤트

기반 상호작용 모델에서 발신 컴포넌트는 이벤트를 발행하기만 하면 될 뿐 수신자를 몰라도 상

관없으며, 실제로는 하나 이상의 수신 컴포넌트가 존재할 수 있다. 수신자도 어느 발신자가 이벤

트를 발행했는지 몰라도 동시에 여러 발신자로부터 여러 개의 이벤트를 리스닝할 수 있다. 이러

한 방식은 발신과 수신 컴포넌트가 느슨하게 결합된 형태다.

스프링에서 모든 이벤트 클래스들은 ApplicationEvent 클래스를 상속해야 하며, 어느

빈이든지 애플리케이션 이벤트 발행자의 publishEvent() 메서드를 호출해 이벤트를 발행

할 수 있다. 빈이 특정 이벤트를 리스닝하려면 ApplicationListener 인터페이스를 구현하고

onApplicationEvent() 메서드에서 이벤트를 처리해야 한다. 실제로 스프링은 한 리스너에게 모

든 이벤트를 통지하므로 이와 같은 이벤트들을 필터링하는 작업은 직접 해줘야 한다. 하지만 제

네릭을 활용하면 스프링이 제네릭 타입 매개변수와 일치하는 메시지만 전달해준다.

◈ 과제 풀이

이벤트 정의

이벤트 기반의 상호작용을 사용하기 위한 첫 번째 단계는 이벤트를 정의하는 것이다. Cashier 빈

이 장바구니를 계산하고 난 뒤에 CheckoutEvent를 발행하게 해 보자. CheckoutEvent는 프로퍼

티로 지불 금액과 계산 시간을 가진다. 스프링에서 모든 이벤트는 ApplicationEvent 추상 클래

스를 상속하고 생성자에 이벤트 소스를 전달해야 한다.

package com.apress.springrecipes.shop;

...

import org.springframework.context.ApplicationEvent;

public class CheckoutEvent extends ApplicationEvent {

Page 146: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

110 l 스프링 3 레시피

private double amount;

private Date time;

public CheckoutEvent(Object source, double amount, Date time) {

super(source);

this.amount = amount;

this.time = time;

}

public double getAmount() {

return amount;

}

public Date getTime() {

return time;

}

}

이벤트 발행

이벤트를 발행하려면 이벤트 인스턴스를 생성하고 애플리케이션 이벤트 발신기의 publish

Event() 메서드를 호출한다. 이벤트 발신기에는 ApplicationEventPublisherAware 인터페이스

를 구현해 접근한다.

package com.apress.springrecipes.shop;

...

import org.springframework.context.ApplicationEventPublisher;

import org.springframework.context.ApplicationEventPublisherAware;

public class Cashier implements BeanNameAware, MessageSourceAware,

ApplicationEventPublisherAware, StorageConfig {

...

private ApplicationEventPublisher applicationEventPublisher;

public void setApplicationEventPublisher(

ApplicationEventPublisher applicationEventPublisher) {

this.applicationEventPublisher = applicationEventPublisher;

}

public void checkout(ShoppingCart cart) throws IOException {

...

CheckoutEvent event = new CheckoutEvent(this, total, new Date());

applicationEventPublisher.publishEvent(event);

}

}

Page 147: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

02 고급 스프링 IoC 컨테이너 l 111

이벤트 리스닝

애플리케이션 컨텍스트에서 ApplicationListener 인터페이스를 구현한 빈은 모든 이벤트를 통

지받으며 onApplicationEvent() 메서드에서 다루고자 하는 이벤트를 필터링한다. 다음 리스

너에서는 고객에게 결제에 관한 이메일을 전달하려고 한다. 여기서는 제네릭을 활용하지 않는

ApplicationEvent 매개변수를 필터링하기 위해 instanceof 검사를 사용한다.

package com.apress.springrecipes.shop;

...

import org.springframework.context.ApplicationEvent;

import org.springframework.context.ApplicationListener;

public class CheckoutListener implements ApplicationListener {

public void onApplicationEvent(ApplicationEvent event) {

if (event instanceof CheckoutEvent) {

double amount = ((CheckoutEvent) event).getAmount();

Date time = ((CheckoutEvent) event).getTime();

// 결제 금액과 시간을 처리한다

System.out.println("Checkout event [" + amount + ", " + time + "]");

}

}

}

제네릭의 이점을 활용해 코드를 다시 작성하면 다음과 같이 좀 더 간결해진다.

package com.apress.springrecipes.shop;

...

import org.springframework.context.ApplicationEvent;

import org.springframework.context.ApplicationListener;

public class CheckoutListener implements ApplicationListener<CheckoutEvent> {

public void onApplicationEvent(CheckoutEvent event) {

double amount = ((CheckoutEvent) event).getAmount();

Date time = ((CheckoutEvent) event).getTime();

// 결제 금액과 시간을 처리한다

System.out.println("Checkout event [" + amount + ", " + time + "]");

}

}

Page 148: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

112 l 스프링 3 레시피

다음으로 애플리케이션 컨텍스트에 리스너를 등록해 모든 이벤트를 리스닝할 수 있게 한다.

리스너 등록은 간단히 리스너의 빈 인스턴스를 선언하는 게 전부다. 애플리케이션 컨텍스트는

ApplicationListener 인터페이스를 구현한 빈을 인식해 이벤트들을 통지해 준다.

<beans ...>

...

<bean class="com.apress.springrecipes.shop.CheckoutListener" />

</beans>

마지막으로 애플리케이션 컨텍스트 자체로도 ContextClosedEvent, ContextRefreshedEvent,

RequestHandledEvent와 같은 컨테이너 이벤트를 발행할 수 있다. 이러한 이벤트를 통지받는 빈

을 작성하려면 ApplicationListener 인터페이스를 구현하면 된다.

2-15. 스프링에 프로퍼티 에디터 등록

◈ 과제

프로퍼티 에디터는 프로퍼티 값과 텍스트 값을 서로 변환해 주는 자바 빈 API의 기능이다. 각 프

로퍼티 에디터는 특정 타입의 프로퍼티를 위해 사용하게끔 설계돼 있다. 빈 설정을 단순화하기

위해 프로퍼티 에디터를 사용하고 싶을 수 있다.

◈ 해결책

스프링 IoC 컨테이너는 빈 설정을 돕기 위해 프로퍼티 에디터를 사용할 수 있게 지원해준다. 예

를 들어 java.net.URL 타입에 대한 프로퍼티 에디터는 URL 문자열을 지정해 URL 타입의 프로

퍼티를 지정해 주며, 스프링은 자동으로 URL 문자열을 URL 객체로 변환해 프로퍼티에 주입한

다. 스프링은 공통 타입의 빈 프로퍼티를 변환하기 위한 여러 가지 프로퍼티 에디터를 제공한다.

일반적으로 프로퍼티 에디터는 사용하기 전에 먼저 IoC 컨테이너에 등록해야 한다. Custom

EditorCon�gurer는 빈이 생성되기 전에 커스텀 프로퍼티 에디터를 등록할 수 있게 빈 팩터리 후

처리기로 구현된다.

◈ 과제 풀이

예제로 특정 기간의 Product의 판매량을 기준으로 순위를 매긴다고 가정해보자. 이를 위해

ProductRanking 클래스에 fromDate와 toDate 프로퍼티를 추가한다.

Page 149: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

02 고급 스프링 IoC 컨테이너 l 113

package com.apress.springrecipes.shop;

...

public class ProductRanking {

private Product bestSeller;

private Date fromDate;

private Date toDate;

// 게터 및 세터

...

}

자바 프로그램에서 java.util.Date 프로퍼티에 값을 지정하려면 DateFormat.parse() 메서드를

사용해 특정 패턴의 날짜 문자열로부터 변환해야 한다.

DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");

productRanking.setFromDate(dateFormat.parse("2007-09-01"));

productRanking.setToDate(dateFormat.parse("2007-09-30"));

스프링에서 동일한 빈 설정을 작성하려면 먼저 패턴이 설정된 dateFormat 빈을 선언해야 한

다. 날짜 문자열을 날짜 객체로 변환하려면 parse() 메서드가 호출돼야 하므로 parse() 메서드를

날짜 빈을 생성하기 위한 인스턴스 팩터리 메서드로 사용할 수 있다.

<beans ...>

...

<bean id="dateFormat" class="java.text.SimpleDateFormat">

<constructor-arg value="yyyy-MM-dd" />

</bean>

<bean id="productRanking"

class="com.apress.springrecipes.shop.ProductRanking">

<property name="bestSeller">

<bean class="com.apress.springrecipes.shop.Disc">

<property name="name" value="CD-RW" />

<property name="price" value="1.5" />

</bean>

</property>

<property name="fromDate">

<bean factory-bean="dateFormat" factory-method="parse">

<constructor-arg value="2007-09-01" />

</bean>

</property>

<property name="toDate">

<bean factory-bean="dateFormat" factory-method="parse">

<constructor-arg value="2007-09-30" />

Page 150: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

114 l 스프링 3 레시피

</bean>

</property>

</bean>

</beans>

보다시피 앞의 설정은 날짜 프로퍼티를 설정하는 것치고 너무 복잡하다. 실제로 스프링 IoC

컨테이너는 프로퍼티 에디터를 사용해 텍스트 값을 프로퍼티로 변환해 주며, 스프링에서 제공

하는 CustomDateEditor 클래스는 날짜 문자열을 java.util.Date 프로퍼티로 변환해 준다. 우선

CustomDateEditor 인스턴스를 빈 설정 파일에 선언한다.

<beans ...>

...

<bean id="dateEditor"

class="org.springframework.beans.propertyeditors.CustomDateEditor">

<constructor-arg>

<bean class="java.text.SimpleDateFormat">

<constructor-arg value="yyyy-MM-dd" />

</bean>

</constructor-arg>

<constructor-arg value="true" />

</bean>

</beans>

이 에디터는 첫 번째 생성자 매개변수로 DateFormat 객체를 요구한다. 두 번째 매개변수는 이

에디터가 빈(empty) 값의 입력을 허용하는지 여부를 나타낸다.

이어서 스프링이 java.util.Date 타입의 프로퍼티들을 변환할 수 있게끔 이 프로퍼티 에디터를

CustomEditorCon�gurer 인스턴스에 등록해야 한다. 이제 java.util.Date 프로퍼티에 텍스트 형

태의 날짜 값을 지정할 수 있다.

<beans ...>

...

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">

<property name="customEditors">

<map>

<entry key="java.util.Date">

<ref local="dateEditor" />

</entry>

</map>

</property>

</bean>

<bean id="productRanking"

Page 151: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

02 고급 스프링 IoC 컨테이너 l 115

class="com.apress.springrecipes.shop.ProductRanking">

<property name="bestSeller">

<bean class="com.apress.springrecipes.shop.Disc">

<property name="name" value="CD-RW" />

<property name="price" value="1.5" />

</bean>

</property>

<property name="fromDate" value="2007-09-01" />

<property name="toDate" value="2007-09-30" />

</bean>

</beans>

아래의 Main 클래스를 사용하면 CustomDateEditor 구성이 잘 동작하는지 테스트할 수 있다.

package com.apress.springrecipes.shop;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

public static void main(String[] args) throws Exception {

ApplicationContext context =

new ClassPathXmlApplicationContext("beans.xml");

...

ProductRanking productRanking =

(ProductRanking) context.getBean("productRanking");

System.out.println(

"Product ranking from " + productRanking.getFromDate() +

" to " + productRanking.getToDate());

}

}

스프링에는 CustomDateEditor 말고도 CustomNumberEditor, ClassEditor, FileEditor,

LocaleEditor, StringArrayPropertyEditor, URLEditor와 같이 공통적인 데이터 타입을 변환해

주는 각종 프로퍼티 에디터를 제공하며, ClassEditor, FileEditor, LocaleEditor, URLEditor도

이미 등록돼 있으므로 다시 등록하지 않아도 된다. 프로퍼티 에디터의 사용에 관한 추가적인 정

보는 자바문서(Javadoc)에서 org.springframework.beans.propertyeditors 패키지의 클래스들

을 살펴보면 된다.

Page 152: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

116 l 스프링 3 레시피

2-16. 커스텀 프로퍼티 에디터 만들기

◈ 과제

내장 프로퍼티 에디터 등록뿐 아니라 커스텀 데이터 타입을 변환하기 위해 직접 커스텀 프로퍼

티 에디터를 만들고 싶을 수 있다.

◈ 해결책

java.beans.PropertyEditor 인터페이스를 구현하거나 java.beans.PropertyEditorSupport 지원

클래스를 상속하면 커스텀 프로퍼티 에디터를 작성할 수 있다.

◈ 과제 풀이

예를 들어, Product 클래스에 대한 프로퍼티 에디터를 만들어 보자. Product의 문자열 표현을

클래스 이름, Product 이름, 가격을 나타내는 세 부분으로 설계할 수 있다. 각 부분은 콤마로 구

분한다. 이 경우 값을 변환하기 위해 다음과 같은 ProductEditor 클래스를 작성할 수 있다.

package com.apress.springrecipes.shop;

import java.beans.PropertyEditorSupport;

public class ProductEditor extends PropertyEditorSupport {

public String getAsText() {

Product product = (Product) getValue();

return product.getClass().getName() + "," + product.getName() + ","

+ product.getPrice();

}

public void setAsText(String text) throws IllegalArgumentException {

String[] parts = text.split(",");

try {

Product product = (Product) Class.forName(parts[0]).newInstance();

product.setName(parts[1]);

product.setPrice(Double.parseDouble(parts[2]));

setValue(product);

} catch (Exception e) {

throw new IllegalArgumentException(e);

}

}

}

Page 153: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

02 고급 스프링 IoC 컨테이너 l 117

getAsText() 메서드는 프로퍼티를 문자열 값으로 변환해 주며, 반대로 setAsText() 메서드는

문자열을 프로퍼티 값으로 변환해 준다. 프로퍼티 값은 getValue() 메서드로 구하고 setValue()

메서드로 설정할 수 있다.

다음으로 커스텀 에디터를 사용하기 위해 CustomEditorCon�gurer 인스턴스에 등록해야 한

다. 등록 방법은 내장 에디터와 같다. 이제 Product 타입의 프로퍼티에 텍스트 형태로 Product

정보를 지정할 수 있다.

<beans ...>

...

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">

<property name="customEditors">

<map>

...

<entry key="com.apress.springrecipes.shop.Product">

<bean class="com.apress.springrecipes.shop.ProductEditor" />

</entry>

</map>

</property>

</bean>

<bean id="productRanking"

class="com.apress.springrecipes.shop.ProductRanking">

<property name="bestSeller">

<value>com.apress.springrecipes.shop.Disc,CD-RW,1.5</value>

</property>

...

</bean>

</beans>

실제로 자바빈 API는 자동으로 클래스에 대한 프로퍼티 에디터를 탐색한다. 프로퍼티 에디

터를 정확히 찾게 하려면 대상 클래스와 동일한 패키지에 에디터를 두고 에디터의 이름은 대

상 클래스의 이름에 Editor라는 접미어를 붙이면 된다. 이 규칙에 따라 앞의 프로퍼티 에디터를

ProductEditor로 작성하면 스프링 IoC 컨테이너에서 다시 등록할 필요가 없다.

2-17. TaskExecutor를 사용한 동시성 처리

◈ 과제

스레드, 동시성(concurrency) 프로그램을 만드는 방법은 매우 다양하지만 표준화된 방식은 없다.

게다가 이러한 프로그램을 만들려면 다양한 유틸리티 클래스를 만들어야 하는 경우가 많다.

Page 154: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

118 l 스프링 3 레시피

◈ 해결책

스프링의 TaskExecutor 추상화를 이용하면 된다. TaskExecutor는 기본적인 자바 SE의

Executor 구현체와 자바 EE의 WorkManager 구현체 및 커스텀 구현체를 비롯해 여러 환경

에 대해 다양한 구현체들을 제공한다. 스프링 3.0에는 모든 구현체가 통합돼 있고 자바 SE의

Executor로도 변환이 가능하다.

◈ 과제 풀이

스레드 처리는 어려운 주제이고 어떤 경우에는 상당한 노력(자바 SE 환경의 표준 스레드 처리 방

식을 활용해도 매우 장황하게 구현되는)을 들여야 하기도 한다. 동시성은 서버측 컴포넌트를 구

현할 때 아키텍처의 중요한 구현 관점이지만 자바 EE 영역에서 표준이 존재하지 않는다. 사실 자

바 EE 명세에서는 스레드를 명시적으로 생성하고 조작하는 것을 금지하고 있는 등 표준화와는

완전히 동떨어진 상태다.

자바 SE

자바 SE 영역에서는 수 년에 걸쳐 다양한 방법들이 도입됐다. java.lang.�read 표준 기능이 JDK

1.0에서부터 도입됐으며, 자바 1.3에서는 주기적인 작업을 할 수 있는 java.util.TimerTask가 도입

됐다. 자바 5에서는 java.util.concurrent.Executor를 중심으로 스레드 풀 구성을 계층적으로 재

가공한 java.util.concurrent 패키지가 도입됐다.

Executor API는 다음과 같이 간단하다.

package java.util.concurrent;

public interface Executor {

void execute(Runnable command);

}

하위 인터페이스인 ExecutorService는 스레드를 관리하고 shutdown() 같은 스레드 이벤트

를 생성하는 기능을 더 제공한다. 자바 5.0부터 각종 구현체가 제공되고 있다. 대부분 java.util.

concurrent.Executors의 스태틱 팩터리 메서드를 통해 이용할 수 있으며, java.util.Collections

클래스에서 제공하는 유틸리티 메서드를 통해 java.util.Collection 인스턴스들을 다룰 수 있

다. 다음 예제의 ExecutorService는 Future<T>를 반환하는 submit() 메서드를 제공한다.

Future<T>의 인스턴스는 (보통 비동기적으로 실행되는) 스레드 실행 경과를 추적하는 용도로

사용될 수 있다. 어느 잡(job)이 종료됐거나 취소됐는지 판단하기 위해 isDone()과 isCancelled()

를 각각 호출할 수 있다. ExecutorService를 이용해 Runnable 타입(run() 메서드가 값을 반환하

Page 155: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

02 고급 스프링 IoC 컨테이너 l 119

지 않는)의 객체에 대해 submit()을 실행하는 경우 반환되는 Future를 상대로 get()을 호출하면

null이나 submit() 실행 시 지정한 값이 반환된다.

Runnable task = new Runnable() {

public void run() {

try {

Thread.sleep(1000 * 60);

System.out.println("잠시 동안의 대기가 완료되어 다시 반환됨!");

} catch (Exception ex) {/* ... */}

}

};

ExecutorService executorService = Executors.newCachedThreadPool();

if (executorService.submit(task, Boolean.TRUE).get().equals(Boolean.TRUE))

System.out.println("잡이 완료되었습니다!");

앞서 설명한 내용을 염두에 두고 다양한 구현체의 특징을 몇 가지 살펴보자. 가령 다음과 같이

Runnable 인터페이스를 사용할 것이다.

package com.apress.springrecipes.spring3.executors;

import java.util.Date;

import org.apache.commons.lang.exception.ExceptionUtils;

public class DemonstrationRunnable implements Runnable {

public void run() {

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

System.out.println(ExceptionUtils.getFullStackTrace(e));

}

System.out.println(Thread.currentThread().getName());

System.out.printf("Hello at %s \n", new Date());

}

}

이 클래스는 오직 호출된 시간을 표시하는 역할만 한다. 자바 SE의 Executors나 스프링의

TaskExecutor 지원 기능을 살펴보면 이것과 동일한 인스턴스를 사용할 수 있다.

package com.apress.springrecipes.spring3.executors;

import java.util.Date;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.ScheduledExecutorService;

import java.util.concurrent.TimeUnit;

Page 156: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

120 l 스프링 3 레시피

public class ExecutorsDemo {

public static void main(String[] args) throws Throwable {

Runnable task = new DemonstrationRunnable();

// 스레드 풀을 새로 생성하거나 사용 가능한 기존의 풀이 있다면 재사용하려고 시도함

ExecutorService cachedThreadPoolExecutorService = Executors

.newCachedThreadPool();

if (cachedThreadPoolExecutorService.submit(task).get() == null)

System.out.printf("The cachedThreadPoolExecutorService "

+ "has succeeded at %s \n", new Date());

// 생성할 수 있는 스레드 개수를 제한, 나머지는 큐에 저장

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(100);

if (fixedThreadPool.submit(task).get() == null)

System.out.printf("The fixedThreadPool has " +

"succeeded at %s \n",

new Date());

// 한 번에 하나 이상의 스레드를 사용할 수 없음

ExecutorService singleThreadExecutorService = Executors

.newSingleThreadExecutor();

if (singleThreadExecutorService.submit(task).get() == null)

System.out.printf("The singleThreadExecutorService "

+ "has succeeded at %s \n", new Date());

// 예상된 결과의 잡을 전달하는 기능을 지원

ExecutorService es = Executors.newCachedThreadPool();

if (es.submit(task, Boolean.TRUE).get().equals(Boolean.TRUE))

System.out.println("Job has finished!");

// 모조 TimerTask

ScheduledExecutorService scheduledThreadExecutorService = Executors

.newScheduledThreadPool(10);

if (scheduledThreadExecutorService.schedule(

task, 30, TimeUnit.SECONDS).get() == null)

System.out.printf("The scheduledThreadExecutorService "

+ "has succeeded at %s \n", new Date());

// 예외나 취소 상황을 만나기 전까지는 멈추지 않음

scheduledThreadExecutorService.scheduleAtFixedRate(task, 0, 5,

TimeUnit.SECONDS);

}

}

Page 157: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

02 고급 스프링 IoC 컨테이너 l 121

Callable<T>를 인자로 받는 버전의 ExecutorService submit() 메서드를 사용하면 Callable의

주 메서드인 call()에서 반환되는 것은 무엇이든지 반환해 준다. Callable 인터페이스는 다음과

같다.

package java.util.concurrent;

public interface Callable<V> {

V call() throws Exception;

}

자바 EE

자바 EE 영역에서는 이러한 종류의 문제를 해결하기 위한 다양한 방안을 고안했지만 대부분이

목적을 달성하지 못하고 있다. 자바 EE는 오랫동안 스레드 처리 문제에 대한 완전한 해결책을 제

공하지 못하고 있다.

이러한 종류의 문제에 대한 또 다른 해결 방안도 있다. Quartz(작업 스케줄링 프레임워크)는

스케줄링과 동시성에 대한 해결책을 제공하면서 부족한 부분을 채워주고 있다. JCA 1.5(J2EE

Connector Architecture, 줄여서 JCA는 자바 암호화 아키텍처(Java Cryptography Architecture)의

약어였지만 이러한 기술과 관련해서도 자주 사용되고 있다)는 통합 기능을 위한 기본적인 유형

의 게이트웨이를 제공함으로써 동시성을 지원하는 명세다. 컴포넌트는 동시에 수신 메시지와 응

답을 통지받을 수 있다. JCA 1.5는 (스프링소스의 스프링 인티그레이션 프레임워크와 같은 정교

한 기술 없이도 유사한 통합 기능을 제공하는) 기본적이고 제한적인 엔터프라이즈 서비스 버스

(Enterprise Service Bus)를 제공한다. 다시 말해, C로 작성된 레거시 애플리케이션을 자바 EE 애플리

케이션 서버와 결합하고 컨테이너 서비스에 해당 애플리케이션이 일부 통합되게 해야 했으며, 그

러한 통합이 이식성 있고 합리적인 방식으로 이뤄지기를 바랐다면 JCA로도 충분했다는 뜻이다.

동시성에 대한 요구사항은 애플리케이션 서버 벤더에게도 예외는 아니다. 2003년도에 IBM

과 BEA가 연합하여 Timer와 WorkManager API를 만들었다. 두 API는 결국 JSR-237이 되었

는데, 자바 EE 같은 관리형 환경에서 동시성을 어떻게 구현하느냐, 라는 문제 때문에 결국 JSR-

236으로 통합되면서 사라졌다. JSR-236도 아직 완성 단계는 아니다. SDO(Service Data Object)

명세인 JSR-235도 방식에 있어서 유사한 해결책을 내놓고 있지만 아직 완성되지 못했다. SDO와

WorkManager API는 그 이후로 아직까지 별도 프로젝트로 진행돼 오고 있는데도 자바 EE 1.4

를 대상으로 한다. CommonJ WorkManager API로도 알려진 Timer와 WorkManager API는

웹로직(WebLogic 9.0 이후 버전)과 웹스피어(WebSphere 6.0 이후 버전)에서 모두 지원되지만

이식성이 완전히 해결되지는 않았다. 결국 CommonJ API의 오픈소스 구현체가 최근 들어 발전

하고 있는 추세다.

Page 158: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

122 l 스프링 3 레시피

여전히 혼란스러운가?

문제는 관리형 환경(또는 비관리형 환경에서도)의 컴포넌트를 위한 스레드 제어나 동시성 제

공을 위한 이식성 있고 표준화된 간단한 방법은 없다는 점이다. 자바 SE에 국한된 해법 측면에서

논의가 진행되더라도 여전히 선택해야 할 사항들이 지나치게 많다.

스프링의 해결책

스프링 2.0에서는 org.springframework.core.task.TaskExecutor 인터페이스를 사용한 통합된

해결책을 제시한다. TaskExecutor 추상 클래스는 위의 요구사항에 잘 부합한다. 스프링이 자

바 1.4를 지원했기에 TaskExecutor가 자바 1.5에 도입된 java.util.concurrent.Executor를 구현

하지는 않았다(인터페이스는 호환 가능했지만 말이다). 또한 TaskExecutor를 구현한 클래스는

Executor 인터페이스도 구현할 수 있다. 왜냐하면 정확히 같은 메서드 시그너처를 가지고 있기

때문이다. 이 인터페이스는 스프링 3.0에서도 존재하며, 스프링 2.0의 JDK 1.4와의 하위 호환성

도 갖추고 있다. 이것은 하위 버전의 JDK를 선택한 사용자들이 JDK 5가 없어도 스프링의 정교

한 기능을 활용해 애플리케이션을 만들 수 있다는 의미다. 자바 5를 기준 버전으로 채택한 스프

링 3.0에서는 TaskExecutor 인터페이스가 Executor를 상속하므로 스프링에서 제공하는 지원

기능이 코어 JDK와도 잘 동작한다.

TaskExecutor 인터페이스는 스프링 프레임워크의 내부에서 두루 사용되고 있으며, 예를 들

면 Quartz 통합(물론, 스레드 처리를 포함한)과 메시지 기반의 POJO 컨테이너 지원 기능에서는

TaskExecutor를 활용하고 있다.

// 스프링에서의 추상화

package org.springframework.core.task;

import java.util.concurrent.Executor;

public interface TaskExecutor extends Executor {

void execute(Runnable task);

}

일각에서는 다양한 해결 방안들이 코어 JDK에서 제공하는 기능들을 그대로 반영하고 있

다. 또 다른 일각에서는 독창적으로 CommonJ WorkManager와 같은 다른 프레임워크와 통

합된 상태로 제공하기도 한다. 이러한 통합은 대상 프레임워크가 기존에 존재하는 클래스의 형

태를 취하지만 다른 TaskExecutor 추상화와 같은 방식으로 다룰 수도 있다. 기존의 자바 SE의

Executor나 ExecutorService를 TaskExecutor에 맞게 조정하는 지원 기능도 있지만 어쨌든 스

프링 3.0에서는 중요하지 않다. 왜냐하면 TaskExecutor의 부모 클래스가 Executor이기 때문이

Page 159: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

02 고급 스프링 IoC 컨테이너 l 123

다. 이런 식으로 스프링의 TaskExecutor는 자바 EE와 자바 SE의 여러 가지 해결책 사이에서 가

교 역할을 한다.

TaskExecutor의 지원 기능을 간단히 살펴보자. 우선 앞에서 정의한 Runnable을 동일하게 사

용하고 있다. 코드를 사용하는 클라이언트는 간단한 스프링 빈이며, 다양한 TaskExecutor의 인

스턴스들을 주입받아 Runnable을 전달하는 단 한 가지 일만 수행한다.

package com.apress.springrecipes.spring3.executors;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import org.springframework.core.task.SimpleAsyncTaskExecutor;

import org.springframework.core.task.SyncTaskExecutor;

import org.springframework.core.task.support.TaskExecutorAdapter;

import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import org.springframework.scheduling.timer.TimerTaskExecutor;

public class SpringExecutorsDemo {

public static void main(String[] args) {

ClassPathXmlApplicationContext ctx =

new ClassPathXmlApplicationContext("context2.xml");

SpringExecutorsDemo demo = ctx.getBean(

"springExecutorsDemo", SpringExecutorsDemo.class);

demo.submitJobs();

}

@Autowired

private SimpleAsyncTaskExecutor asyncTaskExecutor;

@Autowired

private SyncTaskExecutor syncTaskExecutor;

@Autowired

private TaskExecutorAdapter taskExecutorAdapter;

/* 스케줄링이 이미 애플리케이션 컨텍스트에 설정돼 있으므로 필요하지 않다.

@Resource(name = "timerTaskExecutorWithScheduledTimerTasks")

private TimerTaskExecutor timerTaskExecutorWithScheduledTimerTasks;

*/

@Resource(name = "timerTaskExecutorWithoutScheduledTimerTasks")

private TimerTaskExecutor timerTaskExecutorWithoutScheduledTimerTasks;

@Autowired

Page 160: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

124 l 스프링 3 레시피

private ThreadPoolTaskExecutor threadPoolTaskExecutor;

@Autowired

private DemonstrationRunnable task;

public void submitJobs() {

syncTaskExecutor.execute(task);

taskExecutorAdapter.submit(task);

asyncTaskExecutor.submit(task);

timerTaskExecutorWithoutScheduledTimerTasks.submit(task);

/* 한번에 100개만 처리하고 나머지는 큐에 담는다. 즉, 전부 다해서 5초가 걸린다. */

for (int i = 0; i < 500; i++)

threadPoolTaskExecutor.submit(task);

}

}

이 애플리케이션 컨텍스트는 다양한 TaskExecutor 구현체들을 생성하는 방법을 보여준다. 대

부분 직접 생성할 수 있을 정도로 간단하며, 오직 한 경우(timerTaskExecutor)에만 팩터리 빈에게

위임한다.

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

<beans

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

xmlns:p="http://www.springframework.org/schema/p"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:util="http://www.springframework.org/schema/util"

xmlns:context="http://www.springframework.org/schema/context"

xsi:schemaLocation="

http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context-3.0.xsd

http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

http://www.springframework.org/schema/util

http://www.springframework.org/schema/util/spring-util-3.0.xsd">

<context:annotation-config />

<!-- 예제 Runnable -->

<bean

id="task" class="com.apress.springrecipes.spring3.

executors.DemonstrationRunnable" />

Page 161: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

02 고급 스프링 IoC 컨테이너 l 125

<!-- TaskExecutors -->

<bean

class="org.springframework.core.task.support.TaskExecutorAdapter">

<constructor-arg>

<bean

class="java.util.concurrent.Executors"

factory-method="newCachedThreadPool" />

</constructor-arg>

</bean>

<bean

class="org.springframework.core.task.SimpleAsyncTaskExecutor"

p:daemon="false" />

<bean

class="org.springframework.core.task.SyncTaskExecutor" />

<bean

id="timerTaskExecutorWithScheduledTimerTasks"

class="org.springframework.scheduling.timer.TimerTaskExecutor">

<property

name="timer">

<bean

class="org.springframework.scheduling.timer.TimerFactoryBean">

<property

name="scheduledTimerTasks">

<list>

<bean

class="org.springframework.scheduling.timer.

ScheduledTimerTask"

p:delay="10"

p:fixedRate="true"

p:period="10000"

p:runnable-ref="task" />

</list>

</property>

</bean>

</property>

</bean>

<bean

id="timerTaskExecutorWithoutScheduledTimerTasks"

class="org.springframework.scheduling.timer.TimerTaskExecutor"

p:delay="10000" />

Page 162: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

126 l 스프링 3 레시피

<bean

class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"

p:corePoolSize="50"

p:daemon="false"

p:waitForTasksToCompleteOnShutdown="true"

p:maxPoolSize="100"

p:allowCoreThreadTimeOut="true" />

<!-- 클라이언트 빈 -->

<bean

id="springExecutorsDemo"

class="com.apress.springrecipes.spring3.

executors.SpringExecutorsDemo" />

</beans>

앞의 코드는 TaskExecutor 인터페이스의 여러 가지 구현체들을 보여준다. TaskExecutor

Adapter 인스턴스의 첫 번째 빈은 단순히 java.util.concurrence.Executor 인스턴스의 래퍼

이므로 스프링 TaskExecutor 인터페이스와 같은 방식으로 다룰 수 있다. 스프링 3.0에서는

TaskExecutor 인터페이스가 Executor를 확장하도록 바뀌었으므로 지금은 Executor 인터페이

스를 개념적으로만 다룰 수 있어서 그다지 유용하지는 않다. 여기서는 스프링을 Executor의 인

스턴스를 구성하고 생성자의 인자값으로 전달하는 용도로만 사용했다.

SimpleAsyncTaskExecutor는 각 잡을 전달(submit)할 수 있는 새로운 �read를 제공한다. 그

스레드는 스레드 풀링(thread pooling)이나 재사용은 하지 않으며, 각 잡은 스레드 내에서 비동기

적으로 전달된다.

SyncTaskExecutor는 TaskExecutor의 구현체 중에서 가장 간단하다. 잡을 전달하는 것은 동

기적인 연산이고 �read를 띄워 실행한 다음 join()을 사용해서 즉시 해당 �read에 연결하는

것에 해당한다. 그것은 사실상 직접 요청된 스레드의 run() 메서드를 호출해 스레드 처리 전체를

건너뛰는 것과 같다.

TimerTaskExecutor는 java.util.Timer 인스턴스를 사용하고 잡을 Timer상에서 수행하는 식

으로 잡(java.util.concurrent.Callable<T> 또는 java.lang.Runnable 인스턴스)을 관리한다. 사

용자는 TimerTaskExecutor가 언제 생성되고, 전달된(submitted) 잡이 언제 실행될지를 나타내

는 지연 시간(delay)을 지정할 수 있다. 내부적으로 TimerTaskExecutor는 Callable<T>과 전달

된 Runnable 인스턴스들을 TimerTask로 변환하고 Timer에 스케줄링되게 한다. 여러 잡을 스

케줄한 경우 동일한 Timer의 동일한 스레드상에서 연이어 실행될 것이다. Timer를 명시적으로

지정하지 않으면 기본 Timer가 생성된다. Timer에 TimerTask들을 명시적으로 등록하려면 org.

springframework.scheduling.timer.TimerFactoryBean의 scheduledTimerTasks 프로퍼티를

Page 163: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

02 고급 스프링 IoC 컨테이너 l 127

사용하면 된다. TimerTaskExecutor에는 Timer 클래스처럼 고급 스케줄링을 위한 메서드를 제

공하지 않는다. 특정 시점(Date)이나 일정한 주기로 스케줄링하려면 TimerTask를 직접 다뤄야

한다. 이를 위해서는 org.springframework.scheduling.timer.ScheduledTimerTask를 사용하

면 되고 ScheduledTimerTask에서는 TimerFactoryBean이 적절히 스케줄링하게 될 TimerTask

를 손쉽게 설정할 수 있게 제공한다.

다른 TaskExecutors로 했던 것과 같이 잡을 전달하려면 약간의 기다림 후에 간단히 Timer

FactoryBean를 설정하고 평소처럼 잡을 전달하면 된다.

<bean id="timerTaskExecutorWithoutScheduledTimerTasks"

class="org.springframework.scheduling.timer.TimerTaskExecutor" p:delay="10000" />

일정 간격으로 실행하는 것처럼 좀더 복잡한 스케줄링을 하려면 TimerTask를 명시적으로 설

정해야 한다. 잡을 직접 전달하는 것은 실제로는 별로 낫지 않다. 좀더 고급 기능이 필요하다면

Quartz와 같이 크론(cron) 표현식을 지원하는 것을 사용해야 할 것이다.

<bean

id="timerTaskExecutorWithScheduledTimerTasks"

class="org.springframework.scheduling.timer.TimerTaskExecutor">

<property

name="timer">

<bean

class="org.springframework.scheduling.timer.TimerFactoryBean">

<property

name="scheduledTimerTasks">

<list>

<bean

class="org.springframework.scheduling.timer.ScheduledTimerTask"

p:delay="10"

p:fixedRate="true"

p:period="10000"

p:runnable-ref="task" />

</list>

</property>

</bean>

</property>

</bean>

마지막 예제는 �readPoolTaskExecutor로서 java.util.concurrent.�readPoolExecutor상에

구현된 완전한 스레드 풀 구현체다.

IBM 웹스피어 6.0이나 BEA 웹로직 9.0에서 사용할 수 있는 CommonJ WorkManager/

TimerManager를 사용해 애플리케이션을 개발하고 싶다면 org.springframework.scheduling.

Page 164: 스프링 3 레시피 : 친절한 스프링 프레임워크 과제 해결서

128 l 스프링 3 레시피

commonj.WorkManagerTaskExecutor를 사용하면 된다. 이 클래스는 웹스피어나 웹로직에

서 이용할 수 있는 CommonJ Work Manager에 레퍼런스를 위임한다. 보통 CommonJ Work

Manager에 적절한 자원을 가리키는 JNDI 레퍼런스를 제공한다. 앞의 방법으로 했을 때

(Geronimo와 함께 사용하는 경우 등에) 충분히 잘 동작하지만, JBoss나 글래시피시에서 사

용하려면 추가적인 작업이 더 필요하다. 스프링은 JCA 지원 기능을 서버에서 제공하는 기능에

위임하는 클래스들을 제공한다. 글래시피시에서는 org.springframework.jca.work.glassfish.

GlassFishWorkManagerTaskExecutor를 사용하며, JBoss에서는 org.springframework.jca.

work.jboss.JBossWorkManagerTaskExecutor를 사용한다.

TaskExecutor 지원 기능은 통합된 인터페이스를 통해 애플리케이션 서버의 스케줄링 서비스

에 접근할 수 있는 매우 유용한 방식을 제공한다. 어떠한 서버(톰캣과 제티 포함)에도 배포할 수

있는 (훨씬 무겁지만) 더 견고한 지원 기능을 찾는다면 스프링의 Quartz 지원 기능을 고려해보

는 것도 좋다.

정리

이번 장에서는 생성자 호출, 스태틱/인스턴스 팩터리 메서드 호출, 팩터리 빈의 사용, 스태틱 필

드/객체 프로퍼티로부터 객체를 얻는 방법을 비롯해 빈을 생성하는 다양한 방법을 배웠다. 스프

링 IoC 컨테이너는 이와 같이 여러 가지 방법으로 쉽게 빈을 생성할 수 있다. 스프링에서는 빈이

요청됐을 때 어느 빈이 반환될지 결정하는 빈 범위를 지정할 수 있다. 기본 빈 범위는 싱글턴이며

(스프링이 스프링 IoC 컨테이너당 하나의 빈 인스턴스를 생성해 공유함), 자주 사용하는 또 다른

범위로는 프로토타입이 있다(빈이 요청될 때마다 스프링에서 새로운 빈 인스턴스를 생성함).

빈의 초기화 및 소멸을 위해서는 해당하는 콜백 메서드를 지정해 커스터마이징할 수 있으며,

빈이 특정 Aware 인터페이스를 구현하면 컨테이너의 설정과 기반구조를 파악하게 할 수 있다.

스프링 IoC 컨테이너는 이러한 메서드들을 빈 생명주기의 특정 시점에 따라 호출해준다.

스프링은 IoC 컨테이너에 빈 후처리기를 등록해 빈의 초기화 콜백 메서드 전후로 추가적인 처

리를 할 수 있다. 빈 후처리기는 IoC 컨테이너에 있는 모든 빈에서 처리할 수 있는데, 보통 빈 후

처리기는 빈 프로퍼티의 유효성 검증을 목적으로 사용하거나 특정 기준값에 따라 빈 프로퍼티

를 변경할 때 주로 사용한다.

아울러 빈 설정을 외부화해서 프로퍼티 파일로 분리하는 기능과 리소스 번들에서 텍스트 메

시지를 해석하는 기능, 애플리케이션 이벤트를 발행하고 리스닝하는 기능, 프로퍼티 에디터로 텍

스트 값을 프로퍼티 값으로 변환하는 기능, 외부 자원을 읽어들이는 기능 등의 고급 IoC 컨테이