Anyframe

Version 4.1.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. DynamicModule Service
2.2.3. DataSource 서비스
2.2.4. Query 서비스
2.2.5. ID Generation 서비스
2.2.6. Properties 서비스
2.2.7. Transaction 서비스
2.2.8. Hibernate 서비스
2.2.9. Logging 서비스
2.2.10. Remoting 서비스
2.2.11. 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. [필수] Foundation Plugin 설치
3.4. [선택] Other Plugins 설치
3.5. [선택] Plugin 설치 확인
3.6. [선택] 샘플 DB 변경
3.7. [선택] Eclipse WTP만 이용하여 Tomcat 실행
4. Anyframe Plugins
4.1. Anyframe Plugins
4.2. Foundation Plugin 구조
4.3. Other Plugins 구조
4.4. Custom Plugin 추가 정의
5. Anyframe Maven Commands
5.1. anyframe-maven-plugin 정의 확인
5.2. Anyframe Maven Commands
III. Spring
6. IoC(Inversion of Control)
6.1. Basic
6.1.1. Container와 Bean
6.1.2. Container
6.1.2.1. BeanFactory
6.1.2.2. ApplicationContext
6.1.2.3. 설정 메타데이터
6.1.2.4. Spring IoC Container 인스턴스화 시키는 예제
6.1.2.5. XML 기반 설정 메타데이터 조합
6.1.3. Beans
6.1.3.1. Bean
6.1.3.2. Bean 명명하기
6.1.3.3. Bean 인스턴스화
6.1.4. How to refer to Beans
6.1.4.1. 비즈니스 레이어
6.1.4.2. 프리젠테이션 레이어
6.2. Dependencies
6.2.1. Dependency Injection(DI)
6.2.1.1. Setter Injection
6.2.1.2. Constructor Injection
6.2.1.3. Setter Injection vs. Constructor Injection
6.2.1.4. 생성자 인자 분석
6.2.2. Bean Property와 생성자 인자
6.2.2.1. XML 기반의 설정 메타데이터 간략화
6.2.2.2. 혼합된 Property 명(Compound Property) - shortcut 기능 제공
6.2.3. depends-on 속성 사용
6.2.4. Lazy Instantiation
6.2.5. Autowiring
6.2.5.1. 장점
6.2.5.2. 단점
6.2.6. Dependency Check
6.3. Method Injection
6.3.1. Lookup Method Injection
6.3.2. Method Replacement
6.4. Bean과 Container의 확장
6.4.1. Bean Scope
6.4.1.1. Singleton
6.4.1.2. Prototype
6.4.1.3. Other Scopes
6.4.1.4. Custom
6.4.2. Bean Life Cycle
6.4.2.1. Initialization
6.4.2.2. Destruction
6.4.3. Bean 상속
6.4.4. Container 확장
6.4.4.1. Bean 후처리
6.4.4.2. BeanFactory 후처리
6.4.5. ApplicationContext 활용
6.4.5.1. MessageSource를 활용한 국제화(I18N) 지원
6.4.5.2. Event
6.4.5.3. BeanFactory와 ApplicationContext 특징 비교
6.5. XML 스키마 기반 설정
6.6. Resources
7. Aspect Oriented Programming
7.1. AOP 구성 요소
7.1.1. JointPoint
7.1.2. Pointcut
7.1.2.1. Pattern Matching Examples
7.1.2.2. Pointcut Designators
7.1.3. Advice
7.1.4. Weaving 또는 CrossCutting
7.1.5. Aspect
7.2. Annotation based AOP
7.2.1. Configuration
7.2.2. @Aspect 정의
7.2.3. @Pointcut 정의
7.2.4. @Advice 정의
7.2.4.1. Before Advice
7.2.4.2. AfterReturning Advice
7.2.4.3. AfterThrowing Advice
7.2.4.4. After(finally) Advice
7.2.4.5. Around Advice
7.2.5. Aspect 실행
7.3. XML based AOP
7.3.1. Aspect 정의
7.3.2. Pointcut 정의
7.3.3. Advice 정의 및 구현
7.3.3.1. Before Advice
7.3.3.2. AfterReturning Advice
7.3.3.3. AfterThrowing Advice
7.3.3.4. After(finally) Advice
7.3.3.5. Around Advice
7.3.4. Aspect 실행
7.4. AspectJ based AOP
7.4.1. 시작하기 전에
7.4.2. Aspect 정의
7.4.3. Pointcut 정의
7.4.4. Advice 정의
7.4.4.1. Before Advice
7.4.4.2. AfterReturning Advice
7.4.4.3. AfterThrowing Advice
7.4.4.4. After(finally) Advice
7.4.4.5. Around Advice
7.5. AOP Examples
7.5.1. AOP Example - Logging
7.5.1.1. Configuration
7.5.1.2. Aspect 정의
7.5.1.3. Aspect 실행
7.5.2. AOP Example - Exception Transfer
7.5.2.1. Aspect 정의
7.5.2.2. Advice 구현
7.5.3. AOP Example - Profiler
7.5.3.1. Configuration
7.5.3.2. Aspect 정의
7.5.3.3. Aspect 실행
7.5.4. AOP Example - Design Level Assertions
7.5.4.1. Interaction Rule 정의 예제
7.5.4.2. Naming Rule 정의 예제
7.5.4.3. Refactoring
7.6. Resources
8. Spring Remoting
8.1. RMI(Remote Method Invocation)
8.1.1. Server Configuration
8.1.1.1. Samples
8.1.2. Client Configuration
8.1.2.1. Samples
8.2. Hessian
8.2.1. Server Configuration
8.2.1.1. Samples
8.2.2. Client Configuration
8.2.2.1. Samples
8.2.3. Hessian과 Burlap의 차이점
8.3. Burlap
8.3.1. Server Configuration
8.3.1.1. Samples
8.3.2. Client Configuration
8.3.2.1. Samples
8.3.3. Hessian과 Burlap의 차이점
8.4. HTTP Invoker
8.4.1. Server Configuration
8.4.1.1. Samples
8.4.2. Client Configuration
8.4.2.1. Samples
8.5. Resources
9. Annotation
9.1. Bean Management
9.1.1. Auto Detecting
9.1.2. Using Filters to customize scanning
9.1.3. Scope Definition
9.2. Dependency Injection
9.2.1. @Resource
9.2.2. @Autowired
9.2.3. @Qualifier
9.2.4. @Resource vs. @Autowired
9.3. LifeCycle Annotation
9.3.1. @PostConstruct
9.3.2. @PreDestroy
9.3.3. Combining lifecycle mechanisms
9.4. Resources
IV. Spring MVC
10. Architecture
11. Configuration
11.1. web.xml 작성
11.1.1. DispatcherServlet 등록
11.1.2. Spring 설정 파일 위치 등록
11.2. action-servlet.xml 작성
11.2.1. action-servlet.xml 설정
11.2.1.1. Handler Mapping
11.2.1.2. View Resolver
12. Controller
12.1. AbstractController
12.2. MultiActionController
12.3. AbstractCommandController
12.4. SimpleFormController
12.5. UrlFilenameViewController
12.6. ParameterizableViewController
13. View
13.1. Tag library
13.1.1. configuration
13.1.2. form
13.1.3. input
13.1.4. checkbox
13.1.5. checkboxes
13.1.6. radiobutton
13.1.7. radiobuttons
13.1.8. password
13.1.9. select
13.1.10. option
13.1.11. options
13.1.12. textarea
13.1.13. hidden
13.1.14. errors
13.1.15. sample
13.1.15.1. 입력 화면
13.1.15.2. Controller 클래스
13.1.15.3. 출력 화면
13.2. Tiles
13.2.1. Tiles view class 정의
13.2.2. TilesConfigurer 정의
13.2.3. Tiles definition 파일 작성
14. File Upload
15. Internationalization
15.1. 다국어 지원 기능
15.1.1. Locale Resolver를 이용한 Locale 변경
15.1.2. LocaleChangeInterceptor를 이용한 Locale 변경
15.2. Locale Resolver
15.2.1. AcceptHeaderLocaleResolver
15.2.2. CookieLocaleResolver
15.2.3. SessionLocaleResolver
15.2.4. FixedLocaleResolver
16. Validator
16.1. Validator 생성
16.2. Validator 등록
16.3. form:errors 태그 사용
17. Exception Handling
17.1. 특정 error 페이지로 이동하여 에러 메시지 출력
17.2. 에러 페이지에 에러 메시지 출력
17.3. Presentation Layer에서 message key를 이용한 locale 변경
17.3.1. Business Layer의 BaseException 발생
17.3.2. Presentation Layer에서 꺼낸 message key 값에 새로운 Locale로 셋팅
18. Spring Integration
18.1. Listener 등록과 Spring 설정 파일 목록 위치 정의
18.2. Dependency Injection을 통한 Business Service 호출
18.3. Resources
19. Annotation based Spring MVC
19.1. Configuration
19.1.1. Handler 설정
19.1.2. Component Scan 설정
19.1.2.1. Using Filters to customize scanning
19.2. Controller
19.2.1. @Controller
19.2.2. @RequestMapping
19.2.2.1. Form Controller 구현
19.2.2.2. Multi-action Controller 구현
19.2.2.3. Supported argument types
19.2.2.4. Supported return types
19.2.3. @RequestParam
19.2.4. @ModelAttribute
19.2.5. @SessionAttributes
19.3. Dependency Injection
19.4. Preventing Double Form Submission
19.4.1. Annotation을 이용한 Double Form Submission 방지
19.5. Resources
V. Spring MVC Extensions
20. Controller
20.1. ForwardController
20.2. AnyframeFormController
20.3. AnyframeMiPController
21. View
21.1. Tag library
21.1.1. Page Navigator Tag
21.1.2. Message Tag
22. Preventing Double Form Submission
22.1. property 정의하기
22.2. AnyframeFormController를 상속받아 컨트롤러 클래스 구현하기
22.2.1. 페이지 출력 요청 (Ex. /userForm.do)
22.2.2. 폼 submit 요청(Ex. /getUser.do)
22.3. messageSource 추가하기
22.4. Resources
23. JasperReports Integration
23.1. Installation
23.1.1. 다운로드
23.1.2. 설치 환경
23.1.3. Report Designer 설치
23.1.3.1. Configuration
23.2. Report Designer
23.2.1. 목표 결과물
23.2.2. 디자인 파일(JRXML) 작성
23.2.2.1. Step 1 : Open JasperAssistant Perspective
23.2.2.2. Step 2 : Create a new Report
23.2.2.3. Step 3 : Design a report using Palette
23.2.2.4. Step 5 : Preview Report
23.3. Configuration
23.3.1. web.xml 작성하기
23.3.2. jasper-servlet.xml 작성하기
23.4. Controller
23.4.1. HTML Reporting
23.5. Resources
VI. Spring Web Flow
24. configuration
24.1. 기본 설정
24.1.1. FlowRegistry 정의
24.1.1.1. Flow Registry 정의 방법
24.1.1.2. Flow ID 생성
24.1.2. FlowExecutor 정의
24.2. Spring MVC와 연계하기 위한 설정
24.2.1. FlowHandlerAdaptor 정의
24.2.2. FlowHandlerMapping 정의
24.2.3. Spring MVC의 ViewResolver 지정
25. 플로우 정의
25.1. 필수 요소
25.1.1. view-state
25.1.2. transition
25.1.3. end-state
25.2. 메소드 호출
25.2.1. evaluate
25.3. Transition Decision
25.3.1. action-state
25.3.2. decision-state
25.4. Expression Language
25.4.1. Special EL variables
26. View
26.1. model 바인딩
26.2. view backtracking
26.2.1. discard
26.2.2. invalidate
27. Subflow
27.1. subflow-state
27.2. input
27.3. output
28. 플로우 상속
28.1. flow 레벨 상속
28.2. state 레벨 상속
29. Validator
29.1. model 객체 내에 validate 메소드 구현
29.2. validator 클래스 및 메소드 구현
29.3. Resources
VII. Struts
30. Architecture
30.1. Controller Structure
30.2. Request의 흐름
31. Configuration
31.1. web.xml
31.1.1. servlet, servlet-mapping 설정
31.1.1.1. <servlet>설정
31.1.1.2. <servlet-mapping>설정
31.1.1.3. Samples
31.1.2. taglib 설정
31.1.2.1. JSP에서의 설정
31.1.2.2. Samples
31.2. struts-config.xml
31.2.1. controller
31.2.1.1. <controller>설정
31.2.1.2. Samples
31.2.2. message-resources
31.2.2.1. <message-resources>설정
31.2.2.2. Samples
31.2.3. plug-in
31.2.3.1. <plug-in>설정
31.2.3.2. Samples
31.2.4. form-beans
31.2.4.1. <form-beans>설정
31.2.4.2. Samples
31.2.4.3. DynaActionForm
31.2.5. action-mappings
31.2.5.1. <action-mappings>설정
31.2.5.2. <action>의 주요 attribute
31.2.5.3. Samples
31.2.6. global-forwards
31.2.6.1. <global-forwards> 설정
31.2.6.2. Samples
32. Controller
32.1. ActionServlet
32.1.1. ActionServlet의 역할
32.1.2. 초기화 프로세스
32.1.3. 실행 시(ActionServlet 인스턴스가 HTTP Request를 받을 때)
32.1.4. ShutDown 프로세스
32.2. RequestProcessor
32.2.1. RequestProcessor의 역할
32.2.2. process() 메소드의 Request 처리 절차
32.2.3. Sample
32.3. Action
32.3.1. Action의 역할
32.3.2. Action의 구현
32.3.3. Sample
32.4. ActionForward
32.4.1. ActionForward의 역할
32.5. Actions Package
32.5.1. org.apache.struts.actions 패키지에 미리 정의되어 있는 Action
32.5.2. org.apache.struts.actions.ForwardAction
32.5.3. org.apache.struts.actions.IncludeAction
32.5.4. org.apache.struts.actions.DispatchAction
32.5.5. org.apache.struts.actions.LookupDispatchAction
32.5.6. org.apache.struts.actions.SwitchAction
33. View
33.1. Taglib
33.1.1.
33.1.1.1. Taglib의 특징
33.1.1.2. Struts Taglib
33.1.1.3. JSP Standard Tag Library
33.1.1.4. 기타 Taglib
33.2. Tiles
33.2.1. Page Layout 구성 방법
33.2.1.1. 구성 방법
33.2.2. Tiles 설치
33.2.3. Tiles 사용
33.2.3.1. Tiles 적용 시 고려점
33.2.3.2. Tiles Tag Library의 속성
33.2.4. Tiles Layout 정의
33.2.4.1. JSP 로 레이아웃을 정의한 예
33.2.4.2. XML 로 레이아웃을 정의한 예
34. Internationalization
34.1. Internationalization의 특징
34.1.1. Internationalization의 필요성
34.1.2. 지역 (Locale)
34.2. Internationalization Sample
34.2.1. Sample
35. Validator
35.1. Plug-in 등록
35.1.1. struts-config.xml에 plug-in 등록
35.1.2. Samples
35.2. Validator Rules
35.2.1. Struts Validator Rules 기본 기능
35.3. ActionForm
35.3.1. ValidatorForm의 상속
35.3.2. Samples
35.4. formset 설정
35.4.1. formset 설정 방법
35.4.2. Sample
35.5. Action 매핑 설정
35.5.1. struts-config.xml의 Action 매핑 설정
35.5.2. Sample
36. Exception Handling
36.1. Global Level Exception Handling
36.1.1. Global Level Exception Handling의 특징
36.1.2. Samples
36.2. Action Level Exception Handling
36.2.1. Action Level Exception Handling의 특징
36.2.2. Samples
36.3. Resources
VIII. Struts Extensions
37. Controller
37.1. DefaultActionServlet
37.2. DefaultRequestProcessor
37.2.1. DefaultRequestProcessor 기능
37.3. AbstractActionSupport
37.3.1. Action Sample
37.4. DefaultDispathActionSupport
37.4.1. Action Sample
37.5. DefaultForwardAction
37.6. AnyframeMiPAction
37.6.1. Sample Action
38. View
38.1. Tag Library
38.1.1. Page Navigator Tag
38.1.2. Messages Tag
38.1.2.1. Error Page 구성
39. Preventing Double Form Submission
39.1. Double Submit의 개념
39.2. 일반적인 Token 처리
39.3. 선언적인 Token 처리
39.3.1. Samples
39.3.2. 참고 사항
40. Exception Handling
40.1. 선언적인 Exception Handling
40.1.1. Samples
40.2. DefaultBaseExceptionHandler 확장
41. Authentication and Authorization
41.1. Authentication
41.1.1. Samples
41.2. Authorization
41.2.1. 접근 권한 제어 프로세스
41.2.2. Samples
42. Spring Integration
42.1. Configuration
42.1.1. ContextLoaderListener, ContextConfigLocation 정의
42.2. Action
42.3. Resources
IX. Spring BlazeDS Integration
43. Introduction
43.1. Flex
43.2. BlazeDS
44. Flex의 Data 연동
44.1. Flex 기본 Data 연동
44.2. BlazeDS를 이용한 Data연동
44.3. Domain객체와 ASObject의 Mapping
45. Spring BlazeDS Integration 설치
45.1. BlazeDS의 설치
45.2. Spring BlazeDS Integration 설치
45.3. Spring Bean Exporting
45.4. Flex Project 생성
45.5. Remoting Service Call
46. Spring BlazeDS Integration 환경 설정
46.1. Spring BlazeDS MessageBroker 환경 설정
46.2. Exporting Spring Beans
X. Tech.Service
47. Common Configuration
47.1. Anyframe MessageSource
47.1.1. Samples
47.2. Configuration Tag 확장
47.2.1. Samples
48. Generic Service
48.1. Domain 클래스 생성
48.1.1. Query Service 사용 시
48.1.2. Hibernate/JPA 사용 시
48.2. Service 클래스 생성
48.2.1. GenericService
48.2.2. GenericServiceImpl
48.2.3. Samples
48.3. DAO 클래스 생성
48.3.1. GenericDao
48.3.2. GenericDaoQuery
48.3.3. GenericDaoHibernate
48.3.4. Samples
48.4. Resources
49. DynamicModule Service
49.1. Project Structure
49.2. Configuration
49.2.1. module.properties
49.2.1.1. 공통(common) 프로젝트
49.2.1.2. 서비스(service) 프로젝트
49.2.1.3. 웹(web) 프로젝트
49.2.2. web.xml
49.2.3. moduledefinitions.xml
49.2.4. anyframe.properties
49.2.5. impala configuration xml files
49.2.6. context-dynamic.xml
49.3. Build
49.3.1. Eclipse 내에서 웹 어플리케이션 구동
49.3.1.1. anyframe.properties
49.3.1.2. 공통(common) 타입 프로젝트 빌드
49.3.2. WAS에 실제 배포하여 웹 어플리케이션 구동
49.3.2.1. anyframe.properties
49.3.2.2. jar 파일 배포
49.3.2.3. class 파일 배포
49.3.2.4. Default Value Setting (Workspace Root, Version)
49.4. Resources
50. MiPlatform Service
50.1. Controller
50.1.1. MiPController
50.2. Service
50.2.1. MiPService
50.2.2. MiPServiceImpl
50.3. Dao
50.3.1. MiPDao
50.3.2. MiPDaoQuery
50.4. Extension of MiPServiceImpl
50.4.1. [참고] IMiPActionCommand
50.5. Testcase
50.6. Resources
51. FlexService
51.1. Domain Model 클래스 생성
51.1.1. BaseObject
51.2. Service 클래스 생성
51.2.1. FlexService
51.2.2. FlexServiceImpl
51.2.3. FlexService의 상속
51.3. DAO 클래스 생성
51.3.1. FlexDao
51.3.2. FlexDaoQueryImpl
51.4. MXML 생성
52. Cache Service
52.1. DefaultCacheService
52.1.1. Samples
52.2. Resources
53. DataSource Service
53.1. JDBCDataSource Configuration
53.1.1. Samples
53.2. DBCPDataSource Configuration
53.2.1. Samples
53.3. C3P0DataSource Configuration
53.3.1. Samples
53.4. JNDIDataSource Configuration
53.4.1. Samples
53.4.2. jee schema 를 통한 JNDIDataSource 사용
53.5. Test Case
53.6. Resources
54. Hibernate Service
54.1. Resources
54.2. Mapping File
54.2.1. Mapping File의 작성
54.2.1.1. Mapping File 구성
54.2.2. Data Type의 매핑
54.2.2.1. Data Type의 매핑
54.2.3. Hibernate Generator
54.2.3.1. Hibernate 기본 Id Generator
54.2.3.2. 직접생성
54.3. Persistence Mapping
54.3.1. Persistence Mapping - Association
54.3.1.1. One to One Mapping
54.3.1.2. One to Many Mapping
54.3.1.3. Many to Many Mapping
54.3.2. Persistence Mapping - Inheritance
54.3.2.1. Table per Class Hierarchy
54.3.2.2. Table per Subclass
54.3.2.3. Table per Concrete Class
54.4. Basic CRUD
54.4.1. 단건 조회
54.4.2. 단건 저장
54.4.2.1. Tip. A:B=1:m인 경우 A에 대한 save()
54.4.3. 단건 수정
54.4.4. 단건 저장 또는 수정
54.4.5. 단건 삭제
54.4.6. 복수건 저장
54.5. HQL(Hibernate Query Language)
54.5.1. 구성 요소
54.5.1.1. [선택] SELECT 절
54.5.1.2. [필수] FROM 절
54.5.1.3. [선택] WHERE 절
54.5.1.4. [선택] ORDER BY 절
54.5.1.5. [선택] GROUP BY 절
54.5.2. 기본적인 사용 방법
54.5.2.1. Case 1. Basic
54.5.2.2. Case 2. Join
54.5.3. 원하는 객체 형태로 전달
54.5.3.1. Case 1. 특정 객체 형태로 전달
54.5.3.2. Case 2. Map 형태로 전달
54.5.3.3. Case 3. List 형태로 전달
54.5.4. XML에 HQL 정의하여 사용
54.5.5. Pagination
54.5.6. HQL을 이용한 CUD
54.5.6.1. 등록 (Insert)
54.5.6.2. 수정 (Update)
54.5.6.3. 삭제 (Delete)
54.6. Criteria Queries
54.6.1. 기본적인 사용 방법
54.6.1.1. Case 1. Basic
54.6.1.2. Case 2. Join
54.6.2. 원하는 객체 형태로 전달
54.6.2.1. Case 1. 특정 객체 형태로 전달
54.6.2.2. Case 2. Map 형태로 전달
54.6.3. Pagination
54.7. Native SQL
54.7.1. 기본적인 사용 방법
54.7.1.1. Case 1. Basic
54.7.1.2. Case 2. Join
54.7.1.3. Case 3. 검색 조건 명시
54.7.2. XML에 Native SQL 정의하여 사용
54.7.3. Pagination
54.7.4. Callable Statement
54.7.4.1. Case 1. XML에 정의한 Procedure 호출
54.7.4.2. Case 2. Function을 이용한 HQL 실행
54.8. Performance Strategy
54.8.1. Cache
54.8.1.1. 1LC (1 Level Cache)
54.8.1.2. 2LC (2 Level Cache)
54.8.1.3. 분산 Cache
54.8.2. Fetch Strategy
54.8.2.1. Batch를 이용하여 데이터 조회
54.8.2.2. Sub-Query를 이용하여 데이터 조회
54.8.2.3. join fetch를 이용하여 데이터 한꺼번에 조회
54.9. Concurrency
54.9.1. Optimistic Locking
54.9.2. Pessimistic Locking
54.9.3. Offline Locking
54.10. Transaction Management
54.10.1. JDBC - HibernateTransactionManager
54.10.2. JTA - JTATransactionManager
54.11. Spring Integration
54.11.1. Hibernate 속성 정의 파일 작성
54.11.1.1. Session Factory 속성 정의
54.11.1.2. Dynamic HQL, Dynamic Native SQL 실행을 위한 DynamicHibernateService 속성 정의
54.11.2. Mapping XML 파일 작성
54.11.3. DAO 클래스 생성
54.11.3.1. DAO 속성 정의 파일 작성
54.11.3.2. DAO 클래스 개발
54.11.4. Test Code 작성
54.11.5. 선언적인 트랜잭션 관리
54.12. DynamicHibernateService 활용
54.12.1. DynamicHibernateService
54.12.1.1. DynamicHibernate Configuration
54.12.1.2. Dynamic HQL 정의 파일
54.12.1.3. DynamicHibernateService 활용 예제
54.13. Hibernate Configuration
54.13.1. DataSource 속성 정의
54.13.2. Generated SQL 속성 정의
54.13.3. Cache 속성 정의
54.13.4. Logging 속성 정의
54.13.5. 기타 속성 정의
54.13.6. 매핑 파일 정의
55. Id Generation Service
55.1. UUIdGenerationService
55.1.1. Samples
55.2. SequenceIdGenerationService
55.2.1. Samples
55.3. TableIdGenerationService
55.3.1. Samples
55.4. How to use a Generation Strategy
55.4.1. MixPrefix property 정의 방법
55.4.2. Id Generation Strategy를 implements하는 방법
55.5. Resources
56. Logging Service
56.1. Logging Service Configuration 정의하기
56.1.1. appender
56.1.2. logger
56.1.3. root
56.2. Logging Service 사용하기
56.2.1. 기본적인 사용 방법
56.2.2. ResourceBundle을 이용하는 방법
56.3. Tip. SQL문을 로그로 남기기
56.3.1. Step 1. Log4jdbc 라이브러리 다운로드
56.3.2. Step 2. Simple Logging Facade for Java 라이브러리 다운로드
56.3.3. Step 3. DataSource 속성 정의
56.3.3.1. JDBCDataSource를 사용할 경우
56.3.3.2. JNDIDataSource를 사용할 경우
56.3.4. Step 4. Query 서비스 속성 정의
56.3.5. Step 5. Logger 정의
56.4. Resources
57. Properties Service
57.1. PropertiesServiceImpl
57.1.1. Samples
57.2. Sample Property File
57.3. Resources
58. Query Service
58.1. Configuration
58.1.1. jdbcTemplate
58.1.2. sqlRepository
58.1.3. pagingSQLGenerator
58.1.4. lobHandler
58.1.5. Samples
58.1.6. TestCase
58.1.6.1. INSERT
58.1.6.2. SELECT
58.1.6.3. UPDATE
58.1.6.4. DELETE
58.2. Mapping XML Files
58.2.1. table-mapping 정의 방법
58.2.2. queries 정의 방법
58.3. Usecases
58.3.1. Result Mapping
58.3.1.1. 조회 결과 매핑이 별도로 정의되어 있지 않은 경우
58.3.1.2. <result-mapping> 없이 <table-mapping>을 이용할 경우
58.3.1.3. <table-mapping>,<result-mapping>없이 <result>만을 이용할 경우
58.3.1.4. <result-mapping>을 이용할 경우
58.3.1.5. 테스트 코드 Sample
58.3.2. Embedded SQL
58.3.2.1. 속성 정의 파일 Sample
58.3.2.2. 테스트 코드 Sample
58.3.3. OR Mapping
58.3.3.1. 속성 정의 파일 Sample
58.3.3.2. 매핑 XML 파일 Sample
58.3.3.3. OR Mapping시 사용할 매핑 클래스 Sample
58.3.3.4. 테스트 코드 Sample
58.3.4. Dynamic Query
58.3.4.1. 속성 정의 파일 Sample
58.3.4.2. 매핑 XML 파일 Sample
58.3.4.3. 테스트 코드 Sample
58.3.5. Pagination
58.3.5.1. 속성 정의 파일 Sample
58.3.5.2. 매핑 XML 파일 Sample
58.3.5.3. 테스트 코드 Sample
58.3.6. Batch Update
58.3.6.1. 속성 정의 파일 Sample
58.3.6.2. 매핑 XML 파일 Sample
58.3.6.3. 테스트 코드 Sample
58.3.7. Callable Statement
58.3.7.1. 속성 정의 파일 Sample
58.3.7.2. 매핑 XML 파일 Sample
58.3.7.3. 테스트 코드 Sample
58.3.8. CLOB, BLOB
58.3.8.1. Oracle 9i 이상일 경우
58.3.8.2. Oracle 8i일 경우
58.3.9. Named Parameter 'vo' 활용
58.3.9.1. 속성 정의 파일 Sample
58.3.9.2. 매핑 XML 파일 Sample
58.3.9.3. 테스트 코드 Sample
58.3.10. extends AbstractDAO
58.3.10.1. 매핑 XML 파일 Sample
58.3.10.2. DAO 클래스 코드 Sample
58.3.10.3. DAO 클래스 속성 정의 파일 Sample
58.3.10.4. DAO 클래스 테스트 코드 Sample
58.3.11. implements IResultSetMapper
58.3.11.1. 속성 정의 파일 Sample
58.3.11.2. 매핑 XML 파일 Sample
58.3.11.3. ResultSetMapper 코드 Sample
58.3.11.4. 테스트 코드 Sample
58.4. Resources
58.5. Extensions
58.5.1. MiPQueryService 활용
58.5.1.1. MiPQueryService 속성 정의 파일 Sample
58.5.1.2. 매핑 XML 파일 샘플
58.5.1.3. 테스트 코드 Sample
58.5.2. RiaQueryService
58.5.2.1. 속성 정의 파일 Sample
58.6. Resources
59. Scheduling Service
59.1. Quartz Scheduler
59.1.1. Advanced Quartz
59.1.2. Samples
59.2. Resources
60. Service Locator Service
60.1. ServiceLocator
60.1.1. Samples
60.2. Resources
61. Transaction Service
61.1. Declarative Transaction Management
61.1.1. Annotation을 이용한 Transaction 관리
61.1.1.1. Configuration
61.1.1.2. Transaction 관리 대상 정의
61.1.1.3. 테스트 클래스 실행
61.1.2. XML 정의를 이용한 Transaction 관리
61.1.2.1. Configuration
61.1.2.2. Transaction 관리 대상 정의
61.1.2.3. 테스트 클래스 실행
61.1.3. [참고] Propagation Behavior, Isolation Level
61.1.3.1. Propagation Behavior
61.1.3.2. Isolation Level
61.1.4. 테스트 케이스 상세
61.2. Programmatic Transaction Management
61.2.1. TransactionTemplate을 이용한 Transaction 관리
61.2.1.1. Configuration
61.2.1.2. Transaction 관리
61.2.1.3. 테스트 클래스 실행
61.2.2. TransactionManager를 직접 이용한 Transaction 관리
61.2.2.1. Configuration
61.2.2.2. Transaction 관리
61.2.2.3. 테스트 클래스 실행
61.3. Resources
62. Exception Handling
62.1. 서비스 구현 부분에서의 Exception 처리
62.2. Data Access 부분에서의 Exception 처리
62.3. Exception 처리 비용
XI. Web Services
63. Web Services
63.1. Web Services 개념
63.1.1. Architecture
63.1.2. SOAP(Simple Object Access Protocol)
63.1.3. WSDL(Web Services Description Language)
63.1.4. 기술 표준
63.2. 구현 기술
63.2.1. JAX-RPC vs. JAX-WS
63.2.2. XML Schema
63.2.3. 기타 구현 기술
63.3. Web Services Framework
63.3.1. Web Services Framework 종류
63.3.2. Apache CXF 특징
63.4. Tools
64. JAX-WS Frontend
64.1. Web Service 작성
64.1.1. Samples
64.2. Spring Configuration XML - jaxws:endpoint tag 사용
64.2.1. Samples
64.3. Spring Configuration XML - jaxws:server tag 사용
64.3.1. Samples
64.4. Server: JAX-WS Frontend API 사용
64.4.1. Samples
64.5. Spring Configuration XML - jaxws:client tag 사용
64.5.1. Samples
64.6. Client: JAX-WS Frontend API 사용
64.6.1. Samples
64.7. Annotation 작성
64.7.1. @WebService (javax.jws.WebService)
64.7.2. @WebParam (javax.jws.WebParam)
64.7.3. @WebMethod (javax.jws.WebMethod)
64.7.4. @OneWay (javax.jws.OneWay)
64.7.5. @WebResult (javax.jws.WebResult)
64.7.6. Samples
64.8. [참고] Spring Configuration XML Schema
64.9. Resources
65. Simple Frontend
65.1. Web Services 작성
65.1.1. Samples
65.2. Server: Simple Frontend API 코드 사용
65.2.1. Samples
65.3. Spring Configuration XML - simple:server tag 사용
65.3.1. Samples
65.4. Client: Simple Frontend API 코드 사용
65.4.1. Samples
65.5. Spring Configuration XML - simple:client tag 사용
65.5.1. Samples
65.6. [참고] Spring Configuration XML Schema
65.7. Resources
66. Spring Support
66.1. Web Services 작성
66.1.1. Samples
66.2. Spring Configuration XML - simple:server tag 사용
66.2.1. Samples
66.3. Spring Configuration XML - simple:client tag 사용
66.3.1. Samples
66.4. Spring Configuration XML - ClientProxyFactoryBean 사용
66.4.1. Samples
67. Databinding
67.1. JAXB Databinding
67.1.1. Server Configuration
67.1.1.1. Samples
67.1.2. 유의 사항
67.1.2.1. SEI 클래스에서 정의되지 않은 Java Type 클래스가 Runtime시 Databinding되어야 하는 경우
67.2. Aegis Databinding
67.2.1. Server Configuration
67.2.1.1. Samples
67.2.2. Client Configuration
67.2.2.1. Samples
67.3. MTOM Databinding
67.3.1. Server Configuration
67.3.1.1. Samples
67.3.2. Client Configuration
67.3.2.1. Samples
67.3.3. 참고 - MTOM에 관련된 내용
67.4. Resources
68. Asynchronous Invocation
68.1. Server Configuration
68.1.1. Samples
68.2. Client Configuration
68.2.1. Samples
68.3. Resources
69. RESTful Services
69.1. JAX-RS 활용한 RESTful 서비스 구현
69.1.1. Server Configuration
69.1.1.1. Samples
69.1.2. Client Configuration
69.1.2.1. Samples
69.2. HTTP Binding(JRA) 활용한 RESTful 서비스 구현
69.2.1. Server Configuration
69.2.1.1. Samples
69.2.2. Client Configuration
69.2.2.1. Samples
69.2.3. 유의 사항
69.2.3.1. Samples
69.3. HTTP Binding(Naming Convention) 활용한 RESTful 서비스 구현
69.3.1. Server Configuration
69.3.1.1. Samples
69.3.2. Client Configuration
69.3.2.1. Samples
69.4. JAX-WS Provider/Dispatch API 활용한 RESTful 서비스 구현
69.4.1. Server Configuration
69.4.1.1. Samples
69.4.2. Client Configuration
69.4.2.1. Samples
69.5. [참고] Spring Configuration XML Schema
69.6. Resources
70. WAS(Web Application Server) Configuration
70.1. Tomcat
70.1.1. Tomcat 5.5.23
70.2. JEUS
70.2.1. JEUS 5
70.2.2. JEUS 6
70.3. WebLogic
70.3.1. WebLogic 9.2, 10.1

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를 이용하여 개발을 수행하기 위해서는 본 매뉴얼의 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.DynamicModule Service

