Anyframe

Version 4.5.0

본 문서의 저작권은 삼성SDS에 있으며 Anyframe 오픈소스 커뮤니티 활동의 목적하에서 자유로운 이용이 가능합니다. 본 문서를 복제, 배포할 경우에는 저작권자를 명시하여 주시기 바라며 본 문서를 변경하실 경우에는 원문과 변경된 내용을 표시하여 주시기 바랍니다. 원문과 변경된 문서에 대한 상업적 용도의 활용은 허용되지 않습니다. 본 문서에 오류가 있다고 판단될 경우 이슈로 등록해 주시면 적절한 조치를 취하도록 하겠습니다.


I. Overview
1. 특징
2. 주요 기능
2.1. Lightweight 컨테이너
2.1.1. POJO 기반 개발 지원
2.1.2. Dependency Resolution 지원
2.1.3. Aspect Oriented Programming 지원
2.1.4. Life-cycle 관리
2.1.5. 신규 기능 추가 용이
2.2. 기술 공통 서비스
2.2.1. Generic Service
2.2.2. DataSource 서비스
2.2.3. Query 서비스
2.2.4. ID Generation 서비스
2.2.5. Properties 서비스
2.2.6. Transaction 서비스
2.2.7. Hibernate 서비스
2.2.8. Logging 서비스
2.2.9. Remoting 서비스
2.2.10. Web Services
2.3. 웹 화면 개발 시 필요한 공통 기능
2.3.1. 화면 흐름 제어(Screen Workflow Control)
2.3.2. 공통 클래스 제공
2.3.3. 국제화(i18N), 지역화(L10n) 지원
2.3.4. 사용자 입력값 유효성 검증(Validation)
2.3.5. Tag Library를 통한 View 개선
2.3.6. 에러 처리(Exception Handling)
2.3.7. 요청(Request) 권한 처리
2.3.8. Double Form Submission 방지
2.3.9. 다양한 웹 클라이언트(UI) 연계 지원(X-internet Integration)
II. Installation
3. Installation
3.1. [선택] Maven 설치 및 환경 설정
3.2. Resources
3.3. [필수] Basic Archetype 설치
3.4. [필수] Plugin 설치
3.5. [선택] Plugin 설치 확인
3.6. [선택] Eclipse WTP만 이용하여 Tomcat 실행
3.7. [선택] Plugin Catalog Update
3.8. [선택] Plugin Update
3.9. [선택] 샘플 DB 변경
4. Anyframe Plugins
4.1. Plugin 목록
4.2. anyframe-basic-archetype 구조
4.3. Plugin 구조
4.4. Custom Plugin 추가 정의
5. Anyframe Commands
5.1. anyframe-maven-plugin 정의 확인
5.2. Command 목록
III. Foundation Plugin
6. Overview
6.1. Foundation Plugin 설치하기
7. Spring
7.1. IoC(Inversion of Control)
7.1.1. Basic
7.1.2. Dependencies
7.1.3. Method Injection
7.1.4. Bean과 Container의 확장
7.1.5. XML 스키마 기반 설정
7.2. Annotation
7.2.1. Bean Management
7.2.2. Dependency Injection
7.2.3. LifeCycle Annotation
7.2.4. Resources
7.3. Java based Configuration
7.3.1. Bean Management
7.3.2. Combining Java and XML Configuration
7.3.3. Instantiating spring container
7.3.4. Resources
7.4. AOP(Aspect Oriented Programming)
7.4.1. AOP 구성 요소
7.4.2. Annotation based AOP
7.4.3. XML based AOP
7.4.4. AspectJ based AOP
7.4.5. AOP Examples
7.4.6. Resources
7.5. SpEL(Spring Expression Language)
7.5.1. Bean Definition using SpEL
7.5.2. Expression Evaluation using Spring's Expression Interface
7.5.3. Language Reference
7.5.4. Resources
7.6. DataSource
7.6.1. JDBCDataSource Configuration
7.6.2. DBCPDataSource Configuration
7.6.3. C3P0DataSource Configuration
7.6.4. JNDIDataSource Configuration
7.6.5. Test Case
7.6.6. Resources
7.7. Transaction Management
7.7.1. Declarative Transaction Management
7.7.2. Programmatic Transaction Management
7.7.3. Resources
8. Spring MVC
8.1. Architecture
8.2. Configuration
8.2.1. web.xml 작성
8.2.2. action-servlet.xml 작성
8.3. Controller
8.3.1. Configuration
8.3.2. 컨트롤러 구현
8.3.3. Double Form Submission 방지
8.4. View
8.4.1. Apache Tiles
8.5. Validation
8.5.1. Spring Validator
8.5.2. Spring 3 Validation
8.5.3. Resources
8.6. Data Binding and Type Conversion
8.6.1. PropertyEditor
8.6.2. Spring 3 Type Conversion
8.6.3. Spring 3 Formatting
8.7. File Upload
8.8. Internationalization
8.8.1. 다국어 지원 기능
8.8.2. Locale Resolver
8.9. Exception Handling
8.9.1. 특정 error 페이지로 이동하여 에러 메시지 출력
8.9.2. 에러 페이지에 에러 메시지 출력
8.9.3. Presentation Layer에서 message key를 이용한 locale 변경
8.10. REST Style Web Application
8.11. Spring Integration
8.11.1. Listener 등록과 Spring 설정 파일 목록 위치 정의
8.11.2. Dependency Injection을 통한 Business Service 호출
8.11.3. Resources
9. Spring MVC Extensions
9.1. Tag library
9.1.1. Page Navigator Tag
9.1.2. Message Tag
10. Id Generation
10.1. UUIdGenerationService
10.1.1. Samples
10.2. SequenceIdGenerationService
10.2.1. Samples
10.3. TableIdGenerationService
10.3.1. Samples
10.4. How to use a Generation Strategy
10.4.1. MixPrefix property 정의 방법
10.4.2. Id Generation Strategy를 implements하는 방법
10.5. Resources
11. Logging
11.1. Configuration
11.1.1. appender
11.1.2. logger
11.1.3. root
11.2. Logging
11.2.1. 기본적인 사용 방법
11.2.2. ResourceBundle을 이용하는 방법
11.3. SQL Logging
11.3.1. Step 1. Log4jdbc 라이브러리 다운로드
11.3.2. Step 2. Simple Logging Facade for Java 라이브러리 다운로드
11.3.3. Step 3. DataSource 속성 정의
11.3.4. Step 4. Query 서비스 속성 정의
11.3.5. Step 5. Logger 정의
11.4. Resources
12. Message Source
13. Query
13.1. Configuration
13.1.1. jdbcTemplate
13.1.2. sqlRepository
13.1.3. pagingSQLGenerator
13.1.4. lobHandler
13.1.5. Samples
13.1.6. TestCase
13.2. Mapping XML Files
13.2.1. table-mapping 정의 방법
13.2.2. queries 정의 방법
13.3. Usecases
13.3.1. Result Mapping
13.3.2. Embedded SQL
13.3.3. OR Mapping
13.3.4. Dynamic Query
13.3.5. Pagination
13.3.6. Batch Update
13.3.7. Callable Statement
13.3.8. CLOB, BLOB
13.3.9. Named Parameter 'vo' 활용
13.3.10. extends AbstractDAO
13.3.11. implements IResultSetMapper
13.4. Resources
14. Properties Service
14.1. PropertiesServiceImpl
14.1.1. Samples
14.2. Sample Property File
14.3. Dynamic Reloading
14.4. Resources
IV. Hibernate Plugin
15. Overview
15.1. Hibernate Plugin 설치하기
16. Hibernate
16.1. Configuration
16.1.1. DataSource 속성 정의
16.1.2. Generated SQL 속성 정의
16.1.3. Cache 속성 정의
16.1.4. Logging 속성 정의
16.1.5. 기타 속성 정의
16.1.6. 매핑 파일 정의
16.2. Mapping File
16.2.1. Mapping File의 작성
16.2.2. Data Type의 매핑
16.2.3. Hibernate Generator
16.3. Persistence Mapping
16.3.1. Persistence Mapping - Association
16.3.2. Persistence Mapping - Inheritance
16.4. Basic CRUD
16.4.1. 단건 조회
16.4.2. 단건 저장
16.4.3. 단건 수정
16.4.4. 단건 저장 또는 수정
16.4.5. 단건 삭제
16.4.6. 복수건 저장
16.5. HQL(Hibernate Query Language)
16.5.1. 구성 요소
16.5.2. 기본적인 사용 방법
16.5.3. 원하는 객체 형태로 전달
16.5.4. XML에 HQL 정의하여 사용
16.5.5. Pagination
16.5.6. HQL을 이용한 CUD
16.6. Criteria Queries
16.6.1. 기본적인 사용 방법
16.6.2. 원하는 객체 형태로 전달
16.6.3. Pagination
16.7. Native SQL
16.7.1. 기본적인 사용 방법
16.7.2. XML에 Native SQL 정의하여 사용
16.7.3. Pagination
16.7.4. Callable Statement
16.8. Performance Strategy
16.8.1. Cache
16.8.2. Fetch Strategy
16.9. Concurrency
16.9.1. Optimistic Locking
16.9.2. Pessimistic Locking
16.9.3. Offline Locking
16.10. Transaction Management
16.10.1. JDBC - HibernateTransactionManager
16.10.2. JTA - JTATransactionManager
16.11. Spring Integration
16.11.1. Hibernate 속성 정의 파일 작성
16.11.2. Mapping XML 파일 작성
16.11.3. DAO 클래스 생성
16.11.4. Test Code 작성
16.11.5. 선언적인 트랜잭션 관리
17. Dynamic Hibernate
17.1. Configuration
17.2. Dynamic HQL(Hibernate Query Language)
17.3. Dynamic Native SQL
V. Miplatform Plugin
18. Overview
18.1. MiPlatform Plugin 설치하기
19. Miplatform Integration
19.1. MipController
19.2. MipQuery
19.2.1. MiPQueryService 활용
19.2.2. RiaQueryService
20. Miplatform Simplification
20.1. Miplatform Service
20.1.1. Controller
20.1.2. Service
20.1.3. Dao
20.1.4. Extension of MiPServiceImpl
20.1.5. Testcase
VI. Mipsample Plugin
21. Overview
21.1. MiPSample Plugin 설치하기
22. Miplatform UI Sample
22.1. Architecture
22.1.1. Presentation Layer
22.1.2. Business Layer
22.2. Sample UI
22.2.1. Introduction
22.2.2. Set of Sample UI
22.3. Standards
22.3.1. Naming Rules of Form
22.3.2. Naming Rules of UI Component
22.3.3. Naming Rules of Variable
22.3.4. Naming Rules of Function
22.4. Working with Common Flow
22.4.1. Common Script
22.4.2. Common Dataset
22.4.3. Example
22.5. Validation
22.5.1. Using UI Component
22.5.2. Using Script
22.6. Internationalization (i18n)
22.6.1. Domain
VII. Cache Plugin
23. Overview
23.1. Cache Plugin 설치하기
24. Cache
24.1. DefaultCacheService
24.1.1. Samples
24.2. Resources
VIII. CXF Plugin
25. Overview
25.1. CXF Plugin 설치하기
26. Web Services
26.1. Web Services 개념
26.1.1. Architecture
26.1.2. SOAP(Simple Object Access Protocol)
26.1.3. WSDL(Web Services Description Language)
26.1.4. 기술 표준
26.2. 구현 기술
26.2.1. JAX-RPC vs. JAX-WS
26.2.2. XML Schema
26.2.3. 기타 구현 기술
26.3. Web Services Framework
26.3.1. Web Services Framework 종류
26.3.2. Apache CXF 특징
26.4. Tools
26.5. Databinding
26.5.1. JAXB Databinding
26.5.2. Aegis Databinding
26.5.3. MTOM Databinding
26.5.4. Resources
26.6. JAX-WS Frontend
26.6.1. Web Service 작성
26.6.2. Spring Configuration XML - jaxws:endpoint tag 사용
26.6.3. Spring Configuration XML - jaxws:server tag 사용
26.6.4. Server: JAX-WS Frontend API 사용
26.6.5. Spring Configuration XML - jaxws:client tag 사용
26.6.6. Client: JAX-WS Frontend API 사용
26.6.7. Annotation 작성
26.6.8. [참고] Spring Configuration XML Schema
26.6.9. Resources
26.7. Simple Frontend
26.7.1. Web Services 작성
26.7.2. Server: Simple Frontend API 코드 사용
26.7.3. Spring Configuration XML - simple:server tag 사용
26.7.4. Client: Simple Frontend API 코드 사용
26.7.5. Spring Configuration XML - simple:client tag 사용
26.7.6. [참고] Spring Configuration XML Schema
26.7.7. Resources
26.8. Asynchronous Invocation
26.8.1. Server Configuration
26.8.2. Client Configuration
26.8.3. Resources
26.9. RESTful Services
26.9.1. JAX-RS 활용한 RESTful 서비스 구현
26.9.2. HTTP Binding(JRA) 활용한 RESTful 서비스 구현
26.9.3. HTTP Binding(Naming Convention) 활용한 RESTful 서비스 구현
26.9.4. JAX-WS Provider/Dispatch API 활용한 RESTful 서비스 구현
26.9.5. [참고] Spring Configuration XML Schema
26.9.6. Resources
26.10. WAS(Web Application Server) Configuration
26.10.1. Tomcat
26.10.2. JEUS
26.10.3. WebLogic
IX. Jasper Plugin
27. Overview
27.1. Jasper Plugin 설치하기
28. JasperReports Integration
28.1. Configuration
28.1.1. web.xml 작성하기
28.1.2. jasper-servlet.xml 작성하기
28.2. Controller
28.2.1. HTML Reporting
29. JasperReports Designer
29.1. Installation
29.1.1. 다운로드
29.1.2. 설치 환경
29.1.3. Report Designer 설치
29.2. Design Report
29.2.1. 목표 결과물
29.2.2. 디자인 파일(JRXML) 작성
X. Monitoring Plugin
30. Overview
30.1. Monitoring Plugin 설치하기
XI. Remoting Plugin
31. Overview
31.1. Remoting Plugin 설치하기
32. Spring Remoting
32.1. RMI(Remote Method Invocation)
32.1.1. Server Configuration
32.1.2. Client Configuration
32.2. Hessian
32.2.1. Server Configuration
32.2.2. Client Configuration
32.2.3. Hessian과 Burlap의 차이점
32.3. Burlap
32.3.1. Server Configuration
32.3.2. Client Configuration
32.3.3. Hessian과 Burlap의 차이점
32.4. HTTP Invoker
32.4.1. Server Configuration
32.4.2. Client Configuration
XII. Scheduling Plugin
33. Overview
33.1. Scheduling Plugin 설치하기
34. Spring Scheduling
34.1. TaskExecutor
34.2. TaskScheduler
34.3. XML based Scheduling
34.4. Annotation based Scheduling & Asynchronous Execution
34.4.1. Scheduling
34.4.2. Asynchronous Execution
34.5. Resources
35. Quartz Integration
35.1. Quartz Scheduler
35.1.1. Advanced Quartz
35.1.2. Samples
XIII. Security Plugin
36. Overview
36.1. Security Plugin 설치하기
XIV. Struts Plugin
37. Overview
37.1. Security Plugin 설치하기
38. Struts
38.1. Architecture
38.1.1. Controller Structure
38.1.2. Request의 흐름
38.2. Configuration
38.2.1. web.xml
38.2.2. struts-config.xml
38.3. Controller
38.3.1. ActionServlet
38.3.2. RequestProcessor
38.3.3. Action
38.3.4. ActionForward
38.3.5. Actions Package
38.4. View
38.4.1. Taglib
38.4.2. Tiles
38.5. Internationalization
38.5.1. Internationalization의 특징
38.5.2. Internationalization Sample
38.6. Validator
38.6.1. Plug-in 등록
38.6.2. Validator Rules
38.6.3. ActionForm
38.6.4. formset 설정
38.6.5. Action 매핑 설정
38.7. Exception Handling
38.7.1. Global Level Exception Handling
38.7.2. Action Level Exception Handling
39. Struts Extensions
39.1. Controller
39.1.1. DefaultActionServlet
39.1.2. DefaultRequestProcessor
39.1.3. AbstractActionSupport
39.1.4. DefaultDispathActionSupport
39.1.5. DefaultForwardAction
39.1.6. AnyframeMiPAction
39.2. View
39.2.1. Tag library
39.3. Preventing Double Form Submission
39.3.1. Double Submit의 개념
39.3.2. 일반적인 Token 처리
39.3.3. 선언적인 Token 처리
39.4. Exception Handling
39.4.1. 선언적인 Exception Handling
39.4.2. DefaultBaseExceptionHandler 확장
39.5. Authentication and Authorization
39.5.1. Authentication
39.5.2. Authorization
39.6. Spring Integration
39.6.1. Configuration
39.6.2. Action
XV. Webflow Plugin
40. Overview
40.1. WebFlow Plugin 설치하기
41. Spring Webflow
41.1. Configuration
41.1.1. 기본 설정
41.1.2. Spring MVC와 연계하기 위한 설정
41.2. Flow
41.2.1. 필수 요소
41.2.2. 메소드 호출
41.2.3. Transition Decision
41.2.4. Expression Language
41.2.5. Subflow
41.2.6. 플로우 상속
41.3. View
41.3.1. model 바인딩
41.3.2. view backtracking
41.4. Validator
41.4.1. model 객체 내에 validate 메소드 구현
41.4.2. validator 클래스 및 메소드 구현
XVI. Simpleweb Plugin
42. Overview
42.1. Simpleweb Plugin 설치하기
43. Simplification of web
43.1. Configuration
43.1.1. Tiles View 설정
43.1.2. Tiles Configurer 설정
43.1.3. JSON View 설정
43.2. Controller
43.2.1. AbstractServiceController
43.2.2. SimpleServiceController
43.2.3. SimpleMapServiceController
43.2.4. SimpleJSONController
43.2.5. SimpleJSONTreeController
43.2.6. FileUploadController
43.3. Tag Library
43.3.1. Spring JS 연계
43.3.2. submit tag
43.3.3. link tag
43.3.4. setProperty tag
43.3.5. doublesubmit tag
43.3.6. init tag
43.3.7. validation tag
43.3.8. model tag
43.3.9. tiles tag
43.3.10. validate tag
43.4. VO 기반의 Simpleweb 개발
43.4.1. Step 1. JSP 호출
43.4.2. Step 2. JSP 개발
43.5. Map 기반의 Simpleweb 개발
43.6. JSON 기반의 Simpleweb 개발
43.6.1. jqgrid
43.6.2. quickpager
43.6.3. autocomplete
43.6.4. jstree
43.6.5. ui-tab
43.6.6. dropdown
43.6.7. uploadify

I.Overview

Anyframe은 Spring 기반에서 다양한 best-of-breed 오픈 소스를 통합 및 확장하여 구성한 어플리케이션 프레임워크와 MVC 아키텍처를 준수하여 웹 어플리케이션의 프리젠테이션 레이어를 구조적으로 개발할 수 있도록 지원하는 웹 프레임워크를 제공한다. 또한 프레임워크를 기반으로 업무용 프로그램 개발을 효과적으로 진행할 수 있도록 기술 공통 서비스, 템플릿 기반의 프로젝트 구조 및 샘플 코드, 매뉴얼 등을 제공함으로써 설계 및 개발 기간을 단축하고 유지보수를 용이하게 진행할 수 있도록 지원한다.

1.특징

Anyframe은 다음과 같은 특징을 제공한다.

  • 오픈 소스 통합 및 Best Practice 제공을 위한 플러그인 환경 제공 : 사용자가 원하는 Plugin 들을 적절히 선택하고 설치함으로써 해당 프로젝트에 최적화된 샘플 프로젝트를 손쉽게 구성할 수 있도록 지원한다. 자세한 내용은 Anyframe Plugins를 참고하도록 한다.

  • 순수 객체 중심의(POJO) 어플리케이션 개발 지원 : 프레임워크로 인해서 기본 설계와 상세 설계가 이중으로 진행되거나, 개발 시 설계 모델이 구현체와 불일치 되는 것을 줄이기 위해 순수 객체 중심의(POJO) 어플리케이션 개발을 지원한다.

  • Dependency Injection을 통한 의존 관계 처리 : 인터페이스 중심의 개발을 가이드하고 객체나 컴포넌트간의 참조 관계는 Dependency Injection을 통해 처리함으로써 구현체의 변경에 따른 영향력을 최소화한다.

  • 개발자는 비즈니스 로직에만 집중하여 구현 : 로깅, 트랜잭션, 예외처리 등과 같은 비기능 영역에 대한 코드가 업무 기능 개발 영역에서 분리될 수 있도록 함으로써, 개발자는 비즈니스 로직에만 집중하여 구현하도록 한다.

  • 재사용 가능한 기술 공통 서비스 제공 : DB 접근 및 SQL 처리, 캐쉬, WAS와 연동 등과 같은 중요 기능에 대해 재사용 가능한 기술 공통 서비스를 제공함으로써 보다 빠르고 안정적인 개발을 지원한다.

  • 선언적으로 트랜잭션 통제 : Java EE 환경과 독립적으로 JTA이나 JDBC 데이터 소스에 대해 별도의 트랜잭션 처리를 위한 코딩없이 간단한 설정만으로 선언적으로 트랜잭션을 통제할 수 있는 기능을 지원한다.

  • Singleton, Factory 패턴 등 유용한 패턴 실행 지원 : 직접적인 패턴 구현 없이도 Singleton, Factory 패턴 등의 실행을 지원함으로써, 어플리케이션 개발시 인스턴스의 생성 관리, 데이터 무결성 확보 등을 위해 유용한 패턴 등을 직접 구현하는 어려움을 해결해준다.

  • MVC Model2 아키텍처 제공 : Layered Architecture에 기반한 Java EE 웹 어플리케이션을 작성할 때 프리젠테이션 로직과 비지니스 로직을 완전히 분리하여 프리젠테이션 레이어를 구조적으로 개발할 수 있다.

  • 웹 화면 개발 시 필요한 공통 기능 제공 : 어플리케이션 개발에 공통적으로 필요한 화면흐름 제어, 에러처리, 일원화된 권한처리 등 다양한 부분을 프레임워크화하여 Model2 방식의 일관되고 쉬운 개발을 지원한다.

  • Struts/Spring MVC의 활용 최적화 : 프로젝트에서 많이 사용되고 있는 Struts와 Spring MVC를 기반으로 구성된 웹 프레임워크로 둘 중 어느 것을 선택하더라도 Struts와 Spring MVC의 기능들이 최적화된 형태로 활용될 수 있도록 가이드되고 있다.

  • 다양한 웹 클라이언트 기술과 용이한 연계 가능 : 최근 관심이 높아지고 있는 Ajax, 상용 X-internet 툴 등 다양한 웹 클라이언트 기술과 쉽게 연동되는 구조를 제공한다.

2.주요 기능

Anyframe을 통해 활용할 수 있는 주요 기능은 다음과 같다.

2.1.Lightweight 컨테이너

Anyframe의 Lightweight 컨테이너는 순수 POJO(Plain Old Java Objects) 기반 개발을 지원하며, 순수 POJO 기반으로 설계/개발된 모듈들을 엮어서 해당 어플리케이션이 제대로 된 기능을 제공할 수 있도록 지원한다. 반면에 EJB와 같은 컨테이너 기반에서 어플리케이션을 개발하기 위해서는 개발자가 해당 컨테이너에 종속된 인터페이스를 구현해야 하거나 정의된 컴포넌트 모델을 그대로 준수해야 한다. 즉, 전형적인 컨테이너는 정의된 개발 모델을 강제하기 때문에 어플리케이션 코드 내에 컨테이너 의존적인 코드가 추가될 수 밖에 없게 된다.

Anyframe의 Lightweight 컨테이너는 다음과 같은 특징을 가지고 있다.

2.1.1.POJO 기반 개발 지원

설계 결과물에 컨테이너 의존적인 코드를 추가하지 않아도 순수 POJO 기반으로 어플리케이션 개발이 가능하도록 지원한다. 즉, Lightweight 컨테이너 기반 개발시 프레임워크로 인한 기본 설계와 상세 설계가 이중으로 진행되거나, 개발시 설계 모델과 구현체가 불일치되는 것을 방지할 수 있다.

2.1.2.Dependency Resolution 지원

어플리케이션 구성 모듈간 의존 관계를 처리하기 위한 방법을 제공한다. 특정 모듈의 코드 내에서 참조할 모듈을 직접적으로 생성하여 참조함으로써 참조 모듈간에 tightly-coupled 되지 않도록 하기 위해, 대부분의 Lightweight 컨테이너들과 마찬가지로 DI(Dependency Injection)와 DL(Dependency Lookup)을 지원하다.

  • DI란 각 클래스 사이의 의존 관계를 설정 정보를 바탕으로 컨테이너가 자동적으로 연결해주는 것을 말한다. 컨테이너가 참조 관계를 자동적으로 연결시켜주기 때문에 개발자들이 컨테이너 API를 이용하여 의존 관계에 관여할 필요가 없게 되므로 특정 컨테이너의 API에 종속되는 것을 줄일 수 있고 개발자들은 단지 설정 파일에 참조 관계가 필요하다는 정보를 추가적으로 정의해 주기만 하면 된다.

  • DL은 의존 관계에 놓인 특정 모듈을 사용하기 위해 개발자가 해당 모듈의 소스 코드 내에서 리소스들을 관리하는 컨테이너를 통해 직접적으로 찾는 것을 말한다.

    • [1] Dependency Injection : 각 서비스 사이의 의존 관계를 속성 파일을 기반으로 컨테이너가 자동 처리

    • [2] Service Registration : 속성 파일을 기반으로 서비스 컨테이너의 서비스 목록에 해당 서비스 등록

    • [3] Service Lookup : 컨테이너에서 제공하는 API를 이용하여 사용하고자 하는 서비스 Lookup

    • [4] Retrieve Service Reference : 컨테이너는 해당 서비스의 인스턴스를 찾아 전달

    • [5] Invoke Methods : 클라이언트에서는 전달받은 인스턴스에 대해 특정 메소드 호출을 통해 원하는 기능 수행

