Version 4.1.0
저작권 © 2007-2009 삼성SDS
Anyframe은 Spring 기반에서 다양한 best-of-breed 오픈 소스를 통합 및 확장하여 구성한 어플리케이션 프레임워크와 MVC 아키텍처를 준수하여 웹 어플리케이션의 프리젠테이션 레이어를 구조적으로 개발할 수 있도록 지원하는 웹 프레임워크를 제공한다. 또한 프레임워크를 기반으로 업무용 프로그램 개발을 효과적으로 진행할 수 있도록 기술 공통 서비스, 템플릿 기반의 프로젝트 구조 및 샘플 코드, 매뉴얼 등을 제공함으로써 설계 및 개발 기간을 단축하고 유지보수를 용이하게 진행할 수 있도록 지원한다.
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 툴 등 다양한 웹 클라이언트 기술과 쉽게 연동되는 구조를 제공한다.
Anyframe을 통해 활용할 수 있는 주요 기능은 다음과 같다.
Anyframe의 Lightweight 컨테이너는 순수 POJO(Plain Old Java Objects) 기반 개발을 지원하며, 순수 POJO 기반으로 설계/개발된 모듈들을 엮어서 해당 어플리케이션이 제대로 된 기능을 제공할 수 있도록 지원한다. 반면에 EJB와 같은 컨테이너 기반에서 어플리케이션을 개발하기 위해서는 개발자가 해당 컨테이너에 종속된 인터페이스를 구현해야 하거나 정의된 컴포넌트 모델을 그대로 준수해야 한다. 즉, 전형적인 컨테이너는 정의된 개발 모델을 강제하기 때문에 어플리케이션 코드 내에 컨테이너 의존적인 코드가 추가될 수 밖에 없게 된다.
Anyframe의 Lightweight 컨테이너는 다음과 같은 특징을 가지고 있다.
설계 결과물에 컨테이너 의존적인 코드를 추가하지 않아도 순수 POJO 기반으로 어플리케이션 개발이 가능하도록 지원한다. 즉, Lightweight 컨테이너 기반 개발시 프레임워크로 인한 기본 설계와 상세 설계가 이중으로 진행되거나, 개발시 설계 모델과 구현체가 불일치되는 것을 방지할 수 있다.
어플리케이션 구성 모듈간 의존 관계를 처리하기 위한 방법을 제공한다. 특정 모듈의 코드 내에서 참조할 모듈을 직접적으로 생성하여 참조함으로써 참조 모듈간에 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 : 클라이언트에서는 전달받은 인스턴스에 대해 특정 메소드 호출을 통해 원하는 기능 수행
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 부분을 참조하도록 한다.
Lightweight 컨테이너는 정의된 모듈의 Life-cycle 관리 즉, 해당 모듈들을 초기화시키고 종료시키는 역할을 수행함으로써 개발자가 비즈니스 로직에 집중하여 개발할 수 있게 된다.
Anyframe은 자체 개발 또는 오픈 소스 활용 및 확장을 통해 어플리케이션 개발시 용이하게 재사용할 수 있는 Cache, DB 연결, 쿼리문 처리, 트랜잭션 관리, 로깅 등과 같은 다양한 기술 공통 서비스들을 제공한다. 이러한 기술 공통 서비스들은 앞서 언급한 Lightweight 컨테이너에서 동작 가능하도록 설계/개발되었으며, 인터페이스와 구현 클래스로 분리되어 구현되어 있으므로, 인터페이스 규약에 맞게 구현 클래스를 추가하거나 제공된 구현 클래스를 확장함으로써 언제든지 해당 어플리케이션의 용도에 맞게 변경이 용이하다. Anyframe에서 제공하는 주요 기술 공통 서비스는 다음과 같으며 이에 대한 보다 자세한 사항은 매뉴얼을 참조하도록 한다.
Java 5부터 지원하는 Generics 개념을 기반으로 개발되었으며, 도메인 클래스 기반의 Service 인터페이스/구현 클래스, DAO 인터페이스/구현 클래스(Hibernate/JPA, Query Service 지원) 등 기본 CRUD 메소드 기능이 모두 구현된 클래스를 직접 이용하거나 상속받아서 사용할 수 있는 기능을 제공하고 있다.
공통/서비스/웹 등과 같이 타입별로 프로젝트를 구성하는 경우, 몇가지 설정을 추가함으로써 Dynamic Reloading 기능을 제공하는 서비스이다. 리로딩이 되는 단위는 각각의 프로젝트 단위이며 런타임 환경이 아닌 개발 환경에서 사용하도록 한다.
쿼리문이나 객체의 입력만으로 데이터베이스 내에 저장된 데이터 조작을 가능하게 하는 서비스이다. Query 서비스는 JDBC(Java Database Connectivity)를 이용한 데이터 액세스 수행 부분을 추상화함으로써 간편한 데이터 액세스 방법을 제공하고, JDBC 사용시 발생할 수 있는 공통 에러를 줄여준다.
외부 파일이나 환경 정보에 구성되어 있는 key, value의 쌍을 내부적으로 가지고 있으며, 어플리케이션이 특정 key에 대한 value에 접근할 수 있도록 해주는 서비스이다.
Transaction 관리에 대하여 일관성 있는 추상화된 방법을 제공하는 서비스로, Spring에서 제공하는 TransactionManager를 그대로 사용한다. Anyframe 매뉴얼에서는 Spring의 TransactionManager 활용 방법에 대해 가이드하고 있다.
객체 모델링(Object Oriented Modeling)과 관계형 데이터 모델링(Relational Data Modeling) 사이의 불일치를 해결해 주는 ORM 도구인 Hibernate를 효율적으로 사용할 수 있도록 세부 항목별로 활용 방법을 가이드하고 있다. 또한 Hibernate 기반에서도 입력 조건에 따라 HQL(Hibernate Query Language), Native SQL을 Dynamic하게 변경할 수 있도록 Velocity와 연계한 DynamicHibernateService를 추가로 제공한다.
Logging 서비스는 Log4j를 그대로 이용하며 이를 통해 테스팅 코드와 운영 코드를 동일하게 가져가면서, 로깅을 선언적으로 관리할 수 있고, 운영시 성능 오버헤드를 최소화할 수 있게 한다. Anyframe 매뉴얼에서는 Log4j 활용 방법에 대해 가이드하고 있다. 또한 Log4jdbc3를 이용하여 실행된 SQL을 Logging할 수 있는 방법도 제시한다.
클라이언트 어플리케이션과 원격 어플리케이션에서 제공하는 서비스 간의 의사소통 기능을 제공하는 서비스이다. Anyframe 매뉴얼에서는 Spring Remoting 기능을 그대로 활용한 원격 기술 유형(RMI, Hessian/Burlap, HTTP Invoker)별 활용 방법에 대해 가이드하고 있다.
Web Services는 인터넷 네트워크를 통하여 다수의 기존 어플리케이션 시스템을 표준화된 기술로서 상호 작용시키고, 이러한 표준 기술을 이용하여 모든 비즈니스를 가능하게 한다. Web Services는 언제, 어디에서건 원하는 정보나 서비스를 제공해 주는 역할을 수행하며 기존의 다른 소프트웨어처럼 완벽한 정의를 지정하여 구성하는 것이 아니라, 서로 주고받는 데이터 표준에 대한 정의를 규정함으로써 매우 유연하며 서로의 이질적인 운영시스템, 이질적인 프로그램 언어 간의 커뮤니케이션 차이를 극복해 주는 연결고리 역할을 해 준다. Anyframe 매뉴얼에서는 이러한 Web Services 기능에 대한 활용 방법에 대해 가이드한다.
Struts와 Spring MVC를 확장하여 웹 화면 개발시 공통적으로 요구되는 다양한 추가 기능을 제공한다. 자세한 사항은 Anyframe 매뉴얼을 참고하도록 한다.
화면 네비게이션 흐름을 JSP나 클래스 내에서 처리하지 않고 설정 파일(XML)을 통해서 제어할 수 있게 한다. 공통 에러 페이지 등 프로젝트에서 필요한 공통 페이지 설정도 설정 파일을 통해 가능하다.
프리젠테이션 레이어를 개발하는 개발자들은 Struts 사용 시 Action 클래스를, Spring MVC 사용 시 Controller 클래스를 개발해야 한다. 이를 위해 공통 Action 클래스와 Controller 클래스를 제공함으로써 이를 상속받은 모든 하위 클래스에서는 로깅, Exception 처리, Double Form Submission 방지 등의 기능을 사용할 수 있으며 특정 액션 수행 이전과 이후에 공통적으로 수행해야 할 로직을 구현하기 쉬워진다. 아래 그림은 Struts 사용 시 활용할 수 있는 공통 클래스의 모습이다.