공통/서비스/웹 등과 같이 타입별로 프로젝트를 구성하는 경우, 몇가지 설정을 추가함으로써 Dynamic Reloading 기능을 제공하는 서비스이다. 리로딩이 되는 단위는 각각의 프로젝트 단위이며 런타임 환경이 아닌 개발 환경에서 사용하도록 한다.

2.2.3.DataSource 서비스

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

2.2.4.Query 서비스

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

2.2.5.ID Generation 서비스

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

2.2.6.Properties 서비스

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

2.2.7.Transaction 서비스

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

2.2.8.Hibernate 서비스

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

2.2.9.Logging 서비스

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

2.2.10.Remoting 서비스

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

2.2.11.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 Maven Command와 Anyframe Plugin을 이용하여 로컬에 별도 라이브러리나 샘플 프로젝트를 설치하지 않고도 Maven을 이용하여 프로젝트에 적합한 Anyframe 기반의 샘플 프로젝트를 구성할 수 있도록 지원한다. Plugin 기반의 프로젝트 기반 구성 방법 및 Anyframe Maven Command 방법, Plugin 정의 방법에 대해 자세히 살펴보도록 하자.

3.Installation

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

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/artifactory/anyframe-repository</url>
                    <snapshots>
                        <enabled>true</enabled>
                    </snapshots>
                </repository>	
            </repositories>
            <pluginRepositories>
                <pluginRepository>
                    <id>anyframe-plugin</id> 
                    <name>repository for Anyframe</name>       
                    <url>http://dev.anyframejava.org/artifactory/anyframe-repository</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을 추가하고, 압축을 해제한 폴더 위치를 값으로 지정해준다.

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

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