2.1.3.Aspect Oriented Programming 지원

  • AOP는 어플리케이션 전체에 걸쳐 사용되나 쉽게 분리된 모듈로 작성하기 힘든 로깅, 인증, 권한체크, DB 연동, 트랜잭션, 락킹, 에러처리 등과 같은 공통 기능을 재사용 가능하도록 컴포넌트화 할 수 있는 기법이다. AOP에서는 이러한 공통 기능을 Crosscutting Concerns, 해당 어플리케이션이 제공하는 비즈니스 기능을 Core Concerns라고 지칭한다.

  • 즉, Core Concerns 모듈 내에 필요한 Crosscutting Concerns를 직접 추가하는 대신에 AOP에서는 Weaving이라는 작업을 통해 Core Concerns 모듈의 코드를 직접 건드리지 않고도 Core Concerns 모듈의 사이 사이에 필요한 Crosscutting Concerns 코드가 엮어져 동작되도록 한다. 이를 통해 AOP는 기존의 작성된 코드들을 수정하지 않고도 필요한 Crosscutting Concerns 기능을 효과적으로 적용해 낼 수도 있게 되는 것이다.

AOP에는 이외에도 새로운 용어가 많이 등장한다. AOP를 이용하여 개발을 수행하기 위해서는 본 매뉴얼의 AOP(Aspect Oriented Programming) 부분을 참조하도록 한다.

2.1.4.Life-cycle 관리

Lightweight 컨테이너는 정의된 모듈의 Life-cycle 관리 즉, 해당 모듈들을 초기화시키고 종료시키는 역할을 수행함으로써 개발자가 비즈니스 로직에 집중하여 개발할 수 있게 된다.

2.1.5.신규 기능 추가 용이

XML 기반의 설정을 통해서 간단하게 컨테이너 기반 위에 신규 기능을 추가할 수 있도록 지원한다.

2.2.기술 공통 서비스

Anyframe은 자체 개발 또는 오픈 소스 활용 및 확장을 통해 어플리케이션 개발시 용이하게 재사용할 수 있는 Cache, DB 연결, 쿼리문 처리, 트랜잭션 관리, 로깅 등과 같은 다양한 기술 공통 서비스들을 제공한다. 이러한 기술 공통 서비스들은 앞서 언급한 Lightweight 컨테이너에서 동작 가능하도록 설계/개발되었으며, 인터페이스와 구현 클래스로 분리되어 구현되어 있으므로, 인터페이스 규약에 맞게 구현 클래스를 추가하거나 제공된 구현 클래스를 확장함으로써 언제든지 해당 어플리케이션의 용도에 맞게 변경이 용이하다. Anyframe에서 제공하는 주요 기술 공통 서비스는 다음과 같으며 이에 대한 보다 자세한 사항은 매뉴얼을 참조하도록 한다.

2.2.1.Generic Service

Java 5부터 지원하는 Generics 개념을 기반으로 개발되었으며, 도메인 클래스 기반의 Service 인터페이스/구현 클래스, DAO 인터페이스/구현 클래스(Hibernate/JPA, Query Service 지원) 등 기본 CRUD 메소드 기능이 모두 구현된 클래스를 직접 이용하거나 상속받아서 사용할 수 있는 기능을 제공하고 있다.

2.2.2.DataSource 서비스

주어진 데이터베이스에 연결하여 생성된 Connection 객체를 전달해주는 서비스이다.

2.2.3.Query 서비스

쿼리문이나 객체의 입력만으로 데이터베이스 내에 저장된 데이터 조작을 가능하게 하는 서비스이다. Query 서비스는 JDBC(Java Database Connectivity)를 이용한 데이터 액세스 수행 부분을 추상화함으로써 간편한 데이터 액세스 방법을 제공하고, JDBC 사용시 발생할 수 있는 공통 에러를 줄여준다.

2.2.4.ID Generation 서비스

시스템 개발 시 공통적으로 많이 쓰이는 기능 중의 하나로, 해당 항목에 대해 유일한 ID를 생성하기 위해 사용할 수 있는 서비스이다.

2.2.5.Properties 서비스

외부 파일이나 환경 정보에 구성되어 있는 key, value의 쌍을 내부적으로 가지고 있으며, 어플리케이션이 특정 key에 대한 value에 접근할 수 있도록 해주는 서비스이다.

2.2.6.Transaction 서비스

Transaction 관리에 대하여 일관성 있는 추상화된 방법을 제공하는 서비스로, Spring에서 제공하는 TransactionManager를 그대로 사용한다. Anyframe 매뉴얼에서는 Spring의 TransactionManager 활용 방법에 대해 가이드하고 있다.

2.2.7.Hibernate 서비스

객체 모델링(Object Oriented Modeling)과 관계형 데이터 모델링(Relational Data Modeling) 사이의 불일치를 해결해 주는 ORM 도구인 Hibernate를 효율적으로 사용할 수 있도록 세부 항목별로 활용 방법을 가이드하고 있다. 또한 Hibernate 기반에서도 입력 조건에 따라 HQL(Hibernate Query Language), Native SQL을 Dynamic하게 변경할 수 있도록 Velocity와 연계한 DynamicHibernateService를 추가로 제공한다.

2.2.8.Logging 서비스

Logging 서비스는 Log4j를 그대로 이용하며 이를 통해 테스팅 코드와 운영 코드를 동일하게 가져가면서, 로깅을 선언적으로 관리할 수 있고, 운영시 성능 오버헤드를 최소화할 수 있게 한다. Anyframe 매뉴얼에서는 Log4j 활용 방법에 대해 가이드하고 있다. 또한 Log4jdbc3를 이용하여 실행된 SQL을 Logging할 수 있는 방법도 제시한다.

2.2.9.Remoting 서비스

클라이언트 어플리케이션과 원격 어플리케이션에서 제공하는 서비스 간의 의사소통 기능을 제공하는 서비스이다. Anyframe 매뉴얼에서는 Spring Remoting 기능을 그대로 활용한 원격 기술 유형(RMI, Hessian/Burlap, HTTP Invoker)별 활용 방법에 대해 가이드하고 있다.

2.2.10.Web Services

Web Services는 인터넷 네트워크를 통하여 다수의 기존 어플리케이션 시스템을 표준화된 기술로서 상호 작용시키고, 이러한 표준 기술을 이용하여 모든 비즈니스를 가능하게 한다. Web Services는 언제, 어디에서건 원하는 정보나 서비스를 제공해 주는 역할을 수행하며 기존의 다른 소프트웨어처럼 완벽한 정의를 지정하여 구성하는 것이 아니라, 서로 주고받는 데이터 표준에 대한 정의를 규정함으로써 매우 유연하며 서로의 이질적인 운영시스템, 이질적인 프로그램 언어 간의 커뮤니케이션 차이를 극복해 주는 연결고리 역할을 해 준다. Anyframe 매뉴얼에서는 이러한 Web Services 기능에 대한 활용 방법에 대해 가이드한다.

2.3.웹 화면 개발 시 필요한 공통 기능

Struts와 Spring MVC를 확장하여 웹 화면 개발시 공통적으로 요구되는 다양한 추가 기능을 제공한다. 자세한 사항은 Anyframe 매뉴얼을 참고하도록 한다.

2.3.1.화면 흐름 제어(Screen Workflow Control)

화면 네비게이션 흐름을 JSP나 클래스 내에서 처리하지 않고 설정 파일(XML)을 통해서 제어할 수 있게 한다. 공통 에러 페이지 등 프로젝트에서 필요한 공통 페이지 설정도 설정 파일을 통해 가능하다.

2.3.2.공통 클래스 제공

프리젠테이션 레이어를 개발하는 개발자들은 Struts 사용 시 Action 클래스를, Spring MVC 사용 시 Controller 클래스를 개발해야 한다. 이를 위해 공통 Action 클래스와 Controller 클래스를 제공함으로써 이를 상속받은 모든 하위 클래스에서는 로깅, Exception 처리, Double Form Submission 방지 등의 기능을 사용할 수 있으며 특정 액션 수행 이전과 이후에 공통적으로 수행해야 할 로직을 구현하기 쉬워진다. 아래 그림은 Struts 사용 시 활용할 수 있는 공통 클래스의 모습이다.

2.3.3.국제화(i18N), 지역화(L10n) 지원

국제화를 지원한다는 것은 언어나 지역에 영향을 받는 부분과 영향을 받지 않는 코드를 분리하여 쉽게 지역화될 수 있게 만들었다는 것을 의미하는 것으로 소스 코드의 수정없이 다양한 언어를 지원할 수 있도록 한다.

2.3.4.사용자 입력값 유효성 검증(Validation)

Jakarta Commons 프로젝트의 하나로 Validation 모듈이 제공되는데 이를 Struts/Spring MVC와 연계하여 제공하고 있다. 유효성 검증 로직을 소스 코드 내에 작성하거나 소스 외부에서 설정 파일(XML)을 통해 관리할 수 있다. 이 외에 일반적으로 많이 사용되는 자바 스크립트를 통해 입력값 검증을 수행할 수도 있다.

2.3.5.Tag Library를 통한 View 개선

JSP 개발 시 Struts, Spring MVC에서 제공하는 기본 태그 라이브러리와 JSTL의 표준 태그 라이브러리 및 Anyframe에서 확장한 태그 라이브러리를 사용하여 JSP 내에 자바 코드를 추가하지 않고도 개발할 수 있도록 지원함으로써 HTML 디자인 개발과 프리젠테이션 레이어 개발을 분리하여 진행할 수 있게 한다. 목록 조회 페이지에서 페이지 네비게이션 정보를 표현하는 부분에 사용되는 태그 라이브러리가 그 대표적인 예이다.

2.3.6.에러 처리(Exception Handling)

특정 메소드 내에서 try~catch 구문을 이용하여 Exception을 처리하지 않고 설정 파일(XML)을 이용하여 선언적으로 처리할 수 있다. 에러 처리 페이지에서는 Exception 발생 시 설정된 메시지 키에 의한 메시지 내용을 화면에 디스플레이되도록 한다.

2.3.7.요청(Request) 권한 처리

Struts를 사용하는 경우, RequestProcessor 클래스의 processRoles() 메소드를 확장하여 구현함으로써 사용자가 해당 요청(Request)에 대한 권한을 가지고 있는지 판별할 수 있도록 한다. Spring MVC를 사용하는 경우, Spring Security 등 다른 오픈 소스와 연계하여 인증 및 권한 부분을 처리할 수 있다.

2.3.8.Double Form Submission 방지

설정 파일(XML)에 선언적인 방식으로 Double Form Submission 방지 기능을 정의하여 사용할 수 있다. 이를 통해 Form submit 중복(브라우저 Refresh, Submit 2회 이상 수행)으로 인한 오동작을 방지할 수 있는 기능을 제공한다.

2.3.9.다양한 웹 클라이언트(UI) 연계 지원(X-internet Integration)

X-internet과 Anyframe을 연계할 수 있도록 확장 모듈을 제공하고 있다.

II.Installation

Anyframe 4.0이후부터는 Anyframe Plugin을 제공함으로써 로컬에 별도 라이브러리나 샘플 프로젝트를 설치하지 않고도 Maven 기반에서 프로젝트에 적합한 Anyframe 기반의 샘플 프로젝트를 쉽게 구성할 수 있도록 지원한다. Plugin 기반의 프로젝트 구조 생성 방법 및 Anyframe Maven Command 활용, Plugin 정의 방법에 대해 자세히 살펴보도록 하자.

3.Installation

Anyframe 4.0.0 이후부터 오픈 소스 기반으로 어플리케이션을 개발할 때 요구되는 다양한 오픈 소스들이 통합된 템플릿 기반의 프로젝트 구조 및 샘플 코드를 Maven을 이용하여 자동으로 구성할 수 있도록 지원한다. 따라서, 어플리케이션 개발 초기에 프로젝트 특성에 맞는 개발 환경을 구성하는데 소요되는 시간을 대폭 줄이고, 프로젝트에 필요한 최적의 샘플을 제공받을 수 있게 될 것이다. 다음에서는 Anyfrae 4.0.0 이후부터 새롭게 변경된 Anyframe 설치 방법에 대해 알아보도록 하자. (본 장에서 언급되는 모든 Maven 명령어 종류는 Anyframe Maven Commands를 참조하도록 한다.)

3.1.[선택] Maven 설치 및 환경 설정