국제화를 지원한다는 것은 언어나 지역에 영향을 받는 부분과 영향을 받지 않는 코드를 분리하여 쉽게 지역화될 수 있게 만들었다는 것을 의미하는 것으로 소스 코드의 수정없이 다양한 언어를 지원할 수 있도록 한다.
Jakarta Commons 프로젝트의 하나로 Validation 모듈이 제공되는데 이를 Struts/Spring MVC와 연계하여 제공하고 있다. 유효성 검증 로직을 소스 코드 내에 작성하거나 소스 외부에서 설정 파일(XML)을 통해 관리할 수 있다. 이 외에 일반적으로 많이 사용되는 자바 스크립트를 통해 입력값 검증을 수행할 수도 있다.
JSP 개발 시 Struts, Spring MVC에서 제공하는 기본 태그 라이브러리와 JSTL의 표준 태그 라이브러리 및 Anyframe에서 확장한 태그 라이브러리를 사용하여 JSP 내에 자바 코드를 추가하지 않고도 개발할 수 있도록 지원함으로써 HTML 디자인 개발과 프리젠테이션 레이어 개발을 분리하여 진행할 수 있게 한다. 목록 조회 페이지에서 페이지 네비게이션 정보를 표현하는 부분에 사용되는 태그 라이브러리가 그 대표적인 예이다.
특정 메소드 내에서 try~catch 구문을 이용하여 Exception을 처리하지 않고 설정 파일(XML)을 이용하여 선언적으로 처리할 수 있다. 에러 처리 페이지에서는 Exception 발생 시 설정된 메시지 키에 의한 메시지 내용을 화면에 디스플레이되도록 한다.
Struts를 사용하는 경우, RequestProcessor 클래스의 processRoles() 메소드를 확장하여 구현함으로써 사용자가 해당 요청(Request)에 대한 권한을 가지고 있는지 판별할 수 있도록 한다. Spring MVC를 사용하는 경우, Spring Security 등 다른 오픈 소스와 연계하여 인증 및 권한 부분을 처리할 수 있다.
설정 파일(XML)에 선언적인 방식으로 Double Form Submission 방지 기능을 정의하여 사용할 수 있다. 이를 통해 Form submit 중복(브라우저 Refresh, Submit 2회 이상 수행)으로 인한 오동작을 방지할 수 있는 기능을 제공한다.