3.2.Resources

  • 다운로드

    다음은 앞서 언급한 Maven settings.xml 파일이다. settings.xml 파일을 다운로드받은 후, %MAVEN_HOME\conf 내에 복사한다.

    표 3.1. Download List

    NameDownload
    settings.xmlDownload

3.3.[필수] Foundation Plugin 설치

Anyframe 4.x에서는 다양한 오픈 소스들이 통합된 템플릿 기반의 프로젝트 구조와 샘플 코드, 참조 라이브러리 집합을 Plugin이라 칭하며, 다양한 유형의 Plugin을 제공한다. (Plugin에 대한 자세한 내용은 Anyframe Plugins를 참조하도록 한다.) 먼저 다른 Plugin 설치를 위한 기반을 제공하는 기본 Plugin인 Foundation Plugin을 설치해 보도록 하자.

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

    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.plugin.foundation 이라는 이름의 archetype이 Command 창에 제시됨을 알 수 있다.

  2. 제시된 Maven Archetype 목록 중 anyframe.plugin.foundation에 해당하는 번호('1')를 선택하고 요구하는 입력값을 추가 정의해준다.

    위 그림에서와 같이 설치 대상 PC에 anyframe.plugin.foundation-x.x.x.jar 라이브러리를 다운로드받지 않은 경우 Anyframe Repository로부터 이를 다운로드하고, 다음과 같은 인자에 대한 값을 입력하도록 요구할 것이다.

    표 3.2. Input Parameters for Installing Maven Archetype

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

  3. 다음은 Foundation Plugin 설치를 통해 구성된 샘플 프로젝트의 모습이다. 설치된 샘플 프로젝트명은 myproject이며, 하위에 다양한 용도의 폴더를 포함하고 있다.

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