Maven(http://maven.apache.org/)은 POM(Project Object Model) 정보를 기반으로 대상 프로젝에 대한 빌드, 리포팅, 문서화 등을 지원하는 오픈소스 툴이다. 먼저 Maven을 설치한 후, Anyframe 설치를 위한 환경 설정 방법에 대해 알아보도록 하자.

  1. Anyframe 설치 대상 PC에 Maven이 설치되어 있지 않은 경우, Maven을 설치하도록 한다. (본 문서에서는 Maven Ver.2.2.1을 기반으로 설치 작업을 진행할 것이다.) Maven 사이트로부터 Maven(apache-maven-2.2.1-bin.xxx)을 다운로드받은 후, 원하는 위치에 압축을 해제한다.

  2. 설치된 Maven을 기반으로 Anyframe 설치 작업을 진행할 때, Anyframe Repository(http://dev.anyframejava.org/artifactory/anyframe-repository)로부터 참조 라이브러리를 다운로드할 수 있도록 하기 위해 [MAVEN 설치 폴더]\conf\settings.xml 파일을 열고 다음과 같이 속성을 추가로 정의해준다. (settings.xml 파일 다운로드를 원할 경우 여기를 참조한다.)

    <profiles>
        <profile>
            <id>myprofile</id>
            <repositories> 
                <repository>
                    <id>anyframe</id>
                    <name>repository for Anyframe</name>                 
                    <url>http://dev.anyframejava.org/maven/repo</url>
                    <snapshots>
                        <enabled>true</enabled>
                    </snapshots>
                </repository>	
            </repositories>
            <pluginRepositories>
                <pluginRepository>
                    <id>anyframe-plugin</id> 
                    <name>repository for Anyframe</name>       
                    <url>http://dev.anyframejava.org/maven/repo</url>           
                </pluginRepository>
                <pluginRepository>
                    <id>central</id> 
                    <name>Internal Mirror of Central Plugins Repository</name>       
                    <url>http://www.ibiblio.org/maven2/plugins</url>           
                </pluginRepository>
                <pluginRepository>
                    <id>remote</id> 
                    <name>Internal Mirror of Central Plugins Repository</name>       
                    <url>http://repo1.maven.org/maven2</url>           
                </pluginRepository>    
            </pluginRepositories>  
        </profile>
    </profiles>
    중략...
    <activeProfiles>
        <activeProfile>myprofile</activeProfile>
    </activeProfiles>
  3. 작업 대상 PC에서 MAVEN을 인식할 수 있도록 하기 위해서 시스템 변수로 MAVEN_HOME을 추가하고, [MAVEN 설치 폴더]를 값으로 지정해준다.

    또한 시스템 변수 PATH에 다음과 같이 'MAVEN_HOME\bin'을 추가한다.

  4. 설치 및 환경 설정 작업이 완료되었다면 Maven이 성공적으로 설치되었는지 확인해 보도록 하자. Command 창을 띄우고 mvn -version과 같이 명령어를 입력하여 다음과 같은 정보가 에러없이 표시되는지 확인한다.

3.2.Resources

  • 다운로드

    다음은 앞서 언급한 Maven의 settings.xml 파일이다. settings.xml 파일을 다운로드받은 후, [MAVEN 설치 폴더]\conf 내에 복사한다.

    표 3.1. Download List

    NameDownload
    settings.xmlDownload

3.3.[필수] Basic Archetype 설치

Anyframe에서는 생성할 샘플 프로젝트의 기본 구조를 포함하고 있는 anyframe-basic-archeypte을 별도로 제공하고 있다 (Anyframe 4.5.0 이후). 따라서 사용자는 anyframe-basic-archetype을 이용하여 로컬 환경에 샘플 프로젝트의 기본 구조를 생성한 이후 이를 기반으로 Anyframe에서 제공하는 Plugin을 추가적으로 설치해야 한다. 먼저 anyframe-basic-archetype을 설치해 보도록 하자.

  1. Command 창을 띄우고 다음과 같이 명령어를 입력하여 anyframe-basic-archetype 설치를 시작한다.

    mvn archetype:generate 
        -DarchetypeCatalog="http://dev.anyframejava.org/maven/repo/archetype-catalog.xml"

    위와 같이 명령어를 입력하면 Command 창에 archetypeCatalog 속성값으로 정의된 http://dev.anyframejava.org/maven/repo/archetype-catalog.xml 파일 내에 정의된 Maven Archetype 목록이 제시될 것이다.

    위 그림에서와 같이 archetype-catalog.xml 파일 내에 정의된 anyframe-basic-archetype 이라는 이름의 archetype이 Command 창에 제시됨을 알 수 있다. 제시된 Maven Archetype 목록 중 anyframe-basic-archetype에 해당하는 번호('2')를 선택한다.

  2. archetype-catalog.xml 파일에 등록된 anyframe-basic-archetype의 버전이 여러개일 경우 다음과 같이 버전을 선택할 수 있도록 버전 목록이 제시될 것이다. (등록된 anyframe-basic-archetype의 버전이 1개일 경우 버전 목록이 제시되지 않고 바로 다음 단계로 이동된다.) 설치할 anyframe-basic-archetype의 버전에 해당하는 번호를 선택한다.

  3. 샘플 프로젝트 생성을 위해 다음과 같은 인자에 대한 값을 입력하도록 요구할 것이다. (settings.xml 파일 내의 <localRepository></localRepository>를 별도로 정의하지 않은 경우 Maven Local Repository는 기본적으로 C:\Documents and Settings\{로그인id}\.m2\repository가 될 것이다.)

    ParameterDescriptionDefault Value
    groupId설치 대상 프로젝트의 groupId이다.N/A
    artifactId설치 대상 프로젝트의 artifactId로써 해당 프로젝트를 설치할 대상 폴더명과 프로젝트명이 된다.N/A
    version설치 대상 프로젝트의 version이다.1.0-SNAPSHOT
    package설치 대상 프로젝트 내 소스 코드에 대한 대표 패키지명이 된다.groupId로 정의한 값

    anyframe-basic-archetype-x.x.x.jar 라이브러리가 Maven Local Repository 내에 존재하지 않는 경우 앞서 settings.xml 파일에 정의한 Anyframe Repository로부터 이를 다운로드하고, 입려간 속성값을 기반으로 샘플 프로젝트가 생성될 것이다.

  4. 다음은 anyframe-basic-archetype 설치를 통해 구성된 샘플 프로젝트의 기본 모습이다. 설치된 샘플 프로젝트명은 myproject이며, 하위에 다양한 용도의 폴더를 포함하고 있다.

    anyframe-basic-archetype 설치 완료 후에는 [선택] Plugin 설치 확인을 참조하여 정상 동작 여부를 확인하도록 한다.

3.4.[필수] Plugin 설치

Anyframe 4.x에서는 다양한 오픈 소스들이 통합된 템플릿 기반의 샘플 코드, 참조 라이브러리 집합을 Plugin이라 칭하며, 다양한 유형의 Plugin을 제공한다. (Plugin에 대한 자세한 내용은 Anyframe Plugins를 참조하도록 한다.) 먼저 다른 Anyframe Plugin 설치를 위한 기반을 제공하는 기본 Plugin인 Foundation Plugin을 설치해 보도록 하자. Anyframe에서 제공하는 Plugin을 설치하기 위해서는 반드시 Foundation Plugin 설치 작업이 선행되어야 함을 잊지 않도록 하자.

  1. Command 창을 띄우고 앞서 설치한 [샘플 프로젝트 설치 폴더]/db/hsqldb 폴더로 이동하여 start.cmd (or start.sh) 파일을 더블클릭함으로써 샘플용으로 제공되는 HsqlDB를 시작시킨다. 해당 Plugin 실행을 위해 DB 데이터 변경이 필요한 경우 선택한 Plugin 설치시 DB 데이터 변경 작업도 함께 이루어지므로 DB를 시작시켜 놓아야 한다. 만일 선택한 Plugin 설치시 DB가 시작되지 않았다면 Plugin 설치 이후 제공되는 DB 스크립트 파일([샘플 프로젝트 설치 폴더]/db/scripts/{plugin명-insert-data-db명}.sql)을 직접 실행시키면 된다. 또한 샘플용으로 제공되는 HsqlDB가 아닌 다른 DB 기반에서 Plugin을 설치하고자 하는 경우에는 [선택] 샘플 DB 변경을 참고하도록 한다.

  2. Command 창을 띄우고 샘플 프로젝트 설치 폴더로 이동하여 다음과 같이 명령어를 입력한다.

    mvn anyframe:install

    위와 같이 명령어를 입력하면 설치 가능한 Plugin 목록이 제시될 것이다. 설치 가능한 Plugin 목록은 plugin-catalog-x.x.x.xml 파일 기반으로 조회되는데 Anyframe에서는 먼저 해당 파일이 Maven Local Repository 하위에 존재하는지 체크해 보고 있을 경우 해당 파일에 정의된 Plugin 정보를 활용하게 된다. 만일 Maven Local Repository 하위에 해당 파일이 존재하지 않으면 Anyframe Repository인 http://dev.anyframejava.org/maven/repo/plugin-catalog-x.x.x.xml 파일을 Maven Local Repository 하위에 다운로드하고 이 파일을 활용하게 된다. 한 번 다운로드한 plugin-catalog-x.x.x.xml 파일을 Anyframe Repository 내의 plugin-catalog-x.x.x.xml 파일과 동기화하기 위해서는 [선택] Plugin Catalog Update를 참조하도록 한다.

    Plugin 목록에 대한 조회없이 특정 Plugin을 바로 설치하고자 하는 경우에는 다음과 같이 -DpluginName={plugin명}을 직접 입력해주면 된다. 여기서는 Foundation Plugin을 설치할 것이므로 pluginName 변수의 값으로 'foundation'을 입력한다.

    mvn anyframe:install -DpluginName=foundation
  3. 제시된 Plugin 목록 중 설치하고자 하는 Plugin에 해당하는 번호를 선택한다. 여기서는 Foundation Plugin을 설치할 것이므로 foundation에 해당하는 번호('1')를 선택함으로써 Foundation Plugin 설치 작업을 실행시킨다. Plugin 설치 완료 후에는 [선택] Plugin 설치 확인을 참조하여 정상 동작 여부를 확인하도록 한다.

    Foundation Plugin이 정상적으로 설치된 이후에는 Foundation Plugin과 동일한 방법으로 다른 Plugin들을 추가적으로 설치하면 된다.

  4. Command 창을 띄우고 샘플 프로젝트 설치 폴더로 이동하여 다음과 같은 명령어를 입력하면 샘플 프로젝트를 대상으로 추가된 Plugin 목록을 확인할 수 있을 것이다.

    mvn anyframe:installed

기존 Maven 사용자 유의 사항

Anyframe에서는 Maven 기반에서 Anyframe 관련 라이브러리 다운로드시에 참조 관계에 놓인 모든 3rd-party 라이브러리들이 한꺼번에 다운로드되는 현상을 막기 위해 3rd-party 라이브러리들 간의 참조 관계를 끊은 상태로 Anyframe Repository에 배포하고 있다. 때문에 기존 Maven 사용자들은 Local Repository에 이미 존재하는 3rd-party 라이브러리가 가진 참조 관계에 문제가 생겨 Anyframe 설치시에 오류가 발생할 수 있다. 따라서 설치에 문제가 있는 경우에는 Local Repository를 삭제한 후 재설치해 볼 것을 권장한다.

3.5.[선택] Plugin 설치 확인

특정 Plugin 설치 결과 구성된 샘플 어플리케이션이 정상 동작하는지 확인하기 위해서는 Command 창에 명령어를 직접 입력하거나 Eclipse를 이용할 수 있다.

  1. 샘플 어플리케이션의 정상 동작 여부를 체크하기 위해서는 먼저 사용할 DB가 시작되어 있어야 한다. Anyframe Plugin 설치로 구성되는 샘플 프로젝트는 기본적으로 HSQL DB를 사용하도록 구성되어 있다. 따라서, [샘플 프로젝트 설치 폴더]/db/hsqldb/start.cmd (or start.sh) 파일을 더블 클릭하여 DB를 시작시키도록 한다. (HSQL DB가 아닌 다른 DB를 사용하고자 하면 [선택] 샘플 DB 변경 방법을 참조하도록 한다.)

    [샘플 프로젝트 설치 폴더]/db/hsqldb 폴더 내에 제공되는 sqltool.cmd (or sqltool.sh) 파일은 HSQL DB용 SQL Editor를 시작시키는 용도로 제공된다. DB 작업 수행 후 결과를 확인하고자 할 때 유용하게 활용할 수 있을 것이다.

  2. 다음에서는 Maven Command를 직접 입력하거나 Eclipse를 이용하여 샘플 어플리케이션을 시작시키는 방법에 대해서 살펴보도록 한다. (본 문서에서는 Jetty, Tomcat을 기준으로 설명이 진행된다.)

    • Maven Command를 직접 이용하여 Jetty 실행

      Command 창을 띄운 후, 샘플 프로젝트 설치 폴더 위치로 이동하여 다음과 같이 Maven 명령어를 입력하면 Jetty 기반에서 샘플 어플리케이션을 시작시킬 수 있다.

      mvn clean jetty:run

      Jetty가 정상적으로 실행되면 Started Jetty Server라는 INFO 레벨의 로그가 콘솔창에 보일 것이다.

    • Eclipse WTP, m2eclipse를 이용하여 Tomcat 실행

      Eclipse를 이용하여 샘플 어플리케이션을 실행시키려는 경우 Eclipse에서 Maven 관련 작업을 수행할 수 있도록 지원하는 m2eclipse plugin을 설치할 것을 권장한다. m2eclipse plugin이 미설치된 경우, Update Site (m2eclipse core update site : http://m2eclipse.sonatype.org/sites/m2e, m2eclipse extras update site : http://m2eclipse.sonatype.org/sites/m2e-extras)를 통해 설치할 수 있다. m2eclipse plugin을 설치하지 않고 Eclipse WTP만을 이용하여 샘플 어플리케이션을 실행시키고자 하는 경우에는 본 문서의 [선택] Eclipse WTP만을 이용하여 Tomcat 실행 방법을 참조하도록 한다.

      설치된 샘플 프로젝트를 Eclipse 내로 import한 이후 해당 프로젝트에 대해 마우스 오른쪽 버튼을 클릭하여 컨텍스트 메뉴에서 Maven > Enable Dependency Management를 선택함으로써 샘플 프로젝트 관련 컴파일 에러를 해결하도록 한다.

      이 후, 해당 프로젝트에 대해 마우스 오른쪽 버튼을 클릭하여 컨텍스트 메뉴에서 Run As > Run on Server를 선택함으로써 Tomcat Server를 시작시키도록 한다.

  3. WAS가 정상적으로 시작되었으면 브라우저를 열고, 주소창에 http://localhost:8080/myproject (http://localhost:8080/${샘플 프로젝트 이름})와 같이 입력하여 샘플 어플리케이션이 정상적으로 실행되는지 확인해본다. 다음은 Foundation Plugin만 설치된 경우 샘플 어플리케이션의 초기 화면과 Foundation Sample 링크를 클릭하였을 때 이동한 화면에 대한 모습이다.

    Plugin이 추가 설치될 때마다 왼쪽 메뉴에 추가된 Plugin에 대한 샘플을 확인할 수 있도록 메뉴가 추가될 것이다.

Anyframe에서 제공하는 Plugin을 설치함으로써 생성된 샘플 프로젝트는 기본적으로 Maven을 기반으로 하고 있으므로 Maven에서 지원하는 기능들을 동일하게 실행할 수 있다. 예를 들어 'mvn test'와 같은 명령어를 실행함으로써 샘플 프로젝트 내의 테스트 코드를 실행해 볼 수 있으며 'mvn package'와 같은 명령어를 실행함으로써 샘플 프로젝트를 war 형태로 패키징 가능하다. (단, HsqlDB에서 디른 DB로 변경하였을 경우 Driver 클래스를 인식할 수 있도록 하기 위해 pom.xml 파일 내에 사용하는 DB의 Driver 클래스를 포함하고 있는 라이브러리를 Dependency로 추가 정의해 주어야 한다.)

3.6.[선택] Eclipse WTP만 이용하여 Tomcat 실행

Eclipse WTP만을 이용하여 Maven Project가 아닌 일반 Dynamic Web Project 형태로 샘플 어플리케이션을 실행시키기 위해서는 해당 어플리케이션에서 참조 라이브러리를 인식할 수 있도록 필요한 라이브러리를 [샘플 프로젝트 설치 폴더]/src/main/webapp/WEB-INF/lib 폴더에 추가해주는 작업이 필요하다.

  1. Command 창을 띄운 후, 샘플 프로젝트가 있는 위치로 이동하여 다음과 같이 Maven 명령어를 입력함으로써 해당 어플리케이션이 참조하는 라이브러리들을 [샘플 프로젝트 설치 폴더]//src/main/webapp/WEB-INF/lib 폴더에 복사할 수 있다.

    mvn anyframe:inplace
  2. 설치된 샘플 프로젝트를 Eclipse 내로 import한 이후 해당 프로젝트에 대해 컴파일 에러가 존재하지 않는지 체크한다. 그리고 해당 프로젝트에 대해 마우스 오른쪽 버튼을 클릭하여 컨텍스트 메뉴에서 Run As > Run on Server를 선택함으로써 Tomcat Server를 시작시키도록 한다.

대상이 되는 샘플 어플리케이션을 기준으로 특정 Plugin이 설치/삭제될 때마다 참조 라이브러리에 대해 변경 사항이 발생하게 된다. 따라서 Eclipse WTP만 이용하여 Tomcat을 실행하는 경우 변경된 사항을 해당 어플리케이션에 반영하기 위해서는 매번 Maven 명령어(mvn anyframe:inplace)를 실행해주어야 한다는 점에 유의하도록 한다.

3.7.[선택] Plugin Catalog Update

앞서 언급한 바와 같이 Anyframe에서는 설치한 anyframe-basic-archetype 버전별로 이에 상응하는 plugin-catalog-x.x.x.xml 파일 내에 설치 가능한 Plugin 목록을 정의하고 있다. Anyframe에서는 이 파일을 기반으로 설치 가능한 Plugin 목록을 조회하는데 먼저 해당 파일이 Maven Local Repository 하위에 존재하는지 체크해 보고 있을 경우 해당 파일에 정의된 Plugin 정보를 활용하게 된다. 만일 Maven Local Repository 하위에 해당 파일이 존재하지 않으면 Anyframe Repository인 http://dev.anyframejava.org/maven/repo/plugin-catalog-x.x.x.xml 파일을 Maven Local Repository 하위에 다운로드하고 이 파일을 활용하게 된다. 사용자의 Maven Local Repository 하위에 한 번 다운로드가 된 이후에는 해당 파일만을 이용하게 되므로 시간이 지나면 Anyframe Repository 내의 최신 plugin-catalog-x.x.x.xml 파일과 일치하지 않을 수 있다. 따라서 최신 plugin-catalog-x.x.x.xml 파일과의 동기화를 위해서는 다음과 같은 작업이 필요하다.

  1. Command 창을 띄운 후, 샘플 프로젝트가 있는 위치로 이동하여 다음과 같이 Maven 명령어를 입력함으로써 최신 Plugin Catalog 파일을 Local에 다운로드할 수 있다.

    mvn anyframe:update-catalog
  2. 다음과 같이 Maven 명령어를 입력함으로써 최신 Plugin Catalog가 정상적으로 반영되었는지 확인해보자.

    mvn anyframe:list

    또한 다음과 같이 Maven 명령어를 입력하면 샘플 프로젝트를 대상으로 설치된 Plugin의 버전과 해당 Plugin에 대해 설치할 수 있는 최신 버전을 확인해 볼 수 있다. 현재 구성된 샘플 프로젝트에 설치된 Plugin의 버전이 최신 버전이 아닌 경우 이전 버전의 Plugin에 대해 최신 버전을 적용할 수 있도록 지원한다. 이를 원할 경우 [선택] Plugin Update를 참고하도록 한다.

    mvn anyframe:installed

3.8.[선택] Plugin Update

Anyframe에서는 현재 설치된 anyframe-basic-archetype 버전을 기준으로 호환가능한 Plugin 버전을 체크하여 설치된 Plugin이 이전 버전일 경우, 최신 버전의 Plugin을 반영할 수 있도록 지원한다. (이 때 최신으로 반영되는 내용은 최신 Plugin이 참조하는 라이브러리로 한정된다.)

  1. Command 창을 띄운 후, 샘플 프로젝트가 있는 위치로 이동하여 다음과 같이 Maven 명령어를 입력함으로써 특정 Plugin에 대해 최신 버전을 적용할 수 있다.

    mvn anyframe:update -DpluginName=...
  2. Plugin Update 작업이 정상적으로 실행된 경우 변경 적용된 Plugin 이름과 해당 Plugin이 참조하는 라이브러리 목록이 제시될 것이다. 그리고 현재 프로젝트에 설치된 다른 Plugin이 변경된 참조 라이브러리의 다른 버전을 사용하고 있을 경우 이를 제시해 주게 된다.

    다음과 같이 Maven 명령어를 입력하면 샘플 프로젝트를 대상으로 설치된 Plugin의 버전이 정상적으로 반영되었는지 확인해 볼 수 있다.

    mvn anyframe:installed

3.9.[선택] 샘플 DB 변경

앞서 언급한 바와 같이 Plugin 설치를 통해 생성된 샘플 프로젝트는 기본적으로 HSQL DB를 기반으로 동작하도록 구성되어 있다. 그런데 만약 샘플 프로젝트의 실행 DB를 변경하고자 원한다면 다음과 같은 작업을 수행해야 한다. (현재 hsqldb, oracle 스크립트만 제공되고 있다.)

  1. 설치된 샘플 프로젝트 설치 폴더 하위의 .metadata/project.mf 파일을 열고 기 정의된 DB 정보를 적절하게 수정한다.

    db.name=hsqldb
    db.driver=org.hsqldb.jdbcDriver
    db.url=jdbc:hsqldb:hsql://localhost/sampledb
    db.userId=sa
    db.password=
    db.lib=db/hsqldb/hsqldb-1.8.0.10.jar

    위에 제시된 각 속성은 다음과 같은 의미를 지닌다.

    PropertyDescription
    db.name 해당 DB에 대한 대표명을 정의한다. 특정 Plugin 설치 실행해야 할 DB 스크립트가 있다면 db.name 값을 포함하고 있는 스크립트 ({plugin name}-insert-data-{db.name}.sql, {plugin name}-delete-data-{db.name}.sql) 파일이 실행되도록 하는데 사용된다. (예를 들어, db.name이 oracle일 때 security plugin을 설치하면 security-insert-data-oracle.sql 파일이 실행됨.)
    db.driver해당 DB에 대한 Driver Class명을 정의한다.
    db.url해당 DB에 대한 URL을 정의한다.
    db.userId해당 DB에 접근하기 위한 User Id를 정의한다.
    db.password해당 DB에 접근하기 위한 Password를 정의한다.
    db.lib 해당 DB에 접근하여 Connection을 얻어오기 위해 참조해야 하는 DB Library 위치를 정의한다. (절대 경로 또는 샘플 프로젝트 위치 기준으로 상대 경로로 정의할 수 있다. 상대 경로 정의시 ./src/main/..와 같은 형태나 src/main/..와 같은 형태로 정의하도록 한다. /src/main/..와 같은 형태로 정의하는 경우 절대 경로로 인식하여 해당 파일을 찾지 못할 수 있다.) 샘플 어플리케이션 실행시 DB Library를 인식할 수 있도록 하기 위해서 [샘플 프로젝트 설치 폴더]/src/main/webapp/WEB-INF/lib 폴더 내에 저장할 것을 권장한다. 만약 Maven 기반에서 실행하고자 한다면 Maven Local Repository 내에 해당 Library가 존재해야 하며 샘플 프로젝트의 pom.xml 내에 이를 dependency 정보로써 추가해 주어야 한다.

4.Anyframe Plugins

Anyframe 3.x.x에서는 참조용 샘플 프로젝트와 라이브러리를 중심으로 개발자가 직접 프로젝트에 적합한 샘플 프로젝트를 구성해야 했다.

Anyframe 4.x.x부터는 Spring, Hibernate, CXF, Struts 등과 같은 다양한 오픈 소스들을 중심으로 참조 라이브러리와 샘플 코드를 엮어서 구성된 다양한 Plugin들을 제공함으로써 사용자가 원하는 Plugin들을 적절히 선택하고 설치함으로써 해당 프로젝트에 필요한 기능들을 갖춘 샘플 프로젝트를 손쉽게 구성할 수 있도록 지원한다. 이 샘플 프로젝트는 비즈니스 어플리케이션의 기반으로 활용할 수 있게 된다. 따라서 제공된 Plugin들을 재사용함으로써 단시간에 해당 프로젝트에 적합한 개발 기반을 구성할 수 있게 되는 것이다.

다음에서는 Anyframe에서 제공하는 Plugin 및 Plugin 구조에 대해서 살펴보고 Custom Plugin을 정의하는 방법에 대해 알아보도록 하자.

4.1.Plugin 목록

다음은 Anyframe에서 제공하는 Plugin 목록이다.

NoPlugin NameDescription
1foundation 다른 Plugin 설치를 위한 기반을 제공하는 Plugin이며, SpringMVC + Spring + Query Service를 이용한 영화 정보 관리 기능을 제공한다.

[활용기능] IoC, AOP, Annotation, SpringMVC, SpEL, Validation, DataSource, Transaction, Query, Logging, IdGen

2cache OSCache와 Anyframe에서 구현한 Cache Service를 이용한 영화 정보 관리 기능을 제공한다.

[활용기능] Cache

3cxf Apache CXF를 이용한 영화 정보 조회 기능을 포함하고 있으며 생성된 WSDL을 확인할 수 있도록 한다.

[활용기능] Web Services : JAX-WS Frontend

4hibernate Hibernate와 Anyframe에서 구현한 Dynamic Hibernate Service를 이용한 영화 정보 관리 기능을 제공한다.

[활용기능] Hibernate, Dynamic Hibernate

5jasper JasperReports와 Spring을 연계하여 영화 정보 등록 현황을 HTML, PDF 형태의 Report로 보여준다.

[활용기능] Jasper Integration

6miplatform TOBE 소프트에서 제공하는 X-Internet 솔루션인 MiPlatform을 Anyframe과 연계하여 영화 정보 관리 기능을 제공한다.

[활용기능] Miplatform Integration

7mipsample TOBE 소프트에서 제공하는 X-Internet 솔루션인 MiPlatform을 Anyframe과 연계하여 활용할 수 있는 다양한 UI Sample을 제공한다. mipsample plugin 기능 확인을 위해 DB에 추가되어야 할 샘플 데이터가 함께 제공된다. (mipsample plugin이 정상 설치된 경우 별도로 DB 데이터를 추가할 필요는 없으며 DB 연계에 문제가 있는 경우에 한해 제공되는 DB 스크립트를 이용하여 샘플 데이터를 추가하도록 한다.)

[활용기능] Miplatform Simplification

8remoting Spring Remoting 기법 중 HttpInvoker를 활용하여 영화 정보 조회 기능을 제공한다.

[활용기능] Remoting : HTTP Invoker

9restweb Spring 3.0에서 새롭게 선보인 특징 중 하나로써 SpringMVC 기반의 웹어플리케이션에 대해 REST 스타일을 적용하여 영화 정보 관리 기능을 제공한다.

[활용기능] Rest Style Web Application

10scheduling Quartz를 이용하여 주기적으로 월별 영화 등록 현황을 생성하는 기능을 제공한다.

[활용기능] Quartz Integration

11security Spring Security를 이용하여 사용자별 Authentication, 역할별 Authorization 기능을 제공한다. security plugin 기능 확인을 위해 DB에 추가되어야 할 샘플 데이터가 함께 제공된다. (security Plugin이 정상 설치된 경우 별도로 DB 데이터를 추가할 필요는 없으며 DB 연계에 문제가 있는 경우에 한해 제공되는 DB 스크립트를 이용하여 샘플 데이터를 추가하도록 한다.)
12simpleweb-json, simpleweb-map, simpleweb-vo Web Application을 개발 할때 개발자가 웹 개발을 보다 쉽게 할 수 있도록 웹 개발 단순화 방법을 가이드 하며 공통 Controller 클래스와 Tag Library를 제공한다. 또한, 보다 강력하고 간결한 웹 개발을 위한 Spring JS, jQuery 연계 방안을 제공한다.

[활용기능] Simplification of web

13spring-optional Plugin을 통해 설치되지 않는 Spring Optional 라이브러리만을 제공한다.
14struts Struts를 이용하여 영화 정보 관리 기능을 제공한다.

[활용기능] Struts, Struts Extensions

15test 테스트 코드 실행에 필요한 참조 라이브러리만을 제공한다.
16webflow Spring Webflow를 이용하여 영화 정보 관리 기능을 제공한다.

[활용기능] Spring Webflow

4.2.anyframe-basic-archetype 구조

Plugin의 구조를 살펴보기에 앞서 프로젝트 구조 생성을 위해 반드시 미리 설치되어야 하는 anyframe-basic-archetype의 구조에 대해 살펴보기로 하자. anyframe-basic-archetype은 Maven Archetype 형태로 구성되어 있어 명령어 mvn archetype:generate를 이용하면, anyframe-basic-archetype 내에 정의된 프로젝트 템플릿이 정해진 위치에 생성된다.

다음은 anyframe-basic-archetype의 src/main/resources 하위에 위치한 주요 구성 요소를 표현한 그림이다.

archetype-resources는 리소스 템플릿을 관리하기 위한 용도의 폴더로써 pom.xml 파일과 함께 다음과 같은 하위 폴더를 가진다. pom.xml 파일 내에는 anyframe-basic-archetype 설치 후, 생성될 샘플 어플리케이션 실행에 필요한 Maven Plugin 등이 정의되어 있다. 다음은 archetype-resources 폴더를 구성하는 주요 하위 폴더/파일에 대한 설명이다.

FolderDescription
.metadataPlugin 설치 정보를 관리하기 위한 plugins.xml 파일과 샘플 프로젝트 및 DB 정보를 관리하기 위한 project.mf 파일을 가진다.
.settingsEclipse 프로젝트 정보를 관리한다.
db/hsqldb샘플 어플리케이션 실행에 필요한 샘플 DB이다.
src/main/java 소스 코드를 관리하기 위한 용도의 폴더를 가진다.
src/main/resources Spring, SpringMVC 기반의 어플리케이션 실행을 위한 속성 정의 파일과 메시지 파일, 쿼리문을 정의하고 있는 매핑 XML 파일들을 관리하기 위한 용도의 폴더를 가진다.
src/main/webapp웹 어플리케이션을 위한 웹 리소스(*.jsp, *.css, *.js ...)들을 관리하기 위한 용도의 폴더를 가진다.
src/test/java테스트 코드를 관리하기 위한 용도의 폴더를 가진다.
src/test/resources테스트 코드 실행에 필요한 리소스들을 관리하기 위한 용도의 폴더를 가진다.

META-INF는 anyframe-basic-archetype의 리소스 템플릿에 대한 Meta 정보를 관리하기 위한 용도의 폴더로써 다음과 같은 하위 폴더를 가진다.

FolderDescription
maven 리소스 템플릿을 이용하여 샘플 프로젝트를 생성할 때 필요한 Meta 정보를 관리하는 archetype-metadata.xml 파일을 가진다.

4.3.Plugin 구조

anyframe-basic-archetype 설치 후 anyframe-maven-plugin을 이용하여 Foundation, Hibernate, Japser, Miplatform 등과 같은 Plugin들을 설치/삭제할 수 있도록 구성하기 위하여 Plugin들은 Maven Archetype과 흡사한 형태로 구성되어 있다.

다음은 Foundation Plugin의 src/main/resources 하위에 위치한 주요 구성 요소를 표현한 그림이다.

plugin-resources는 리소스 템플릿을 관리하기 위한 용도의 폴더로써 pom.xml, plugin-descriptor.xml 파일과 여러 하위 폴더를 가진다.

pom.xml 파일 내에는 해당 Plugin 설치로 인해 추가될 기능 실행에 필요한 참조 라이브러리가 정의되어 있다.

또한 plugin-descriptor.xml 파일은 정의된 리소스 템플릿을 이용하여 샘플 프로젝트를 생성할 때 필요한 Meta 정보를 관리한다. plugin-resources 하위 폴더 내에 정의된 리소스를 샘플 프로젝트에 추가할 때 대표 패키지를 부여할 것인지, Velocity를 이용하여 리소스 템플릿과 사용자의 입력값이 Merge된 리소스를 추가할 것인지 여부를 정의하는데 사용된다. 다음은 plugin-descriptor.xml 파일에 대한 예이다.

<?xml version="1.0" encoding="UTF-8"?>
<archetype-descriptor name="foundation">
    <fileSets>
        <fileSet filtered="true" packaged="true">
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.java</include>
            </includes>
        </fileSet>
        중략...
   </fileSets>
</archetype-descriptor>

위 파일을 살펴보면 <fileSet/> 내에서는 특정 디렉토리에 속한 파일 그룹에 대해 filetered, packaged 속성값을 정의하고 있음을 알 수 있다. 만일 리소스 파일 중의 하나인 MovieService.java가 다음과 같이 구성되어 있다라고 가정해 보자.

package ${package}.foundation.moviefinder.service;

import ${package}.domain.Movie;

public interface MovieService {

	Movie get(String movieId) throws Exception;

	void create(Movie movie) throws Exception;

	void update(Movie movie) throws Exception;

	void remove(String movieId) throws Exception;

}

filtered 속성값이 true인 경우, ${package}로 정의된 부분이 Velocity Engine에 의해 해석되어 package의 값이 'anyframe' 이라면 'package anyframe.foundation.moviefinder.service;'로 재조합되어 샘플 프로젝트 내에 MovieService.java 파일이 추가될 것이다. filtered 속성값이 false인 경우, 정의된 모든 부분에 대한 변경없이 샘플 프로젝트 내에 MovieService.java 파일이 추가될 것이다.

다음으로 packaged 속성에 대해 알아보도록 하자. plugin-resources/src/main/java 하위에 foundation/moviefinder/service/MovieService.java라는 리소스가 정의되어 있다라고 가정해 보자. packaged 속성값이 true인 경우 package의 값이 'anyframe'이라면 샘플 프로젝트 내 src/main/java/anyframe/foundation/moviefinder/service 하위에 해당 리소스가 추가될 것이다. 즉, packaged 속성값에 따라 패키지 구조를 반영하여 리소스를 생성할 위치가 정의된다.

다음은 plugin-resources 하위 폴더에 대한 설명이다.

FolderDescription
db/resources 해당 Plugin이 DB 유형에 따라 변경되어야 하는 리소스를 포함하고 있는 경우에 활용한다. 예를 들어, QueryService의 경우 DB의 종류에 따라 사용할 PagingSQLGenerator 구현체가 달라지므로 Foundation Plugin을 설치할 때 DB 종류에 따라 QueryService 속성 정의가 달라져야 한다. 이를 위해 db/resources 폴더 하위에 DB 명을 폴더명으로 두고 그 폴더 하위에 해당 리소스의 위치를 그대로 정의하고 변경되어야 하는 파일을 정의해둘 수 있다. Foundation Plugin의 경우 Oracle용 QueryService 속성 파일(context-query.xml)을 db/resources/oracle/src/main/resources/spring 하위에 포함하고 있다.
db/scripts 해당 Plugin 설치로 인해 추가될 기능이 기본 DB 데이터 이외의 데이터를 필요로 하는 경우를 위해 실행될 DB 스크립트를 포함한다. 현재 HSQL DB, Oracle DB 스크립트를 제공하고 있다.
src/main/java 소스 코드를 관리한다. 모든 소스 코드의 패키지는 Plugin 이름으로 시작하도록 정의해야 한다.
src/main/resources Spring, SpringMVC 기반의 어플리케이션 실행을 위한 속성 정의 파일과 메시지 파일, 쿼리문을 정의하고 있는 매핑 XML 파일들을 관리한다. Spring 속성 정의 파일명은 context-{plugin name}-xxx.xml, SpringMVC 속성 정의 파일명은 {plugin name}-servlet.xml으로 정의해야 한다. 이 외, 리소스 파일이 필요한 경우에는 Plugin명과 동일한 이름의 폴더를 생성하여 관리하도록 한다.
src/main/webapp 웹 어플리케이션을 위한 웹 리소스(*.jsp, *.css, *.js ...)들을 관리한다. JSP 파일은 WEB-INF/jsp 하위에 Plugin명과 동일한 폴더를 생성하여 관리하도록 한다. src/main/webapp 폴더 하위에는 설치 대상 리소스가 아니기 때문에 plugin-descriptor.xml 파일 내의 정의에서는 제외되었으나 생성된 샘플 어플리케이션에 통합되어야 할 정보를 포함한 리소스들이 있다. 다음에서는 이들 각각을 정의하는 방법에 대해서 살펴보도록 하자.
  • Plugin이 동작하기 위해 WebApplication 속성 정의가 필요한 경우 src/main/webapp/WEB-INF/web.xml 파일 내에 다음과 같은 형태로 정의할 수 있다.

    <servlet>
        <servlet-name>struts</servlet-name>
        <servlet-class>anyframe.web.struts.action.DefaultActionServlet</servlet-class>
        <init-param>
            <param-name>config</param-name>
            <param-value>
                struts/struts-config-common.xml
                ,struts/struts-config-movie.xml
            </param-value>
        </init-param>
        <init-param>
            <param-name>character-encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
    </servlet>
    	
    <servlet-mapping>
        <servlet-name>struts</servlet-name>
        <url-pattern>*.action</url-pattern>
    </servlet-mapping>

    위의 내용은 Struts Plugin 설치로 추가되는 WebApplication 속성 정보로써 Struts의 경우 SpringMVC와 달리 DefaultActionServlet을 통해 사용자의 요청 처리가 이루어져야 하므로 이를 위한 servlet 정의 및 servlet-mapping 정보가 정의되어 있음을 알 수 있다. Struts Plugin 설치 이후 샘플 프로젝트의 src/main/webapp/WEB-INF/web.xml 파일 내에 위에 정의된 내용이 통합되어 Struts 기반에서도 샘플 어플리케이션이 동작 가능하게 된다.

  • Plugin 설치 이후, 샘플 어플리케이션에 해당 Plugin과 관련된 메뉴가 추가되어야 하는 경우 src/main/webapp/sample/layouts/left.jsp 파일 내에 다음과 같이 메뉴명과 접근 URL을 정의한다.

    <tr>
        <td valign="top" bgcolor="#eeeeee" class="depth01"><a href="${ctx}/hibernateMovieFinder.do?method=list">Hibernate Sample</a></td>
    </tr>

    위의 내용은 Hibernate Plugin 설치로 추가되는 메뉴 정보로써 Hibernate Plugin이 설치되면 Hibernate Sample이라는 이름의 메뉴가 추가될 것이고, 이 메뉴를 클릭하였을 때 http://${ctx}/hibernateMovieFinder.do?method=list라는 요청이 처리될 것이다.

  • Anyframe Plugin들은 기본적으로 Tiles 기반에서 동작하도록 구성되어 있다. Tiles에 대한 모든 정보는 src/main/webapp/WEB-INF/tilesviews.xml 파일 내에 정의되어 있게 된다. 따라서 Plugin 설치 이후, 해당 Plugin과 관련된 화면들을 보여주기 위해 필요한 Tiles Definition을 src/main/webapp/WEB-INF/tilesviews.xml 내에 다음과 같이 정의할 수 있다.

    <definition name="hibernateListMovie" extends="standardLayout">
        <put-attribute name="body" value="/WEB-INF/jsp/hibernate/moviefinder/movie/list.jsp" />
    </definition>
    	
    <definition name="hibernateViewMovie" extends="standardLayout">
        <put-attribute name="body" value="/WEB-INF/jsp/hibernate/moviefinder/movie/form.jsp" />
    </definition>

    위의 내용은 Hibernate Plugn 설치로 추가되는 Tiles Definition으로써 Foundation Plugin 설치로써 추가된 standardLayout을 기반으로 body 부분을 정의된 JSP 파일로 대체하도록 정의하고 있음을 알 수 있다.

src/test/java테스트 코드를 관리한다. 테스트 코드의 패키지는 Plugin 이름으로 시작하도록 정의해야 한다.
src/test/resources테스트 코드 실행에 필요한 리소스들을 관리한다. src/main/resources와 동일한 규칙을 따라 리소스를 정의한다.

4.4.Custom Plugin 추가 정의

앞서 언급했던 Anyframe Plugin 외에 사용자가 Plugin을 자체 정의하여 프로젝트 내부적으로 재사용할 수 있다. 여기에서는 Custom Plugin 정의 방법에 대해 살펴보도록 하자.

  1. Custom Plugin 정의

    1. Custom Plugin을 위한 프로젝트를 생성하고 해당 프로젝트에 대한 pom.xml 파일을 다음과 같이 정의한다. groupId와 artifactId,version은 원하는 대로 적절하게 정의하면 된다.

      <?xml version="1.0" encoding="UTF-8"?>
      <project>
          <modelVersion>4.0.0</modelVersion>
          <groupId>anyframe</groupId>
          <artifactId>anyframe.plugin.custom</artifactId>
          <packaging>jar</packaging>
          <version>4.5.0</version>
      
          <build>
              <resources>
                  <resource>
                      <filtering>false</filtering>
                      <directory>src/main/resources</directory>
                  </resource>
              </resources>
          </build>
      </project>
    2. 해당 프로젝트 내에 src/main/resources라는 폴더를 생성하고 하위에 plugin-resources라는 폴더를 생성한다. (Eclipse 프로젝트로 생성할 경우 src/main/resources라는 소스 폴더를 생성하되 Build Path에서 **/*.java 파일이 excludes되도록 설정한다.) plugin-resources 폴더 하위에는 Custom Plugin 설치로써 샘플 프로젝트에 추가되어야 할 리소스(Java, XML, ... 등)들에 대한 템플릿과 plugin-descriptor.xml, pom.xml 파일을 정의해 주어야 한다.

    3. 샘플 프로젝트에 추가되어야 할 리소스(Java, XML, ... 등)는 Anyframe Plugin의 폴더/파일 명명 규칙에 맞추어 정의하도록 한다. (Plugin 구조 참고)

    4. plugin-descriptor.xml 파일 내에는 Plugin 내에 포함된 리소스에 대한 Meta 정보를 정의한다. (Plugin 구조 참고)

    5. 해당 Plugin 설치/삭제시 anyframe-maven-plugin을 통해 수행되는 기본 작업 외에 별도로 처리해야 하는 작업이 필요한 경우에는 Interceptor 클래스 구현을 통해 처리할 수 있다. Interceptor 클래스는 Custom Plugin 프로젝트 하위의 src/main/java 하위에 위치시키고 다음과 같이 구현할 수 있다.

      public class CustomPluginInterceptor {
      
          // 설치 후 별도 작업이 필요한 경우
          public void postInstall(String baseDir, File pluginJarFile)
              throws Exception {
              System.out.println("#### call postInstall ####");
          }
      
          // 삭제 후 별도 작업이 필요한 경우
          public void postUninstall(String baseDir, File pluginJarFile)
              throws Exception {
              System.out.println("#### call postUninstall ####"); 
          }
      
          // 설치 전 별도 작업이 필요한 경우
          public void preInstall(String baseDir, File pluginJarFile) throws Exception {
              System.out.println("#### call preInstall ####");
          }
      
          // 삭제 후 별도 작업이 필요한 경우
          public void preUninstall(String baseDir, File pluginJarFile)
              throws Exception {
              System.out.println("#### call preUninstall ####");
          }
      }
    6. 해당 Plugin 실행을 위해 별도로 DB 데이터가 추가되어야 하는 경우에는 plugin-resourcs/db/scripts 하위에 {plugin name}-insert-data-{db.name}.sql, {plugin name}-delete-data-{db.name}.sql 파일을 정의하고 필요한 DDL, DML을 정의해 두면 해당 Plugin 설치/삭제시에 anyframe-maven-plugin에 의해 실행될 것이다.

    7. Command 창에서 'mvn deploy' 명령어를 실행시킴으로써 지정된 Local 또는 Remote Maven Repository에 Custom Plugin을 배포한다.

  2. Custom Plugin 목록 정의

    1. Custom Plugin 목록 정의를 위해 anyframe-custom-plugins.xml 이라는 이름의 파일을 생성한다.

      <plugins/> 내에는 Custom Plugin 목록을 정의하며, <plugin/> 내에는 각 Custom Plugin에 대한 상세 정보를 정의한다. anyframe-maven-plugin은 Plugin 목록 조회시 이 정보를 읽게 될 것이다. 또한 해당 Plugin 설치/삭제시 anyframe-maven-plugin을 통해 수행되는 기본 작업 외에 별도로 처리해야 하는 작업이 있어 Interceptor를 구현한 경우에는 <interceptor/> 내에 Interceptor 클래스명을 기술해 주도록 한다.

      <plugins>
          <plugin>
              <pluginName>custom</pluginName>
              <groupId>anyframe</groupId>
              <artifactId>anyframe.plugin.custom</artifactId>
              <version>4.1.0</version>
              <interceptor>custom.interceptor.CustomPluginInterceptor</interceptor>
          </plugin>					
      </plugins>
    2. 해당 파일을 Maven Local Repository 또는 Remote Repository 하위에 복사한다. Remote Repository에 복사한 경우 settings.xml 파일 내의 Repository 정보에 해당 Repository 정보가 추가되어 있어야 한다.

      ...
          <profile>
              <id>myprofile</id>
              <repositories> 
                  <repository>
                      <id>anyframe</id>
                      <name>repository for Anyframe</name>                 
                      <url>http://dev.anyframejava.org/artifactory/anyframe-repository</url>
                      <snapshots>
                          <enabled>true</enabled>
                      </snapshots>
                   </repository>    
                   <!-- define your remote repository -->                              
              </repositories>   
              ...
          </profile>
      ...

Command 창에서 'mvn anyframe:list' 명령어를 실행시켜 Custom Plugin이 설치 가능한 Plugin 목록에 추가되었는지 확인해 보도록 하자. 정상적으로 추가된 경우에는 Anyframe에서 제공하는 다른 Plugin과 동일하게 설치/삭제가 가능해진다.

5.Anyframe Commands

Anyframe에서는 자체 구현한 anyframe-maven-plugin이라는 Maven Plugin을 제공함으로써 Anyframe Repository 내에 등록된 다양한 Anyframe Plugin들을 다운로드하여 설치/삭제/조회할 수 있도록 지원한다.

5.1.anyframe-maven-plugin 정의 확인

Maven 기반의 프로젝트에서 anyframe-maven-plugin을 사용하기 위해서는 해당 프로젝트 폴더 하위의 pom.xml 파일 내에 다음과 같이 plugin 정의가 추가되어 있어야 한다.

<project>
    중략...
    <build>
        <plugins>
            중략...						
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>anyframe-maven-plugin</artifactId>
                <version>4.5.0</version>
            </plugin>
        </plugins>
    </build>
</project>

5.2.Command 목록

anyframe-maven-plugin은 Anyframe에서 제공하는 Plugin들을 활용하여 Anyframe만의 고유 기능을 수행할 수 있도록 구현된 다양한 Maven Mojo(Maven-old-java-object) 클래스들로 구성되어 있으며 각 Mojo는 한 개의 Goal과 매핑된다. 여기에서 Goal이란 Maven에서 사용되는 용어로써 한번의 실행으로 이루어지는 특정 기능 단위라고 볼 수 있다. 다음은 anyframe-maven-plugin을 구성하는 Goal의 목록으로써 사용자는 Command 창에서 mvn anyframe:{goal}과 같이 명령어를 입력함으로써 원하는 기능을 실행할 수 있게 된다. Option은 -D{option}=...와 같은 형태로 명령어 입력 뒤에 추가하면 된다.

GoalDescription
help사용 가능한 Command 목록을 보여준다.
install anyframe-basic-archetype 설치후, 생성된 샘플 프로젝트 구조를 기반으로 지정된 Plugin을 설치한다. 다음과 같은 Option을 가진다.

pluginName : 설치 대상 Plugin명을 정의한다.

uninstall 이미 설치된 Plugin을 삭제한다. 다음과 같은 Option을 가진다.

pluginName : 삭제 대상 Plugin 명을 정의한다.

update 현재 설치된 anyframe-basic-archetype 버전을 기준으로 호환가능한 Plugin 버전을 체크하여 설치된 Plugin이 이전 버전일 경우, 최신 버전의 Plugin을 반영한다. (최신으로 반영되는 내용은 최신 Plugin이 참조하는 라이브러리로 한정된다.)
update-catalog Maven Local Repository 내에 존재하는 plugin-catalog-x.x.x.xml 파일에 최신 내용을 반영하여 Plugin 목록 조회시 최신 정보가 조회될 수 있도록 한다.
list설치 가능한 Plugin 목록과 Plugin 정보를 보여준다.
installed설치된 Plugin 현황 및 해당 Plugin의 최신 버전 정보를 보여준다.
inplace pom.xml 파일을 이용하여 생성된 샘플 프로젝트 하위의 src/main/webapp/WEB-INF/lib 폴더 내로 샘플 어플리케이션 실행에 필요한 모든 참조 라이브러리를 다운로드한다.
version 현재 샘플 프로젝트의 기반을 구성하고 있는 Anyframe의 버전 정보와 함께 Java, OS 정보를 보여준다.

III.Foundation Plugin

Foundation Plugin은 Anyframe에서 제공하는 모든 Plugin의 기반이 되는 기본 Plugin으로 Spring MVC, Spring, Query Service 등의 기본 활용 방법을 가이드하기 위한 샘플 코드와 이 오픈소스들을 활용하는데 필요한 참조 라이브러리들로 구성되어 있다. Foundation Plugin에 포함된 오픈소스 활용 방법에 대해 상세히 살펴보도록 하자.

6.Overview

본 장에서는 Foundation Plugin 설치로 생성된 샘플 코드를 중심으로 보다 폭넓게 오픈소스 활용 방법에 대해 보다 상세히 다루게 될 것이다.

6.1.Foundation Plugin 설치하기

본 장의 내용을 본격적으로 시작하기에 앞서 로컬 PC에 Foundation Plugin을 설치해 보도록 하자. (본 장에서는 Foundation Plugin 설치 절차만을 간략히 소개하므로 보다 자세한 Plugin 설치에 대해서는 본 문서 내의 Installation을 참조하도록 한다.)

  1. Foundation Plugin 설치는 Maven 기반으로 진행되므로, 본 문서 내의 Maven 설치 및 환경 설정을 참조하여 설치 환경이 제대로 셋팅되었는지 확인한다.

  2. 샘플 프로젝트의 구조 생성을 위해 Command 창을 띄우고 설치 대상 폴더로 이동한 후, 다음과 같이 명령어를 입력하여 anyframe-basic-archetype을 설치한다.

    mvn archetype:generate 
        -DarchetypeCatalog="http://dev.anyframejava.org/maven/repo/archetype-catalog.xml
  3. 설치 대상 폴더 하위에 anyframe-basic-archetype 설치시 입력한 artifactId와 동일한 이름의 샘플 프로젝트가 생성되었는지 확인한다.

  4. Command 창에서 샘플 프로젝트의 위치로 이동한 후, db/hsqldb/start.cmd (or start.sh)을 실행시킴으로써 샘플 DB를 시작시킨다. (기본적으로 제공되는 HsqlDB가 아닌 다른 DB를 활용하고자 하는 경우에는 본 문서 내의 샘플 DB 변경 을 참조하도록 한다.) DB가 시작되지 않은 경우 Foundation Plugin 실행을 위한 샘플 데이터 추가가 정상적으로 이루어지지 않음에 유의해야 한다.

  5. Command 창에서 다음과 같이 명령어를 입력하여 foundation plugin을 설치한다.

    mvn anyframe:install -DpluginName=foundation
  6. Command 창에서 다음과 같이 명령어를 입력한 후, Jetty Server가 정상적으로 시작되었으면 브라우저를 통해 foundation plugin이 정상적으로 설치되었는지 확인한다. (생성된 샘플 프로젝트명이 myproject인 경우 브라우저 주소창에 http://localhost:8080/myproject를 입력한다.)

    mvn clean jetty:run

WAS(Web Application Server)별 유의사항

본 문서에서는 Plugin 설치로 생성된 샘플 어플리케이션을 실행시키기 위한 WAS로써 Jetty, Tomcat를 채택하여 설명을 기술하고 있다. 그러나 Plugin 설치로 생성된 샘플 어플리케이션은 특정 WAS에 종속되지 않으므로 mvn clean compile war:war와 같은 명령어 실행을 통해 패키징한 후 WebLogic, JEUS와 같은 다른 WAS에 deploy하여 실행시키는 것도 가능하다. 단, 샘플 어플리케이션이 참조하는 일부 라이브러리의 버전을 해당 WAS에서 지원하지 않는 경우가 있어 조치 사항을 언급하고자 한다.

  • WebLogic 9.2 : 샘플 어플리케이션은 Tiles 기반으로 구성되어 있으며 Tiles 정의시 EL(Expression Language)를 활용할 수 있도록 하기 위해 tiles-config_2_1.dtd를 채택하고 있다. 그런데 이는 Servlet Spec. 2.5 이후부터 지원되는 기능이며 WebLogic 9.2는 Servlet Spec. 2.4를 구현한 WAS이므로 이 기능 처리에 문제가 있다. 따라서 Foundation Plugin 설치 이후 생성된 샘플 프로젝트 하위의 src/main/resources/spring/moviefinder-servlet.xml 파일을 열고 'tilesConfigurer' Bean 정의에서 tilesInitializer 속성 정의를 제거하고 Tiles Definition XML 파일의 위치만을 남겨두도록 한다. 수정된 'tilesConfigurer' Bean의 속성은 다음과 같다.

    <bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">
        <property name="definitions">
            <list>
                <value>/WEB-INF/tilesviews.xml</value>
            </list>
        </property>
    </bean>

    실질적으로 EL(Expression Language)을 활용한 Tiles 정의는 SimpleWeb-xxx Plugin이 활용하고 있으며 따라서 WebLogic 9.2에서는 SimpleWeb-xxx Plugin은 정상동작하지 않음에 유의하도록 한다.

  • JEUS 6 : Foundation Plugin 설치로 생성된 샘플 프로젝트는 Model Validation 기능을 수행하기 위해 JSR-303 Annotation을 활용하여 도메인 클래스를 구성하고 있다. 이 때 JSR-303 Provider로 Hibernate Validator 4.0.2.GA 라이브러리를 활용하고 있으며 이 라이브러리는 JPA 2.0 라이브러리를 참조한다. 그런데 JEUS 6.0은 JPA 1.0 라이브러리를 포함하고 있고, WAS 시작 시점에 JPA 라이브러리르 필요로 하므로 다음과 같은 처리가 필요하다.

    • [JEUS 설치 Home]/lib/system 폴더 내의 javaee.jar 파일에서 javax.persistence 제거

    • [JEUS 설치 Home]/lib/system 폴더 내의 toplink-*.jar 파일 삭제

    • [JEUS 설치 Home]/lib/system 폴더 내에 [샘플 프로젝트]/src/main/webapp/WEB-INF/lib/hibernate-jpa-2.0-api-1.0.0.Final.jar 파일 복사

    또한 Security Plugin이 올바르게 동작하기 위해서는 JEUS 6 최신 패치가 설치되어 있어야 함에 유의하도록 한다.

7.Spring

Spring은 객체의 라이프 사이클을 관리하고 객체들간의 의존 관계를 최소화할 수 있는 Lightweight 컨테이너를 제공한다. 다음은 Spring Lightweight 컨테이너의 주요 특징이다.

  • POJO 기반 개발 지원

    설계 결과물에 컨테이너 의존적인 코드를 추가하지 않아도 순수 POJO 기반으로 어플리케이션 개발이 가능하도록 지원하다. 즉, Lightweight 컨테이너 기반 개발시 프레임워크로 인한 기본 설계와 상세 설계가 이중으로 진행되거나, 개발시 설계 모델과 구현체가 불일치되는 것을 방지할 수 있다.

  • Dependency Resolution 지원

    어플리케이션 구성 모듈간 의존 관계를 처리하기 위한 방법을 제공한다. 특정 모듈의 코드 내에서 참조할 모듈을 직접적으로 생성하여 참조함으로써 참조 모듈간에 tightly-coupled 되지 않도록 하기 위해, 대부분의 Lightweight 컨테이너들과 마찬가지로 DI(Dependency Injection)을 지원하며, 이외에 DL(Dependency Lookup)도 가능하다.

  • Aspect Oriented Programming 지원

    AOP는 어플리케이션 전체에 걸쳐 사용되나 쉽게 분리된 모듈로 작성하기 힘든 로깅, 인증, 권한체크, DB 연동, 트랜잭션, 락킹, 에러처리 등과 같은 공통 기능을 재사용 가능하도록 컴포넌트화 할 수 있는 기법이다. AOP에서는 이러한 공통 기능을 Crosscutting Concerns, 해당 어플리케이션이 제공하는 비즈니스 기능을 Core Concerns라고 지칭한다. 즉, Core Concerns 모듈 내에 필요한 Crosscutting Concerns를 직접 추가하는 대신에 AOP에서는 Weaving이라는 작업을 통해 Core Concerns 모듈의 코드를 직접 건드리지 않고도 Core Concerns 모듈의 사이 사이에 필요한 Crosscutting Concerns 코드가 엮어져 동작되도록 한다. 이를 통해 AOP는 기존의 작성된 코드들을 수정하지 않고도 필요한 Crosscutting Concerns 기능을 효과적으로 적용해 낼 수도 있게 되는 것이다.

  • Life-cycle 관리

    Lightweight 컨테이너는 정의된 모듈의 Life-cycle을 관리하여 해당 모듈들을 초기화시키고 종료시키는 역할을 수행함으로써 개발자가 비즈니스 로직에 집중하여 개발할 수 있게 된다.

  • 신규 기능 추가 용이

    XML 또는 Annotation 기반의 설정을 통해서 간단하게 컨테이너 기반 위에 신규 기능을 추가할 수 있도록 지원한다.

여기에서는 Spring Lightweight 컨테이너를 통해 지원되는 주요 기능들에 대해 살펴볼 것이다. 이와 함께 클라이언트 어플리케이션과 원격 어플리케이션에서 제공하는 서비스 간의 의사 소통을 위한 Spring Remoting 기법에 대해서도 알아보자.

7.1.IoC(Inversion of Control)

Anyframe은 Spring 기반에서 다양한 best-of-breed 오픈 소스를 통합 및 확장하여 구성한 어플리케이션 프레임워크를 포함하고 있다. Anyframe 4.5.0 이후부터는 Spring Framework 3.0을 기반으로 하고 있다.

Spring Framework가 가지는 가장 핵심적인 기능이 IoC이다. IoC 개념은 과거에도 많은 곳에서 사용된 개념이지만 최근 Spring Framework과 같은 Lightweight Container 개념이 등장하면서 많은 개발자들에게 관심의 대상이 되고 있다. IoC 개념은 Spring Framework 뿐만 아니라 컨테이너 기능을 가지는 모든 영역에서 사용되고 있는 개념이므로 반드시 이해할 필요가 있다.

  • IoC(Inversion of Control)개념

    IoC는 Inversion of Control의 약자이다. 우리나라 말로 직역해 보면 "역제어"라고 할 수 있다. 제어의 역전 현상이 무엇인지 살펴본다. 기존에 자바 기반으로 어플리케이션을 개발할 때 자바 객체를 생성하고 서로간의 의존 관계를 연결시키는 작업에 대한 제어권은 보통 개발되는 어플리케이션에 있었다. 그러나 Servlet, EJB 등을 사용하는 경우Servlet Container, EJB Container에게 제어권이 넘어가서 객체의 생명주기(Life Cycle)를 Container들이 전담하게 된다. 이처럼 IoC에서 이야기하는 제어권의 역전이란 객체의 생성에서부터 생명주기의 관리까지 모든 객체에 대한 제어권이 바뀌었다는 것을 의미한다. Spring Framework도 객체에 대한 생성 및 생명주기를 관리할 수 있는 기능을 제공하고 있다. 즉, IoC Container 기능을 제공하고 있다.

    Inversion of Control(이하 IoC)이란?

    • Component dependency resolution, configuration 및 lifecycle을 해결하기 위한 Design Pattern

    • DIP(Dependency Inversion Principle) 또는 Hollywood Principle (Don't call us we will call you)라는 용어로도 사용

    • 특정 작업을 수행하기 위해 필요한 다른 컴포넌트들을 직접 생성하거나 획득하기 보다는 이러한 의존성들을 외부에 정의하고 컨테이너에 의해 공급받는 방법으로 동작

    이러한 IoC는 다음과 같은 장점을 가지고 있다.

    • 클래스 / 컴포넌트의 재사용성 증가

    • 단위 테스트 용이

    • Assemble과 configure를 통한 시스템 구축 용이

  • IoC와 Dependency Injection간의 관계

    Spring Framework의 가장 큰 장점으로 IoC Container 기능이 부각되어 있으나, IoC 기능은 Spring Framework이 탄생하기 훨씬 이전부터 사용되던 개념이었다. 그러므로 "IoC 기능을 Spring Framework의 장점이라고 이야기하는 것은 적합하지 않다."고 반론을 제기하면서 "새로운 개념을 사용하는 것이 적합하다."고 주장한 사람이 Martin Flowler이다. Lightweight 컨테이너들이 이야기하는 IoC를 Dependency Injection이라는 용어로 사용하는 것이 더 적합하다고 이야기하고 있다. Martin Flowler의 이 같은 구분 이후 IoC 개념을 개발자들마다 다양한 방식으로 분류하고 있으나 다음 그림과 같이 IoC와 Dependency Injection 간의 관계를 분류하는 것이 일반적이다.

    • Dependency Lookup

      저장소에 저장되어 있는 Bean에 접근하기 위하여 Container에서 제공하는 API를 이용하여 사용하고자 하는 Bean을 Lookup 하는 것을 말한다. 따라서, Bean을 개발자가 직접 Lookup하여 사용함으로써 Container에서 제공하는 API와 의존관계 발생하게 된다.

      • 객체 관리 저장소(Repository)

        모든 IoC Container는 각 Container에서 관리해야 하는 객체들을 관리하기 위한 별도의 저장소(Repository)를 가진다. Servlet Container는 web.xml에서 Servlet을 관리하고 있으며, EJB Container는 ejb-jar.xml에 설정되어 있는 정보들이 JNDI 저장소에 저장되어 관리되고 있다. 이처럼 Spring Framework도 POJO들을 관리하기 위하여 별도의 저장소로 XML 파일을 가지게 된다.

      • Dependency Lookup 예시

        구현 클래스는 다음과 같이 작성한다.

        public class IoCServiceImpl1 implements IoCService1, 
                ApplicationContextAware {
            public void setApplicationContext (ApplicationContext context) {
                IoCService2 iocService2 = (IoCService2)context.getBean("IoCService2");
            }
        }

        속성 정의 파일은 다음과 같이 작성한다.

        <bean id="IoCService1" class="….IoCServiceImpl1">
            중략...
        </bean>
        <bean id="IoCService2" class="….IoCServiceImpl2">
            중략...
        </bean>
    • Dependency Injection (DI)

      각 클래스 사이의 의존관계를 빈 설정(Bean Definition)정보를 바탕으로 컨테이너가 자동적으로 연결해주는 것을 말한다. 컨테이너가 의존관계를 자동적으로 연결시켜주기 때문에 개발자들이 컨테이너 API를 이용하여 의존관계에 관여할 필요가 없게 되므로 컨테이너 API에 종속되는 것을 줄일 수 있다. 개발자들은 단지 빈 설정파일(저장소 관리 파일)에서 의존관계가 필요하다는 정보를 추가하기만 하면 된다. 또한 Dependency Injection은 Setter Injection과 Constructor Injection 형태로 구분한다.

      • Dependency Injection 예시

        구현 클래스는 다음과 같이 작성한다.

        public class IoCServiceImpl implements IoCService {	
            public void setDependencyBean(DepBean dependencyBean) {
                this.dependencyBean = dependencyBean;
            }
             중략... 
        }

        속성 정의 파일은 다음과 같이 작성한다.

        <bean id="IoCService" class="….IoCServiceImpl">
            <property name="dependencyBean" ref="depBean"/>
        </bean>
    • Dependency Lookup과 Dependency Injection의 차이점

      Bean을 개발자가 직접 Lookup하여 사용하는 것을 Dependency Lookup이라고 하고, Dependency Injection은 이와 달리 각 계층 사이, 각 클래스 사이에 필요로 하는 의존관계가 있다면 이 같은 의존관계를 Container가 자동적 으로 연결시켜주는 것을 말한다. Dependency Lookup을 사용할 경우 Bean을 Lookup하기 위하여 Container에서 제공하는 API와 의존관계가 발생한다. 이처럼 Container API와 많은 의존관계를 가지면 가질수록 어플리케이션이 Container에 대하여 가지는 종속성은 증가할 수 밖에 없다. 따라서 가능한 Dependency Lookup을 사용하지 않는 것 이 Container와의 종속성을 줄일 수 있게 된다.Container와의 종속성을 줄이기 위한 방법으로는 이후에 다루게 될 Dependency Injection 을 통하여 가능하게 된다.

7.1.1.Basic

Spring Framework는 기본적으로 어플리케이션의 비즈니스 서비스를 구동시키고 관리하는 Spring Container와 이러한 Container에 의해 관리되는 Bean으로 구성된다. Bean은 Container를 통해서 인스턴스화되는 객체이며 Container에 의해 다른 Bean들과 Wiring(엮기)되고 관리된다.

7.1.1.1.Container와 Bean

Bean은 Spring Framework에서 어플리케이션의 중요 부분을 형성하고 Spring IoC Container에 의해 관리된다.

  • Bean 설정, 생성, Life Cycle 관리

  • Bean Wiring(엮기) - Bean들과 각각에 대한 Dependency 관계는 Spring IoC Container에 의해 사용되는 설정 메타데이터로 반영

7.1.1.2.Container

Spring IoC Container는 다음 두 가지 유형의 Container를 제공한다.

  • BeanFactory

    설 명
    설 명 Bean의 생성과 소멸 담당
    Bean 생성 시 필요한 속성 설정
    Bean의 Life Cycle에 관련된 메소드 호출
    다수의 BeanFactory 인터페이스 구현 클래스를 제공하며 이중 가장 유용한 것은 XmlBeanFactory임
  • ApplicationContext

    설 명
    BeanFactory의 모든 기능 제공
    ResourceBundle 파일을 이용한 국제화(I18N) 지원
    다양한 Resource 로딩 방법 제공
    이벤트 핸들링
    다양한 Resource 로딩 방법 제공
    이벤트 핸들링
    Context 시작 시 모든 Singleton Bean을 미리 로딩(preloading) 시킴-> 초기에 설정 및 환경에 대한 에러 발견 가능함
    다수의 ApplicationContext 구현 클래스 제공(XmlWebApplicationContext, FileSystemXmlApplicationContext,ClassPathXmlApplicationContext)

    org.springframework.beans 와 org.springframework.context 패키지가 Spring Framework의 IoC Container를 위한 기본을 제공한다. BeanFactory는 객체를 관리하는 고급 설정 기법을 제공하고 ApplicationContext는 Spring의 AOP기능, 메시지 자원 핸들링, 이벤트 위임, 웹 어플리케이션에서 사용하기 위한 WebApplicationContext와 같은 특정 ApplicationContext 통합과 같은 기능을 추가 제공한다. 즉, BeanFactory가 설정 프레임워크와 기본 기능을 제공하는 반면 ApplicationContext는 BeanFactory의 모든 기능 뿐 아니라 전사적 중심의 기능이 추가되어 있다. ApplicationContext가 제공하는 부가 기능과는 별개로, ApplicationContext와 BeanFactory의 또 다른 차이점은 Singleton Bean을 로딩하는 방법에 있다. BeanFactory는 getBean() 메소드가 호출될 때까지 Bean의 생성을 미룬다. 즉 BeanFactory는 모든 Bean을 늦게 로딩(Lazy loading)한다. ApplicationContext는 Context를 시작시킬 때 모든 Singleton Bean을 미리 로딩함으로써, 그 Bean이 필요할 때 즉시 사용될 수 있도록 보장해준다. 즉, 어플리케이션 동작 시 Bean이 생성되기를 기다릴 필요가 없게 된다.

BeanFactory

Bean을 포함하고 관리하는 책임을 지는 Spring IoC Container의 실제 표현이다.가장 공통적으로 사용되는 BeanFactory의 구현체인 XmlBeanFactory 클래스는 XML 형태로 어플리케이션과 객체간의 참조 관계를 조합하는 객체를 정의함으로써 XML 설정 메타데이터를 기반으로 완전히 설정된 시스템이나 어플리케이션을 생성한다. 또한 아래의 예와 같이 XmlBeanFactory는 XML 파일에 기술되어 있는 정의를 바탕으로 Bean을 Loading해준다. (생성자에org.springframework.core.io.Resource타입의 객체 넘겨줌)

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

org.springframework.beans.factory.BeanFactory인터페이스에 관한 API는 여기를 참고한다.

Resource ImplementationPurpose
org.springframework.core.io.ByteArrayResourceDefines a resource whose content is given by an array of bytes
org.springframework.core.io.ClassPathResourceDefines a resource that is to be retrieved from the classpath
org.springframework.core.io.DescriptiveResource Defines a resource that holds a resource description but no actual readable resource
org.springframework.core.io.FileSystemResourceDefines a resource that is to be retrieved from the file system
org.springframework.core.io.InputStreamResourceDefines a resource that is to be retrieved from an input stream
org.springframework.web.portlet.context. PortletContextResourceDefines a resource that is available in a portlet context
org.springframework.web.context.support. ServletContextResourceDefines a resource that is available in a servlet context
org.springframework.core.io.UrlResourceDefines a resource that is to be retrieved from a given URL
ApplicationContext

다음은 org.springframework.context.ApplicationContext 인터페이스의 대략적인 구조이다.

자주 사용되는 ApplicationContext의 구현 클래스는 아래와 같다.

  • XmlWebApplicationContext - 웹 기반의 Spring 어플리케이션을 작성할 때 내부적으로 사용

  • FileSystemXmlApplicationContext - 파일 시스템에 위치한 XML 설정 파일을 읽어들이는 ApplicationContext

  • FileSystemXmlApplicationContext - 파일 시스템에 위치한 XML 설정 파일을 읽어들이는 ApplicationContext

ApplicationContext 구현 클래스를 아래와 같이 사용할 수 있다.

ApplicationContext context = new FileSystemXmlApplicationContext("c:/beans.xml”);
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml”);

설정 메타데이터

Container에 의해 "인스턴스화, 설정, 그리고 조합[어플리케이션내 객체를]"하기 위한 설정 방법에 대해 알아 보기로 하자. 대부분은 간단하고 직관적인 XML 형태로 제공되며 XML 기반의 설정 메타데이터를 사용하여 Bean을 정의하도록한다. 다음은 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-2.5.xsd">
    <bean id="…" class="…">
        <!-- collaborators and configuration for this bean go here -->
    </bean>
        <!-- more bean definitions go here -->
</beans>

XML 기반의 메타데이터 정의는 설정 메타데이터의 가장 많이 사용되는 형태이다. XML 외에 Java Properties 파일을 이용하거나 프로그램으로 처리(Spring의 Public API를 사용하여)함으로써, 설정 메타데이터를 제공할 수 있다. Spring IoC Container 자체는 설정 메타데이터의 형태로부터 분리될 수 있기 때문이다.

Spring IoC Container 인스턴스화 시키는 예제
  1. BeanFactory 사용한 예제

    Resource resource = new FileSystemResource("beans.xml");
    BeanFactory factory = new XmlBeanFactory(resource); 
    ClassPathResource resource = new ClassPathResource("beans.xml");
    BeanFactory factory = new XmlBeanFactory(resource);
  2. ApplicationContext 사용한 예제

    ApplicationContext context = 
            new ClassPathXmlApplicationContext(new String ("beans.xml"));
    // of course, an ApplicationContext is just a BeanFactory
    BeanFactory factory = (BeanFactory) context;
XML 기반 설정 메타데이터 조합

XML 기반 설정 메타데이터는 다중 XML파일로 분리하여 정의할 수 있다. 여기서 주의할 점은 <import>를 <bean> 이전에 두어야만 하는 것이다.

<beans>
    <import resource="services.xml"/>
    <import resource="resources/messageSource.xml"/>
    <import resource="/resources/themeSource.xml"/>

    <bean id="bean1" class="…"/>
    <bean id="bean2" class="…"/>
</beans>

위의 예제에서 외부 Bean정의는 3개의 파일(services.xml, messageSource.xml, 과 themeSource.xml)로부터 로드된다. 모든 위치 경로는 import를 수행하는 XML 파일에 상대적이다. 그래서 이 경우에 messageSource.xml 과 themeSource.xml이 import 대상 XML 파일의 위치 아래의 resources 에 두어야 하는 반면에 services.xml은 import를 수행하는 파일과 같은 디렉토리나 클래스패스 경로 내에 두어야만 한다. 이 예제처럼 /는 실제로 무시된다. import된 파일 의 내용은 <beans>를 가장상위 레벨에 포함하는 스키마나 DTD에 따라 완전히 유효한 XML Bean 정의 파일 이어야만 한다.

7.1.1.3.Beans

Bean

Spring IoC Container에 의해 관리되는 객체로 Container에 제공된 설정 메타데이터 내 정의(대개 XML <bean> 형태로)에 의해 생성되며 실제로 아래 표로 나타낸 주요 메타데이터 정보를 포함하는 BeanDefinition 객체로 표현한다.

주요 메타데이터속성설명
idBean의 구분을 위한 정보로 해당 bean에 접근하기 위한 Key임
class정의된 Bean의 실제 구현클래스로 항상 full name으로 작성
scope 정의된 Bean의 인스턴스 생성 유형 정의. singleton, prototype, request, session, globalSession 중 선택. Default는 singleton이며, 보다 자세한 Bean Scope에 대해서는 본 매뉴얼의 Extensions Bean Scope 을 참고하도록 한다.
init-method해당 bean이 초기화된 후 context에 저장되기 전 호출되는 초기화 메소드 정의
desrtoy-method해당 bean 제거 시 호출되는 메소드 정의
factory-method해당 bean 생성 시 생성자를 사용하지 않고 특정 factory method를 호출하여 생성 시 정의
lazy-inittrue/false 값을 가지며 해당 bean이 호출되기 전에 초기화 시킬지 여부를 결정함. Default는 false이며 true인 경우, 해당 bean이 호출되는 시점에 초기화됨
Bean 명명하기

Bean 정의 시 Bean들을 구분하기 위해 'id' 혹은 'name' 속성을 사용하는데 'id'를 사용하는 경우, 하나의 Bean은 Container내에서 Unique한 id를 가지도록 한다. 일반적으로 Bean을 명명할때 인스턴스 필드명에 대한 표준 Java 규칙을 사용한다. Bean 이름은 소문자로 시작하고 camel-cased(첫 번째 단어는 소문자로 시작하고 두 번째 단어는 대문자로 시작)된다. 이러한 이름의 예제는 ‘categoryService', 'productDao', 'loginController' 등이다. Bean을 명명하는 일관적인 방법을 적용하는 것은 설정을 좀 더 읽기 쉽고 이해하기 쉽도록 만들어준다. 이러한 명명표준을 적용하는 것은 어려운 일이 아니다. Spring AOP를 사용한다면 특정 Bean 이름과 관련된 Bean의 세트에 advice를 적용할 때 용이해질 수 있다.

Bean 인스턴스화
  • 생성자를 이용한 인스턴스화

    특정 인터페이스를 구현하거나 특정 형태로 코딩 할 필요가 없다.

    <bean id="sampleBean" class="sample.SampleBean"/>
    <bean name="anotherSample" class="sample.SampleBeanTwo"/>

  • static factory 메소드를 사용한 인스턴스화

    Bean 객체가 factory 메소드를 호출하여 생성되는 것으로, 반환 객체의 타입을 명시하지 않고 factory 메소드를 포함하는 클래스를 정의하고 있음에 주의한다. 아래 예제에서 createInstance() 메소드는 static 메소드이어야 한다.

    <bean id="sampleBean" class="sample.SampleBean2" 
        factory-method="createInstance"/>

  • 인스턴스 factory 메소드를 사용한 인스턴스화

    'class' 속성을 정의하지 않고 'factory-bean' 속성에 factory메소드를 포함하는 Bean을 정의한다.

    <!-- the factory bean, which contains a method 
    called createInstance() --><bean id="myFactoryBean" class="…"/>
    <!-- the bean to be created via the factory bean -->
    <bean id="sampleBean"
        factory-bean="myFactoryBean"
        factory-method="createInstance"/>
        중략...

7.1.1.4.How to refer to Beans

비즈니스 레이어와 프리젠테이션 레이어에서 Spring Bean에 접근하는 방법에는 여러 가지가 있다.

비즈니스 레이어

비즈니스 레이어에서 사용하고자 하는 Spring Bean에 접근하는 방법은 크게 2가지 형태로 구분할 수 있다. Dependency Lookup과 Dependency Injection 방식이 그것이다.

  • Dependency Lookup

    저장소에 저장되어 있는 Bean에 접근하기 위하여 사용하고자 하는 Bean을 Lookup 한다. 이때 Bean을 개발자가 직접 Lookup하여 사용함으로써 Container에서 제공하는 API와 의존관계가 발생한다. Spring IoC 컨테이너 Dependency Lookup에 대한 자세한 사항은 본 매뉴얼의 IoC 를 참고한다.

    구현 클래스는 다음과 같이 작성한다.

    public class IoCServiceImpl1 implements IoCService1, 
            ApplicationContextAware {
        public void setApplicationContext (ApplicationContext context) {
            IoCService2 iocService2 = (IoCService2)context.getBean("IoCService2");
        }
    }

    속성 정의 파일은 다음과 같이 작성한다.

    <bean id="IoCService1" class="….IoCServiceImpl1">
        중략...
    </bean>
    <bean id="IoCService2" class="….IoCServiceImpl2">
        중략...
    </bean>

  • Dependency Injection

    각 클래스 사이에 필요로 하는 의존 관계가 있는 경우, 의존관계를 Container가 자동적으로 연결시켜 줌으로써 Container에서 제공하는 API와 의존관계가 없다. Spring IoC 컨테이너 Dependency Injection에 대한 자세한 사항은 본 매뉴얼의 Dependencies를 참고한다.

    구현 클래스는 다음과 같이 작성한다. 이 예제에서는 Setter Injection 방식을 보여주고 있다.

    public class IoCServiceImpl implements IoCService {
        public void setDependencyBean(DepBean dependencyBean) {
            this.dependencyBean = dependencyBean;
        }
        중략... 
    }

    속성 정의 파일은 다음과 같이 작성한다.

    <bean id="IoCService" class="….IoCServiceImpl">
        <property name="dependencyBean" ref="depBean"/>
    </bean>

프리젠테이션 레이어

프리젠테이션 레이어에서 Spring Bean에 접근하는 방법은 비즈니스 레이어와 마찬가지로 Dependency Lookup과 Dependency Injection 방식 2가지 중 선택할 수 있는데 이때 사용하는 Web Framework이 무엇인지에 따라 사용 가능한 방식이 제한될 수 있으므로 주의하도록 한다. Web Framework 사용과 관련된 설정 방법은 Spring MVC를 참조하도록 한다.

  • Dependency Lookup (Struts)

    Web Framework으로 Struts를 사용하는 경우, Struts Action 내에서 Spring의 Web ApplicationContext를 얻어내어 Spring Bean을 Lookup하도록 한다. Spring에서 제공해주는 ActionSupport 클래스의 getWebApplicationContext() 메소드를 이용하여 ApplicationContext를 얻는다.

    Action 클래스는 다음과 같이 작성한다. 이 예제는 Anyframe 을 이용하여 작성된 코드로 Anyframe 의 DefaultActionSupport을 상속한 UpdateProductAction 클래스의 일부이다. productService Bean을 사용하고 있으며 이때 Bean의 id 값이 Action 클래스에 명시되어야 함에 유의하도록 한다.

    public class UpdateProductAction extends DefaultActionSupport {
        public ActionForward process(ActionMapping mapping, ActionForm form,
        HttpServletRequest req, HttpServletResponse res) throws Exception {
            ApplicationContext ctx = getWebApplicationContext();
            ProductService productService = (ProductService) ctx.getBean ("productService");
            중략...
         }
    }

    속성 정의 파일은 다음과 같이 작성한다.

    <bean id="productService"
        class="anyframe.sample.foundation.sales.service.impl.ProductServiceImpl">
        중략...
    </bean>

  • Dependency Injection (Spring MVC)

    Web Framework으로 Spring MVC를 사용하는 경우, Controller 클래스 내에서 Dependency Injection 방식을 이용하여 Spring Bean을 참조할 수 있다.

    Controller 클래스는 다음과 같이 작성한다. 이 예제는 Anyframe 을 이용하여 작성된 코드로 Anyframe 의 AnyframeFormController을 상속한 ProductController 클래스의 일부이다. productService Bean을 사용하고 있으며 이때 Bean의 id 값이 Spring MVC 속성 정의 파일에서 정의되고 있다.

    public class ProductController extends AnyframeFormController {
        private ProductService productService;;
    
        public void setProductService(ProductService productService) {
            this.productService = productService;
        }
        중략...
    
        public ModelAndView list(HttpServletRequest request,
        HttpServletResponse response) throws Exception {
            ProductSearchVO searchVO = new ProductSearchVO();
            bind(request, searchVO);
            Page resultPage = productService.getPagingList(searchVO);
        중략...
        }
    }

    속성 정의 파일은 다음과 같이 작성한다.

    <bean name="/foundationProduct.do"
            class="anyframe.sample.foundation.sales.web.ProductController">
        <property name="productService"
        ref="foundationProductService"/>
        <property name="categoryService" ref="foundationCategoryService"/>
        <property name="idGenenrationService" ref="idGenerationService"/>
        <property name="methodNameResolver" ref="paramResolver" />
        <property name="success_addView" 
            value="/WEB-INF/jsp/foundation/sales/product/viewProduct.jsp"/>
        <property name="success_add" 
            value="/foundationProduct.do?method=list"/>
        <property name="success_get" 
            value="/WEB-INF/jsp/foundation/sales/product/viewProduct.jsp" />
        <property name="success_update" 
            value="/foundationProduct.do?method=list" />
        <property name="success_list" 
            value="/WEB-INF/jsp/foundation/sales/product/listProduct.jsp" />
        <property name="success_delete" value="/foundationProduct.do?method=list" />		
     </bean>

  • Dependency Lookup (Spring MVC)

    Web Framework으로 Spring MVC를 사용하는 경우, Controller 클래스가 아닌 일반 클래스에서 Dependency Lookup 방식으로 Spring Bean을 참조할 수 있다. 웹에서 Spring 설정 파일을 읽어들인 후, WebApplicationContext를 생성하고 이것을 해당 웹 어플리케이션의 ServletContext에 저장하므로 ServletContext에 접근 가능하다면 일반 클래스에서도 WebApplicationContext를 얻어낼 수 있게 된다.

    일반 클래스에서 다음과 같이 작성한다.

    WebApplicationContext ctx = 
         WebApplicationContextUtils.getWebApplicationContext(servletContext);
    ProductService productService = (ProductService)ctx.getBean("productService");
        중략...

    속성 정의 파일은 다음과 같이 작성한다.

    <bean id="productService"
            class="anyframe.sample.foundation.sales.service.impl.ProductServiceImpl">
        중략...
    </bean>

7.1.2.Dependencies

전형적인 기업용 어플리케이션은 한 개의 객체(또는 Spring내 Bean)로 만들어지지는 않는다. 가장 간단한 어플리케이션조차도 함께 작동하는 소량의 객체를 가진다는 것을 의심할 필요가 없을 것이다. 이 장에서는 독립적인 많은 수의 Bean들이 객체가 몇 가지 목표(대개 최종사용자가 원하는 것을 수행하는 어플리케이션)를 달성하기 위해 함께 작동하는 방법에 대해 알아보기로한다.

7.1.2.1.Dependency Injection(DI)

각 클래스 사이의 의존관계를 빈 설정(Bean Definition)정보를 바탕으로 컨테이너가 자동적으로 연결해주는 것을 말한다. 컨테이너가 의존관계를 자동적으로 연결시켜주기 때문에 개발자들이 컨테이너 API를 이용하여 의존관계에 관여할 필요가 없게 되어 컨테이너 API에 종속되는 것을 줄일 수 있고 개발자들은 단지 Bean 설정파일(저장소 관리 파일)에서 의존 관계가 필요하다는 정보를 추가하기만 하면 된다. 이는 Setter Injection과 Constructor Injection 형태로 구분한다.

Setter Injection

setter 메소드 구현을 통해 초기화 시 Container로부터 의존 관계에 놓인 특정 리소스를 할당받는 방법으로 인자가 없는 생성자나 인자가 없는 static factory 메소드가 Bean을 인스턴스화하기 위해 호출된 후 Bean의 setter 메소드를 호출하여 실제화된다. 다음은 구현 클래스인 ProductServiceImpl.java 의 Setter Injection 부분이다.

public class ProductServiceImpl 
        extends GenericServiceImpl<Product, String> implements ProductService {
    public void setProductDao(ProductDao productDao) {
        this.productDao = productDao;
    }
    중략... 
}

다음은 Setter Injcetion 속성 정의 파일인 context-foundation-services.xml 의 일부이다.

<bean id="foundationProductService"
        class="anyframe.sample.foundation.sales.service.impl.ProductServiceImpl">
    <property name="productDao" ref="foundationProductDao" />
</bean>
	
<bean id="foundationProductDao" 
        class="anyframe.sample.foundation.sales.dao.impl.ProductDaoImpl">
</bean>

Constructor Injection

Constructor 구현을 통해 초기화 시 Container로부터 의존 관계에 놓인 특정 리소스를 할당받는 방법으로 각각의 협력자를 표시하는 다수의 인자를 가진 생성자를 호출하여 실제화된다. 추가적으로, Bean을 생성하기 위한 특정 인자를 가진 static factory 메소드를 호출하는 것은 대부분 동등하게 간주될 수 있다. 다음은 구현 클래스인 ProductServiceImpl.java 의 Constructor Injection 부분이다.

public class ProductServiceImpl 
    extends GenericServiceImpl<Product, String> implements ProductService {
    ProductDao productDao;
    public ProductServiceImpl(ProductDao productDao) {
        super(productDao);
        this.productDao = productDao;}
        중략...
}

다음은 Constructor Injcetion 속성정의 파일인 context-foundation-services.xml 의 일부이다.

<bean id="foundationProductService" 
    class="anyframe.sample.foundation.sales.service.impl.ProductServiceImpl">
    <constructor-arg ref="foundationProductDao"/>
</bean>
        
<bean id="foundationProductDao" 
        class="anyframe.sample.foundation.sales.dao.impl.ProductDaoImpl">
    중략...
</bean>

type 속성 정의를 이용하면, Constructor의 argument에 대한 클래스 타입을 명시적으로 정의할 수도 있다.

<bean id="foundationProductService" 
    class="anyframe.sample.foundation.sales.service.impl.ProductServiceImpl">
    <constructor-arg type="anyframe.sample.foundation.sales.service.BeanA" ref="beanA"/>
    <constructor-arg type="anyframe.sample.foundation.sales.service.BeanB" ref="beanB"/>
</bean>

Constructor의 argument 개수가 2개 이상이고, 동일한 클래스 타입의 argument가 존재할 경우 모호함을 없애기 위해, index 속성 정의를 통해 argument의 순서대로 할당할 값을 정의할 수 있다.

<bean id="foundationProductService"
    class="anyframe.sample.foundation.sales.service.impl.ProductServiceImpl">
    <constructor-arg index="0" ref="beanA" /> 
    <constructor-arg index="1" ref="beanB" /> 
</bean>

Setter Injection vs. Constructor Injection
Setter Injection 장점Constructor Injection 장점
- 생성자 Parameter 목록이 길어 지는 것 방지- 강한 의존성 계약 강제
- 생성자의 수가 많아 지는 것 방지- Setter 메소드 과다 사용 억제
- Circular dependencies 방지 - 불필요한 Setter 메소드를 제거함으로써 실수로 속성 값을 변경하는 일을 사전에 방지
  • Circular dependencies

    Constructor Injection 사용 시 주의해야 한다. 다음과 같이 두 개의 서로 다른 Bean이 생성자 Argument로 서로의 Bean을 참조하는 경우가 그 예이다.

    <bean id="beanFirst" class="test.BeanFirst">
        <constructor-arg ref="beanSecond" />
    </bean>
    
    <bean id="beanSecond" class="test.BeanSecond">
        <constructor-arg ref="beanFirst" />
    </bean>

생성자 인자 분석

생성자 인자 분석 시 사용되는 방법에는 타입 대응과 인덱스가 있다.

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

    'type' 속성을 사용하여 생성자의 인자 타입을 명확하게 명시함으로써 간단한 타입으로의 타입 매치를 사용할 수 있다.

    <bean id="sampleBean" class="sample.SampleBean">
        <constructor-arg type="int"><value>7500000</value></constructor-arg>
        <constructor-arg type="java.lang.String"><value>42</value></constructor-arg>
    </bean>

  • 생성자의 인자 인덱스

    생성자의 인자는 index 속성을 사용하여 명확하게 명시된 인덱스를 가질 수 있다. 또한 인덱스를 명시하는 것은 생성자의 인자들이 같은 타입을 가질 경우 발생하는 모호함의 문제도 해결한다. (인덱스는 0 부터 시작된다 는 것에 주의하여야 한다.)

    <bean id="sampleBean" class="sample.SampleBean">
        <constructor-arg index="0" value="7500000"/>
        <constructor-arg index="1" value="42"/>
    </bean>

7.1.2.2.Bean Property와 생성자 인자

Bean Property와 생성자의 인자는 다른 관리 Bean(협력자), 또는 인라인으로 정의된 값을 참조할 수 있다. Spring의 XML-기반의 설정 메타데이터는 이러한 목적을 위한 <property> 와 <constructor-arg> 내에서 많은 수의 하위 태그를 지원한다.

  • Primitive Type - 순수값 지원

    <value>는 사람이 읽을 수 있는 문자열 표현처럼 Property나 생성자의 인자를 명시한다.

    <bean id="myDataSource" destroy-method="close">
        <property name="driverClassName">
            <value>com.mysql.jdbc.Driver</value>
        </property>
    </bean>

  • <ref> 요소

    다른 bean에 대한 참조인 <ref>는 <constructor-arg> 또는 <property> 내부에 허용되는 마지막 요소이다. 이것은 Container에 의해 관리되는 다른 Bean을 참조하기 위해 Property의 값을 셋팅하는데 사용된다. 모든 참조는 궁극적으로 다른 객체에 대한 참조이지만 다른 객체의 id/name을 명시하는 방법은 3가지가 있다. <ref>의 bean 속성을 사용하여 대상 bean을 명시하는 것이 가장 일반적인 형태이고 같은 Container(같은 XML파일이든 아니든)나 부모 Container 내에서 어떠한 Bean에 대한 참조를 생성하는 것을 허용할 것이다. 'bean' 속성의 값은 대상 bean의 'id' 속성이나 'name' 속성의 값 중 하나가 될 것이다.

    • 타 Bean 참조

      <!-- ‘bean’ 속성 값은 타 Bean의 ‘id’ 속성 혹은 ‘name’ 속성이다. -->
      <ref bean="someBean"/>

      <!-- ‘local’ 속성 값은 동일 XML 파일 내 타 Bean의 ‘id’ 속성이다. -->
      <ref local="someBean"/>

    • parent context에 존재하는 타 Bean 참조(parent 속성 사용)

      <!-- in the parent context -->
      <bean id="accountService" 
              class="com.foo.SimpleAccountService">
          <!-- insert dependencies as required as here -->	
      </bean>

      <!-- in the child (descendant) context -->
      <bean id="productService" class="com.foo.SimpleProductService">
          <ref parent="accountService"/>
      </bean>

  • inner Bean

    <property> 나 <constructor-arg> 내부의 <bean> 은 inner bean이라 불리는 것을 정의하기 위해 사용된다. inner bean 정의시 언급된 id나 name, scope값은 Container에 의해 무시되기 때문에 id나 name값을 명시하지 않는 것이 가장 좋다. inner bean은 언제나 익명이고 prototype 형태로 동작한다.

    <bean id="outer" class="…">
        <!-- instead of using a reference to a target bean, simply define the target inline -->
        <property name="target">
            <bean class="com.mycompany.Person"> 
                <!-- this is the inner bean -->
                <property name="name" value="Fiona Apple"/>
                <property name="age" value="25"/>
            </bean>
        </property>
    </bean>

  • Collection

    <list> , <set> , <map>과 <props>은 Java Collection의 List, Set, Map and Properties의 타입으로 매핑된다. 또한 객체 Array 타입의 경우에도 콤마(,)를 이용하여 값을 설정할 수 있다(ex. String]).

    <bean id="moreComplexObject" class="sample.ComplexObject">
        <!-- results in a setAdminEmails(java.util.Properties) call -->
        <property name="adminEmails">
            <props>
                <prop key="administrator">administrator@somecompany.org</prop>
            </props>
        </property>
    
        <!-- results in a setSomeList(java.util.List) call -->
        <property name="someList"> 
            <list>
                <value>a list element followed by a reference</value>
                    <ref bean="myDataSource" />
            </list>
        </property>
    
        <!-- results in a setSomeMap(java.util.Map) call -->
        <property name="someMap">
            <map>
                <entry>
                    <key>
                        <value>entry key</value>
                    </key>
                        <value>entry value</value>
                 </entry>
            </map>
        </property>
    
        <!-- results in a setSomeSet(java.util.Set) call -->
        <property name="someSet">
            <set>
                <value>just some string</value>
                    <ref bean="myDataSource" />
            </set>
        </property>
    
        <!-- results in a setSomeArray(String[]) call -->
        <property name="someArray" value="str1,str2,str3,str4"/>  
    </bean>

  • Collection 병합

    부모역할을 하는 <list> , <map > , <set> 또는 <props>를 정의하고 이를 상속받는 <list> , <map> , <set> 또는 <props>를 정의하는 것이 가능하다. 예를 들면, 자식 collection의 값은 부모 collection내 명시된 값과 자식 collection내 명시된 값을 병합하여 얻어진다.

    예제설명) child Bean의 adminEmails Property의 <props>에서 merge=true속성을 사용하면, child Bean이 Container에 의해 실질적으로 분석되고 인스턴스화 될때, 부모의 adminEmails collection과 자식의 adminEmails collection이 병합된 형태의 adminEmails collection 을 가지게 된다. 이 병합 행위는 <list> , <map>, 그리고 <set> collection 타입에 유사하게 적용된다. 단, <list>의 경우, 이 의미는 List collection 타입과 관련된다. 이를테면, value의 ordered collection의 개념은 유지관리된다. 부모값은 모든 자식 목록의 값에 선행한다. Map, Set, Properties collection 타입의 경우, Container에 의해 내부적으로 사용되는 Map, Set 그리고 Properties 객체 타입에 관련된 collection 타입의 영향을 받는다.

    <beans>
        <bean id="parent" abstract="true" class="sample.ComplexObject">
            <property name="adminEmails">
                <props>
                    <prop key="administrator">administrator@somecompany.com</prop>
                    <prop key="support">support@somecompany.com</prop>
                </props>
            </property>
        </bean>
        <bean id="child" parent="parent">
            <property name="adminEmails">
            <!-- the merge is specified on the *child* collection definition -->
                <props merge="true">
                    <prop key="sales">sales@somecompany.com</prop>
                    <prop key="support">support@somecompany.co.uk</prop>
                </props>
            </property>
        </bean>
    <beans>

    위 설정 결과 adminEmails Collection은 다음과 같이 구성된다.

    administrator=administrator@somecompany.com sales=sales@somecompany.com support=support@somecompany.co.uk

  • <null> 요소

    <null>은 null 값을 다루기 위해 사용된다.

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

    위코드는 Java Code의 sampleBean.setEmail(null)과 동일하다. 다음과 같이 정의한 경우에는 Java Code의 sampleBean.setEmail("")과 동일하다.

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

XML 기반의 설정 메타데이터 간략화

value나 Bean 참조를 위해 필요한 공통사항이다. 완전한 형태의 <value> 와 <ref>를 사용하는 것보다 간략화한 몇 가지 형태를 사용할 수 있다. <property>, <constructor-arg>, 그리고 <entry> 모두 완전한 형태의 <value> 요소 대신에 'value' 속성을 지원한다. 예를 들어 코드1이 코드2의 형태로 간략화 될 수 있다.

<!-- 코드 1 -->
<property name="myProperty"><value>hello</value></property>

<!-- 코드 2 -->
<property name="myProperty" value="hello"/> 

혼합된 Property 명(Compound Property) - shortcut 기능 제공

복합적인 형태의 Property 정의가 가능하다. 마지막 Property명을 제외한 나머지 Property는 null이 아니어야 함에 유의하도록 한다.

<bean id="foo" class="foo.Bar">
    <property name="fred.bob.sammy" value="123" />
</bean>
위 예제에서 foo bean은 bob Property를 가지는 fred Property를 가진다. 그리고 bob Property는 sammy Property를 가지고 마지막 sammy Property는 123값으로 셋팅된다. 이렇게 되도록 하기 위해서는 foo의 fred Property, 그리고 fred의 bob Property는 bean이 생성된 후에 null이 아니어야만 한다. 그렇지 않으면 NullPointerException이 던져질 것이다.

7.1.2.3.depends-on 속성 사용

'depends-on' 속성은 Bean 이전에 초기화되어야 하는 하나 이상의 Bean을 명시적으로 강제하기 위해 사용된다. 다음은 depends-on 속성이 설정되어 있는 context-foundation-services.xml 파일의 일부이다.

<bean id="foundationProductService"
    class="anyframe.sample.foundation.sales.service.impl.ProductServiceImpl" 
    autowire="byType" depends-on="foundationProductDao">
</bean>

다중 bean에 의존성을 표시할 필요가 있다면 아래의 예와 같이 콤마, 공백 그리고 세미콜론과 같은 모든 유효한 구분자를 사용하여 'depends-on'속성의 값으로 bean 이름 목록을 정의할 수 있다. 그러나 이 ‘depends-on’ 속성을 사용하게 될 상황은 매우 드물다.

<bean id="beanOne" class="SampleBean" depends-on="manager,accountDao">
    <property name="manager" ref="manager" />
</bean>

<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />

위의 예제는 beanOne Bean이 생성되기 이전에 manager Bean이 생성되어 특정 서버를 구동시켜놓거나 특정 리소스에 대한 작업을 수행해놓고 있어야 beanOne Bean이 정상적으로 동작하므로 강제적으로 manager Bean을 초기화시킨다.

7.1.2.4.Lazy Instantiation

기본적으로 Spring IoC Container가 Start될 때 singleton Bean에 대해서는 모두 인스턴스화한다.

- 특정 singleton Bean을 Container가 Start될 때 인스턴스화 시키지 않고 처음 Bean 요청이 들어왔을 때 인스턴스화 시키고자 하면 ‘lazy-init’ 속성을 설정한다. 다음은 Lazy Instantiation 속성이 설정되어 있는 파일인 context-foundation-services.xml 파일의 일부이다.

<bean id="foundationProductDao" 
    class="anyframe.sample.foundation.sales.dao.impl.ProductDaoImpl" lazy-init="true"/>
<bean id="foundationProductService" 
        class="anyframe.sample.foundation.sales.service.impl.ProductServiceImpl"/>

- 모든 Bean들에 대해서 기본적으로 Lazy 인스턴스화 시키고자 하면 ‘default-lazy-init’ 속성을 설정하면 된다.

<beans default-lazy-init="true">
    <!-- no beans will be eagerly pre-instantiated -->
</beans>

7.1.2.5.Autowiring

Spring IoC Container는 Bean들 사이의 관계를 autowire 할 수 있다. 이것은 BeanFactory의 내용을 조사함으로써 Spring이 자동적으로 협력자(다른 bean)를 분석하는 것이 가능하다는 것을 의미한다. autowiring을 사용하면 명백하게 많은 양의 타이핑을 줄이고 Property나 생성자의 인자를 명시할 필요를 줄이거나 제거하는 것이 가능해진다. XML-기반의 설정 메타데이터를 사용할 때, Bean정의를 위한 autowire 모드는 <bean>의 autowire 속성을 사용하면 된다. <bean>의 autowire 속성에 정의할 수 있는 값은 다음과 같다.

속성설명
no[기본 설정] Autowiring 기능 사용 안 함
byNameProperty 명과 동일한 id나 name을 가진 Bean을 찾아 Autowiring 기능 적용
byType해당 Property 타입의 Bean이 하나 존재한다면 Autowiring되나 하나 이상 존재 시 UnsatisfiedDependencyException 발생됨. 만약 대응되는 Bean이 없다면 Property 셋팅 안됨
constructor이것은 byType과 유사하지만 생성자의 인자에 적용됨. BeanFactory내 생성자의 인자 타입과 맞는 Bean이 정확하게 하나가 아닐 경우 UnsatisfiedDependencyException 발생됨
autodetectconstructor 모드 수행 후 byType 모드가 수행됨
default <beans>의 default-autowire 속성에 설정한 autowire 모드가 해당 Bean에 적용됨

다음은 Autowiring 속성이 설정되어 있는 context-foundation-services.xml 파일의 일부이다.

<bean id="foundationProductService"
    class="anyframe.sample.foundation.sales.service.impl.ProductServiceImpl" 
    autowire="byType" depends-on="foundationProductDao">
</bean>

장점
  • Property나 생성자의 인자를 XML에 설정할 필요 없음

  • XML 파일 크기 줄어듬

  • 참조 관계에 있는 타 Bean들의 변경 및 추가 시 XML 파일의 변경이 최소화됨

  • 동일한 이름의 Bean을 XML에 중복 정의하여 사용하는 혼동을 없애 줌

단점
  • Bean들의 관계가 명시적으로 문서화되지 않음으로써 기대되지 않는 결과를 가지지 않게 주의해야 함

  • 타입에 의한 Autowiring은 잠재적인 모호함을 가져올 수 있음

* Autowiring 대상에서 특정 Bean을 제외하려면 autowire-candidate 속성을 false로 설정해주어야 한다.

<bean id="bean" class="sample.TestBean” autowire-candidate="false" />

7.1.2.6.Dependency Check

해당 Bean에 설정된 모든 Property들(Primitive Type/Collection 및 Bean 참조)이 제대로 설정되었는지 확인한다.

  • <bean>의 dependency-check 속성 설정

    모드설명
    none [기본 설정] 의존성 확인 안 함. 참조관계의 Bean이 존재하지 않는 경우 Property 설정 안 함
    simple Primitive Type과 collection을 위해 의존성 확인 수행
    object 참조관계의 Bean을 위해 의존성 확인 수행
    all simple과 object 모드를 모두 수행

    다음은 Dependency Check의 속성 정의 예시이다.

    <bean id="foundationProductService" 
        class="anyframe.sample.….ProductServiceImpl" dependency-check="object">
        property name="foundationProductDao" ref="foundationProductDao" />
    </bean>

    또한 다음과 같은 방법으로 모든 Bean들에 대해서 동일하게 Dependency Check 여부를 설정할 수 있다.

    <beans default-dependency-check="none">
        <!-- no beans will be eagerly pre-instantiated -->
    </beans>

7.1.3.Method Injection

Dependency Injection의 방법인 setter injection과 constructor injection을 사용할 경우, Singleton Bean은 참조하는 Bean들을 Singleton 형태로 유지하게 된다. 그런데 특별한 경우에는 Singleton Bean이 Non Singleton Bean(즉, Prototype Bean)과 Dependency 관계를 가질 수 있다. 이 같은 상황이 발생할 때 Lookup Method Injection을 사용하여 해결하는 것이 가능 하다. 동일한 상황에서 BeanFactoryAware를 구현하여 해결하는 방법도 존재하나 Spring Container API에 종속적으로 Bean 코드가 변경되므로 바람직한 해결 방법이 아니다.

  • Lookup Method Injection

  • Method Replacement

7.1.3.1.Lookup Method Injection

Singleton Bean이 Prototype Bean을 참조해야 할 경우 <lookup-method>를 설정한다. 다음은 Lookup Method Injection을 이용하여 참조 관계를 정의한 context=foundation-services.xml 의 일부이다.

<bean id="foundationProductService"
    class="anyframe.sample.foundation.sales.service.impl.ProductServiceImpl">
    <!-- method injection -->
    <lookup-method name="getProductDao" bean="foundationProductDao"/> 		
</bean>

<!-- change scope from singleton to prototype (non singleton) -->
<bean id="foundationProductDao" 
        class="anyframe.sample.foundation.sales.dao.impl.ProductDaoImpl" scope="prototype"/>

해당 lookup 메소드는 다음과 같이 ProductDao를 리턴하는 형태로 메소드를 구현하도록 한다.

public class ProductServiceImpl … {
    public ProductDao getProductDao(){
        // do nothing - this method will be overrided by Spring Container
        return null;
    }
    중략...
}

7.1.3.2.Method Replacement

이미 존재하는 기존의 메소드를 수정하지 않은 상태에서 메소드의 기능을 변경하고자 할 때 <replaced-method>를 이용한다. 사용 예제는 다음과 같다.

  • 구현 클래스

    Spring Framework에서 제공하는 MethodReplacer 인터페이스를 구현한 클래스를 생성하고, reimplement 메소드 내에 로직을 구성한다.

    import org.springframework.beans.factory.support.MethodReplacer;
    public class SayHelloMethodReplacer implements MethodReplacer{
        public Object reimplement (Object target, Method method, Object[] args)
            throws Throwable {
            중략...

  • 속성 정의 파일

    <bean id="beanFirst" class="test.BeanFirst"/>          
    <bean id="beanSecond" class=" test.BeanSecond">
        <replaced-method name="sayHello" replacer="methodReplacer">
            <arg-type>String</arg-type>
        </replaced-method>
    </bean>
    <bean id="methodReplacer" class="test.SayHelloMethodReplacer"/>

    위 속성 정의 파일에서는 BeanSecond 클래스의 sayHello 메소드 실행 시점에, 앞서 정의한 MethodReplacer가 적용되도록 정의하고 있음을 알 수 있다.

7.1.4.Bean과 Container의 확장

Spring Framework의 Container는 기본적으로 확장이 되도록 설계되어 있다. 모든 어플리케이션 개발자들이 확장하여 사용할 필요는 없고 확장할 필요성이 있는 경우에 확장하여 사용하도록 한다. 다음 각각의 항목 별로 기본적으로 제공되는 내용과 확장하여 사용할 수 있는 내용을 설명한다.

  • Bean Scope

  • Bean Life Cycle

  • Bean 상속

  • Container 확장

  • ApplicationContext 활용

7.1.4.1.Bean Scope

Spring Framework에서 지원하는 5가지 Scope에 따라 Bean의 인스턴스 생성 메커니즘이 결정된다. 서비스 Scope은 설계, 개발 단계에서 결정하기 어려우므로, 기본적으로는 Default Scope인 Singleton으로 개발하고, 추후 해당 서비스의 성격에 따라 Scope을 정의하는 것이 좋다.

  • <bean>의 scope 속성값

    속성설명
    singleton [기본 설정] Spring IoC Container 내에서 Bean 정의 당 하나의 Bean 객체 생성
    prototype 매번 같은 Type의 새로운 Bean 객체 생성
    request WebApplicationContext 유형의 Container 사용 시, Http request 당 하나의 Bean 객체 생성
    session WebApplicationContext 유형의 Container 사용 시, Http session 당 하나의 Bean 객체 생성
    globalSession WebApplicationContext 유형의 Container 사용 시, portlet context 내에서만 유효하며 global Http session 당 하나의 Bean 객체 생성

    이 외에도, custom scope을 통해 신규 Scope에 대해 정의할 수 있다.

Singleton

Singleton Scope은 기본 Scope으로 여러 개의 요청에 대해 하나의 Bean 인스턴스를 생성하여 제공한다. 따라서 Client Request마다 유지해야 하는 Data가 있다면, Singleton Scope의 서비스는 적합하지 않다. 다음은 Singleton Scope의 속성 정의 예시이다.

<bean id="foundationProductService" 
        class="anyframe.sample.foundation.sales.service.impl.ProductServiceImpl" 
        scope="singleton”>
    <property name="foundationProductDao" ref="foundationProductDao" />
</bean>
<bean id="foundationProductDao" 
        class="anyframe.sample.foundation.sales.dao.impl.ProductDaoImpl”>
    중략...
</bean>

위와 같이 singleton scope을 정의 할 수 있지만 scope의 기본 설정값이 singleton이므로 따로 정의해야 할 필요가 없다.

Prototype

Prototype Scope은 요청시마다 Bean 인스턴스를 생성하여 제공한다. 따라서 여러 Client가 동시에 한 Bean 인스턴스에 접근할 수 없다. 다음은 Prototype Scope의 속성 정의 예시이다.

<bean id="foundationProductService" 
        class="anyframe.sample.foundation.sales.service.impl.ProductServiceImpl"
        scope="prototype”>
    <property name="foundationProductDao" ref="foundationProductDao" />
</bean>

※ 일반적으로 인스턴스의 Singleton 여부를 판단하기 위해서 전역변수의 존재 여부를 이용한다. 즉, 전역변수가 존재하지 않은 인스턴스의 경우에는 Singleton, 전역변수가 존재하는 경우에는 Prototype 으로 정의할 수 있다. 그러나 해당 전역변수가 read-only인지 writable 가능한지에 따라서 이 같은 구분은 변경될 수 있다. 따라서 인스턴스를 Singleton으로 생성할지 Prototype으로 생성할지에 대한 여부에 대해서는 개발자들이 해당 Scope의 인스턴스가 메모리에서 어떻게 사용되는지를 이해하는 것이 가장 좋다.

  • Singleton

    - Shared objects with no state

    - Shared object with read-only state

    - Shared object with shared state : 이 경우에는 Synchronization을 적절하게 사용하여 동시성을 제어하도록 해야 한다.

    - High throughput objects with writable state : 일반적으로 Object Pooling과 같은 기능을 사용하는 것을 예로 들 수 있다. 인스턴스를 생성하는데 많은 비용이 발생하거나 무수히 많은 인스턴스를 관리할 필요가 있는 경우에는 Object Pooling을 사용하고 Pooling 대상이 되는 인스턴스는 Singleton으로 사용할 수 있다. 이 경우에도 Writable State에 변경이 발생할 때 Synchronization을 적절하게 사용해야 한다.

  • Prototype

    - Objects with writable state

    - Objects with private state

Other Scopes

request, session, globalSession Scope 사용 시 주의 사항은 다음과 같다.

  • Web 기반의 ApplicationContext 사용시에만 이 Scope들을 사용할 수 있으며 그 외의 경우 사용하게 되면 IllegalStateException이 발생한다.

  • Scope이 다른 Bean에서 참조하는 경우 Bean 정의 시<aop:scoped-proxy/>와 함께 작성해야 한다.(아래의 예시 참고)

    productPreferences Bean은 scope이 session이지만 foundationProductService Bean의 scope이 singleton(default가 singleton)이기 때문에 문제가 발생한다. 즉, 매 세션마다 ProductPreferences 객체를 만들어줘야 하지만 foundationProductService Bean에 의해 ProductPreferences 객체가 한 번만 생성되기 때문에 원하던 대로 동작하지 못하는 것이다. 따라서 매 세션 마다 새로운 객체를 만들어서 줄 Proxy를 만들기 위해서 <aop:scoped-proxy/>를 사용하도록 한다.

    <!-- a HTTP Session-scoped bean exposed as a proxy -->
    <bean id="productPreferences"
            class="anyframe.sample.foundation.sales.service.impl.ProductPreferences" 
            scope="session">
        <!-- this next element effects the proxying of the surrounding bean -->
        <aop:scoped-proxy/>
    </bean>
    <!-- a singleton-scoped bean injected with a proxy to the above bean -->
    <bean id="foundationProductService" 
            class="anyframe.sample.foundation.sales.service.impl.ProductServiceImpl">
        <!-- a reference to the proxied 'productPreferences' bean -->
        <property name="productPreferences" ref="productPreferences"/>
    </bean>

Custom

신규 Scope을 정의하기 위한 클래스를 생성하고, org.springframework.beans.factory.config.Scope 인터페이스를 implements한다. 또한 CustomScopeConfigurer를 이용하여 신규 정의한 Custom Scope을 등록하여 Custom Scope를 사용할 수 있도록 한다.

해당 프로젝트에 적합한 Scope을 아래의 예시와 같이 직접 정의할 수 있다.

<!-- 신규 Scope 정의를 위한 클래스를 정의하고, 
org.springframework.beans.factory.config.Scope 인터페이스를 implement한다.-->
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<!-- CustomScopeConfigurer를 이용하여 Custom Scope 등록  -->
    <property name="scopes">
        <map>
            <entry key="thread">
            <bean class="com.foo.ThreadScope"/>
            </entry>
        </map>
    </property>
</bean>

<!-- Custom Scope 사용 -->
<bean id="bar" class="x.y.Bar" scope="thread">
    <property name="name" value="Rick"/>
    <aop:scoped-proxy/>
</bean>

7.1.4.2.Bean Life Cycle

Bean의 Life Cycle은 다음 그림에서와 같이 Initialization, Activation, Destruction으로 구성된다.

Initialization

Spring Container는 아래 그림에서 보여지는 여러 과정을 통해 구동된다. Spring Bean 클래스가 아래 그림에서 보여지는 각각의 인터페이스들을 구현하였을 때 각각의 메소드들이 호출된다.

Spring Framework에서 지원하는 Life Cycle 메소드를 그대로 사용할 경우 특정한 인터페이스를 구현해야 하므로, 해당 코드가 Spring Framework에 의존적일 수 있게 된다. 즉, 위 그림에서 제시하고 있는 Life Cycle 메소드를 사용하기 위해서는 Spring Bean 클래스에서 해당 Life Cycle 인터페이스 클래스를 구현해줘야 한다. 예를 들어, ApplicationContextAware 인터페이스 클래스를 구현한 Spring Bean에서는 setApplicationContext(ApplicationContext context) 메소드를 작성하고, Spring Bean 내부에서 ApplicationContext를 이용하여 ApplicationContext에서 제공하는 메소드를 호출할 수 있다.

public class IoCServiceImpl1 implements IoCService1, 
    ApplicationContextAware {
    public void setApplicationContext (ApplicationContext context){
        IoCService2 iocService2 = (IoCService2)context.getBean("IoCService2");
    }
}

또다른 예로 MessageSourceAware 인터페이스 클래스의 경우, Spring Container에 정의된 MessageSource를 얻기 위해 사용될수 있다. MessageSourceAware 인터페이스 클래스를 구현한 Spring Bean에서 setMessages(MessageSource messages) 메소드를 작성하여 MessageSource에 접근할 수 있다.

public class IoCServiceImpl1 implements IoCService1, MessageSourceAware {
    private MessageSource messageSource;
    public void setMessageSource(MessageSource messageSource) {
        this.messageSource = messageSource;
    }
}

이와는 달리 Bean 속성(init-method, destroy-method) 정의를 통해 특정 인터페이스에 대한 구현없이 별도 Life Cycle 메소드를 정의할 수도 있다. 다음은 init-method 속성이 정의된 context-foundation-services.xml 의 일부이다.

<bean id="foundationProductService"
        class="anyframe.sample.foundation.sales.service.impl.ProductServiceImpl" 
        init-method="productInitialize" destroy-method="productDestroy" parent="parent">
</bean>

모든 Bean에 대한 초기화 method 설정은 <beans>의 default-init-method 속성을 이용하도록 한다.

Destruction

Destruction 단계에서는 BeanFactory와 ApplicationContext가 동일하게 동작한다.

다음은 destroy-method 속성이 정의된 context-foundation-services.xml 의 일부이다.

<bean id="foundationProductService"
        class="anyframe.sample.foundation.sales.service.impl.ProductServiceImpl" 
        init-method="productInitialize" destroy-method="productDestroy"
        parent="parent">
</bean>

모든 Bean의 소멸자 method 설정은 <beans>의 default-destroy-method 속성을 이용한다.

7.1.4.3.Bean 상속

Bean 정의는 여러 속성 정보들, 생성자 인자, Property 값을 포함하여 많은 양의 설정 정보를 포함한다. 자식 Bean은 부모 정의로부터 설정 정보를 상속하여 정의한다. 그러므로 값을 오버라이드하거나 다른 것을 추가할 수 있다. 상속 관계를 이용하여 Bean을 정의하는 것은 XML 파일의 양을 줄일 수 있으므로 템플릿 형태의 부모 Bean을 정의하는 것은 유용하다. XML 기반의 속성 정의시 자식 Bean은 부모 Bean을 명시하기 위해 'parent' 속성을 사용해야 한다.

  • 부모 Bean 정의

    특수 설정 없이 부모 Bean으로 사용이 가능하며 class 속성 값을 설정하지 않은 경우, 반드시 abstract 속성 값을 "true"로 설정한다. abstract 속성 값이 "true"인 경우 Bean의 인스턴스화가 불가능하다.

  • 자식 Bean 정의

    parent 속성 값에 부모 Bean의 id 혹은 name을 설정한다.

다음은 Bean 상속이 표현되어 있는 context-foundation-services.xml 의 일부이다.

<!-- register parent bean that has a dependency with foundationProductDao bean -->
<bean id="parent" abstract="true">
    <property name="foundationProductDao" ref="foundationProductDao" />	
</bean>

<bean id="foundationProductService" 
        class="anyframe.sample.foundation.sales.service.impl.ProductServiceImpl" 
        init-method="productInitialize" destroy-method="productDestroy" 
        parent="parent">
</bean>

7.1.4.4.Container 확장

Bean 후처리

Bean의 LifeCycle 중 Initialization 단계에서 Bean 초기화 시점 전후에 수행되는 것을 Bean 후처리라고 하며, BeanPostProcessor를 구현하면 기능을 확장할 수 있다. ApplicationContext 유형의 Container 사용 시에는 XML 파일에 BeanPostProcessor 인터페이스를 구현한 클래스를 등록만 시키면 Container가 해당 클래스를 BeanPostProcessor로 인식하여 각각의 Bean을 초기화하기 전과 후에 후처리 메소드를 호출해준다. 그러나 BeanFactory 유형의 Container를 사용하고 있다면 BeanFactory의 addBeanPostProcessor() 메소드를 이용하여 프로그램 상에서 등록해야 한다. 예시는 다음과 같다.

public class InstantiationTracingBeanPostProcessor 
    implements BeanPostProcessor {
    // simply return the instantiated bean as-is
    public Object postProcessBeforeInitialization(Object bean, String beanName) 
        throws BeansException {
        return bean; // we could potentially return any object reference here
    }
  
    public Object postProcessAfterInitialization(Object bean, String beanName) 
        throws BeansException {
        System.out.println("Bean '" + beanName + "' created : " + bean.toString());
        return bean;
    }
}  

<bean class="scripting.InstantiationTracingBeanPostProcessor"/>

BeanFactory 후처리

BeanFactoryPostProcessor를 구현하여 BeanFactory 후처리 기능을 확장할 수 있다. 모든 Bean에 대한 정의가 로딩된 후, BeanPostProcessor Bean을 포함한 어떤 Bean이라도 인스턴스화되기 이전에 Spring Container에 의해 BeanFactoryPostProcessor의 postProcessBeanFactory() 메소드가 호출된다. 따라서, BeanFactoryPostProcessor 인터페이스를 구현한 클래스 내에서 postProcessBeanFactory 메소드를 작성하고, Bean으로 정의하면 된다. 예시는 다음과 같다.

public class BeanCounterBeanFactoryPostProcessor
    implements BeanFactoryPostProcessor {
    public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) 
        throws BeansException {
        중략...
    }
}
<bean class="test.BeanCounterBeanFactoryPostProcessor"/>
BeanFactoryPostProcessor는 BeanFactory 유형의 Container와 함께 사용될 수 없다. 유용한 BeanFactoryPostProcessor 구현 클래스는 PropertyPlaceholderConfigurer와 CustomEditorConfigurer이다.

다음은 PropertyPlaceholderConfigurer와 CustomEditorConfigurer에 대한 사용 예이다.

  • 설정 정보의 외부화

    PropertyPlaceholderConfigurer를 사용하여 하나 이상의 외부 Property 파일로부터 속성들을 로딩하고 그 속성들을 이용하여 Bean 정의 XML 파일에서의 위치소유자(placeholder) 변수들을 채운다.

    다음은 설정 정보 외부화를 위해 PropertyPlaceholderConfigurer 클래스를 Bean으로 등록하고 있는 context-foundation-services.xml 의 속성 정의 부분이다.

    <!-- set file locations --> 
    <bean id="configurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>productConfigurer.properties</value>
            </list>
        </property>
    </bean>
     
    <bean id="foundationProductService" 
            class="anyframe.sample.foundation.sales.service.impl.ProductServiceImpl">
        <property name="foundationProductDao" ref="foundationProductDao" />
        <!-- set productCompany value using key name in properties file -->
        <property name="productCompany" value="${product.company}"></property>
    </bean>

    위에서 외부 파일로 정의된 productConfigurer.properties 의 내용은 다음과 같다.

    product.company=SamsungSDS   

  • PropertyEditor 확장

    CustomEditorConfigurer를 사용하여 java.beans.PropertyEditor의 커스텀 구현 클래스를 등록하여 특성 값을 다른 특성 타입으로 번역할 수 있도록 한다. 확장한 PropertyEditor 클래스를 속성 정의 파일에 등록 후 PropertyEditor로 사용한다.

    <bean id="customEditorConfigurer"          
            class="org.springframework.beans.factory.config.CustomEditorConfigurer">
        <property name="customEditors">
            <map>
                <entry key="com.springinaction.knight.PhoneNumber">
                <bean id="phoneEditor"          
    	            class="com.springinaction.springcleaning.PhoneNumberEditor" />
                </entry>
            </map>
        </property>
    </bean>

    <bean id="knight" class="com.springinaction.knight.KnightOnCall">
        <property name="url" value="http://www.knightoncall.com" />
        <property name="phoneNumber" value="940-555-1234" />
    </bean>

7.1.4.5.ApplicationContext 활용

MessageSource를 활용한 국제화(I18N) 지원

ApplicationContext 인터페이스는 MessageSource라고 불리는 인터페이스를 확장해서 메시징(국제화 지원)기능을 제공하며 HierarchicalMessageSource와 함께 구조적인 메시지를 분석하는 능력을 가진다. MessageSourceAware인터페이스를 구현하는 Bean은 ApplicationContext의 messageSource Bean을 사용할 수 있다.

다음은 context-common.xml 의 messageSource 속성 정의 부분이다.

<bean id="messageSource"
         class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basenames">
        <list><value>message/message-productmgmt</value></list>
    </property>
</bean>

Resource Bundle 파일은 국제화 지원을 위해 Locale별 파일로 구성하며 위에서 참조하는 message-productmgmt.properties 파일은 다음과 같다.

errors.required={0} is a required field.  

또한 ProductServiceImpl.java 파일에 messageSource를 얻는 부분은 다음과 같이 구현되어 있다.

new String(messageSource.getMessage("errors.required", new Object[] {"PROD_NO"},
    Locale.KOREA).getBytes("8859_1"), "euc-kr")

messageSource 부분을 테스트 할수있는 ContainerTest.java 파일을 수행시키면 다음과 같은 message를 확인할 수 있다.

"PROD_NO" 필드는 반드시 필요하다.

Event

ApplicationContext는 어플리케이션이 구동하는 동안 다수의 이벤트를 발생시킬 수 있으므로, Listener를 Bean으로 등록하게 되면, Container는 해당하는 Event가 발생하면 관련 Listener의 onApplicationEvent() 메소드를 호출한다.

  • Built-in Events

    이벤트설명
    ContextRefreshedEvent ApplicationContext가 초기화되거나 갱신(refresh)될 때 발생하는 이벤트 - 여기서 초기화는 모든 Bean이 로드되고 Singleton Bean들은 미리 인스턴스화되며 ApplicationContext는 사용할 준비가 된다는 것을 의미함
    ContextClosedEvent ApplicationContext의 close()메소드를 사용하여 ApplicationContext가 종료될 때 발생하는 이벤트 - 여기서 종료는 Singleton Bean들이 소멸(destroy)되는 것을 의미함
    RequestHandledEvent HTTP Request가 처리되었을 때 WebApplicationContext 내에서 발생하는 이벤트 - 이 이벤트는 Spring의 DispatcherServlet을 사용하는 웹 어플리케이션에서만 적용 가능함

    ApplicationListener를 구현한 Listener의 예시는 다음과 같다.

    public class RefreshListener implements ApplicationListener {
        public void onApplicationEvent(ApplicationEvent evt) {
            if (evt instanceof ContextRefreshedEvent) { 
                 중략...
            }
        }
    }

    앞서 구현한 RefreshListener 클래스에 대한 속성 정의 예시는 다음과 같다.

    <bean id="refreshListener" class="sample.RefreshListener"/>

  • Custom Event 발생

    사용자 정의 Event를 직접 발생시키고 해당 Event 발생 시 처리될 수 있도록 Listener를 등록하는 것도 가능하다. Event Listening을 하기 위해서는 Listener 등록이 필요하다. 다음은 Listener Bean을 등록하는 context-foundation-services.xml 파일의 일부이다.

    <bean id="productEventListener" 
        class="anyframe.sample.foundation.sales.service.impl.ProductEventListener"/>

    다음은 ProductEventListener.java 의 일부로, Custom Event인 ProductEvent를 처리하고 있음을 알 수 있다.

    public class productEventListener implements ApplicationListener {
        public void onApplicationEvent(ApplicationEvent evt) {
            if (evt instanceof ProductEvent) {
                ProductEvent event = (ProductEvent)evt;
                System.out.println("Received in ProductEventListener : " + 
                    event.getProductMessage());
            }
        }
    }

    다음은 ProductServiceImpl.java 파일로, Custom Event인 ProductEvent를 발생시키는 부분이다.

    this.ctx.publishEvent(new ProductEvent(this,"new product is added successfully."));

BeanFactory와 ApplicationContext 특징 비교
FeatureBeanFactoryApplicationContext
Bean instantiation/wiringYesYes
Automatic BeanPostProcessor registrationNoYes
Automatic BeanFactoryPostProcessor registrationNoYes
Convenient MessageSource access (for i18n)NoYes
ApplicationEvent publicationNoYes

대부분의 전형적인 어플리케이션 구축 시에는 ApplicationContext 사용을 권장한다.

7.1.5.XML 스키마 기반 설정

XML 스키마에 기초하여 새로운 XML 설정 문법이 나오고 있으며 점점 더 쉽게 XML을 설정할 수 있도록 Spring Framework은 진화하고 있다. 또한 XML 스키마를 확장하여 사용할 수도 있다.

  • 기본으로 제공되는 XML 스키마

    Spring Framework에서 기본으로 제공하는 XML 스키마의 종류는 다음과 같다.

    [util, jee, lang, jms, tx, aop, context, tool, beans] (각각의 사용법은 Spring 매뉴얼 사이트 를 참고하도록 한다.)

  • XML 스키마 확장 가능

    어플리케이션 개발 시 어플리케이션 도메인을 좀더 잘 표현할 자체적인 도메인 속성의 설정 태그를 정의할 수 있다.

    확장한 스키마를 실제 XML 파일에 적용하여 사용하는 방법은 Spring 매뉴얼 사이트를 참고하도록 한다.

  • XML 스키마 참조 방법

    xmlns:~를 이용하여 사용하고자 하는 namespace를 정의하고, 해당 namespace의 XML 스키마를 정의한 XSD 파일의 location을 정의한다.

    <?xml version="1.0" encoding="UTF-8"?>
    <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"
        xmlns:jee="http://www.springframework.org/schema/jee"
        xmlns:lang="http://www.springframework.org/schema/lang"
        xmlns:jms="http://www.springframework.org/schema/jms"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xmlns:tx="http://www.springframework.org/schema/tx"
        xmlns:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="http://www.springframework.org/schema/beans 
            http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
            http://www.springframework.org/schema/util 
            http://www.springframework.org/schema/util/spring-util-2.5.xsd
            http://www.springframework.org/schema/jee 
            http://www.springframework.org/schema/jee/spring-jee-2.5.xsd
            http://www.springframework.org/schema/lang 
            http://www.springframework.org/schema/lang/spring-lang-2.5.xsd
            http://www.springframework.org/schema/jms 
            http://www.springframework.org/schema/jms/spring-jms-2.5.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
            http://www.springframework.org/schema/tx 
            http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
            http://www.springframework.org/schema/context 
            http://www.springframework.org/schema/context/spring-context-2.5.xsd">
            <!-- <bean/> definitions here -->
    </beans>

  • XML 설정에 대한 부담시 Annotation 활용 제안

    XML 기반에서 Bean을 정의하는 방식 외에 Annotation을 활용하면 XML 설정에 대한 부담을 덜 수 있다.

7.2.Annotation

Spring XML 만을 독립적으로 사용할 경우 때때로 방대하고 복잡한 속성 파일들로 인해 시스템 개발 및 유지보수의 지연을 초래할 가능성이 높아진다. 이러한 문제점을 해결하기 위해 Spring Framework에서는 별도 XML 정의없이도 사용 가능한 annotation 지원에 주력하고 있는 실정이다. Spring 2.0에서는 @Transactional, @Required, @PersistenceConetxt /@PersistenceUnit과 같은 Transaction 관리 또는 Persistence 관리 영역에 대한 annotation들을 지원했다면 Spring 2.5부터는 Bean 또는 Dependency 정의 등과 같이 Spring 속성 정의 XML과 직접적으로 관련된 annotation들을 선보이고 있다. 또한 Spring 3에서는 Spring 특화된 Annotation 외에 Dependency Injection에 관한 표준 Annotation인 JSR-330(Dependency Injection for Java) Annotation 사용을 지원하기 시작했다. 본 문서에서는 annotation 사용 용도를 Bean Management, Dependency Injection, Life Cycle로 구분하고 각각의 경우에 따른 사용법에 대해 상세히 살펴보도록 하자.

기본적으로, Annotation은 JDK 1.5 이상에서 활용이 가능하며, Spring Container가 Annotation을 인식할 수 있도록 하기 위해서는 속성 정의 XML 파일 내에 다음과 같은 정의가 추가되어야 함에 유의해야 한다.

<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:schemalLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd>
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/contxt/spring-context-2.5.xsd">
        <context:annotation-config/>	                            
</beans>

  • XML vs. Annotation

    다음은 특정 서비스를 구성하는 구현 클래스, DAO 클래스, 속성 정의 XML에 대해 XML을 이용하는 경우와 Annotation을 이용하는 경우로 나누어 비교해 본 그림이다.

7.2.1.Bean Management

Stereotype Annotation을 사용하면 Spring Framework의 컨테이너에 의해 관리되어야 하는 Bean들을 정의할 수 있다. 일반적으로 Parent Stereotype Annotation인 @Component를 활용하면 모든 Bean에 대한 정의가 가능하다. 그러나 Spring Framework에서는 레이어별로 구성 요소를 구분하여 다음과 같은 Annotation을 사용할 것을 권장하고 있고, 향후 지속적으로 레이어별 특성을 반영할 수 있는 속성들을 추가해 나아갈 예정이다.

  • @Service

    비즈니스 로직을 처리하는 클래스를 정의하는데 사용한다.

  • @Controller

    프리젠테이션 레이어를 구성하는 Controller 클래스를 정의하는데 사용하며, Spring MVC 기반인 경우에 한해 활용 가능하다.

  • @Repository

    데이터 접근 로직을 처리하는 클래스를 정의하는데 사용하며, 퍼시스턴스 레이어에서 발생한 Exception에 대한 Translation이 지원된다.

JSR-330에서는 컴포넌트 식별을 위해 @Named Annotation을 제공하고 있으며 Spring 3에서는 특정 Bean 클래스에 대해 @Named를 부여한 경우 Stereotype Annotation을 부여한 경우와 마찬가지로 컨테이너에 의해 해당 Bean이 관리될 수 있도록 지원한다. 단, @Named를 부여한 Bean에 대해서는 기본 Scope인 'Singleton'으로 적용되며 다른 유형의 Scope 처리는 향후 릴리즈 시에 반영될 예정이다.

본 문서에서는 위에서 나열한 annotation을 사용하는 방법에 대해서 자세히 살펴보도록 한다.

7.2.1.1.Auto Detecting

Stereotype Annotation을 사용하여 Bean을 정의하면 XML에 따로 Bean 정의를 명시하지 않아도 Spring Container가 Bean을 인식하고 관리할 수 있다. 단, 자동 인식이 되기 위해서는 서비스 속성 정의 XML 내에 <context:component-scan/> 을 정의해 주어야 한다. 이 설정을 추가하면 Spring Container는 클래스패스 상에 존재하는 클래스들을 스캔하여 Stereotype Annotation이 정의된 클래스들 Bean으로 인식하고 자동으로 등록한다.

<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:schemalLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/contxt/spring-context-2.5.xsd">
    <context:component-scan base-package="anyframe.sample" />	                            
</beans>

<context:component-scan />을 정의한 경우 Annotation 인식을 위한 설정 <context:annotation-config/> 을 별도로 추가하지 않아도 된다.

다음은 서비스 레이어의 구성 요소인 ProductServiceImpl 클래스에 대해 @Service라는 Stereotype Annotation을 사용한 예이다.

@Service
public class ProductServiceImpl extends GenericServiceImpl<Product, String>
        implements ProductService {
    @Resource
    MessageSource messageSource;
    @Resource
    ProductDao productDao;
}

위 예제에서는 해당 클래스의 클래스명(소문자로 시작)이 Bean name으로 셋팅되어 해당 Bean을 찾을 때 productServiceImpl 이라는 문자열을 사용해야 한다.

ProductService service = (ProductService) context.getBean("productServiceImpl");

해당 Annotation에 속성을 부여하면, 원하는 Bean name을 지정하는 것 또한 가능하다.

@Service("productService")
public class ProductServiceImpl extends GenericServiceImpl<Product, String>;
        implements ProductService {
    @Resource
    MessageSource messageSource;
    @Resource
    ProductDao productDao;
}

이 경우에 해당되는 Bean을 찾기 위해서는 속성으로 정의한 Name을 활용해야 한다.

ProductService service = (ProductService) context.getBean("productService")

7.2.1.2.Using Filters to customize scanning

<context:component-scan>의 여러 속성들을 이용하면 검색 대상의 범위를 조정하여 자동으로 검색되어 Bean으로 등록되는 클래스들을 filtering 할 수 있다. base-package 속성은 <context:component-scan> 내에 정의 가능한 속성으로 검색 대상 패키지를 정의하는 용도로 사용된다. 이외에도 <context:component-scan> 은 하위 element로 <context:include-filter>, <context:exclude-filter>를 가질 수 있는데, 다양한 Filter Type(type)에 해당하는 표현식(expression)을 정의함으로써 이에 해당하는 클래스들을 포함 또는 제외시킬 수가 있다. 다음은 <context:include-filter>, <context:exclude-filter> 사용 예이다.

<context:component-scan base-package="anyframe.sample">
    <context:include-filter type="regex" expression=".*Stub.*Repository">
    <context:exclude-filter type="annotation" 
        expression="org.springframework.stereotype.Repository"/>
</context:component-scan>

정의 가능한 Filter Type은 4가지이며, 다음과 같다.

Filter TypeExample Expressions
annotation org.example.SomeAnnotation
assignable org.example.SomeClass
regex org\.example\.Default.*
aspectj org.example..*Service+

참고

Bean 정의를 위해 Annotation을 부여한 클래스를 auto detection하는 디폴트 설정을 사용하지 않고자 하는 경우에는 <context:component-scan />태그에 use-default-filters="false" 속성을 추가하면 된다.

7.2.1.3.Scope Definition

Spring Framework에서는 Bean의 인스턴스 생성 메커니즘 에 따라 5가지 Scope 을 제공하는데 이러한 Bean Scope을 정의하기 위해서는 다음과 같이 @Scope을 사용하도록 한다.

@Scope("prototype")
@Service("productService")
public class ProductServiceImpl extends GenericServiceImpl<Product, String>
        implements ProductService {
    @Resource
    MessageSource messageSource;
    @Resource
    ProductDao productDao;
}

7.2.2.Dependency Injection

특정 Bean의 기능 수행을 위해 다른 Bean을 참조해야 하는 경우 사용하는 Annotation으로는 @Autowired, @Resource 그리고 @Inject가 있다.

  • @Autowired

    Spring Framework에서 지원하는 Dependency 정의 용도의 Annotation으로, Spring Framework에 종속적이긴 하지만 정밀한 Dependency Injection이 필요한 경우에 유용하다.

  • @Resource

    JSR-250 표준 Annotation으로 Spring Framework 2.5.* 부터 지원하는 Annotation이다. @Resource는 JNDI 리소스(datasource, java messaging service destination or environment entry)와 연관지어 생각할 수 있으며, 특정 Bean이 JNDI 리소스에 대한 Injection을 필요로 하는 경우에는 @Resource를 사용할 것을 권장한다.

  • @Inject

    JSR-330 표준 Annotation으로 Spring 3 부터 지원하는 Annotation이다. 특정 Framework에 종속되지 않은 어플리케이션을 구성하기 위해서는 @Inject를 사용할 것을 권장한다. @Inject를 사용하기 위해서는 클래스 패스 내에 JSR-330 라이브러리인 javax.inject-x.x.x.jar 파일이 추가되어야 함에 유의해야 한다.

@Autowired, @Resource, @Inject를 사용할 수 있는 위치는 다음과 같이 약간의 차이가 있으므로 필요에 따라 적절히 사용하면 된다.

  • @Autowired : 멤버변수, setter 메소드, 생성자, 일반 메소드에 적용 가능

  • @Resource : 멤버변수, setter 메소드에 적용가능

  • @Inject : 멤버변수, setter 메소드, 생성자, 일반 메소드에 적용 가능

@Autowired, @Resource, @Inject를 멤버변수에 직접 정의하는 경우 별도 setter 메소드는 정의하지 않아도 된다.

7.2.2.1.@Inject

Spring의 @Autowired와 동일한 역할을 수행하는 표준 Annotation이다. 단, @Autowired와 달리 'required' 속성을 가지고 있지 않다. 또한 @Named와 같이 사용하였을 경우 정의된 Bean 이름을 이용하여 Injection이 수행된다. 다음은 Foundation Plugin 설치로 추가된 서비스 클래스 ~/moviefinde/service/impl/MovieServiceImpl.java의 일부로써 @Inject를 사용한 예이다.

@Service("foundationMovieService")
@Transactional(rollbackFor = { Exception.class }, propagation = Propagation.REQUIRED)
public class MovieServiceImpl implements MovieService {

    @Inject
    @Named("idGenerationServiceMovie")
    IIdGenerationService idGenerationService;

    @Inject
    @Named("foundationMovieDao")
    private MovieDao movieDao;

    // ...	
}

7.2.2.2.@Autowired

@Autowired는 Spring에 종속적이긴 하지만, 적용할 수 있는 위치가 @Resource나 @Inject보다 다양하고, 정밀한 Dependency Injection이 필요한 경우에 유용하다.

다음은 @Autowired를 사용한 예이다.

@Service("productService")
public class ProductServiceImpl extends GenericServiceImpl<Product, String>
        implements ProductService {
     @Autowired
     ProductDao productDao;
}

@Autowired 적용 위치 별로 사용 예를 들면 다음과 같다.

  • 생성자 및 멤버 변수

    @Service("productService")
    public class ProductServiceImpl extends GenericServiceImpl<Product, String>
            implements ProductService {
        @Autowired
        ProductDao productDao;
        MessageSource messageSource;
    		
        @Autowired
        public ProductServiceImpl(MessageSource messageSource) {
            this.messageSource = messageSource;
        }
    }
    위의 예제와 같이 @Autowired를 사용하면 ProductServiceImpl 클래스가 생성될 때 Spring Container에 의해서 MessageSource 타입의 Bean이 생성자의 argument로 자동으로 injection 된다. 또한 productDao 멤버변수에도 @Autowired가 적용되어 있으므로 ProductDao 타입의 Bean이 자동 injection된다.

  • setter 메소드

    @Service("productService")
    public class ProductServiceImpl extends GenericServiceImpl<Product, String>
            implements ProductService {
        ProductDao productDao;
        @Autowired
        public void setProductDao(ProductDao productDao) {
            this.productDao = productDao;
        }
    }
    Spring Container에 의해서 자동으로 setProductDao() 메소드가 호출되어 ProductDao 타입의 Bean이 productDao 멤버변수로 injection된다.

  • 일반 메소드

    @Service("productService")
    public class ProductServiceImpl extends GenericServiceImpl<Product, String>
            implements ProductService {
        ProductDao productDao;
        MessageSource messageSource;
        @Autowired
        public void prepare(ProductDao productDao, MessageSource messageSource) {
            this.productDao = productDao;
            this.messageSource = messageSource;
        }
    }
    @Resource 와는 달리 위의 prepare()와 같은 일반 메소드에도 @Autowired를 적용함으로써 Spring Container에 의한 Dependency Injection 처리를 할 수 있다. 위의 예제에서는 ProductDao 타입의 Bean이 productDao로, MessageSource 타입의 Bean이 messageSource로 injection된다.

  • 배열이나 Collection 형태의 멤버변수와 메소드

    @Service("productService")
    public class ProductServiceImpl extends GenericServiceImpl<Product, String>
            implements ProductService {
        ProductDao productDao;
        @Autowired
        Category[] categories;
    }
    @Service("productService")
    public class ProductServiceImpl extends GenericServiceImpl<Product, String>
            implements ProductService {
        ProductDao productDao;
        Set<Category> categories;
        @Autowired
        public void setCategories(ProductDao productDao, Set<Category> categories) {
            this.productDao = productDao;
            this.categories = categories;
        }
    }
    위 예제 소스의 경우, Spring Container에 등록된 Category 타입의 Bean들이 모두 categories 배열 (또는 collection)에 injection된다.

  • Map(key=Bean Name, value=Bean 객체) 형태의 멤버변수와 메소드

    @Service("productService")
    public class ProductServiceImpl extends GenericServiceImpl<Product, String>
            implements ProductService {
        ProductDao productDao;
        Map<String, Category> categories;
        @Autowired
        public void setCategories(ProductDao productDao, Map<String, Category> categories) {
            this.productDao = productDao;
            this.categories = categories;
        }
    }
    위 예제 소스의 경우, Spring Container에 등록된 Category 타입의 Bean들이 Bean name이 key로, Bean 객체가 value인 쌍으로 모두 categories Map에 injection된다.

기본적으로 @Autowired가 적용된 참조 관계는 반드시 해당 빈이 존재해야 하지만, required 속성을 false로 설정하는 경우에는 해당되는 Bean을 찾지 못하더라도 에러가 발생하지 않는다.

@Service
public UserService implements UserService {
    @Autowired(required=false)
    private UserDAO userDAO;
}

또한, @Resource에서 설명했던 바와 같이 @Autowired도 BeanFactory, ApplicationContext, ResourceLoader, ApplicationEventPublisher, MessageSource 인터페이스와 하위 인터페이스들을 별도 설정 없이 바로 사용할 수 있게 해준다.

@Service("productService")
public class ProductServiceImpl extends GenericServiceImpl<Product, String>
        implements ProductService {
    @Autowired
    ApplicationContext context;
}

7.2.2.3.@Resource

@Resource는 Bean name을 지정하여 Dependency Injection을 하고자 하는 경우에 사용한다. @Resource는 name이라는 속성을 가지고 있어서, Spring Container가 @Resource로 정의된 요소에 injection하기 위한 Bean을 검색할 때, name 속성에 지정한 이름을 검색할 Bean Name으로 사용한다.

@Service("productService")
public class ProductServiceImpl extends GenericServiceImpl<Product, String>
        implements ProductService {
    @Resource
    MessageSource messageSource;
    @Resource (name="productDao")
     productDao;

명시적으로 name 속성에 이름을 지정하지 않는 경우, 검색할 Bean Name은 다음과 같은 규칙을 따른다.

  • @Resource가 멤버 변수에 정의되었을 때 : 멤버 변수의 이름

  • @Resource가 setter 메소드에 정의되었을 때 : 해당 setter 메소드의 이름에서 'set'을 제외한 이름(첫 글자는 소문자)

    예) setFoo(...) --> 'foo'

해당하는 Bean Name으로 injection할 Bean을 찾지 못했을 경우에는 @Autowired 처럼 Bean의 type으로 검색한다.

@Resource를 이용하면 BeanFactory, ApplicationContext, ResourceLoader, ApplicationEventPublisher, MessageSource 인터페이스와 하위 인터페이스들을 별도 설정 없이 바로 사용 가능하다.

@Service("productService")
public class ProductServiceImpl extends GenericServiceImpl<Product, String>
        implements ProductService {
    @Resource
    ApplicationContext context;
}

7.2.2.4.@Qualifier

type-driven injection의 경우 Spring Container가 해당 Bean을 찾을 때 객체의 type을 기준으로 검색을 수행하게 된다. 이와 같은 경우 동일한 type의 Bean이 여러 개 검색되었을 때 injection 대상이 되는 Bean을 결정하기 위한 세밀한 제어가 필요하며 이 때 @Qualifier를 사용할 수 있다.

Spring @Qualifier

기본적으로 @Autowired는 type-driven injection 형태로 동작하여, 동일한 객체 type의 Bean이 여러 개 검색되었을 때 injection 대상이 되는 Bean을 결정하기 위해 @Qualifier를 사용할 수 있다.

다음은 @Autowired와 함께 @Qualifier를 사용한 예이다.

@Service("productService")
public class ProductServiceImpl extends GenericServiceImpl<Product, String>
        implements ProductService {
    @Autowired
    @Qualifier("sports")
    Category sportsCategory;
}

위와 같이 정의하면 "sports"라는 qualifier 속성 값이 정의된 Bean이 sportsCategory 멤버변수로 injection된다.

위의 @Qualifier에 의해 연결될 Bean은 다음과 같이 정의할 수 있다.

<?xml version="1.0" encoding="UTF-8"?>
<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-2.5.xsd
                http://www.springframework.org/schema/context 
                http://www.springframework.org/schema/context/spring-context-2.5.xsd">
        				
    <context:annotation-config/>

    <bean class="anyframe.sample.domain.Category">
        <qualifier value="sportsCategory"/>
        <!-- inject any dependencies required by this bean -->
    </bean>
    <bean class="anyframe.sample.domain.Category">
         <qualifier value="livingCategory"/>
         <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="productService" 
        class="anyframe.sample.annotation.sales.service.impl.ProductServiceImpl"/>
</beans>

JSR-330 @Qualifier

JSR-330 @Qualifier는 앞서 언급한 Spring @Qualifier 또한 type driven injection 수행시 정밀한 제어를 위해 사용될 수 있다. 단 Spring @Qualifier와 다르게 Qualifier Annotation을 정의하는데만 적용될 수 있다. 다음은 javax.inject.Qualifier를 사용하여 정의된 @Qualifier의 예로 'type'이라는 속성을 가지고 있다.

//...
import javax.inject.Qualifier;

@Target( { ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface DaoQualifier {
    public abstract String type() default "query";
}

MovieDao라는 인터페이스가 2개의 구현체(MovieDaoQueryImpl, MovieDaoHibernateImpl)를 가지고 있다라고 가정해보자. 다른 Bean에서 MovieDao의 구현체들에 대해 Type Injection할 수 있도록 하기 위해서 해당 Bean을 정의할 때 @Named와 함께 @DaoQualifier를 사용할 수 있을 것이다.

@Named
@DaoQualifier(type = "hibernate")
public class MovieDaoHibernateImpl implements MovieDao {
    // ...
}

@Named
@DaoQualifier
public class MovieDaoQueryImpl implements MovieDao {
    // ...
}

앞서 정의한 Bean을 Injection하기 위해서는 다음과 같이 @Inject와 함께 @DaoQualifier를 사용하면 된다.

@Named
public class MovieServiceImpl implements MovieService {

    @Inject
    @DaoQualifier(type = "hibernate")
    private MovieDao hibernateMovieDao;

    @Inject
    @DaoQualifier
    private MovieDao queryMovieDao;
	
    // ...	
}

위에서 언급한 jsr-330 qualifier 샘플 코드는 본 섹션 내의 다운로드 - anyframe.sample. di.qualifier를 통해 다운로드받을 수 있다.

7.2.2.5.@Provider

JSR-303에서 제공하는 Annotation 중의 하나로써, 참조하고자 하는 Bean을 직접 Inject하지 않고 Inject 대상이 되는 클래스 타입 T에 대해 Provider<T> 형태로 Inject한다. javax.inject.Provider를 통해 Injection을 수행하는 경우 Spring에서는 DefaultListableBeanFactory 내의 DependencyProvider라는 구현체의 get() 메소드를 이용하여 Generic Type으로 제공된 T 타입의 새로운 인스턴스를 전달해주도록 하고 있다. 따라서, Singleton Bean에서 Prototype Bean을 참조하고자 할 때 적용할 수 있다.

다음은 Provider<T> 형태로 특정 Bean을 참조한 예이다.

@Named
public class MovieServiceImpl implements MovieService {
    @Inject
    private Provider<MovieDao> movieDaoFactory;

    public Movie get(String movieId) throws Exception {
        // get movieDao instance calling get()
        return movieDaoFactory.get().get(movieId);
    }
}

위의 코드에서는 Provider 타입의 movieDaoFactory 객체를 통해 get() 메소드를 호출할 때마다 새로운 MovieDao 인스턴스를 전달받게 될 것이다.

특정 Bean을 참조하는 경우 직접 Inject하지 않고 Provider<T> 형태로 Inject하였을 때 다음과 같은 이점을 제공한다. (출처: JSR-000330 Dependency Injection for Java 1.0 Final Release for Documentation)

  • retrieving multiple instances

  • lazy or optional retrieval of an instance

  • breaking circular dependencies

  • abstracting scope so you can look up an instance in a smaller scope from an instance in a containing scope

위에서 언급한 jsr-330 provider 샘플 코드는 본 섹션 내의 다운로드 - anyframe.sample. di.provider를 통해 다운로드받을 수 있다.

7.2.2.6.@Inject / @Autowired / @Resource 비교

@Inject / @Autowired / @Resource를 비교하면 다음과 같다.

Annotation@Inject@Autowired@Resource
Injection 방식type-driven injectiontype-driven injectionname-matching injection
사용가능한 위치멤버변수, setter 메소드, 생성자, 일반 메소드멤버변수, setter 메소드, 생성자, 일반 메소드멤버변수, setter 메소드

7.2.3.LifeCycle Annotation

IoC의 Life Cycle 에서 설명한 바와 같이 Bean의 LifeCycle은 Initializaion ->Activation -> Destruction으로 구성되어 있으며, LifeCycle 메소드를 정의하는 경우 컨테이너 기동시 또는 종료시 필요한 로직을 수행할 수 있게 된다. Bean을 초기화 또는 소멸화 하는 시점에 별도 작업이 필요한 경우 기존에는 InitializingBean과 DesposableBean 인터페이스를 상속하거나, Bean 정의시 명시적으로 초기화 메소드나 소멸화 메소드를 별도로 지정해야 했다. 그러나, 다음과 같은 Annotation을 사용하면 XML 정의 또는 별도 인터페이스 상속없이 Bean의 LifeCycle 관리가 가능해진다.

7.2.3.1.@PostConstruct

JSR-250 표준 Annotation으로 Bean 초기화시 필요한 작업을 담은 메소드에 대해 정의한다. @PostConstruct를 사용하기 위해서는 클래스패스 내에 jsr250-api.jar 파일이 추가되어 있어야 한다.

@PostConstruct
// 메소드명은 자유롭게 정의할 수 있다.
public void initialize() {
    // ...
}

7.2.3.2.@PreDestroy

JSR-250 표준 Annotation으로 Bean 소멸시 필요한 작업을 담은 메소드에 대해 정의한다. @PreDestroy를 사용하기 위해서는 클래스패스 내에 jsr250-api.jar 파일이 추가되어 있어야 한다.

@PreDestroy
// 메소드명은 자유롭게 정의할 수 있다.
public void dispose() {
    // ...
}

7.2.3.3.Combining lifecycle mechanisms

앞에서 설명한 바와 같이, Spring 2.5에서 bean lifecycle을 관리할 수 있는 방법은 다음과 같이 세가지가 있다.

  • InitializingBean과 DisposableBean callback 인터페이스 이용

  • 사용자가 작성한 초기화/소멸화 메소드를 XML에서 init-method/destroy-method 속성을 이용하여 정의

  • @PostConstruct와 @PreDestroy annotation 이용

위의 3가지 방법이 동시에 존재하는 경우(예를 들어, 3가지 방법이 각각 정의된 클래스가 Parent-child 관계를 가지는 경우), 실행되는 순서는 다음과 같다.

Initialization 메소드

  1. @PostConstruct를 이용하여 정의한 메소드

  2. InitializingBean 인터페이스의 afterPropertiesSet() 메소드

  3. XML에서 init-method 속성으로 정의된 초기화 메소드

Destroy 메소드

  1. @PreDestroy를 이용하여 정의한 메소드

  2. DisposableBean 인터페이스의 destroy() 메소드

  3. XML에서 destroy-method 속성으로 정의된 소멸화 메소드

7.2.4.Resources

  • 다운로드

    다음에서 테스트 DB를 포함하고 있는 hsqldb.zip과 sample 코드를 포함하고 있는 anyframe.sample.annotation.zip 파일을 다운받은 후, 압축을 해제한다. 그리고 hsqldb 폴더 내의 start.cmd (or start.sh) 파일을 실행시켜 테스트 DB를 시작시켜 놓는다.

    • Maven 기반 실행

      Command 창에서 압축 해제 폴더로 이동한 후 mvn clean jetty:run이라는 명령어를 실행시킨다. Jetty Server가 정상적으로 시작되었으면 브라우저를 열고 주소창에 http://localhost:8080/ anyframe.sample.annotation를 입력하여 실행 결과를 확인한다.

    • Eclipse 기반 실행 - m2eclipse, WTP 활용

      Eclipse에서 압축 해제 프로젝트를 import한 후, 해당 프로젝트에 대해 마우스 오른쪽 버튼을 클릭 하고 컨텍스트 메뉴에서 Maven > Enable Dependency Management를 선택하여 컴파일 에러를 해결한다. 그리고 해당 프로젝트에 대해 마우스 오른쪽 버튼을 클릭한 후, 컨텍스트 메뉴에서 Run As > Run on Server (Tomcat 기반)를 클릭한다. Tomcat Server가 정상적으로 시작되었으면 브라우저를 열고 주소창에 http://localhost:8080/anyframe.sample.annotation를 입력하여 실행 결과를 확인한다.

    • Eclipse 기반 실행 - WTP 활용

      Eclipse에서 압축 해제 프로젝트를 import한 후, build.xml 파일을 실행하여 참조 라이브러리를 src/main/webapp 폴더의 WEB-INF/lib내로 복사시킨다. 해당 프로젝트를 선택하고 마우스 오른쪽 버튼을 클릭한 후, 컨텍스트 메뉴에서 Run As > Run on Server를 클릭한다. Tomcat Server가 정상적으로 시작되었으면 브라우저를 열고 주소창에 http://localhost:8080/anyframe.sample.annotation 를 입력하여 실행 결과를 확인한다. (* build.xml 파일 실행을 위해서는 ${ANT_HOME}/lib 내에 maven-ant-tasks-2.0.10.jar 파일이 있어야 한다.)

    표 7.1. Download List

    NameDownload
    hsqldb.zipDownload
    anyframe.sample.annotation.zipDownload
    maven-ant-tasks-2.0.10.jarDownload
    anyframe.sample.di.qualifier.zipDownload
    anyframe.sample.di.provider.zipDownload

7.3.Java based Configuration

Spring 3에서는 Spring Java Configuration 프로젝트의 일부 주요 특징들을 추가함으로써 Java 기반의 Configuration 정의가 가능하도록 지원하고 있다. Java 기반의 속성 정의는 Java 코드를 중심으로 이루어지므로 Injection 속성 정의시 Type 오류가 있으면 컴파일부터 수행되지 않으므로 Type Safety를 보장하게 된다. 또한 Bean 인스턴스 관리를 로직으로 직접 구현해주기 때문에 Bean 구현체가 Spring에 의존되지 않고, 순수한 Java 코드로만 구현될 수 있도록 보장해준다.

활용 가능한 Annotation들은 위에서 나열한 바와 같으며, 본 섹션에서는 이러한 Annotation들에 대해 예제와 함께 자세히 살펴보도록 하자.

Java 기반의 Configuration 정의시 가장 기본이 되는 Annotation은 @Configuration과 @Bean이다. @Configuration은 클래스 레벨에 정의가능한 Annotation이다. @Configuration 정의를 포함한 클래스는 Bean 정의 정보를 담고 있어 Spring Container에 의해 처리되는 Configuration 클래스임을 의미한다. @Bean은 메소드 레벨에 정의 가능한 Annotation으로 XML 기반의 속성 정보 중 <bean/>과 동일한 역햘을 수행한다.

@Configuration
public class MovieFinderConfig {
    // ...
    @Bean
    public MovieFinder movieFinder() {
        return new MovieFinderImpl(movieDao);
    }
}

위 코드에서 언급한 MovieFinderConfig 클래스는 Configuration 클래스로써 'movieFinder'라는 이름을 가진 Bean을 정의하고 있음을 알 수 있다. 위 코드 내용을 XML 형태로 변경해 보면 다음과 같다.

<bean id="movieFinder" class="anyframe.sample.javaconfig.service.impl">
	<constructor-arg ref="movieDao"/>
</bean>

7.3.1.Bean Management

앞서 언급한 바와 같이 @Bean은 메소드 레벨에 정의 가능한 Annotation으로 특정 Bean을 정의하기 위해 사용한다. XML 기반의 속성 정보 중 <bean/>과 동일한 역햘을 수행하며, @Configuration 또는 @Component 클래스 내에 정의 가능하다. @Bean 정의가 추가된 메소드는 해당하는 Bean의 인스턴스 생성하여 전달하는 로직을 포함하고 있어야 하며 기본적으로 Spring Container는 메소드명을 Bean 이름으로 등록한다.

@Bean
public MovieFinder movieFinder() {
    return new MovieFinderImpl(movieDao);
}

위 코드에 의하면 @Bean 정의가 추가된 movieFinder() 메소드로 인해 'movieFinder'라는 이름의 Bean이 Spring Container에 등록될 것이다. 또한 'movieFinder' Bean을 요청하면 정의된 메소드 로직에 의해 MovieDao 객체가 셋팅된 MovieFinderImpl 객체가 전달될 것이다.

7.3.1.1.Naming

@Bean Annotation은 'name'이라는 속성 정보를 가지고 있다. name 속성에 대해 값을 부여하는 경우 이 값이 해당 Bean의 이름이 된다.

@Bean(name="movieFinderImpl")
public MovieFinder movieFinder() {
    return new MovieFinderImpl(movieDao);
}

7.3.1.2.Lifecycle Management

@Bean을 이용하여 정의된 Bean들에 대해서도 XML이나 Annotation 기반의 Bean들과 동일하게 기본 Lifecycle 관리가 가능하다. 즉, 해당 Bean이 @PreDestroy, @PostConstruct와 같은 JSR-250 Annotation을 포함하고 있거나 Spring의 InitializingBean, DisposableBean 등과 같은 인터페이스르 구현하였을 경우 Spring Container에 의해 해당 Bean의 Lifecycle이 관리된다. 이 외에도 @Bean은 'init-method', 'destroy-method'라는 속성 정보를 가질 수 있어서 속성값을 부여하는 경우 초기화/소멸화시에 정의된 메소드가 실행된다. 이것은 <bean/>의 init-method, destroy-method와 동일한 역할을 수행한다.

@Bean(initMethod = "initialize", destroyMethod = "destroy")
public MovieFinder movieFinder() {
    return new MovieFinderImpl(movieDao);
}

위 코드에 의하면 'movieFinder'라는 Bean의 초기화 시점에는 MovieFinderImpl.initialize(), 소멸화 시점에는 MovieFinderImpl.destroy() 메소드가 각각 실행될 것이다.

Spring Container는 시작 시점에 모든 Singleton Bean을 미리 로딩함으로써, 그 Bean이 필요할 때 즉시 사용될 수 있도록 보장해준다. 그러나 Container 시작 시점에 특정 Singleton Bean을 인스턴스화 시키지 않고 처음으로 해당 Bean에 대해 요청이 들어왔을 때 인스턴스화 시키기 위해서는 @Lazy 설정을 부여해 주어야 한다. 이것은 <bean/>의 lazy-init과 동일한 역할을 수행한다.

@Bean
@Lazy
public MovieFinder movieFinder() {
    return new MovieFinderImpl(movieDao);
}

7.3.1.3.Scope

@Bean과 함께 @Scope 정의를 추가하는 경우 해당 Bean에 대해 특정 Scope을 부여할 수 있다. @Scope을 부여하지 않는 경우 기본적으로 Singleton Scope이 적용된다.

@Bean
@Scope("prototype")
public MovieFinder movieFinder() {
    return new MovieFinderImpl(movieDao);
}

또한 request, session, globalSession Scope의 Bean에 대한 요청시 전달될 AOP Proxy 객체를 만들기 위해서 'proxyMode'라는 속성값을 추가적으로 부여할 수 있다. 'proxyMode'는 기본적으로 ScopedProxyMode.NO로 지정되며 ScopedProxyMode.TARGET_CLASS 또는 ScopedProxyMode.INTERFACES으로 정의 가능하다. 이것은 <bean/> 하위의 <aop:scoped-proxy/>와 동일한 역할을 수행한다.

@Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public MoviePreferences moviePreferences() {
    return new MoviePreferences();
}

@Bean
public MovieFinder movieFinder() {
    return new MovieFinderImpl(moviePreferences);
}

7.3.1.4.Dependency Injection

Bean 사이에 참조 관계가 성립될 경우 기본적으로 Injection은 참조하려는 Bean에 해당하는 메소드를 호출함으로써 이루어진다.

@Configuration
public class MovieFinderConfig {
    @Bean
    public MovieFinder movieFinder() {
        return new MovieFinderImpl(movieDao());
    }
    
    @Bean
    public MovieDao movieDao() {
        return new MovieDao();
    }    
}

'movieFinder' Bean이 'movieDao' Bean을 참조하고 있다라고 가정해 보자. 이를 Java 기반의 Configuration으로 표현하기 위해서는 위의 코드에서와 같이 movieFinder() 메소드 내에서 MovieFinderImpl 인스턴스 생성시 movieDao()라는 메소드를 호출함으로써 'movieDao' Bean을 Injection 할 수 있다. 또는 MovieFinderImpl 객체의 setter를 호출할 때 movieDao() 호출 결과를 전달함으로써 'movieDao' Bean을 Injection할 수도 있을 것이다.

@Configuration
public class MovieFinderConfig {
    @Bean
    public MovieFinder movieFinder() {
        MovieFinderImpl movieFinder = new MovieFinderImpl();
        movieFinder.setMovieDao(movieDao());
        return movierFinder;
    }
    
    @Bean
    public MovieDao movieDao() {
        return new MovieDao();
    }    
}

참조 대상 Bean이 XML/Annotation 기반으로 정의되었거나 다른 Configuration 클래스에 정의된 경우 Spring에서 Dependency Injection 처리를 위해 지원하는 Annotation(@Inject, @Autowired, @Resource)을 그대로 적용할 수도 있다.

@Configuration
public class MovieDaoConfig {
    @Autowired
    private IIdGenerationService idGenService;

    @Bean
    public MovieDao movieDao() {
        MovieDao movieDao = new MovieDao();
        movieDao.setIIdGenerationService(idGenService);
        return movieDao;
    }
}

위 코드는 'movieDao' Bean을 관리하는 Configuration 클래스 MovieDaoConfig의 일부이다. 'movieDao' Bean은 내부적으로 Anyframe의 IdGenerationService를 필요로 하므로 XML 기반으로 정의된 IdGenerationService를 @Autowired를 이용하여 Inject하고 movieDao() 메소드 내에서는 setter를 이용하여 MovieDao 객체를 대상으로 IdGenerationService 객체를 셋팅해주는 형태를 취하고 있다.

해당 Bean 이전에 초기화되어야 하는 하나 이상의 Bean을 명시적으로 강제하기 위해서는 @DependsOn을 활용할 수 있다. 이것은 <bean/>의 depends-on와 동일한 역할을 수행한다.

@Configuration
public class MovieFinderConfig {
    @Bean
    public MovieService movieService(){
        return new MovieServiceImpl();
    }

    @Bean
    @DependsOn(value = { "movieService" })		
    public MovieFinder movieFinder() {
        return new MovieFinderImpl(movieDao());
    }

    // ...
}

위 코드에 의하면 @DependsOn 속성 부여에 의해 'movieFinder' Bean이 초기화되기 전에 'movieService' Bean이 초기화 될 것을 짐작할 수 있다.

동일한 Type을 가지는 Bean이 여러개 정의되어 있어서 Type Injection 대상이 되는 Bean이 여러개 식별되었을 경우 @Primary를 부여한 Bean이 우선적으로 Injection 후보가 된다. 이것은 <bean/>의 primary와 동일한 역할을 수행한다.

@Configuration
public class MovieDaoConfig {
    @Bean
    public MovieDao defaultMovieDao() {
        return new MovieDaoImpl();
    }

    @Bean
    @Primary
    public MovieDao anotherMovieDao() {
        return new AnotherMovieDaoImpl();
    }
}

위와 같이 Configuration을 정의한 경우 @Autowired MovieDao movieDao;와 같은 코드에 의해 Injection되는 Bean은 @Primary 속성을 부여한 'anotherMovieDao' Bean이 될 것이다.

7.3.1.5.Method Injection

Setter injection과 Constructor injection을 사용할 경우, 기본적으로 Singleton Bean은 참조하는 Bean들을 Singleton 형태로 유지하게 된다. 그런데 Singleton Bean이 Non Singleton Bean(즉, Prototype Bean)과 참조 관계가 있을 경우에는 다음과 같이 처리해야 한다.

  1. Singleton Bean의 구현체 내에는 참조하려는 Non Singleton Bean 타입을 리턴하는 abstract 메소드 정의.

  2. Singleton Bean의 구현체 내의 비즈니스 메소드에서는 abstract 메소드를 이용해 Non Singleton Bean을 Injection하여 로직 수행.

  3. Java 기반 Configuration 정의시 Singleton Bean에 해당하는 메소드 내에서 인스턴스 생성과 함께 앞서 정의한 abstract 메소드 구현 로직 추가. 이 때 abstract 메소드 구현 로직에서는 Non Singleton Bean의 인스턴스 생성하여 리턴.

  4. 위와 같은 순서로 처리된 경우 Singleton Bean의 비즈니스 메소드 내에서 abstract 메소드가 호출될 때마다 해당 Bean의 인스턴스가 가진 abstract 메소드 구현 로직에 의해 새로운 Non Singleton Bean의 인스턴스 전달이 가능해짐. 즉, Singleton Bean에서 Non Singleton Bean에 대한 참조가 가능해짐.

다음은 Singleton Bean('movieFinder')에서 Non Singleton Bean('movieDao')에 대한 참조가 이루어질 수 있도록 하기 위해 정의된 Configuration 클래스의 내용이다.

@Configuration
public class MovieFinderConfig {
    @Bean
    @Scope("prototype")
    public MovieDao movieDao() {
        return new MovieDaoImpl();
    }

    @Bean
    public MovieFinder movieFinder() {
        return new MovieFinderImpl() {
            protected MovieDao getMovieDao() {
                return movieDao();
            }
        };
    }
}

위 Configuration 클래스에서 언급한 MovieFinderImpl 클래스는 다음과 같은 모습을 취할 것이다.

public abstract class MovieFinderImpl implements MovieFinder {
    protected abstract MovieDao getMovieDao();

    public List<Movie> getPagingList(Movie movie, int pageIndex) 
        throws Exception{
        return getMovieDao().getPagingList(movie, pageIndex);
    }
}

7.3.1.6.Spring Expression Language

Java 기반 Configuration 정의시 @Value와 함꼐 Spring Expression Language를 정의하면 Expression 처리 결과를 Bean의 인스턴스 생성시 반영하는 것도 가능하다.

@Configuration
public class MovieFinderConfig {
    private @Value("${jdbc.url}") String dbUrl;
    private @Value("${jdbc.username}") String userName;
    private @Value("${jdbc.password}") String password;

    @Bean
    public MovieDao movieDao() {
        return new MovieDaoImpl(dbUrl, userName, password);
    }
}

7.3.2.Combining Java and XML Configuration

@Import/@ImportResource를 활용하면 XML 또는 다른 @Configuration 클래스에 정의된 Bean 정보를 참조할 수 있게 된다.

7.3.2.1.Combine Java Configuration

@Import 정의시 다른 @Configuration 클래스를 속성값으로 부여해주면 현재 @Configuration 클래스에서 다른 @Configuration 클래스 내에 정의된 @Bean 정보를 참조할 수 있게 된다. Import 대상이 되는 @Configuration 클래스가 다수일 경우 { } 내에 ','를 식별자로 하여 클래스를 명시해주면 된다. @Import는 <import/>와 동일한 역할을 수행한다.

@Configuration
@Import(value = { MovieDaoConfig.class })
public class MovieFinderConfig {
    @Autowired
    private MovieDao movieDao;

    @Bean
    public MovieFinder movieFinder() {
        return new MovieFinderImpl(movieDao);
    }
}

@Configuration
public class MovieDaoConfig {
    // ...

    @Bean
    public MovieDao movieDao() {
        MovieDao movieDao = new MovieDao();
        movieDao.setIIdGenerationService(idGenService);
        return movieDao;
    }
}

위에서 언급한 @Configuration MovieFinderConfig 클래스는 MovieDaoConfig 클래스를 @Import하고 있어서 이 클래스 내에 정의된 Bean 'movieDao'를 참조할 수 있게 된다. 다른 @Configuration 클래스 내에 정의된 @Bean을 참조하기 위해서는 @Autowired를 사용하고 있음을 알 수 있다. @Inject, @Resource를 사용하는 것 또한 가능하다.

7.3.2.2.Combine XML Configuration

@ImportResource 정의시 XML 속성 파일의 위치를 속성값으로 부여해주면 현재 @Configuration 클래스에서 XML 내에 정의된 Bean 정보를 참조할 수 있게 된다. Import 대상이 되는 XML 파일이 다수일 경우 @Import와 동일한 형태로 { } 내에 ','를 식별자로 하여 XML 파일명을 명시해주면 된다.

@Configuration
@ImportResource("classpath:/spring/context-*.xml")
public class MovieDaoConfig {
    @Autowired
    private IIdGenerationService idGenService;

    @Bean
    public MovieDao movieDao() {
        MovieDao movieDao = new MovieDao();
        movieDao.setIIdGenerationService(idGenService);
        return movieDao;
    }
}

위에서 언급한 @Configuration MovieDaoConfig 클래스는 spring 폴더 하위의 context로 시작하는 모든 XML 파일을 @ImportResource하고 있어서 이 XML 내에 정의된 Bean 'idGenerationService'를 참조할 수 있게 된다. 위 코드에서는 다른 XML 내에 정의된 Bean을 참조하기 위해 @Autowired를 사용하고 있음을 알 수 있다. @Inject, @Resource를 사용하는 것 또한 가능하다. 다음은 위의 코드에서 Import한 /spring/context-idgen.xml 파일의 일부 내용이다.

<bean name="idGenerationService" class="anyframe.core.idgen.impl.UUIdGenerationService">
    <config:configuration>
        <address>00:00:F0:79:19:5B</address>			
    </config:configuration>		
</bean>

7.3.3.Instantiating spring container

Spring 3에서는 @Configuration 클래스를 인식하여 정의된 Bean들을 관리할 수 있도록 하기 위해 ApplicationContext의 구현체인 AnnotationConfigApplicationContext를 추가적으로 제공하고 있다. AnnotationConfigApplicationContext는 @Configuration 클래스 외에도 Stereotype Annotation, JSR-330 Annotation에 대해 인식 가능하다. 다음에서는 @Configuration 클래스를 기반으로 Spring Container를 시작시키는 방법에 대해서 살펴보도록 하자.

7.3.3.1.AnnotationConfigApplicationContext

XML/Annotation 기반에서 Spring Container를 시작시키기 위해서는 XmlWebApplicationContext, FileSystemXmlApplicationContext, ClassPathXmlApplicationContext와 같은 구현체를 활용했었다.

String[] locations = new String[] { "classpath:spring/context-*.xml" };
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(locations, false);
context.refresh();

그러나 @Configuration 클래스를 인식할 수 있도록 하기 위해서는 AnnotationConfigApplicationContext 구현체를 이용하여 Spring Container를 시작시켜야 한다. 인식해야 할 @Configuration 클래스가 다수일 경우 해당되는 클래스들을 입력 인자의 값으로 정의해주면 된다.

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MovieFinderConfig.class, ...);

또는 AnnotationConfigApplicationContext의 Default Constructor를 호출하여 인스턴스를 생성한 뒤 인식 대상이 되는 @Configuration 클래스들을 register할 수도 있다.

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(MovieFinderConfig.class, ...);
context.register(...);
context.refresh();

Spring Container가 Annotation 기반의 Bean을 검색할 수 3