Anyframe 4.0이후부터는 Anyframe Maven Command와 Anyframe Plugin을 이용하여 로컬에 별도 라이브러리나 샘플 프로젝트를 설치하지 않고도 Maven을 이용하여 프로젝트에 적합한 Anyframe 기반의 샘플 프로젝트를 구성할 수 있도록 지원한다. Plugin 기반의 프로젝트 기반 구성 방법 및 Anyframe Maven Command 방법, Plugin 정의 방법에 대해 자세히 살펴보도록 하자.
Anyframe 4.0.0 이후부터 오픈 소스 기반으로 어플리케이션을 개발할 때 요구되는 다양한 오픈 소스들이 통합된 템플릿 기반의 프로젝트 구조 및 샘플 코드를 Maven을 이용하여 자동으로 구성할 수 있도록 지원한다. 따라서, 어플리케이션 개발 초기에 프로젝트 특성에 맞는 개발 환경을 구성하는데 소요되는 시간을 대폭 줄이고, 프로젝트에 필요한 최적의 샘플을 제공받을 수 있게 될 것이다. 다음에서는 Anyfrae 4.0.0 이후부터 새롭게 변경된 Anyframe 설치 방법에 대해 알아보도록 하자.
Maven(http://maven.apache.org/)은 POM(Project Object Model) 정보를 기반으로 대상 프로젝에 대한 빌드, 리포팅, 문서화 등을 지원하는 오픈소스 툴이다. Maven을 설치한 후, Anyframe 설치를 위한 환경 설정 방법에 대해 알아보도록 하자.
Anyframe 설치 대상 PC에 Maven이 설치되어 있지 않은 경우, Maven을 설치한다. (본 문서에서는 Maven Ver.2.2.1을 기반으로 설치 작업을 진행할 것이다.) Maven 사이트로부터 Maven(apache-maven-2.2.1-bin.xxx)을 다운로드받은 후, 원하는 위치에 압축을 해제한다.
설치된 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>작업 대상 PC에서 MAVEN을 인식할 수 있도록 하기 위해서 시스템 변수로 MAVEN_HOME을 추가하고, 압축을 해제한 폴더 위치를 값으로 지정해준다.

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

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

다운로드
다음은 앞서 언급한 Maven settings.xml 파일이다. settings.xml 파일을 다운로드받은 후, %MAVEN_HOME\conf 내에 복사한다.
Anyframe 4.x에서는 다양한 오픈 소스들이 통합된 템플릿 기반의 프로젝트 구조와 샘플 코드, 참조 라이브러리 집합을 Plugin이라 칭하며, 다양한 유형의 Plugin을 제공한다. (Plugin에 대한 자세한 내용은 Anyframe Plugins를 참조하도록 한다.) 먼저 다른 Plugin 설치를 위한 기반을 제공하는 기본 Plugin인 Foundation Plugin을 설치해 보도록 하자.
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 창에 제시됨을 알 수 있다.
제시된 Maven Archetype 목록 중 anyframe.plugin.foundation에 해당하는 번호('1')를 선택하고 요구하는 입력값을 추가 정의해준다.

위 그림에서와 같이 설치 대상 PC에 anyframe.plugin.foundation-x.x.x.jar 라이브러리를 다운로드받지 않은 경우 Anyframe Repository로부터 이를 다운로드하고, 다음과 같은 인자에 대한 값을 입력하도록 요구할 것이다.
표 3.2. Input Parameters for Installing Maven Archetype
| Parameter | Description | Default Value |
|---|---|---|
| groupId | 설치 대상 프로젝트의 groupId이다. | N/A |
| artifactId | 설치 대상 프로젝트의 artifactId로써 해당 프로젝트를 설치할 대상 폴더명과 프로젝트명이 된다. | myproject |
| version | 설치 대상 프로젝트의 version이다. | 1.0.0 |
| package | 설치 대상 프로젝트 내 소스 코드에 대한 대표 패키지명이 된다. | groupId로 정의한 값 |
다음은 Foundation Plugin 설치를 통해 구성된 샘플 프로젝트의 모습이다. 설치된 샘플 프로젝트명은 myproject이며, 하위에 다양한 용도의 폴더를 포함하고 있다.

Plugin 설치 완료 후에는 [선택] Plugin 설치 확인 방법을 참조하여 정상 동작 여부를 확인하도록 한다.
Anyframe에서는 Maven 기반에서 Anyframe 관련 라이브러리 다운로드시에 참조 관계에 놓인 모든 3rd-party 라이브러리들이 한꺼번에 다운로드되는 현상을 막기 위해 3rd-party 라이브러리들 간의 참조 관계를 끊은 상태로 Anyframe Repository에 배포하고 있다. 때문에 기존 Maven 사용자들은 Local Repository에 이미 존재하는 3rd-party 라이브러리가 가진 참조 관계에 문제가 생겨 Anyframe 설치시에 오류가 발생할 수 있다. 따라서 설치에 문제가 있는 경우에는 Local Repository를 삭제한 후 재설치해 볼 것을 권장한다.
Foundation Plugin 설치 완료 이후에는 Anyframe에서 제공하는 anyframe-maven-plugin을 이용하여 Anyframe에서 제공하는 다양한 Plugin들을 추가로 설치할 수 있게 된다. 이번에는 Foundation Plugin 이외 Plugin들을 설치하는 방법에 대해서 살펴보기로 하자. (본 절에서 언급되는 anyframe-maven-plugin에서 지원하는 모든 Maven 명령어 종류는 Anyframe Maven Commands를 참조하도록 한다.)
Command 창을 띄운 후, Foundation Plugin 설치로 생성된 샘플 프로젝트가 있는 위치로 이동하여 다음과 같이 Maven 명령어를 입력함으로써 현재 설치 가능한 Plugin 목록 및 설치된 Plugin 목록을 확인할 수 있다.
mvn anyframe:list
다음은 Foundation Plugin 설치 후, 위와 같이 명령어를 실행하였을 때 Command 창에 나타난 결과를 보여주는 그림으로 전체 15개의 Plugin에 대해 설치 가능하며 Foundation Plugin만 설치된 상태임을 알 수 있다.

Command 창의 샘플 프로젝트가 있는 위치에서 다음과 같이 Maven 명령어를 입력함으로써 원하는 Plugin을 선택하여 설치할 수 있다. pluginName이라는 속성에 대해서 설치 대상 Plugin 이름을 정의해주면 된다.
mvn anyframe:install -DpluginName=hibernate
Plugin 설치 완료 후에는 [선택] Plugin 설치 확인 방법을 참조하여 정상 동작 여부를 확인하도록 한다.
특정 Plugin 설치 결과 구성된 샘플 어플리케이션이 정상 동작하는지 확인하기 위해서는 Command 창에 명령어를 직접 입력하거나 Eclipse를 이용할 수 있다.
샘플 어플리케이션에 대한 정상 동작 여부를 체크하기 위해서는 먼저 사용할 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 작업 수행 후 결과를 확인하고자 할 때 유용하게 활용할 수 있을 것이다.
다음에서는 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를 시작시키도록 한다.
WAS가 정상적으로 시작되었으면 브라우저를 열고, 주소창에 http://localhost:8080/myproject (http://localhost:8080/${샘플 프로젝트 이름})와 같이 입력하여 샘플 어플리케이션이 정상적으로 실행되는지 확인해본다. 다음은 Foundation Plugin만 설치된 경우 샘플 어플리케이션의 초기 화면과 Foundation Sample 링크를 클릭하였을 때 이동한 화면에 대한 모습이다.

Plugin이 추가 설치될 때마다 첫번째 화면에 추가된 Plugin에 대한 샘플을 확인하기 위한 링크가 추가될 것이다.
Anyframe에서 제공하는 Plugin을 설치함으로써 생성된 샘플 프로젝트는 기본적으로 Maven을 기반으로 하고 있으므로 Maven에서 지원하는 기능들을 동일하게 실행할 수 있다. 예를 들어 'mvn test'와 같은 명령어를 실행함으로써 샘플 프로젝트 내의 테스트 코드를 실행해 볼 수 있으며 'mvn package'와 같은 명령어를 실행함으로써 샘플 프로젝트를 war 형태로 패키징 가능하다.
앞서 언급한 바와 같이 Plugin 설치를 통해 생성된 샘플 프로젝트는 기본적으로 HSQL DB를 기반으로 동작하도록 구성되어 있다. 그런데 만약 샘플 프로젝트의 실행 DB를 변경하고자 원한다면 다음과 같은 작업을 수행해야 한다.
설치된 샘플 프로젝트 하위의 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
| Property | Description |
|---|---|
| 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 정보로써 추가해야 한다. |
[샘플 프로젝트 설치 폴더]/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>Eclipse WTP만을 이용하여 Maven Project가 아닌 일반 Dynamic Web Project 형태로 샘플 어플리케이션을 실행시키기 위해서는 해당 어플리케이션에서 참조 라이브러리를 인식할 수 있도록 필요한 라이브러리를 [샘플 프로젝트 설치 폴더]/src/main/webapp/WEB-INF/lib 폴더에 추가해주는 작업이 필요하다.
Command 창을 띄운 후, 샘플 프로젝트가 있는 위치로 이동하여 다음과 같이 Maven 명령어를 입력함으로써 해당 어플리케이션이 참조하는 라이브러리들을 [샘플 프로젝트 설치 폴더]//src/main/webapp/WEB-INF/lib 폴더에 복사한다.
mvn anyframe:inplace
설치된 샘플 프로젝트를 Eclipse 내로 import한 이후 해당 프로젝트에 대해 컴파일 에러가 존재하지 않는지 체크한다. 그리고 해당 프로젝트에 대해 마우스 오른쪽 버튼을 클릭하여 컨텍스트 메뉴에서 Run As > Run on Server를 선택함으로써 Tomcat Server를 시작시키도록 한다.
대상이 되는 샘플 어플리케이션에 대해 특정 Plugin이 설치/삭제될 때마다 참조 라이브러리에 대해 변경 사항이 발생하게 된다. 따라서 Eclipse WTP만 이용하여 Tomcat을 실행하는 경우 변경된 사항을 해당 어플리케이션에 반영하기 위해서는 매번 Maven 명령어(mvn anyframe:inplace)를 실행해주어야 한다는 점에 유의하도록 한다.
Anyframe 3.x.x에서는 참조용 샘플 프로젝트와 라이브러리를 중심으로 개발자가 직접 프로젝트에 적합한 샘플 프로젝트를 구성해야 했다.

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

다음에서는 Anyframe에서 제공하는 Plugin 및 Plugin 구조에 대해서 살펴보고 Custom Plugin을 정의하는 방법에 대해 알아보도록 하자.
다음은 Anyframe에서 제공하는 Plugin 목록이다.
표 4.1. The List of Plugin
| No | Plugin Name | Description |
|---|---|---|
| 1 | foundation | 모든 Plugin 설치를 위한 기반을 제공하는 Plugin이다. 샘플 프로젝트의 구조를 정의하고 있으며, SpringMVC + Spring + Query Service를 이용한 상품 관리 기능을 제공한다. |
| 2 | cache | OSCache와 Anyframe에서 구현한 Cache Service를 이용한 상품 관리 기능을 제공한다. |
| 3 | cxf | Apache CXF를 이용한 상품 관리 기능을 포함하고 있으며 생성된 WSDL을 확인할 수 있도록 한다. |
| 4 | dynamicmodule | 특정 어플리케이션에 대한 Dynamic Reloading을 위해 필요한 참조 라이브러리만을 제공한다. |
| 5 | hibernate | Hibernate와 Anyframe에서 구현한 Dynamic Hibernate Service를 이용한 상품 관리 기능을 제공한다. |
| 6 | jasper | JasperReports와 Spring을 연계하여 상품 현황을 HTML, PDF 형태의 Report로 보여준다. |
| 7 | miplatform | TOBE 소프트에서 제공하는 X-Internet 솔루션인 MiPlatform을 Anyframe과 연계하여 상품 관리 기능을 제공한다. |
| 8 | mipsample | TOBE 소프트에서 제공하는 X-Internet 솔루션인 MiPlatform을 Anyframe과 연계하여 활용할 수 있는 다양한 UI Sample을 제공한다. mipsample plugin 기능 확인을 위해 DB에 추가되어야 할 샘플 데이터가 함께 제공된다. (mipsample plugin이 정상 설치된 경우 별도로 DB 데이터를 추가할 필요는 없으며 DB 연계에 문제가 있는 경우에 한해 제공되는 DB 스크립트를 이용하여 샘플 데이터를 추가하도록 한다.) |
| 9 | remoting | Spring Remoting 기법 중 HttpInvoker를 활용하여 상품 관리 기능을 제공한다. |
| 10 | scheduling | Quartz를 이용하여 주기적으로 월별 상품 등록 현황을 생성하는 기능을 제공한다. |
| 11 | security | Spring Security를 이용하여 사용자별 Authentication, 역할별 Authorization 기능을 제공한다. security plugin 기능 확인을 위해 DB에 추가되어야 할 샘플 데이터가 함께 제공된다. (security Plugin이 정상 설치된 경우 별도로 DB 데이터를 추가할 필요는 없으며 DB 연계에 문제가 있는 경우에 한해 제공되는 DB 스크립트를 이용하여 샘플 데이터를 추가하도록 한다.) |
| 12 | spring-optional | Plugin을 통해 설치되지 않는 Spring Optional 라이브러리만을 제공한다. |
| 13 | struts | Struts를 이용하여 상품 관리 기능을 제공한다. |
| 14 | test | 테스트 코드 실행에 필요한 참조 라이브러리만을 제공한다. |
| 15 | webflow | Spring Webflow를 이용하여 상품 관리 기능을 제공한다. |
모든 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
| Folder | Description |
|---|---|
| .metadata | Plugin 설치 정보를 관리하는 anyframe-plugins-metadata.mf 파일을 가진다. |
| .settings | Eclipse 프로젝트 정보를 관리한다. |
| 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
| Folder | Description |
|---|---|
| maven | 리소스 템플릿을 이용하여 샘플 프로젝트를 생성하기 위해 필요한 Meta 정보를 관리하는 archetype-metadata.xml 파일을 가진다. |
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
| Folder | Description |
|---|---|
| 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
| Folder | Description |
|---|---|
| src/test/java | 테스트 코드를 관리한다. 단, 테스트 코드의 패키지는 Plugin 이름으로 시작하도록 정의해야 한다. |
| src/test/resources | 테스트 코드 실행에 필요한 리소스들을 관리한다. src/main/resources와 동일한 규칙을 따라 리소스를 정의한다. |
앞서 언급했던 Anyframe Plugin 외에 사용자가 Plugin을 자체 정의하여 프로젝트 내부적으로 재사용할 수도 있다. 여기에서는 Custom Plugin 정의 방법에 대해 살펴보도록 하자.
Custom Plugin을 정의한다.
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>해당 프로젝트 내에 src/main/resources 폴더를 생성하고 하위에 plugin-resources라는 폴더를 생성한다. plugin-resources 폴더 하위에는 Custom Plugin 설치 결과 샘플 프로젝트에 추가되어야 할 리소스(Java, XML, ... 등)들에 대한 템플릿과 plugin-descriptor.xml, pom.xml 파일을 정의해 주어야 한다.
샘플 프로젝트에 추가되어야 할 리소스(Java, XML, ... 등)는 Anyframe Plugin의 폴더 및 파일 명명 규칙에 맞추어 정의하도록 한다. (Anyframe Others Plugins에 대한 구조 참고)
plugin-descriptor.xml 파일 내에는 앞서 정의한 리소스에 대한 Meta 정보를 정의한다. (Anyframe Others Plugins에 대한 구조 참고)
해당 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 ####");
}
}해당 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에 의해 실행될 것이다.
Command 창에서 'mvn install' 명령어를 실행시킴으로써 로컬 Maven Repository에 Custom Plugin을 배포한다.
Custom Plugin 목록을 정의한다.
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>해당 프로젝트 내에 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 클래스명을 기술해 주도록 한다.
Command 창에서 'mvn install' 명령어를 실행시킴으로써 로컬 Maven Repository에 Custom Plugin 목록을 배포한다.
Command 창에서 'mvn anyframe:list' 명령어를 실행시켜 Custom Plugin이 설치 가능한 Plugin 목록에 추가되었는지 확인해 보도록 하자. 정상적으로 추가된 경우에는 Anyframe에서 제공하는 다른 Plugin과 동일하게 설치/삭제가 가능해진다.
Anyframe에서는 자체 구현한 anyframe-maven-plugin이라는 Maven Plugin을 제공함으로써 Anyframe Repository로부터 다양한 Anyframe 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>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
| Goal | Description |
|---|---|
| help | 사용 가능한 Command 목록을 보여준다. |
| install |
Foundation Plugin 설치후, 생성된 샘플 프로젝트에 특정 Plugin을 설치한다.
다음과 같은 Option을 가진다.
pluginName : 설치 대상 Plugin 명을 정의한다. package : 설치 대상 Plugin으로 인해 추가될 샘플 코드에 대표 Package를 부여할 수 있다. |
| uninstall |
이미 설치된 Plugin을 삭제한다. 다음과 같은 Option을 가진다.
pluginName : 삭제 대상 Plugin 명을 정의한다. |
| list | 설치 가능한 Plugin 목록과 Plugin 설치 현황을 보여준다. |
| install-list | Plugin 설치 현황을 보여준다. |
| inplace | pom.xml 파일을 기반으로 하여 Plugin 설치로 생성된 샘플 프로젝트 하위의 src/main/webapp/WEB-INF/lib 폴더 내로 샘플 어플리케이션 실행에 필요한 모든 참조 라이브러리를 다운로드한다. |
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 기법에 대해서도 알아보자.
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을 통하여 가능하게 된다.
Spring Framework는 기본적으로 어플리케이션의 비즈니스 서비스를 구동시키고 관리하는 Spring Container와 이러한 Container에 의해 관리되는 Bean으로 구성된다. Bean은 Container를 통해서 인스턴스화되는 객체이며 Container에 의해 다른 Bean들과 Wiring(엮기)되고 관리된다.
Bean은 Spring Framework에서 어플리케이션의 중요 부분을 형성하고 Spring IoC Container에 의해 관리된다.
Bean 설정, 생성, Life Cycle 관리
Bean Wiring(엮기) - Bean들과 각각에 대한 Dependency 관계는 Spring IoC 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이 생성되기를 기다릴 필요가 없게 된다.
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 Implementation | Purpose |
|---|---|
| org.springframework.core.io.ByteArrayResource | Defines a resource whose content is given by an array of bytes |
| org.springframework.core.io.ClassPathResource | Defines a resource that is to be retrieved from the classpath |
| org.springframework.core.io.DescriptiveResource | Defines a resource that holds a resource description but no actual readable resource |
| org.springframework.core.io.FileSystemResource | Defines a resource that is to be retrieved from the file system |
| org.springframework.core.io.InputStreamResource | Defines a resource that is to be retrieved from an input stream |
| org.springframework.web.portlet.context. PortletContextResource | Defines a resource that is available in a portlet context |
| org.springframework.web.context.support. ServletContextResource | Defines a resource that is available in a servlet context |
| org.springframework.core.io.UrlResource | Defines a resource that is to be retrieved from a given URL |
다음은 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”);
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 자체는 설정 메타데이터의 형태로부터 분리될 수 있기 때문이다.
BeanFactory 사용한 예제
Resource resource = new FileSystemResource("beans.xml");
BeanFactory factory = new XmlBeanFactory(resource); ClassPathResource resource = new ClassPathResource("beans.xml");
BeanFactory factory = new XmlBeanFactory(resource);ApplicationContext 사용한 예제
ApplicationContext context = new ClassPathXmlApplicationContext(new String ("beans.xml"));
// of course, an ApplicationContext is just a BeanFactory
BeanFactory factory = (BeanFactory) context;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 정의 파일이어야만 한다.
Spring IoC Container에 의해 관리되는 객체로 Container에 제공된 설정 메타데이터 내 정의(대개 XML <bean> 형태로)에 의해 생성되며 실제로 아래 표로 나타낸 주요 메타데이터 정보를 포함하는 BeanDefinition 객체로 표현한다.
| 주요 메타데이터 속성 | 설명 |
|---|---|
| id | Bean의 구분을 위한 정보로 해당 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-init | true/false 값을 가지며 해당 bean이 호출되기 전에 초기화 시킬지 여부를 결정함. Default는 false이며 true인 경우, 해당 bean이 호출되는 시점에 초기화됨 |
Bean 정의 시 Bean들을 구분하기 위해 'id' 혹은 'name' 속성을 사용하는데 'id'를 사용하는 경우, 하나의 Bean은 Container내에서 Unique한 id를 가지도록 한다. 일반적으로 Bean을 명명할때 인스턴스 필드명에 대한 표준 Java 규칙을 사용한다. Bean 이름은 소문자로 시작하고 camel-cased(첫 번째 단어는 소문자로 시작하고 두 번째 단어는 대문자로 시작)된다. 이러한 이름의 예제는 ‘categoryService', 'productDao', 'loginController' 등이다. Bean을 명명하는 일관적인 방법을 적용하는 것은 설정을 좀 더 읽기 쉽고 이해하기 쉽도록 만들어준다. 이러한 명명표준을 적용하는 것은 어려운 일이 아니다. Spring AOP를 사용한다면 특정 Bean 이름과 관련된 Bean의 세트에 advice를 적용할 때 용이해질 수 있다.
생성자를 이용한 인스턴스화
특정 인터페이스를 구현하거나 특정 형태로 코딩 할 필요가 없다.
<bean 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"/> 중략...
비즈니스 레이어와 프리젠테이션 레이어에서 Spring Bean에 접근하는 방법에는 여러 가지가 있다.
비즈니스 레이어에서 사용하고자 하는 Spring Bean에 접근하는 방법은 크게 2가지 형태로 구분할 수 있다. Dependency Lookup과 Dependency Injection 방식이 그것이다.
Dependency Lookup
저장소에 저장되어 있는 Bean에 접근하기 위하여 사용하고자 하는 Bean을 Lookup 한다. 이때 Bean을 개발자가 직접 Lookup하여 사용함으로써 Container에서 제공하는 API와 의존관계가 발생한다. Spring IoC 컨테이너 Dependency Lookup에 대한 자세한 사항은 본 매뉴얼의 IoC 를 참고한다.
구현 클래스는 다음과 같이 작성한다.
public class IoCServiceImpl1 implements IoCService1, ApplicationContextAware { public void setApplicationContext (ApplicationContext context){ IoCService2 iocService2 = (IoCService2)context.getBean("IoCService2"); } }
속성 정의 파일은 다음과 같이 작성한다.
<bean id="IoCService1" class="….IoCServiceImpl1">
중략...
</bean>
<bean id="IoCService2" class="….IoCServiceImpl2">
중략...
</bean>
Dependency Injection
각 클래스 사이에 필요로 하는 의존 관계가 있는 경우, 의존관계를 Container가 자동적으로 연결시켜 줌으로써 Container에서 제공하는 API와 의존관계가 없다. Spring IoC 컨테이너 Dependency Injection에 대한 자세한 사항은 본 매뉴얼의 Dependencies 를 참고한다.
구현 클래스는 다음과 같이 작성한다. 이 예제에서는 Setter Injection 방식을 보여주고 있다.
public class IoCServiceImpl implements IoCService {
public void setDependencyBean(DepBean dependencyBean) {
this.dependencyBean = dependencyBean;
}
중략...
}
속성 정의 파일은 다음과 같이 작성한다.
<bean id="IoCService" class="….IoCServiceImpl">
<property name="dependencyBean" ref="depBean"/>
</bean>
프리젠테이션 레이어에서 Spring Bean에 접근하는 방법은 비즈니스 레이어와 마찬가지로 Dependency Lookup과 Dependency Injection 방식 2가지 중 선택할 수 있는데 이때 사용하는 Web Framework이 무엇인지에 따라 사용 가능한 방식이 제한될 수 있으므로 주의하도록 한다. Web Framework 사용과 관련된 설정 방법은 Spring MVC를 참조하도록 한다.
Dependency Lookup (Struts)
Web Framework으로 Struts를 사용하는 경우, Struts Action 내에서 Spring의 Web ApplicationContext를 얻어내어 Spring Bean을 Lookup하도록 한다. Spring에서 제공해주는 ActionSupport 클래스의 getWebApplicationContext() 메소드를 이용하여 ApplicationContext를 얻는다.
Action 클래스는 다음과 같이 작성한다. 이 예제는 Anyframe 을 이용하여 작성된 코드로 Anyframe 의 DefaultActionSupport을 상속한 UpdateProductAction 클래스의 일부이다. productService Bean을 사용하고 있으며 이때 Bean의 id 값이 Action 클래스에 명시되어야 함에 유의하도록 한다.
public class UpdateProductAction extends DefaultActionSupport { public ActionForward process(ActionMapping mapping, ActionForm form, HttpServletRequest req, HttpServletResponse res) throws Exception { ApplicationContext ctx = getWebApplicationContext(); ProductService productService = (ProductService) ctx.getBean("productService"); 중략... } }
속성 정의 파일은 다음과 같이 작성한다.
<bean id="productService"
class="anyframe.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>
전형적인 기업용 어플리케이션은 한 개의 객체(또는 Spring내 Bean)로 만들어지지는 않는다. 가장 간단한 어플리케이션조차도 함께 작동하는 소량의 객체를 가진다는 것을 의심할 필요가 없을 것이다. 이 장에서는 독립적인 많은 수의 Bean들이 객체가 몇 가지 목표(대개 최종사용자가 원하는 것을 수행하는 어플리케이션)를 달성하기 위해 함께 작동하는 방법에 대해 알아보기로 한다.
각 클래스 사이의 의존관계를 빈 설정(Bean Definition)정보를 바탕으로 컨테이너가 자동적으로 연결해주는 것을 말한다. 컨테이너가 의존관계를 자동적으로 연결시켜주기 때문에 개발자들이 컨테이너 API를 이용하여 의존관계에 관여할 필요가 없게 되어 컨테이너 API에 종속되는 것을 줄일 수 있고 개발자들은 단지 Bean 설정파일(저장소 관리 파일)에서 의존 관계가 필요하다는 정보를 추가하기만 하면 된다. 이는 Setter Injection과 Constructor 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"/>
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>
| Setter Injection 장점 | Constructor Injection 장점 |
|---|---|
| - 생성자 Parameter 목록이 길어 지는 것 방지 | - 강한 의존성 계약 강제 |
| - 생성자의 수가 많아 지는 것 방지 | - Setter 메소드 과다 사용 억제 |
| - Circular dependencies 방지 | - 불필요한 Setter 메소드를 제거함으로써 실수로 속성 값을 변경하는 일을 사전에 방지 |
Circular dependencies
Constructor Injection 사용 시 주의해야 한다. 다음과 같이 두 개의 서로 다른 Bean이 생성자 Argument로 서로의 Bean을 참조하는 경우가 그 예이다.
<bean id="beanFirst" class="test.BeanFirst">
<constructor-arg ref="beanSecond" />
</bean>
<bean id="beanSecond" class="test.BeanSecond">
<constructor-arg ref="beanFirst" />
</bean>
생성자 인자 분석 시 사용되는 방법에는 타입 대응과 인덱스가 있다.
생성자의 인자 타입 대응(match)
'type' 속성을 사용하여 생성자의 인자 타입을 명확하게 명시함으로써 간단한 타입으로의 타입 매치를 사용할 수 있다.
<bean id="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>
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>
value나 Bean 참조를 위해 필요한 공통사항이다. 완전한 형태의 <value> 와 <ref>를 사용하는 것보다 간략화한 몇 가지 형태를 사용할 수 있다. <property>, <constructor-arg>, 그리고 <entry> 모두 완전한 형태의 <value> 요소 대신에 'value' 속성을 지원한다. 예를 들어 코드1이 코드2의 형태로 간략화 될 수 있다.
<!-- 코드 1 --> <property name="myProperty"><value>hello</value></property>
<!-- 코드 2 -->
<property name="myProperty" value="hello"/>
복합적인 형태의 Property 정의가 가능하다. 마지막 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이 던져질 것이다.
'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을 초기화시킨다.
기본적으로 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>
Spring IoC Container는 Bean들 사이의 관계를 autowire 할 수 있다. 이것은 BeanFactory의 내용을 조사함으로써 Spring이 자동적으로 협력자(다른 bean)를 분석하는 것이 가능하다는 것을 의미한다. autowiring을 사용하면 명백하게 많은 양의 타이핑을 줄이고 Property나 생성자의 인자를 명시할 필요를 줄이거나 제거하는 것이 가능해진다. XML-기반의 설정 메타데이터를 사용할 때, Bean정의를 위한 autowire 모드는 <bean>의 autowire 속성을 사용하면 된다. <bean>의 autowire 속성에 정의할 수 있는 값은 다음과 같다.
| 속성 | 설명 |
|---|---|
| no | [기본 설정] Autowiring 기능 사용 안 함 |
| byName | Property 명과 동일한 id나 name을 가진 Bean을 찾아 Autowiring 기능 적용 |
| byType | 해당 Property 타입의 Bean이 하나 존재한다면 Autowiring되나 하나 이상 존재 시 UnsatisfiedDependencyException 발생됨. 만약 대응되는 Bean이 없다면 Property 셋팅 안됨 |
| constructor | 이것은 byType과 유사하지만 생성자의 인자에 적용됨. BeanFactory내 생성자의 인자 타입과 맞는 Bean이 정확하게 하나가 아닐 경우 UnsatisfiedDependencyException 발생됨 |
| autodetect | constructor 모드 수행 후 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>
Property나 생성자의 인자를 XML에 설정할 필요 없음
XML 파일 크기 줄어듬
참조 관계에 있는 타 Bean들의 변경 및 추가 시 XML 파일의 변경이 최소화됨
동일한 이름의 Bean을 XML에 중복 정의하여 사용하는 혼동을 없애 줌
해당 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>
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
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;
}
중략...
}
이미 존재하는 기존의 메소드를 수정하지 않은 상태에서 메소드의 기능을 변경하고자 할 때 <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가 적용되도록 정의하고 있음을 알 수 있다.
Spring Framework의 Container는 기본적으로 확장이 되도록 설계되어 있다. 모든 어플리케이션 개발자들이 확장하여 사용할 필요는 없고 확장할 필요성이 있는 경우에 확장하여 사용하도록 한다. 다음 각각의 항목 별로 기본적으로 제공되는 내용과 확장하여 사용할 수 있는 내용을 설명한다.
Bean Scope
Bean Life Cycle
Bean 상속
Container 확장
ApplicationContext 활용
Spring Framework에서 지원하는 5가지 Scope에 따라 Bean의 인스턴스 생성 메커니즘이 결정된다. 서비스 Scope은 설계, 개발 단계에서 결정하기 어려우므로, 기본적으로는 Default Scope인 Singleton으로 개발하고, 추후 해당 서비스의 성격에 따라 Scope을 정의하는 것이 좋다.
<bean>의 scope 속성값
| 속성 | 설명 |
|---|---|
| singleton | [기본 설정] Spring IoC Container 내에서 Bean 정의 당 하나의 Bean 객체 생성 |
| prototype | 매번 같은 Type의 새로운 Bean 객체 생성 |
| request | WebApplicationContext 유형의 Container 사용 시, Http request 당 하나의 Bean 객체 생성 |
| session | WebApplicationContext 유형의 Container 사용 시, Http session 당 하나의 Bean 객체 생성 |
| globalSession | WebApplicationContext 유형의 Container 사용 시, portlet context 내에서만 유효하며 global Http session 당 하나의 Bean 객체 생성 |
이 외에도, custom scope을 통해 신규 Scope에 대해 정의할 수 있다.