기존 Maven 사용자 유의 사항

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

3.4.[선택] Other Plugins 설치

Foundation Plugin 설치 완료 이후에는 Anyframe에서 제공하는 anyframe-maven-plugin을 이용하여 Anyframe에서 제공하는 다양한 Plugin들을 추가로 설치할 수 있게 된다. 이번에는 Foundation Plugin 이외 Plugin들을 설치하는 방법에 대해서 살펴보기로 하자. (본 절에서 언급되는 anyframe-maven-plugin에서 지원하는 모든 Maven 명령어 종류는 Anyframe Maven Commands를 참조하도록 한다.)

  1. Command 창을 띄운 후, Foundation Plugin 설치로 생성된 샘플 프로젝트가 있는 위치로 이동하여 다음과 같이 Maven 명령어를 입력함으로써 현재 설치 가능한 Plugin 목록 및 설치된 Plugin 목록을 확인할 수 있다.

    mvn anyframe:list

    다음은 Foundation Plugin 설치 후, 위와 같이 명령어를 실행하였을 때 Command 창에 나타난 결과를 보여주는 그림으로 전체 15개의 Plugin에 대해 설치 가능하며 Foundation Plugin만 설치된 상태임을 알 수 있다.

  2. Command 창의 샘플 프로젝트가 있는 위치에서 다음과 같이 Maven 명령어를 입력함으로써 원하는 Plugin을 선택하여 설치할 수 있다. pluginName이라는 속성에 대해서 설치 대상 Plugin 이름을 정의해주면 된다.

    mvn anyframe:install -DpluginName=hibernate
  3. Plugin 설치 완료 후에는 [선택] Plugin 설치 확인 방법을 참조하여 정상 동작 여부를 확인하도록 한다.

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 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를 선택함으로써 샘플 프로젝트 관련 Problems를 해결하도록 한다.

      이 후, 해당 프로젝트에 대해 마우스 오른쪽 버튼을 클릭하여 컨텍스트 메뉴에서 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 형태로 패키징 가능하다.

3.6.[선택] 샘플 DB 변경

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

  1. 설치된 샘플 프로젝트 하위의 pom.xml 파일을 열고 <properties/> 내에 정의된 DB 정보를 적절하게 수정한다.

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

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

    표 3.3. DB Propperties

    PropertyDescription
    db.name 해당 DB에 대한 대표명을 정의한다. 특정 Plugin이 실행해야 할 DB 스크립트를 포함하고 있는 경우 db.name 값을 포함하고 있는 스크립트(${plugin name}-insert-data-${db.name}.sql, ${plugin name}-delete-data-${db.name}.sql) 파일이 실행되도록 하는데 사용된다. (현재 제공되는 Plugin 중 실행 대상 DB Script를 포함하고 있는 Plugin은 mipsample과 security임. 예를 들어, 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 위치를 정의한다. (샘플 프로젝트 위치 기준) 샘플 어플리케이션 실행시 DB Library를 인식할 수 있도록 하기 위해서 [샘플 프로젝트 설치 폴더]/src/main/webapp/WEB-INF/lib 폴더 내에 저장할 것을 권장한다. 만약 Maven 기반에서 실행하고자 한다면 로컬 Maven Repository 내에 DB Library가 존재해야 하며 샘플 프로젝트의 pom.xml 내에 이를 dependency 정보로써 추가해야 한다.

  2. [샘플 프로젝트 설치 폴더]/src/main/resources/spring 폴더 내에 위치한 spring 속성 정의 파일(context-datasource.xml, context-query.xml, context-hibernate.xml)의 DB 관련 속성 정의 부분을 수정한다.

    다음은 context-datasource.xml 파일의 일부이다. drvierClassName, url, username, password를 해당 DB에 맞게 수정하도록 한다.

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" 
        destroy-method="close">
        <property name="driverClassName" value="net.sf.log4jdbc.DriverSpy"/>
        <property name="url" value="jdbc:log4jdbc:hsqldb:hsql://localhost/sampledb"/>
        <property name="username" value="sa"/>
        <property name="password" value=""/>
    </bean>	

    다음은 context-query.xml 파일의 일부이다. PagingSQLGenerator를 해당 DB에 맞게 수정하도록 한다. QueryService를 통해 제공되는 PagingSQLGenerator는 QueryService 설정 정보 중 [pagingSQLGenerator] 부분을 참고하도록 한다.

    <bean name="queryService" class="anyframe.core.query.impl.QueryServiceImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
        <property name="pagingSQLGenerator" ref="pagingSQLGenerator"/>
        <property name="sqlRepository" ref="sqlLoader"/>				
    </bean>
    	
    중략...
    
    <bean id="pagingSQLGenerator" 
        class="anyframe.core.query.impl.jdbc.generator.HSQLPagingSQLGenerator"/>

    다음은 context-hibernate.xml 파일의 일부이다. Hibernate Plugin을 설치한 경우에 한해 Dialect 클래스를 해당 DB에 맞게 수정하도록 한다.

    <bean id="sessionFactory" 
        class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"
        lazy-init="true">
        <property name="dataSource" ref="dataSource" />
        중략...
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
                중략...
            </props>
        </property>
    </bean>
  3. [샘플 프로젝트 설치 폴더]/db/scripts 폴더 내에 있는 DB 스크립트를 이용하여 해당 DB에 테이블을 생성하고, 초기 데이터를 입력하도록 한다. 현재 Anyframe에서는 HSQL DB, Oracle DB 스크립트만을 제공하고 있다.

3.7.[선택] 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)를 실행해주어야 한다는 점에 유의하도록 한다.

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.Anyframe Plugins

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

표 4.1. The List of Plugin

NoPlugin NameDescription
1foundation 모든 Plugin 설치를 위한 기반을 제공하는 Plugin이다. 샘플 프로젝트의 구조를 정의하고 있으며, SpringMVC + Spring + Query Service를 이용한 상품 관리 기능을 제공한다.
2cacheOSCache와 Anyframe에서 구현한 Cache Service를 이용한 상품 관리 기능을 제공한다.
3cxfApache CXF를 이용한 상품 관리 기능을 포함하고 있으며 생성된 WSDL을 확인할 수 있도록 한다.
4dynamicmodule특정 어플리케이션에 대한 Dynamic Reloading을 위해 필요한 참조 라이브러리만을 제공한다.
5hibernateHibernate와 Anyframe에서 구현한 Dynamic Hibernate Service를 이용한 상품 관리 기능을 제공한다.
6jasperJasperReports와 Spring을 연계하여 상품 현황을 HTML, PDF 형태의 Report로 보여준다.
7miplatformTOBE 소프트에서 제공하는 X-Internet 솔루션인 MiPlatform을 Anyframe과 연계하여 상품 관리 기능을 제공한다.
8mipsample TOBE 소프트에서 제공하는 X-Internet 솔루션인 MiPlatform을 Anyframe과 연계하여 활용할 수 있는 다양한 UI Sample을 제공한다. mipsample plugin 기능 확인을 위해 DB에 추가되어야 할 샘플 데이터가 함께 제공된다. (mipsample plugin이 정상 설치된 경우 별도로 DB 데이터를 추가할 필요는 없으며 DB 연계에 문제가 있는 경우에 한해 제공되는 DB 스크립트를 이용하여 샘플 데이터를 추가하도록 한다.)
9remotingSpring Remoting 기법 중 HttpInvoker를 활용하여 상품 관리 기능을 제공한다.
10schedulingQuartz를 이용하여 주기적으로 월별 상품 등록 현황을 생성하는 기능을 제공한다.
11security Spring Security를 이용하여 사용자별 Authentication, 역할별 Authorization 기능을 제공한다. security plugin 기능 확인을 위해 DB에 추가되어야 할 샘플 데이터가 함께 제공된다. (security Plugin이 정상 설치된 경우 별도로 DB 데이터를 추가할 필요는 없으며 DB 연계에 문제가 있는 경우에 한해 제공되는 DB 스크립트를 이용하여 샘플 데이터를 추가하도록 한다.)
12spring-optionalPlugin을 통해 설치되지 않는 Spring Optional 라이브러리만을 제공한다.
13strutsStruts를 이용하여 상품 관리 기능을 제공한다.
14test테스트 코드 실행에 필요한 참조 라이브러리만을 제공한다.
15webflowSpring Webflow를 이용하여 상품 관리 기능을 제공한다.

4.2.Foundation Plugin 구조

모든 Plugin 설치를 위한 기반을 제공하는 Plugin으로 Maven Archetype 형태로 구성되어 있어 명령어 mvn archetype:generate를 이용하면, Foundation Plugin 내에 정의된 프로젝트 템플릿을 정해진 위치에 설치할 수 있도록 하고 있다.

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

archetype-resources는 리소스 템플릿을 관리하기 위한 용도의 폴더로써 pom.xml 파일과 다음과 같은 하위 폴더를 가진다. pom.xml 파일 내에는 Foundation Plugin 설치 결과 생성될 샘플 어플리케이션 실행에 필요한 참조 라이브러리와 Maven 기반 샘플 어플리케이션 실행에 필요한 Maven Plugin 등이 정의되어 있다.

표 4.2. Structure of Foundation Plugin - archetype-resources

FolderDescription
.metadataPlugin 설치 정보를 관리하는 anyframe-plugins-metadata.mf 파일을 가진다.
.settingsEclipse 프로젝트 정보를 관리한다.
db/hsqldb샘플 어플리케이션 실행에 필요한 샘플 DB이다.
db/scripts DB 샘플 데이터 생성을 위해 사용된 DB 스크립트 파일을 제공한다. 현재 HSQL DB, Oracle DB 스크립트를 제공하고 있다.
src/main/java 소스 코드를 관리한다. 소스 코드의 패키지는 Plugin 이름인 foundation으로 시작한다. 단, Domain 클래스 및 Exception, Aspect 클래스는 각각 domain, common 패키지로 구분지어져 있다.
src/main/resources Spring, SpringMVC 기반의 어플리케이션 실행을 위한 속성 정의 파일과 메시지 파일, 쿼리문을 정의하고 있는 매핑 XML 파일들을 관리한다.
src/main/webapp웹 어플리케이션을 위한 웹 리소스(*.jsp, *.css, *.js ...)들을 관리한다.
src/test/java테스트 코드를 관리한다. 테스트 코드의 패키지는 Plugin 이름인 foundation으로 시작한다.
src/test/resources테스트 코드 실행에 필요한 리소스들을 관리한다.

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

표 4.3. Structure of Foundation Plugin - META-INF

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

4.3.Other Plugins 구조

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