Singleton 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이므로 따로 정의해야 할 필요가 없다.

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
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>
신규 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>
Bean의 Life Cycle은 다음 그림에서와 같이 Initialization, Activation, Destruction으로 구성된다.

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 속성을 이용하도록 한다.
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 속성을 이용한다.
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>
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"/>
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>
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" 필드는 반드시 필요하다.
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."));
| Feature | BeanFactory | ApplicationContext |
|---|---|---|
| Bean instantiation/wiring | Yes | Yes |
| Automatic BeanPostProcessor registration | No | Yes |
| Automatic BeanFactoryPostProcessor registration | No | Yes |
| Convenient MessageSource access (for i18n) | No | Yes |
| ApplicationEvent publication | No | Yes |
대부분의 전형적인 어플리케이션 구축 시에는 ApplicationContext 사용을 권장한다.
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 설정에 대한 부담을 덜 수 있다.
다운로드
다음에서 테스트 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 파일이 있어야 한다.)
다음 내용은 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로 꼽힌다.
| AspectJ | AspectWerkz | JBossAOP | SpringAOP | |
|---|---|---|---|---|
| 출시 | 2001 | 2002 | 2004 | 2004 |
| 버전 | 1.2.1 | 2.0 | 1.3.0 | 1.2.5 |
| Aspect 선언 | 전용코드 | XML, Annotation | XML, Annotation | XML |
| Advice | 전용코드 | 자바 메소드 | 자바 메소드 | 자바 메소드 |
| JoinPoint | 메소드, 생성자, Advice, Field Access, 인스턴스 | 메소드, 생성자, Advice, Field Access, 인스턴스 | 메소드, 생성자, Advice, Field Access, 인스턴스 | 메소드 |
| Pointcut 매칭 | Signature, WildCard, Annotation | Signature, WildCard, Annotation | Signature, WildCard, Annotation | 정규식 |
| Weaving | 컴파일 및 로딩 타임, 바이트 코드 생성 | 컴파일 및 로딩 타임, 바이트 코드 생성 | 런타임 인터셉션 및 Proxy | 런타임 인터셉션 및 Proxy |
| IDE 지원 | Eclipse, JDeveloper, JBuilder, NetBeans | Eclipse, NetBeans | Eclipse |
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 등을 이용하면 설정 코드를 매우 단순하게 작성하는 것이 가능하다.
AOP에는 새로운 용어가 많이 등장한다. 이 중에서 특히 AOP를 이용해서 개발하는데 필요한 다음의 주요 구성 요소들에 대해 정확한 이해가 필요하다.
Crosscutting Concerns 모듈이 삽입되어 동작할 수 있는 실행 가능한 특정 위치를 말한다. 예를 들어 메소드가 호출되는 부분 또는 리턴되는 시점이 하나의 JoinPoint가 될 수 있다. 또 필드를 액세스하는 부분, 인스턴스가 만들어지는 지점, 예외가 던져지는 시점, 등이 대표적인 JoinPoint가 될 수 있다. 각각의 JoinPoint들은 그 전후로 Crosscutting Concerns의 기능이 AOP에 의해 자동으로 추가되어져서 동작할 수 있는 후보지가 되는 것이다.
Pointcut은 어느 JoinPoint를 사용할 것인지를 결정하는 선택 기능을 말한다. AOP가 항상 모든 모듈의 모든 JoinPoint를 사용할 것이 아니기 때문에 필요에 따라 사용해야 할 모듈의 특정 JoinPoint를 지정할 필요가 있다. 일종의 JoinPoint 선정 룰과 같은 개념으로 다음과 같은 Pattern Matching 방법들을 이용하여 룰을 정의할 수 있다.
Basics
set*(..) : set으로 시작하는 모든 메소드명
* main(..) : return type이 any type이고, 0개 이상의 any type parameter를 가진 main 메소드
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
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 의미
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 메소드
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 정의 방법에 대해 살펴보기로 하자.
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 호출시
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 호출시
handler
Exception 핸들링을 위한 JoinPoint를 정의한다.
handler(DataAccessException) : matches cach(DataAccessException){...} and doesn't match catch(RuntimeException)
handler(RuntimeException+) : matches both
within
특정 유형에 속하는 JoinPoint를 정의하며, 주로 &&, ||, ! 등과 함께 조합된 형태로 사용된다.
within(*) : matches any JoinPoint
within(org.xyz.myapp..*) : org.xyz.myapp 패키지 내에 속하는 모든 요소
within(IInterface+) : IInterface type의 모든 요소
withincode
해당되는 메소드 또는 constructor 내에 정의된 코드를 위한 JoinPoint를 정의한다.
withincode(!void get*()) : return type이 void가 아니고 메소드명이 get으로 시작하며 parameter가 없는 메소드 내의 코드
args
입력값의 개수, type 등에 대한 JoinPoint를 정의한다.
call(* transfer(..)) && args(DepositAccount,CheckingAccount,*) : 메소드명이 transfer이고, 입력 인자가 2개 이상이며, 1,2번째 입력 인자의 type이 DepositAccount,CheckingAccount인 메소드 호출시
this
JoinPoint를 가진 object의 type을 정의한다. (Runtime type)
this(Account) : 인터페이스 Account를 구현한 클래스(Proxy)의 모든 JoinPoint
target
JoinPoint를 가진 target object의 type을 정의한다. (Runtime type)
call(* *(..)) && target(Account) : Account 클래스 내의 모든 메소드 호출시
Spring은 메소드 호출 부분에 대한 AOP만을 지원하므로, 위에 정의한 다양한 Pointcut Designator 중 execution, within, target, this, args만이 사용 가능하다.
Advice는 각 JoinPoint에 삽입되어져 동작할 수 있는 코드로 동작 시점은 pointcut에 Matching되는 JoinPoint 실행 전후이며 before, after, after returning, after throwing, around 중에서 선택 가능하다.
| 동작시점 | 설명 |
|---|---|
| Before | Before Advice는 Matching된 JoinPoint 전에 동작하는 Advice이다. |
| After | After Advice는 동작 시점에 따라 after (finally), after returning,
after throwing 으로 구분할 수 있다.
|
| Around | 가장 강력한 Advice로 Matching된 JoinPoint 전, 후에 동작하며 JoinPoint 실행 시점을 결정할 수 있다. 또한 다른 Advice와는 달리 입력값, target object, return 값 등에 대한 변경이 가능하다. |
동작 시점별 Advice 정의 방법에 대해서는 매뉴얼 Spring >> AOP 하위의 Annotation based AOP , XML based AOP , AspectJ based AOP 를 참고하도록 한다.
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, ...) |
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 를 참고하도록 한다.
다음에서는 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 이상에서만 정의 가능함에 유의하도록 한다.)
@AspectJ(Annotation)이 적용된 클래스들을 로딩하여 해당 클래스에 정의된 Pointcut, Advice를 실행하기 위해서는 Spring 속성 정의 XML 파일에 다음과 같이 추가해주어야 한다.
<aop:aspectj-autoproxy/>
@Aspect를 이용하여 특정 클래스가 Aspect임을 나타낸다. 다음 LoggingAspect 에서는 @Aspect를 이용하여 해당 클래스가 Aspect임을 나타내고 있다.
@Aspect
public class LoggingAspect {
//...
}@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(){}다음에서는 Annotation을 이용하여 동작 시점별 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라는 입력 인자를 선언하지 않아도 된다.
@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 정의시 필요에 따라 선택 정의할 수 있다.
@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 정의시 필요에 따라 선택 정의할 수 있다.
@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라는 입력 인자를 선언하지 않아도 된다.