다음은 Security 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="hibernate">
    <fileSets>
        <fileSet filtered="true" packaged="true">
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.java</include>
            </includes>
            <excludes>
                <exclude>**/*.properties</exclude>
            </excludes>
        </fileSet>
        중략...
   </fileSets>
</archetype-descriptor>

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

#if($package && !$package.equals(""))
package ${package}.hibernate.sales.service;
#else
package hibernate.sales.service;	
#end

import java.util.List;

import anyframe.core.generic.service.GenericService;

#if($foundationpackage && !$foundationpackage.equals(""))
import ${foundationpackage}.domain.Category;
#else
import domain.Category;
#end

public interface CategoryService extends GenericService<Category, String> {

	List getDropDownCategoryList() throws Exception;
}

filtered 속성값이 true인 경우, #if ~ #end 부분이 Velocity Engine에 의해 해석되어 입력된 패키지명이 'anyframe' 이라면 'package anyframe.hibernate.sales.service;'로 입력된 패키지명이 없으면 'package hibernate.sales.service;' 구문으로 재조합되어 샘플 프로젝트 내에 CategoryService.java 파일이 추가될 것이다. filtered 속성값이 false인 경우, 정의된 모든 부분에 대한 변경없이 샘플 프로젝트 내에 CategoryService.java 파일이 추가될 것이다.

다음으로 packaged 속성에 대해 알아보도록 하자. plugin-resources/src/main/java 하위에 hibernate/sales/service/CategoryService.java라는 리소스가 정의되어 있다라고 가정해 보자. packaged 속성값이 true인 경우 입력된 패키지명이 'anyframe'이라면 샘플 프로젝트 내 src/main/java/anyframe/hibernate/sales/service 하위에 해당 리소스가 추가될 것이다.

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

표 4.4. Structure of Other Plugins - plugin-resources #1

FolderDescription
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명과 같은 폴더를 생성하여 관리하도록 한다.

표 4.5. Structure of Other Plugins - plugin-resources #2

FolderDescription
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.1.0</version>
      
          <build>
              <resources>
                  <resource>
                      <filtering>false</filtering>
                      <directory>src/main/resources</directory>
                  </resource>
              </resources>
          </build>
      </project>
    2. 해당 프로젝트 내에 src/main/resources 폴더를 생성하고 하위에 plugin-resources라는 폴더를 생성한다. plugin-resources 폴더 하위에는 Custom Plugin 설치 결과 샘플 프로젝트에 추가되어야 할 리소스(Java, XML, ... 등)들에 대한 템플릿과 plugin-descriptor.xml, pom.xml 파일을 정의해 주어야 한다.

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

    4. plugin-descriptor.xml 파일 내에는 앞서 정의한 리소스에 대한 Meta 정보를 정의한다. (Anyframe Others Plugins에 대한 구조 참고)

    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 install' 명령어를 실행시킴으로써 로컬 Maven Repository에 Custom Plugin을 배포한다.

  2. Custom Plugin 목록을 정의한다.

    1. Custom Plugin 목록 정의를 위한 프로젝트를 생성하고 해당 프로젝트에 대한 pom.xml 파일을 다음과 같이 정의한다. 다음에 제시된 바와 같이 해당 프로젝트의 groupId는 anyframe, artifactId는 anyframe.plugin.custom-list로 정의해 주도록 한다.

      <?xml version="1.0" encoding="UTF-8"?>
      <project>
          <modelVersion>4.0.0</modelVersion>
          <groupId>anyframe</groupId>
          <artifactId>anyframe.plugin.custom-list</artifactId>
          <packaging>jar</packaging>
          <version>4.1.0</version>
          <name>Define Custom Plugin List</name>
      	
          <build>
              <resources>
                  <resource>
                      <filtering>false</filtering>
                      <directory>src/main/resources</directory>
                  </resource>
              </resources>
          </build>	
      </project>
    2. 해당 프로젝트 내에 src/main/resources 라는 폴더를 생성하고 plugin-list.xml 파일을 다음과 같이 정의한다.

      <list>
          <plugin>
              <pluginName>custom</pluginName>
              <groupId>anyframe</groupId>
              <artifactId>anyframe.plugin.custom</artifactId>
              <version>4.1.0</version>
              <interceptor>custom.interceptor.CustomPluginInterceptor</interceptor>
          </plugin>					
      </list>

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

    3. Command 창에서 'mvn install' 명령어를 실행시킴으로써 로컬 Maven Repository에 Custom Plugin 목록을 배포한다.

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

5.Anyframe Maven Commands

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

5.1.anyframe-maven-plugin 정의 확인

Maven 기반의 프로젝트를 대상으로 anyframe-maven-plugin을 사용하기 위해서는 해당 프로젝트의 pom.xml 파일 내에 다음과 같은 정의가 추가되어 있는지 확인해 보도록 한다.

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

5.2.Anyframe Maven Commands

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

표 5.1. The List of Goal

GoalDescription
help사용 가능한 Command 목록을 보여준다.
install Foundation Plugin 설치후, 생성된 샘플 프로젝트에 특정 Plugin을 설치한다. 다음과 같은 Option을 가진다.

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

package : 설치 대상 Plugin으로 인해 추가될 샘플 코드에 대표 Package를 부여할 수 있다.

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

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

list설치 가능한 Plugin 목록과 Plugin 설치 현황을 보여준다.
install-listPlugin 설치 현황을 보여준다.
inplace pom.xml 파일을 기반으로 하여 Plugin 설치로 생성된 샘플 프로젝트 하위의 src/main/webapp/WEB-INF/lib 폴더 내로 샘플 어플리케이션 실행에 필요한 모든 참조 라이브러리를 다운로드한다.

III.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 기법에 대해서도 알아보자.

6.IoC(Inversion of Control)

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

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을 통하여 가능하게 된다.

6.1.Basic

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

6.1.1.Container와 Bean

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

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

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

6.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이 생성되기를 기다릴 필요가 없게 된다.

6.1.2.1.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.DescriptiveResourceDefines 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

6.1.2.2.ApplicationContext

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

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

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

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

  • ClassPathXmlApplicationContext - 클래스 패스에 위치한 XML 설정 파일을 읽어들이는 ApplicationContext

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

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

6.1.2.3.설정 메타데이터

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 자체는 설정 메타데이터의 형태로부터 분리될 수 있기 때문이다.

6.1.2.4.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;

6.1.2.5.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 정의 파일이어야만 한다.

6.1.3.Beans

6.1.3.1.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이 호출되는 시점에 초기화됨

6.1.3.2.Bean 명명하기

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

6.1.3.3.Bean 인스턴스화

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

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

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

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

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

    <bean id="exampleBean" class="examples.ExampleBean2" 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="exampleBean"
      factory-bean="myFactoryBean"
      factory-method="createInstance"/>
    중략...

6.1.4.How to refer to Beans

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

6.1.4.1.비즈니스 레이어

비즈니스 레이어에서 사용하고자 하는 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>

6.1.4.2.프리젠테이션 레이어

프리젠테이션 레이어에서 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.example.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.example.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.example.foundation.sales.service.impl.ProductServiceImpl">
        중략...
    </bean>

6.2.Dependencies

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

6.2.1.Dependency Injection(DI)

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

6.2.1.1.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.example.foundation.sales.service.impl.ProductServiceImpl">
    <property name="productDao" ref="foundationProductDao" />
</bean>
	
<bean id="foundationProductDao" class="anyframe.example.foundation.sales.dao.impl.ProductDaoImpl"/>

6.2.1.2.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.example.foundation.sales.service.impl.ProductServiceImpl">
    <constructor-arg ref="foundationProductDao"/>
</bean>
        
<bean id="foundationProductDao" class="anyframe.example.foundation.sales.dao.impl.ProductDaoImpl">
        중략...
</bean>

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

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

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

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

6.2.1.3.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>

6.2.1.4.생성자 인자 분석

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

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

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

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

  • 생성자의 인자 인덱스

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

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

6.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="example.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="example.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="ExampleBean">
      <property name="email"><null/></property>
    </bean>

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

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

6.2.2.1.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"/> 

6.2.2.2.혼합된 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이 던져질 것이다.

6.2.3.depends-on 속성 사용

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

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

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

<bean id="beanOne" class="ExampleBean" 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을 초기화시킨다.

6.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.example.foundation.sales.dao.impl.ProductDaoImpl" lazy-init="true">
<bean id="foundationProductService" 
    class="anyframe.example.foundation.sales.service.impl.ProductServiceImpl"/>

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

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

6.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.example.foundation.sales.service.impl.ProductServiceImpl" 
	autowire="byType" depends-on="foundationProductDao">
</bean>

6.2.5.1.장점

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

  • XML 파일 크기 줄어듬

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

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

6.2.5.2.단점

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

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

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

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

6.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.example.….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>

6.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

6.3.1.Lookup Method Injection

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

<bean id="foundationProductService"
      class="anyframe.example.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.example.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;
    }
    중략...
}  

6.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가 적용되도록 정의하고 있음을 알 수 있다.

6.4.Bean과 Container의 확장

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

  • Bean Scope

  • Bean Life Cycle

  • Bean 상속

  • Container 확장

  • ApplicationContext 활용

6.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에 대해 정의할 수 있다.

6.4.1.1.Singleton

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

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

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

6.4.1.2.Prototype

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

<bean id="foundationProductService" 
        class="anyframe.example.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

6.4.1.3.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.example.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.example.foundation.sales.service.impl.ProductServiceImpl">
        <!-- a reference to the proxied 'productPreferences' bean -->
        <property name="productPreferences" ref="productPreferences"/>
    </bean>

6.4.1.4.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>

6.4.2.Bean Life Cycle

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

6.4.2.1.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.example.foundation.sales.service.impl.ProductServiceImpl" 
        init-method="productInitialize" destroy-method="productDestroy" parent="parent">
</bean>

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

6.4.2.2.Destruction

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

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

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

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

6.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.example.foundation.sales.service.impl.ProductServiceImpl" 
    init-method="productInitialize" destroy-method="productDestroy" parent="parent">
</bean>

6.4.4.Container 확장

6.4.4.1.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"/>

6.4.4.2.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.example.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>

6.4.5.ApplicationContext 활용

6.4.5.1.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" 필드는 반드시 필요하다.        

6.4.5.2.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.example.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."));

6.4.5.3.BeanFactory와 ApplicationContext 특징 비교

FeatureBeanFactoryApplicationContext
Bean instantiation/wiringYesYes
Automatic BeanPostProcessor registrationNoYes
Automatic BeanFactoryPostProcessor registrationNoYes
Convenient MessageSource access (for i18n)NoYes
ApplicationEvent publicationNoYes

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

6.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 설정에 대한 부담을 덜 수 있다.

6.6.Resources

  • 다운로드

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

    • Maven 기반 실행

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

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

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

    • Eclipse 기반 실행 - WTP 활용

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

    표 6.1. Download List

    NameDownload
    hsqldb.zipDownload
    anyframe.example.foundation.zipDownload
    maven-ant-tasks-2.0.10.jarDownload

7.Aspect Oriented Programming

다음 내용은 ZDNet Korea의 제휴 매체인 마이크로소프트웨어에 게재된 내용 에서 발췌함. 관점지향 프로그래밍(Aspect Oriented Programming, 이하 AOP)은 지금까지의 프로그래밍 기술 변화의 흐름에 다른 차원의 관점을 제시함으로써 새로운 프로그래밍 패러다임을 이끌어내고 있다고 볼 수 있다. AOP의 필요성을 이해하는데 기초가 되는 개념은 Separation of Concerns로, 다음과 같이 거의 모든 프로그래밍 패러다임은 바로 이 Separation of Concerns 과정을 통해 문제 영역을 독립적인 모듈로 분해한다.

  • 절차적 프로그래밍 : 분리된 관심을 프로시저로 구성

  • 객체지향 프로그래밍(Object Oriented Programming, 이하 OOP) : 분리된 관심을 클래스로 작성

AOP 필요성

AOP는 OOP를 적용한다고 할지라도 결코 쉽게 분리된 모듈로 작성하기 힘든 요구사항이 실제 어플리케이션 설계와 개발에서 자주 발견된다는 문제 제기에서 출발한다. AOP에서는 이를 Crosscutting Concerns(횡단 관심)라고 한다. 또한 해당 시스템의 핵심 가치와 목적이 그대로 드러난 관심 영역을 Core Concerns(핵심 관심)라고 부른다. 이 Core Concerns는 기존의 객체지향 분석/설계(OOAD)를 통해 쉽게 모듈화와 추상화가 가능하지만 Crosscutting Concerns은 객체지향의 기본 원칙을 지키면서 이를 분리해서 모듈화하는 것이 매우 어렵다.

예를 들어, 은행 업무를 처리하는 시스템을 생각해보면 Core Concerns는 예금입출금, 계좌간이체, 이자계산, 대출처리 등으로 구분할 수 있다. 이는 전체 어플리케이션의 핵심 요구 사항과 기능들을 구분해서 모듈화할 수 있고 OOP에서라면 클래스와 컴포넌트 형태로 구성이 가능하다. 하지만 현실은 그렇지 못하다. 실제로 개발되어 돌아가는 각 모듈에는 해당 업무를 처리하기 위한 로직만 존재해서는 불완전할 수밖에 없다. 일단 각 업무를 처리하는 클래스와 구현된 메소드에는 향후 시스템을 분석하거나 추적을 위해 로그를 작성해야 하며, 인증받은 사용자가 접근하는지를 체크하고 권한 여부를 따지는 보안 기능이 필요하다. 또한 내부에서 사용하는 Persistence 처리를 위해 Transaction을 시작하고, 또 필요에 따라서 그것을 Commit 또는 Rollback하는 부분도 추가되어야 한다. 예외 상황이나 문제가 발생했을 때는 그것을 기록에 남기는 부분도 있어야 하고, 필요하면 관리자에게 이메일을 발송해야 한다.

이러한 부가적인 기능들은 독립적인 클래스로 구현될 수 있지만, 그렇게 구현된 기능들을 호출하고 사용하는 코드들이 핵심 모듈 안의 필요한 영역에 모두 포함될 수밖에 없다. 로깅, 인증, 권한체크, DB 연동, 트랜잭션, 락킹, 에러처리 등의 기능을 아무리 뛰어난 OOP 기술을 이용해 모듈로 구성하고 추상화를 통해 최대한 독립시킨다고 해도 핵심 모듈의 모든 클래스와 메소드 속에 이와 연동되는 부분이 매우 깊이 그리고 상당한 양을 갖으면서 자리 잡게 된다.

실제로 모듈화가 잘 된 어플리케이션 클래스를 보더라도 핵심 기능을 위한 코드보다 부가적인 기능과 처리를 위한 부분의 양이 더 많아지게 되는데 만약 다른 종류의 로깅 플랫폼을 사용해 로그 처리하는 클래스와 메소드가 달라지고 로그 메시지가 변경되어야 한다면 개발자들은 모든 클래스 안에 있는 로그 관련 코드를 일일이 다 수정해 주는 수밖에 없다. 그러다가 만약 중요한 클래스에서 한두 군데 로그 기록 코드가 빠졌고 이로 인해 결과를 확인하는데 문제가 생겼다면 이를 다시 확인하고 찾아내는 일만 해도 엄청난 작업이 아닐 수 없을 것이다. 이렇게 작성된 어플리케이션은 몇 가지 심각한 문제를 가지고 있다.

  • 중복되는 코드 : 복사-붙이기에 의해 만들어진 여러 모듈에서 중복되는 코드의 문제점은 이미 잘 알려져 있다. 하지만 AOP를 사용하지 않은 대부분의 어플리케이션에서는 어떠한 추상화와 리팩토링을 통해서도 반복되는 코드를 피하기가 어렵다.

  • 지저분한 코드 : Crosscutting Concerns과 관련된 코드들이 핵심 기능 코드 사이 사이에 끼어들어가 있기 때문에 코드가 지저분해지고 이에 따라 가독성이 떨어지며 개발자들의 실수나 버그를 유발하고 후에 코드를 유지보수하는데 큰 어려움을 준다.

  • 생산성의 저하 : 어플리케이션 개발자들이 자주 등장하는 Crosscutting Concerns을 구현한 코드를 함께 작성해야 하기 때문에 개발의 집중력을 떨어뜨리고 결과적으로 전체 생산성의 저하를 가져온다. 또 모듈별로 개발자들을 구분하고 분산시키는 것에 한계가 있다.

  • 재활용성의 저하 : OOP의 장점인 재활용성이 매우 떨어진다.

  • 변화의 어려움 : 새로운 요구사항으로 인해 전체적으로 많은 부분에 영향을 미치는 경우 쉽게 새로운 요구사항을 적용하기 힘들게 된다. 또 새로운 관심 영역의 등장이나 이의 적용을 매우 어렵게 한다.

대표적인 AOP 툴

AOP는 OOP의 확장에 가깝기 때문에 전용 언어나 독립된 개발 툴을 가지고 있지 않고 대신 기존의 OOP를 확장한 언어 확장(languageextension) 또는 툴이나 프레임워크 형태로 사용할 수 있게 되어 있다. 대표적으로 AOP 구현의 시초가 된 Eclipse 프로젝트의 AspectJ를 들 수 있다. AspectJ는 초기에 제록스 PARC 연구소에서 개발되었다가 2002년에 이클립스 프로젝트에 기증되었고, 현재 IBM의 전폭적인 지원을 받으면서 개발되어 사용되고 있다. 그리고 BEA가 중심이 되어 개발하고 있는 AspectWerkz가 있다. AspectWerkz는 AspectJ와 달리 자바 언어 자체를 확장하지 않고 기존의 자바 언어만으로 AOP의 사용이 가능하도록 되어 있다. 그리고 의존성 삽입(Dependency Injection, 이하 DI) 기반의 프레임워크로 유명한 SpringAOP가 있다. 가장 최근에 등장한 AOP로는 JBossAOP도 있다. SpringAOP와 함께 대표적인 인터셉터체인 방식의 AOP로 꼽힌다.

 AspectJAspectWerkzJBossAOPSpringAOP
출시2001200220042004
버전 1.2.12.01.3.01.2.5
Aspect 선언전용코드XML, AnnotationXML, AnnotationXML
Advice전용코드자바 메소드자바 메소드자바 메소드
JoinPoint메소드, 생성자, Advice, Field Access, 인스턴스메소드, 생성자, Advice, Field Access, 인스턴스메소드, 생성자, Advice, Field Access, 인스턴스메소드
Pointcut 매칭 Signature, WildCard, AnnotationSignature, WildCard, AnnotationSignature, WildCard, Annotation정규식
Weaving컴파일 및 로딩 타임, 바이트 코드 생성컴파일 및 로딩 타임, 바이트 코드 생성런타임 인터셉션 및 Proxy런타임 인터셉션 및 Proxy
IDE 지원Eclipse, JDeveloper, JBuilder, NetBeansEclipse, NetBeansEclipse 
  • AspectJ

    AspectJ의 가장 큰 특징은 다른 AOP 툴과는 달리 자바 언어를 확장해서 만들어진 구조라는 것이다. 마치 새로운 AOP 언어를 사용하듯이 aspect라는 키워드를 이용해 Aspect나 Pointcut, Advice를 만들 수 있다. 따라서 일반 자바 컴파일러로는 컴파일이 불가능하고 특별한 AOP 컴파일러를 사용해야 한다. 하지만 이렇게 만들어진 바이너리는 표준 JVM에서 동작 가능한 구조로 되어있기 때문에 특별한 클래스 로더의 지원 없이도 실행 가능하다. AspectJ는 가장 오래되고 가장 많이 사용되는 AOP 툴이다. 동시에 가장 풍부한 기능을 가지고 있고 확장성이 뛰어나기 때문에 가장 이상적인 AOP 툴로 꼽히고 있다. 하지만 자바 언어를 확장했기 때문에 새로운 문법과 언어를 이해할 필요가 있고 프로젝트 빌드시 특별한 컴파일러를 사용해야 하는 불편함이 있다. Weaving이 컴파일시에 일어나기 때문에 Pointcut에 의해 선택된 모든 클래스들은 Aspect가 바뀔 때마다 모두 다시 컴파일이 되어야 한다.

  • AspectWerkz

    AspectWerkz는 AspectJ와는 달리 자바 언어를 확장하지 않는다. 따라서 표준 자바 클래스를 이용해서 AOP를 구현해 낼 수 있다. 일반 클래스와 메소드를 이용해 쉽게 구현이 가능한 Advice와 달리 복잡한 문법이 필요한 Pointcut은 별도의 XML 파일을 이용해 설정할 수 있도록 되어 있다. 자바 클래스와 XML 설정 파일의 접근법에 익숙한 개발자들에게는 매우 편리한 접근 방식이라고 볼 수 있다. 최근에는 JDK5의 지원에 따라 Annotation을 이용할 수 있어 더욱 편리해졌다. Weaving은 특별한 클래스 로더를 이용한 로딩타임 바이트코드 생성을 이용한다. AspectJ 못지않은 다양한 JoinPoint와 AOP기능을 지원하고 있으며 편리한 개발을 위한 IDE 플러그인이 개발되어 있다.

  • JBossAOP

    JBossAOP는 기본적으로 컨테이너에서 동작하지만 컨테이너와 상관없는 독립된 자바 프로그램에서도 사용할 수 있다. 하지만 주 용도는 JBoss 서버와 앞으로 나올 EJB3 컨테이너 등에 AOP를 적용하는 데에 사용되어지는 것이다. AspectWerkz와 마찬가지로 Advice는 표준 자바 코드로 작성하고 Pointcut과 다른 설정은 XML 파일이나 JDK5의 Annotation으로 작성할 수 있다. 아직까지는 JBoss 사용자의 일부에서만 사용되고 있으나 향후 EJB3를 중심으로 한 POJO 기반의 엔터프라이즈 미들웨어 프레임워크가 개발되어짐에 따라 점차로 사용률이 올라갈 것으로 기대된다.

  • SpringAOP

    SpringAOP는 Spring Framework의 핵심기능 중의 한가지로 Spring의 Dependency Injection(이후 DI) 컨네이너에서 동작하는 엔터프라이즈 서비스에서 주로 사용된다. SpringAOP는 다른 AOP와 달리 기존 클래스의 바이트코드를 수정하지 않는다. 대신 JDK의 Dynamic Proxy를 사용해서 Proxy 방식으로 AOP의 기능을 수행한다. 이 때문에 다른 AOP의 기능과 비교해서 매우 제한적인 부분만을 지원한다. 하지만 SpringAOP의 구현 목적은 엔터프라이즈 어플리케이션에서 주로 사용되는 핵심적인 기능에 AOP의 장점을 살려 이를 Spring 내에서 사용하는 것이기 때문에 다른 AOP와 같은 AOP의 복잡한 전체 기능을 굳이 다 필요로 하지 않는다. 프록시 기반의 SpringAOP는 SpringIoC/DI와 매우 긴밀하게 연동이 된다. 따라서 SpringAOP를 사용하는 방법은 Spring 내에 ProxyBean을 설정해서 쉽게 사용할 수 있다. JDK의 표준 기능만을 사용하기 때문에 특별한 빌드 과정이 필요없고 클래스 로더를 변경한다거나 하는 번거로운 작업이 없다. 대신 JoinPoint의 종류가 메소드 기반으로 제한되나 대부분의 엔터프라이즈 어플리케이션에서 필요로 하는 주요 AOP 기능들은 메소드 호출을 기반으로 충분히 처리가 가능하기 때문에 SpringAOP는 그 제한된 AOP 기능에도 불구하고 현장에서 가장 빠른 속도로 적용되어 사용되는 AOP 솔루션 중의 하나이다. SpringAOP는 Advice와 Pointcut을 모두 표준 자바 클래스로 작성할 수 있다. 필요에 따라서 Pointcut은 설정 파일 내에서 Pointcut FactoryBean을 이용해서 정규식으로 표현이 가능하다. SpringAOP의 최대 단점은 복잡한 Proxy 설정 구조이다. Spring Bean을 정의한 파일에서 Proxy를 정의한 부분의 다른 XML기반의 AOP에 비해서도 복잡한 편인데 이 경우 SpringAOP가 지원하는 AutoProxyingCreatorBean 등을 이용하면 설정 코드를 매우 단순하게 작성하는 것이 가능하다.

7.1.AOP 구성 요소

AOP에는 새로운 용어가 많이 등장한다. 이 중에서 특히 AOP를 이용해서 개발하는데 필요한 다음의 주요 구성 요소들에 대해 정확한 이해가 필요하다.

7.1.1.JointPoint

Crosscutting Concerns 모듈이 삽입되어 동작할 수 있는 실행 가능한 특정 위치를 말한다. 예를 들어 메소드가 호출되는 부분 또는 리턴되는 시점이 하나의 JoinPoint가 될 수 있다. 또 필드를 액세스하는 부분, 인스턴스가 만들어지는 지점, 예외가 던져지는 시점, 등이 대표적인 JoinPoint가 될 수 있다. 각각의 JoinPoint들은 그 전후로 Crosscutting Concerns의 기능이 AOP에 의해 자동으로 추가되어져서 동작할 수 있는 후보지가 되는 것이다.

7.1.2.Pointcut

Pointcut은 어느 JoinPoint를 사용할 것인지를 결정하는 선택 기능을 말한다. AOP가 항상 모든 모듈의 모든 JoinPoint를 사용할 것이 아니기 때문에 필요에 따라 사용해야 할 모듈의 특정 JoinPoint를 지정할 필요가 있다. 일종의 JoinPoint 선정 룰과 같은 개념으로 다음과 같은 Pattern Matching 방법들을 이용하여 룰을 정의할 수 있다.

7.1.2.1.Pattern Matching Examples

  1. Basics

    • set*(..) : set으로 시작하는 모든 메소드명

    • * main(..) : return type이 any type이고, 0개 이상의 any type parameter를 가진 main 메소드

  2. Matching Type

    • java.io.* : java.io 패키지 내에 속한 모든 요소

    • org.myco.myapp..* : org.myco.myapp 패키지 또는 서브 패키지 내에 속한 모든 요소

    • Number+ : Number 또는 Number의 서브 type으로 Integer, Float, Double ..등이 이에 해당

    • !(Number+) : Number 또는 Number의 서브 type이 아닌 모든 type

    • org.xyz.myapp..* && !Serializable+ : org.xyz.myapp 패키지 또는 서브 패키지 내에 존재하면서 Serializable type이 아닌 모든 요소

    • int || Integer : int 또는 Integer type

  3. Matching Modifiers

    • public static void main(..) : 0개 이상의 any type parameter를 가진 public static void main 메소드

    • !private * *(..) : return type이 any type이고, 0개 이상의 any type parameter를 가진 모든 메소드중 modifier가 private이 아닌 메소드

    • * main(..) : modifier를 별도로 명시하지 않은 경우, default modifier가 아닌 any modifier 의미

  4. Matching Parameter

    • * main(*) : return type이 any type이고, 1개의 any type parameter를 가진 main 메소드

    • * main(*,..) : return type이 any type이고, 최소 1개의 any type parameter를 가진 main 메소드

    • * main(*,..,String,*) : return type이 any type이고, 최소 3개의 any type parameter를 가지며 끝에서 두번째 parameter type이 String인 main 메소드

  5. Matching Constructor

    • new(..) : 0개 이상의 any type parameter를 가진 constructor

    • Account.new(..) : 0개 이상의 any type parameter를 가진 Account 클래스의 constructor

AspectJ는 Pointcut을 명시할 수 있는 다양한 Pointcut Designator(지시자)를 제공한다. 이제부터 앞서 정의한 Pattern Matching 방법을 이용하여, 본격적으로 Pointcut Designator별 Pointcut 정의 방법에 대해 살펴보기로 하자.

7.1.2.2.Pointcut Designators

  1. execution 또는 call

    특정 메소드나 생성자 실행을 위한 JoinPoint를 정의하는 것으로, JoinPoint의 특정 method name, parameter types, return type, declared exceptions, declaring type, modifiers에 대한 matching이 가능하며, 단, return type pattern, method name pattern, parameter list pattern은 필수적으로 정의해야 한다. 다음은 execution, call을 이용한 pointcut 정의 예이다.

    • execution(* main(..)) : return type이 any type이고, 0개 이상의 any type parameter를 가진 main 메소드 실행시

    • call(Account.new(..)) : any type parameter를 가진 Account 클래스의 constructor 호출시

  2. get 또는 set

    특정 Field에 접근하거나 특정 Field 수정을 위한 JoinPoint를 정의한다.

    • get(Collection+ org.xyz.myapp..*.*) : Collection type의 org.xyz.myapp 패키지에 속한 any field에 대한 getter 호출시

    • set(!private * Account+.*) : Account type의 non-private field에 대한 setter 호출시

  3. handler

    Exception 핸들링을 위한 JoinPoint를 정의한다.

    • handler(DataAccessException) : matches cach(DataAccessException){...} and doesn't match catch(RuntimeException)

    • handler(RuntimeException+) : matches both

  4. within

    특정 유형에 속하는 JoinPoint를 정의하며, 주로 &&, ||, ! 등과 함께 조합된 형태로 사용된다.

    • within(*) : matches any JoinPoint

    • within(org.xyz.myapp..*) : org.xyz.myapp 패키지 내에 속하는 모든 요소

    • within(IInterface+) : IInterface type의 모든 요소

  5. withincode

    해당되는 메소드 또는 constructor 내에 정의된 코드를 위한 JoinPoint를 정의한다.

    • withincode(!void get*()) : return type이 void가 아니고 메소드명이 get으로 시작하며 parameter가 없는 메소드 내의 코드

  6. args

    입력값의 개수, type 등에 대한 JoinPoint를 정의한다.

    • call(* transfer(..)) && args(DepositAccount,CheckingAccount,*) : 메소드명이 transfer이고, 입력 인자가 2개 이상이며, 1,2번째 입력 인자의 type이 DepositAccount,CheckingAccount인 메소드 호출시

  7. this

    JoinPoint를 가진 object의 type을 정의한다. (Runtime type)

    • this(Account) : 인터페이스 Account를 구현한 클래스(Proxy)의 모든 JoinPoint

  8. target

    JoinPoint를 가진 target object의 type을 정의한다. (Runtime type)

    • call(* *(..)) && target(Account) : Account 클래스 내의 모든 메소드 호출시

Spring은 메소드 호출 부분에 대한 AOP만을 지원하므로, 위에 정의한 다양한 Pointcut Designator 중 execution, within, target, this, args만이 사용 가능하다.

7.1.3.Advice

Advice는 각 JoinPoint에 삽입되어져 동작할 수 있는 코드로 동작 시점은 pointcut에 Matching되는 JoinPoint 실행 전후이며 before, after, after returning, after throwing, around 중에서 선택 가능하다.

동작시점설명
BeforeBefore Advice는 Matching된 JoinPoint 전에 동작하는 Advice이다.
AfterAfter Advice는 동작 시점에 따라 after (finally), after returning, after throwing 으로 구분할 수 있다.
  • after returning : Matching된 JoinPoint가 성공적으로 return된 후에 동작하는 Advice이다.

  • after throwing : Exception이 발생하여 Matching된 JoinPoint가 종료된 후에 동작하는 Advice이다.

  • after (finally) : Matching된 JoinPoint 종료 후에 동작하는 Advice이며 잘 사용되지는 않는다.

Around가장 강력한 Advice로 Matching된 JoinPoint 전, 후에 동작하며 JoinPoint 실행 시점을 결정할 수 있다. 또한 다른 Advice와는 달리 입력값, target object, return 값 등에 대한 변경이 가능하다.

동작 시점별 Advice 정의 방법에 대해서는 매뉴얼 Spring >> AOP 하위의 Annotation based AOP , XML based AOP , AspectJ based AOP 를 참고하도록 한다.

7.1.4.Weaving 또는 CrossCutting

AOP가 Core Concerns 모듈의 코드를 직접 건드리지 않고 필요한 기능이 작동하도록 하는 데는 Weaving 또는 CrossCutting이라고 불리는 특수한 작업이 필요하다. Core Concerns 모듈이 자신이 필요한 Crosscutting Concerns 모듈을 찾아 사용하는 대신에 AOP에서는 Weaving 작업을 통해 Core Concerns 모듈의 사이 사이에 필요한 Crosscutting Concerns 코드가 동작하도록 엮어지게 만든다. 이를 통해 AOP는 기존의 OOP로 작성된 코드들을 수정하지 않고도 필요한 Crosscutting Concerns 기능을 효과적으로 적용해 낼 수 있다.

Weaving은 기존의 자바 언어와 컴파일러에서는 쉽게 구현할 수 있는 방법이 아니었으며 본격적인 AOP 기술이 등장한 것은 1990년대 후반 제록스 PARC 연구소에서 그레거 킥제일(Gregor Kiczales)에 의해 AspectJ가 개발되면서라고 볼 수 있다.

Weaving을 처리하는 방법은 다음과 같이 3가지가 존재한다.

Weaving 방식설명
Compiletime Weaving 별도 컴파일러를 통해 Core Concerns 모듈의 사이 사이에 Aspect 형태로 만들어진 Crosscutting Concerns 코드들이 삽입되어 Aspect가 적용된 최종 바이너리가 만들어지는 방식이다. (ex. AspectJ, ...)
Loadingtime Weaving 별도의 Agent를 이용하여 JVM이 클래스를 로딩할 때 해당 클래스의 바이너리 정보를 변경한다. 즉, Agent가 Crosscutting Concerns 코드가 삽입된 바이너리 코드를 제공함으로써 AOP를 지원하게 된다. (ex. AspectWerkz, ...)
Runtime Weaving 소스 코드나 바이너리 파일의 변경없이 Proxy를 이용하여 AOP를 지원하는 방식이다. Proxy를 통해 Core Concerns를 구현한 객체에 접근하게 되는데, Proxy는 Core Concerns 실행 전후에 Cross Concerns를 실행한다. 따라서 Proxy 기반의 Runtime Weaving의 경우 메소드 호출시에만 AOP를 적용할 수 있다는 제한점이 있다. (ex. Spring AOP, ...)

7.1.5.Aspect

Aspect는 어디에서(Pointcut) 무엇을 할 것인지(Advice)를 합쳐놓은 것을 말한다. AspectJ와 같은 자바 언어를 확장한 AOP에서는 마치 자바의 클래스처럼 Aspect를 코드로 작성할 수 있다. 다음은 모든 클래스의 main 메소드 실행(pointcut main()) 후에 "Hello from AspectJ"라는 문자열을 남기는 (after returning advice) Aspect HelloFromAspectJ의 일부이다.

public aspect HelloFromAspectJ{
	// define pointcut
	pointcut main(): execution(public static void main(String[]));
	// define advice
	after() returning : main() {
		System.out.println("Hello from AspectJ!");
	}
}

Aspect 정의에 대한 자세한 설명은 매뉴얼 Spring >> AOP 하위의 Annotation based AOP , XML based AOP , AspectJ based AOP 를 참고하도록 한다.

7.2.Annotation based AOP

다음에서는 AOP 대표적인 툴 중 @AspectJ(Annotation)을 이용하여 Aspect를 정의하고 테스트하는 방법에 대해서 다루고자 한다. @AspectJ(Annotation)은 AspectJ 5 버전에 추가된 Annotation이며, Spring 2.0 에서부터 이러한 Annotation에 대한 처리가 가능하므로, Spring 기반일 경우 별도의 Compiler나 Weaver 없이 @AspectJ(Annotation) 기반의 AOP 적용이 가능하다. 또한 Annotation을 이용하여 Aspect를 정의할 경우 별도 XML 파일에 대한 정의가 불필요하므로, Aspect 적용이 보다 간결해짐을 알 수 있을 것이다. (단, Annotation은 JAVA 5 이상에서만 정의 가능함에 유의하도록 한다.)

7.2.1.Configuration

@AspectJ(Annotation)이 적용된 클래스들을 로딩하여 해당 클래스에 정의된 Pointcut, Advice를 실행하기 위해서는 Spring 속성 정의 XML 파일에 다음과 같이 추가해주어야 한다.

<aop:aspectj-autoproxy/>

7.2.2.@Aspect 정의

@Aspect를 이용하여 특정 클래스가 Aspect임을 나타낸다. 다음 LoggingAspect 에서는 @Aspect를 이용하여 해당 클래스가 Aspect임을 나타내고 있다.

@Aspect
public class LoggingAspect {
	//...
}

7.2.3.@Pointcut 정의

@Pointcut을 이용하여 해당 Aspect를 적용할 부분을 정의한다. (Pointcut 정의시에는 Pointcut Designator와 Pattern Matching 활용 방법 을 참고한다.) 다음은 PrintStringUsingAnnotation 의 Pointcut 정의 부분이다. @Pointcut을 "execution(* *..GenericService+.*(..))"와 같이 정의하고, 해당 Pointcut에 대해 식별자로써 serviceMethod라는 메소드명을 부여하였다. 이것은 클래스명이 GenericService로 끝나는 모든 메소드의 실행 부분이 Aspect을 적용할 Pointcut임을 의미한다. 해당 Pointcut은 serviceMethod()라는 이름으로 이용 가능하다.

@Pointcut("execution(* *..GenericService+.*(..))")
	public void serviceMethod(){}

7.2.4.@Advice 정의

다음에서는 Annotation을 이용하여 동작 시점별 Advice를 정의하는 방법에 대해 살펴보기로 한다.

7.2.4.1.Before Advice

@Before를 이용하여 Before Advice를 정의한다. 다음은 Before Advice 정의 부분이다. Before Advice인 beforeLogging()는 앞서 정의한 serviceMethod()라는 Pointcut 전에 "Logging Aspect : executed "라는 문자열과 해당 Pointcut을 가진 메소드명 클래스명을 출력하는 역할을 수행한다.

@Before("serviceMethod()")
public void beforeLogging(JoinPoint thisJoinPoint) {
	Class clazz = thisJoinPoint.getTarget().getClass();
	String className = (thisJoinPoint.getTarget().getClass().getName())
			.toLowerCase();
	String methodName = thisJoinPoint.getSignature().getName();

	StringBuffer buf = new StringBuffer();
	buf.append("\n** Logging Aspect : executed " + methodName + "() in "
			+ className + " Class.");
	Object[] arguments = thisJoinPoint.getArgs();
	if (arguments.length > 0) {
		for (int i = 0; i < arguments.length; i++) {
			buf
					.append("\n*************"
							+ arguments[i].getClass().getName()
							+ "*************\n");
			buf.append(arguments[i].toString());
			buf.append("\n*******************************************\n");
		}
	} else
		buf.append("\nNo arguments\n");

	Log logger = LogFactory.getLog(clazz);
	if (logger.isDebugEnabled())
		logger.debug(buf.toString());
}

beforeLogging()는 1개의 입력 인자(JoinPoint)를 가지고 있는데 Target 클래스명, 메소드명 등과 같은 Target 정보를 포함하고 있다. Target 정보가 불필요한 Advice인 경우에는 JoinPoint라는 입력 인자를 선언하지 않아도 된다.

7.2.4.2.AfterReturning Advice

@AfterReturning을 이용하여 AfterReturning Advice를 정의한다. 다음은 AfterReturning Advice 정의 부분으로 해당 Pointcut 실행 결과를 retVal이라는 변수에 담도록 정의하고 있다. AfterReturning Advice인 afterReturningExecuteGetMethod()는 앞서 정의한 Pointcut 후에 , "AfterReturning Advice of PrintStringUsingAnnotation"라는 문자열과 해당 Pointcut을 가진 클래스명, 메소드명을 출력하는 역할을 수행한다.

@AfterReturning(pointcut = "serviceMethod()", returning = "retVal")
public void afterReturningExecuteGetMethod(JoinPoint thisJoinPoint,
		Object retVal) {
	Class targetClass = thisJoinPoint.getTarget().getClass();
	Signature signature = thisJoinPoint.getSignature();
	String opName = signature.getName();

	System.out.println("AfterReturning Advice of PrintStringUsingAnnotation");
	System.out.println("***" + targetClass + "." + opName + "()" + "***");
}

afterReturningExecuteGetMethod()는 2개의 입력 인자(JoinPoint, Object)를 가지고 있는데 첫번째 인자는 Target 클래스명, 메소드명 등과 같은 Target 정보를 포함하고 있으며, 두번째 인자는 해당 Pointcut의 실행 결과이다. AfterReturning Advice에서 특정 Pointcut 실행 결과를 참조해야 한다면, Advice 정의시 returning의 값을 정의하고 해당하는 메소드의 입력 인자명을 동일하게 정의해주도록 한다. 각 입력 인자는 AfterReturning Advice 정의시 필요에 따라 선택 정의할 수 있다.

7.2.4.3.AfterThrowing Advice

@AfterThrowing을 이용하여 AfterThrowing Advice를 정의한다. 다음은 transfer의 AfterThrowing Advice 정의 부분으로 해당 Pointcut 실행시 발생한 Exception 객체를 exception이라는 변수에 담도록 정의하고 있다. AfterThrowing Advice인 serviceMethod()는 앞서 정의한 Pointcut에서 Exception이 발생한 후에 Exception을 핸들링하고 Exception의 종류에 따라 Exception meesage를 출력하게된다.

@AfterThrowing(pointcut = "serviceMethod()", throwing = "exception")
public void transfer(JoinPoint thisJoinPoint, Exception exception)
		throws SalesException {
	Object target = thisJoinPoint.getTarget();
	while (target instanceof Advised) {
		try {
			target = ((Advised) target).getTargetSource().getTarget();
		} catch (Exception e) {
			LogFactory.getLog(this.getClass()).error(
					"Fail to get target object from JointPoint.", e);
			break;
		}
	}

	String className = target.getClass().getSimpleName().toLowerCase();
	String opName = (thisJoinPoint.getSignature().getName()).toLowerCase();
	Log logger = LogFactory.getLog(target.getClass());

	if (exception instanceof SalesException) {
		SalesException empEx = (SalesException) exception;
		logger.error(empEx.getMessage(), empEx);
		throw empEx;
	}
	
	if (exception instanceof BaseException) {
		BaseException baseEx = (BaseException) exception;
		logger.error(baseEx.getMessage(), baseEx);
		throw new SalesException(messageSource, "error." + className + "."
				+ opName, new String[] {}, exception);
	}		

	logger.error(messageSource.getMessage("error." + className + "."
			+ opName, new String[] {}, "no messages", Locale.getDefault()),
			exception);

	throw new SalesException(messageSource, "error." + className + "."
			+ opName, new String[] {}, exception);
}

transfer()는 2개의 입력 인자(JoinPoint, Exception)를 가지고 있는데 첫번째 인자는 Target 클래스명, 메소드명 등과 같은 Target 정보를 포함하고 있으며, 두번째 인자는 Pointcut 실행시 발생한 Exception 객체이다. AfterThrowing Advice에서 특정 Pointcut 실행시 발생한 Exception을 참조해야 한다면, Advice 정의시 throwing의 값을 정의하고 해당하는 메소드의 입력 인자명을 동일하게 정의해주도록 한다. 각 입력 인자는 AfterThrowing Advice 정의시 필요에 따라 선택 정의할 수 있다.

7.2.4.4.After(finally) Advice

@After를 이용하여 After(finally) Advice를 정의한다. 다음은 PrintStringUsingAnnotation 의 After(finally) Advice 정의 부분이다. After(finally) Advice인 afterExecuteGetMethod()는 앞서 정의한 getMethods()라는 Pointcut 후에 "After(finally) Advice of PrintStringUsingAnnotation"라는 문자열과 해당 Pointcut을 가진 클래스명, 메소드명을 출력하는 역할을 수행한다.

@After("getMethods()")
public void afterExecuteGetMethod(JoinPoint thisJoinPoint) {
	Class targetClass = thisJoinPoint.getTarget().getClass();
	Signature signature = thisJoinPoint.getSignature();
	String opName = signature.getName();

	System.out
			.println("After(finally) Advice of PrintStringUsingAnnotation");
	System.out.println("***" + targetClass + "." + opName + "()" + "***");
}      

afterExecuteGetMethod()는 1개의 입력 인자(JoinPoint)를 가지고 있는데 Target 클래스명, 메소드명 등과 같은 Target 정보를 포함하고 있다. Target 정보가 불필요한 Advice인 경우에는 JoinPoint라는 입력 인자를 선언하지 않아도 된다.

7.2.4.5.Around Advice

@Around를 이용하여 Around Advice를 정의한다. 다음은 PrintStringAroundUsingAnnotation 의 Around Advice 정의 부분이다. Around Advice인 aroundExecuteUpdateMethod()는 updateMethods()라는 Pointcut 후에 "Around Advice of PrintStringUsingAnnotation"라는 문자열과 해당 Pointcut을 가진 클래스명, 메소드/