데이터 보안은 시스템 보안 만큼 중요하다. 따라서 데이터를 보호하고 기밀성, 기밀성, 가용성을 확실히 보장하는 것은 관리자들에게는 중요한 문제이다.

이 글에서 데이터 보안의 기밀성에 대해 설명하겠다. 보호를 받고 있는 데이터는 권한을 받은 사용자 또는 시스템만이 접근할 수 있다. 리눅스 시스템에서 Remote Authentication Dial-In User Service server (RADIUS)를 설정 및 구성하여 사용자 인증, 권한, 계정 부여 (AAA)를 수행하는 방법을 설명하겠다.

소개

RADIUS 프로토콜에 대해 먼저 설명한 다음 AAA 컴포넌트와 이것의 작동방법 그리고 LDAP 프로토콜을 설명하겠다.

Remote Authentication Dial-In User Service 프로토콜은 IETF의 RFC 2865(참고자료)에서 정의된다. 이것은 네트워크 액세스 서버(NAS)로 사용자 인증, 권한 부여, 계정 설정 등을 수행한다. RADIUS는 UDP에 기반한 클라이언트/서버 프로토콜이다. RADIUS 클라이언트(네트워크 액세스 서버)는 일반적으로 라우터, 스위치 또는 무선 액세스 포인트이다. (액세스 포인트는 네트워크상의 노드에 설정된다; WAP은 무선 버전이다.) RADIUS 서버는 유닉스 또는 Windows 2000 서버상에서 실행되는 데몬 프로세스이다.

RADIUS와 AAA

NAS가 사용자 연결 요청을 받으면 NAS는 이것을 지정된 RADIUS 서버로 보낸다. RADIUS 서버는 사용자를 인증하고 사용자 설정 정보를 NAS로 리턴한다. 그런 다음, NAS는 연결 요청을 수락 또는 거절한다.

완전한 기능을 갖춘 RADIUS 서버는 LDAP 외에도 다양한 방식을 지원하여 사용자를 인증한다.

  • PAP (Password Authentication Protocol, 패스워드가 텍스트로 보내지는 곳에서 PPP와 함께 사용됨.);
  • CHAP (Challenge Handshake Authentication Protocol, PAP 보다 안전, 사용자 이름과 패스워드 사용.);
  • 로컬 UNIX/Linux 시스템 패스워드 데이터베이스 (/etc/passwd);
  • 기타 로컬 데이터베이스

인증과 권한부여는 RADIUS에서 함께 결합된다. 사용자 이름이 맞고 패스워드가 정확하면 RADIUS 서버는 사용자의 접근을 허용하는 몇 가지 매개변수(애트리뷰트-값 쌍)을 포함하여 Access-Accept 응답을 리턴한다. 이 매개변수들은 RADIUS에서 설정되고 서비스 유형, 프로토콜 유형, 사용자를 할당하는 IP 주소, 액세스 제어 리스트(ACL) 또는 NAS에서 적용할 정적 라우트, 기타 값 등을 포함한다.

RADIUS 계정 기능을 사용하면(참고자료), 보안 또는 빌링에 사용되는 세션동안 사용된 시간, 패킷, 바이트 같은 리소스의 양을 나타내면서 연결 세션의 시작과 끝에 데이터를 보낼 수 있다.

Lightweight Directory Access Protocol

Lightweight Directory Access Protocol (LDAP)은 X.500 계열 디렉토리의 정보에 접근하고 업데이트하는 방식을 정의한 오픈 표준이다. LDAP는 사용자 정보를 중앙 로케일에 저장하여 동일한 사용자 정보를 각 시스템에 저장하지 않아도 된다. 또한 일관성 있고 제어된 방식으로 정보를 관리하고 정보에 액세스하는데 사용되기도 한다.

LDAP는 중앙 디렉토리에서 사용자를 관리하기 때문에 사용자 관리 태스크가 간단해진다. 사용자 정보를 저장하는 것 외에도, LDAP에서 사용자를 정의할 수 있다. 로그인의 수를 제한하는 것과 같은 선택 기능이 가능하다. 이 글에서 RADIUS 서버가 설정되어 LDAP에 대해 사용자를 인증하는 방법을 설명하겠다. 이 글은 RADIUS에 초점을 맞추었기 때문에 LDAP 서버 설정 및 설치에 대해서는 자세히 설명하지 않겠다.

OpenLDAP는 LDAP의 오픈 소스 구현이다; OpenLDAP.org에서 자세한 정보를 참조하기 바란다.(참고자료)

시나리오

다음과 같은 시나리오를 상상해보자:

  • 집에 있는 사용자가 dial-up 인증을 사용하여 회사의 인트라넷에 접근할 수 있다.
  • 무선기능이 되는 랩탑이 무선 인증에 의해 캠퍼스 네트워크와 연결될 수 있다.
  • 관리자가 자신의 워크스테이션을 사용하여 텔넷이나 HTTP를 통해 관리자 인증으로 네트워크 장치에 로그인 할 수 있다.

이 모든 인증 작업은 중앙 LDAP 서버에 대해 RADIUS 서버로 수행될 수 있다. (그림 1)


그림 1. RADIUS와 LDAP를 통한 인증
Authentication via RADIUS and LDAP 

이 글에서, 마지막 옵션을 구현해 보겠다. 먼저 RADIUS 서버를 설치하는 것으로 시작한다.

RADIUS 설치

RADIUS 서버 소프트웨어는 여러 소스들로 얻어질 수 있다. 이 글에서는 FreeRADIUS를 사용하겠지만(참고자료), Cisco Secure Access Control Server (ACS)도 중앙화 된 사용자 액세스 제어 프레임웍으로서 UNIX 와 Windows에서 실행되는 Cisco 장치를 통해 사용자 관리를 한다. 또한 Cisco 상용 프로토콜인 TACACS+도 지원한다. (이것은 TACACS+ 기능이 되는 장치에서 더 많은 사용자 관리 기능을 수행한다.)

FreeRADIUS는 리눅스 상에서 실행되는 강력한 RADIUS 서버로서 오픈 소스 커뮤니티에서 만들어 졌으며 오늘날 분산 및 이종의 컴퓨팅 환경에 알맞다. FreeRADIUS 1.0.2는 LDAP, MySQL, PostgreSQL, Oracle 데이터베이스를 지원하고 EAP와 Cisco LEAP 같은 네트워크 프로토콜과 호환된다. FreeRADIUS는 현재 많은 대규모 네트워크 시스템에 전개되고 있다.

다음 단계는 Red Hat Enterprise Linux Advanced Server 3.0에 FreeRADIUS 1.0.2를 설치 및 테스트 하는 방법을 설명한 것이다.:


Listing 1. FreeRADIUS 설치와 테스트
tar -zxvf freeradius-1.0.2.tar.gz         - extract it with gunzip and tar
./configure
make
make install                              - run this command as root
radiusd or                                - start RADIUS server
radiusd -X                                - start RADIUS server in debug mode
radtest test test localhost 0 testing123  - test RADIUS server

radtest 응답을 받으면 FreeRADIUS 서버가 작동한다.

또 다른 무료 툴인 NTRadPing(참고자료)을 소개하겠다. 이것은 Windows 클라이언트에서 온 인증 및 권한부여 요청을 테스트한다. RADIUS 서버에서 돌아온 애트리뷰트 값과 같은 자세한 응답을 디스플레이 할 수 있다.

이제 FreeRADIUS를 설정한다.

FreeRADIUS 설정하기

RADIUS 서버 구성은 서버, 클라이언트, 사용자(권한부여와 인증용)를 설정하는 것이다. RADIUS 서버와는 다른 필요 때문에 다른 설정이 될 수도 있다. 다행히 대부분의 설정은 비슷하다.

서버 설정하기

FreeRADIUS 설정 파일은 /etc/raddb 폴더에 저장된다. radiusd.conf 파일을 변경해야 한다.


Listing 2. radiusd.conf 변경하기
1) Global settings:

log_auth = yes                - log authentication requests to the log file
log_auth_badpass = no         - don't log passwords if request rejected
log_auth_goodpass = no        - don't log passwords if request accepted

2) LDAP Settings:

modules {
   ldap {
      server = "bluepages.ibm.com"   - the hostname or IP address of the LDAP server
      port = 636                     - encrypted communications
      basedn = "ou=bluepages,o=ibm.com"   - define the base Distinguished Names (DN),
                                          - under the Organization (O) "ibm.com",
                                          - in the Organization Unit (OU) "bluepages"
      filter = "(mail=%u)"                   - specify search criteria
      base_filter = "(objectclass=person)"   - specify base search criteria
   }

authenticate {                - enable authentication against LDAP
   Auth-Type LDAP {
      ldap
   }

매개변수는 LDAP 서비스의 인스턴스인 IBM BluePages와 작동하도록 설정된다. 매개변수들은 다른 LDAP 서버들마다 달라진다.

클라이언트 설정하기

클라이언트는 /etc/raddb/clients.conf 에서 설정된다. RADIUS 클라이언트를 설정하는 두 가지 방식이 있다. IP 서브넷으로 NAS를 그룹핑하거나(Listing 3) 호스트 이름 또는 IP 주소로 NAS를 리스팅 할 수 있다. (Listing 4). 두 번째 방식을 따르면, shortnamenastype이 정의된다.


Listing 3. IP 서브넷으로 NAS 그룹핑하기
client 192.168.0.0/24 {
   secret      = mysecret1   - the "secret" should be the same as configured on NAS
   shortname   = mylan       - the "shortname" can be used for logging
   nastype      = cisco      - the "nastype" is used for checkrad and is optional
}


Listing 4. 호스트네임과 IP 주소로 NAS 리스팅하기
client 192.168.0.1 {
   secret      = mysecret1
   shortname   = myserver
   nastype      = other
}

사용자 인증 설정

/etc/raddb/user 파일에는 각 사용자에 대한 인증과 설정 정보가 포함되어 있다.


Listing 5. /etc/raddb/user 파일
1) Authentication type:

Auth-Type := LDAP       - authenticate against LDAP
Auth-Type := Local, User-Password == "mypasswd"
                        - authenticate against the
                        - password set in /etc/raddb/user
Auth-Type := System     - authenticate against the system password file
                        - /etc/passwd or /etc/shadow

2) Service type:

Service-Type = Login,   - for administrative login

사용자 권한 설정

다음 인증 서버 애트리뷰트 값(AV) 쌍은 사용자 권한을 위해 설정되어야 한다. 인증이 수락된 후 관리자 로그인 요청을 위해 NAS로 리턴된다.

Cisco 라우터의 경우 다른 권한 레벨이 있다:

  • Level 1은 non-privileged이다. 프롬프트는 router>이고, 로그인용 기본 레벨이다.
  • Level 15는 privileged이다. 프롬프트는 router#이고, 사용 가능 모드로 들어간 후 레벨이다.
  • Levels 2에서 14는 기본 설정에는 사용되지 않는다.

다음 명령어를 사용하면 네트워크 접근을 통한 사용자 로그인으로 EXEC 명령어에 즉시 접근할 수 있다.:

cisco-avpair ="shell:priv-lvl=15"

다음 코드는 CISCO 무선 액세스 포인트에 대해 같은 작업을 핸들한다.:

Cisco:Avpair = "aironet:admin-capability=write+snmp+ident+firmware+admin"

어떤 기능의 조합으로도 다음 애트리뷰트와 함께 리턴된다.:

Cisco:Avpair = "aironet:admin-capability=ident+admin"
Cisco:Avpair = "aironet:admin-capability=admin"

Cisco에서 명령어에 관한 더 많은 정보를 숙지하기 바란다.

네트워크 액세스 서버 설정하기

다음에는 NAS를 설정한다. 우선 Cisco 라우터 그 다음에는 Cisco WAP이다.

Cisco IOS 12.1 라우터의 경우 AAA를 실행한 다음 인증, 권한, 계정을 설정할 것이다.


Listing 6. AAA 실행
aaa new-model
radius-server host 192.168.0.100
radius-server key mysecret1

AAA는 라우터 상에서 실행되어야 한다. NAS에 AAA 서비스를 제공할 RADIUS 서버의 리스트가 설정된다. 암호화 키는 NAS와 RADIUS 서버 간 데이터 전송을 암호화하는데 사용된다. FreeRADIUS에서 설정된 것과 동일해야 한다.


Listing 7. 인증 설정
aaa authentication login default group radius local
line vty 0 4
login authentication default

이 예제에서, 네트워크 관리자는 RADIUS 인증을 사용한다. RADIUS 서버를 사용할 수 없다면 NAS의 로컬 사용자 데이터베이스 패스워드를 사용하라.


Listing 8. 권한 설정
aaa authorization exec default group radius if-authenticated

NAS에 로그인할 때, 사용자가 EXEC 쉘을 사용하도록 한다.


Listing 9. 계정 설정
aaa accounting system default start-stop group radius
aaa accounting network default start-stop group radius
aaa accounting connection default start-stop group radius
aaa accounting exec default stop-only group radius
aaa accounting commands 1 default stop-only group radius
aaa accounting commands 15 default wait-start group radius

라우터는 RADIUS 서버에 계정 기록을 보내도록 설정되어야 한다. Listing 9의 명령어를 사용하여 NAS 시스템 이벤트, 네트워크 연결, 아웃바운드 연결, EXEC 연산, 레벨 1에서 15까지의 명령어에 대한 계정 정보를 기록한다.

여기까지다. 이제 Cisco 무선 액세스 포인트 설정을 보도록 하자. 다음 설정은 Firmware 12.01T1과 함께 Cisco 1200 Series AP에 적용된다. (그림 2):

  • 서버 이름 또는 IP 주소, 공유 비밀을 입력한다.
  • 유형을 "Radius"로 선택하고, "User Authentication."를 체크한다.

그림 2. WAP용 NAS 설정
Configuring NAS for WAP 

실제로 EAP Authentication을 설정하여 FreeRADIUS가 일반 사용자들을 무선 LAN에 인증하도록 하는데 사용되도록 할 수 있다.

계정: RADIUS 작동

모든 설정이 완료되었기 때문에 FreeRADIUS 서버는 NAS에서 보내진 모든 정보를 기록하여 /var/log/radius/radius.log 파일에 저장할 수 있다.:


Listing 10. /var/log/radius/radius.log 파일
Thu Mar 3 21:37:32 2005 : Auth: Login OK: [David] (from client
                                mylan port 1 cli 192.168.0.94)
Mon Mar 7 23:39:53 2005 : Auth: Login incorrect: [John] (from
                                client mylan port 1 cli 192.168.0.94)

자세한 계정 정보는 /var/log/radius/radacct 디렉토리에 저장된다. Listing 11은 David가 2005년 3월 4일, 7시 40분에서 7시 51분 사이에 192.168.0.94에서 192.168.0.1로 로그인 했음을 나타내고 있다. 이렇게 자세한 정보는 관리자에게 매우 도움이 된다.


Listing 11. RADIUS가 제공하는 계정 상세 샘플
Fri Mar  4 19:40:12 2005
        NAS-IP-Address = 192.168.0.1
        NAS-Port = 1
        NAS-Port-Type = Virtual
        User-Name = "David"
        Calling-Station-Id = "192.168.0.94"
        Acct-Status-Type = Start
        Acct-Authentic = RADIUS
        Service-Type = NAS-Prompt-User
        Acct-Session-Id = "00000026"
        Acct-Delay-Time = 0
        Client-IP-Address = 192.168.0.1
        Acct-Unique-Session-Id = "913029a52dacb116"
        Timestamp = 1109936412

Fri Mar  4 19:51:17 2005
        NAS-IP-Address = 192.168.0.1
        NAS-Port = 1
        NAS-Port-Type = Virtual
        User-Name = "David"
        Calling-Station-Id = "192.168.0.94"
        Acct-Status-Type = Stop
        Acct-Authentic = RADIUS
        Service-Type = NAS-Prompt-User
        Acct-Session-Id = "00000026"
        Acct-Terminate-Cause = Idle-Timeout
        Acct-Session-Time = 665
        Acct-Delay-Time = 0
        Client-IP-Address = 192.168.0.1
        Acct-Unique-Session-Id = "913029a52dacb116"
        Timestamp = 1109937077

결론

이 글에서 제시한 간단한 단계를 거치면 외부 LDAP 서버를 사용하는 Remote Authentication Dial-In User Service 서버를 설정하여 인증, 권한, 계정 등을 핸들 할 수 있다. 이 글을 다음과 같이 요약할 수 있다.:

  • RADIUS와 LDAP 서버 및 AAA 개념 소개.
  • 설치 및 구현 시나리오.
  • RADIUS 서버의 설치 및 설정.
  • 네트워크 액세스 서버 설정.
  • RADIUS가 제공하고 관리하는 상세 정보 샘플.

이 글에서 제시한 것은 권한이 있는 엔터티에 의해서만 보안 데이터에 접근될 수 있도록 하기 위한 절차이다.


참고자료

참조 - http://www.ibm.com/developerworks/kr/library/l-radius/index.html

'System > Linux' 카테고리의 다른 글

ssh 및 telnet 서비스 포트 변경  (2) 2011.01.12
시스템 로그파일과 로그기록 삭제 방법  (0) 2010.12.27
vi(vim) 기본 사용법  (0) 2010.12.24
리눅스 명령어 - netstat  (0) 2010.12.24
configure 옵션  (0) 2010.12.22
Posted by linuxism
,

Spring의 IoC적인 특징은 AOP를 구현하는 핵심적인 원리가 되어 왔습니다.
AOP란 무엇입니까? 사실, AOP는 그닥 어려운 개념은 아닙니다. AOP는 그동안의 전통적인 프로그래밍에서 개발자들이 느끼고 있던 불편함을 개선한 새로운 프로그래밍 패러다임이라고 보여집니다.


AOP가 필요한 이유를 설명하기 전에 일반적인 형태의 프로그램을 작성하는 방식을 살펴보겠습니다.


우리가 은행업무를 위한 프로그램을 작성한다고 가정해 보겠습니다. 은행업무에는 기본적인 입/출금업무가 있을 것이고 계좌이체업무도 있을 것입니다. 이러한 업무들이 바로 은행업무에 있어서 본질적인 비즈니스 기능이라고 할 수 있을 것입니다. 하지만, 이런 업무들만 있는 것은 아니지요. 누군가 돈을 찾아갔다면 그 정보도 남겨야 할 것이고 ID도용이 발생했다면 원인을 밝혀내야 하며, 온라인 뱅킹이 발생했다면 정상적으로 거래은행에 입금이 되었는지도 체크해야 합니다.


이와같이 은행업무를 위한 프로그램에는 핵심 비즈니스 기능뿐 아니라 부가적이라고 말할 수도 있는 보안, 인증, 로그 , 중복체크등과 같은 기능들도 통합되어 있어야만 비로소 완전하게 구현되었다고 말할 수 있습니다. 어쩌면은 은행 본연의 업무를 위한 코드들보다 부가적인 측면을 다루는 코드가 더 많아질수도 있겠습니다.


하지만 이 부가적인 기능을 다루는 코드는 비즈니스 로직과는 상관관계가 없는 경우가 많아서 여러업무에 중복적으로 사용되는 것이 일반적이지요. 보통 개발이 진행되면 진행될수록 사실 핵심 비즈니스로직보다는 이러한 부가적인 처리에 사용되는 코드가 문제가 되기도 합니다. 시스템 전체에 걸쳐서 사용되고 있기 때문에 수정도 힘들고 관리도 힘들게 됩니다.


AOP세계에서는 상기 설명했던 기능중에서 비즈니스로직을 구현한 기능들을 Primary(Core) concern이라고 부릅니다. 보안, 인증, 로그등과 같은 부가적인 기능으로서 시스템 전반에 산재되어 사용되는 기능들은 Cross-cutting concern이라고 부르고 있습니다. 바로 AOP의 핵심이 여기에 있습니다. 바로 AOP는 우리에게 있어서 골치거리인 Cross-cutting concern을 어떻게 다룰 것인가에 대한 새로운 패러다임을 제고하고 있습니다.


사실 AOP가 등장하기 전에는 Primary concern과 Cross-cutting concern이 같이 하나의 프로그램에 구현되어져 왔습니다. 당연히 비즈니스 로직과 상관없는 코드들이 여기저기 산재해 있게 되었기에 가독성과 유지보수성에 악영향이 있을 수 밖에 없었을 것입니다. 생산성 저하와 비용증가는 당연한 결과였겠지요.


하지만, AOP세계에서는 다릅니다. AOP는 Primary concern과 Cross-cutting concern을 별도의 코드로 구현합니다. 최종적인 프로그램은 이 둘을 조합하여 완성하게 되는 것이죠. 이것은 하나의 프로그램에 각각의 코드들이 혼재해 있는 것과는 명확히 다른 것입니다. 사실 이렇게 되기를 많은 개발자들이 마음속으로 바래마지 않았을 것이지만 그 방법을 몰랐었습니다. AOP는 모든 개발자들이 바랬던 이상을 구현해 놓은 것에 불과(?)한것이지요.

이제, AOP세계에서 새로운 용어들이 등장하게 되었습니다. 바로 Advice와 Code , Point-Cut 그리고 Weaving입니다.


Code는 Primary(Core) concern을 구현해 놓은 코드를 이야기합니다. Advice는 Cross-cutting Concern을 구현한 코드를 지칭하고 있습니다. 그럼, Point-cut은 무엇일까요? 바로 Advice와 Code를 연결해주는 설정 정보를 말합니다. 다시 말하면 Point-cut은 Code의 어느 위치에 Advice를 위치할 것인가에 대한 것입니다. 마지막으로 Weaving은 이 둘(Code와 Advice)를 조합하여 완성된 어플리케이션을 만드는 과정을 이야기합니다.


정리해 볼까요?

  • Code : Primary(core) concern을 구현한 코드
  • Advice:  Cross-cutting concern을 구현한 코드
  • Jointpoint:Code와 Advice를 연결해주는 설정 정보, Advice가 적용 가능한 지점(메소드 호출, 필드값 변경)
  • Point-cut: Jointpoint의 부분집합으로서 실제 Advice가 적용되는 Jointpint
  • Weaving : Code, Advice, Point-cut등을 조합하여서 어플리케이션을 만들어 가는 과정

이 용어들을 이해하는 것으로도 벌써 AOP세계의 절반은 여행하셨다고 볼 수 있습니다. 사실 이 용어를 이해한다면 AOP는 그닥 어려운 것이 아닐 수 있습니다. 따라서 위의 용어들은 반드시 숙지하시길 권면드립니다.


그럼 왜 AOP(Aspect Oriented Programming)이라고 하는 걸까요?

AOP의 Aspect는 Advice와 Point-cut을 함께 지칭하는 단어입니다. 따라서 AOP는 Advice와 Point-cut에 대한 이야기를 하고 싶었던 것이네요.

Spring의 AOP패키지는 이러한 AOP개념을 멋드러지게 구현한 것으로서 그 배경에는 IoC 또는 DI가 자리하고 있습니다.


Spring AOP도 여러 AOP 프레임워크중의 하나입니다. 따라서 나름의 특징이 있습니다. 간단히 소개를 해보자면 다음과 같습니다.

  • 자체적인 프록시 기반의 AOP 지원

필드값 변경과 같은 Joinpoint는 사용할 수 없고 메서드 호출 Joinpoint만 지원합니다. Spring AOP는 완전한 AOP를 지원하는 것이 목적이 아니라 엔터프라이즈 어플리케이션을 구현하는데 필요한 정도의 기능 제공을 목적으로 하고 있습니다.

  •  Spring AOP는 자바 기반

AspectJ는 별도의 문법을 알아야 하지만 Spring AOP는 자바를 기반으로 하고 있기때문에 다른 언어를 익힐 필요가 없습니다.


Spring AOP는 내부적으로 프록시를 이용하여 AOP가 구현되기 때문에 메서드 호출에 대해서만 AOP를 적용할 수 있다는 점이 아쉬운 점이라고 할 만하지만, 크게 문제될 것은 없다는 개인적인 생각입니다.


참조 - http://blog.naver.com/go828258/112903221

2010/03/04 13:53


Posted by linuxism
,


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

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

  • IoC(Inversion of Control)개념

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

    Inversion of Control(이하 IoC)이란?

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

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

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


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

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

    • 단위 테스트 용이

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

  • IoC와 Dependency Injection간의 관계

    Spring Framework의 가장 큰 장점으로 IoC Container 기능이 부각되어 있으나, IoC 기능은 Spring Framework이 탄생하기 훨씬 이전부터 사용되던 개념이었다. 그러므로 "IoC 기능을 Spring Framework의 장점이라고 이야기하는 것은 적합하지 않다."고 반론을 제기하면서 "새로운 개념을 사용하는 것이 적합하다."고 주장한 사람이 Martin Fowler이다. 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

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

    [표제목-ApplicationContext 역할]
    설 명
    BeanFactory의 모든 기능 제공
    ResourceBundle 파일을 이용한 국제화(I18N) 지원
    다양한 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 구현체 목록]
Resource ImplementationPurpose
org.springframework.core.io.ByteArrayResourceDefines a resource whose content is given by an array of bytes
org.springframework.core.io.ClassPathResourceDefines a resource that is to be retrieved from the classpath
org.springframework.core.io.DescriptiveResourceDefines a resource that holds a resource description but no actual readable resource
org.springframework.core.io.FileSystemResourceDefines a resource that is to be retrieved from the file system
org.springframework.core.io.InputStreamResourceDefines a resource that is to be retrieved from an input stream
org.springframework.web.portlet.context. PortletContextResourceDefines a resource that is available in a portlet context
org.springframework.web.context.support. ServletContextResourceDefines a resource that is available in a servlet context
org.springframework.core.io.UrlResourceDefines a resource that is to be retrieved from a given URL

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


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

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

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

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

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

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

Container에 의해 "인스턴스화, 설정, 그리고 조합[어플리케이션내 객체를]"하기 위한 설정 방법에 대해 알아 보기로 하자. 대부분은 간단하고 직관적인 XML 형태로 제공되며 XML 기반의 설정 메타데이터를 사용하여 Bean을 정의하도록한다. 다음은 XML 기반의 설정 메타데이터의 기본 구조 예제이다.

<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-3.1.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 자체는 설정 메타데이터의 형태로부터 분리될 수 있기 때문이다.

  1. BeanFactory 사용한 예제

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

    ApplicationContext context = 
            new ClassPathXmlApplicationContext(new String ("beans.xml"));
    // of course, an ApplicationContext is just a BeanFactory
    BeanFactory factory = (BeanFactory) context;

XML 기반 설정 메타데이터는 다중 XML파일로 분리하여 정의할 수 있다. 여기서 주의할 점은 <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 객체로 표현한다.

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

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

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

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

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

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

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

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

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

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

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

비즈니스 레이어와 프리젠테이션 레이어에서 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을 상속한 UpdateMovieAction 클래스의 일부이다. movieService Bean을 사용하고 있으며 이때 Bean의 id 값이 Action 클래스에 명시되어야 함에 유의하도록 한다.

    public class UpdateMovieAction extends DefaultActionSupport {
        public ActionForward process(ActionMapping mapping, ActionForm form,
        HttpServletRequest req, HttpServletResponse res) throws Exception {
            ApplicationContext ctx = getWebApplicationContext();
            MovieService movieService = (MovieServiceImpl) ctx.getBean ("movieServiceImpl");
            중략...
         }
    }

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

    <bean id="movieServiceImpl"
        class="org.anyframe.plugin.core.moviefinder.service.impl.MovieServiceImpl">
        중략...
    </bean>

  • Dependency Injection (Spring MVC)

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

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

    public class MovieController {
        private MovieService movieService;;
    
        public void setMovieService(MovieService movieService) {
            this.movieService = movieService;
        }
        중략...
    
        public ModelAndView list(HttpServletRequest request,
        HttpServletResponse response) throws Exception {
            Movie movie = new Movie();
            bind(request, movie);
            Page resultPage = movieService.getPagingList(movie);
        중략...
        }
    }

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

    <bean name="/coreMovie.do"
            class="org.anyframe.plugin.core.moviefinder.web.MovieController">
        <property name="movieService" ref="coreMovieService"/>
        <property name="genreService" ref="coreGenreService"/>
    </bean>

  • Dependency Lookup (Spring MVC)

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

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

    WebApplicationContext ctx = 
         WebApplicationContextUtils.getWebApplicationContext(servletContext);
    MovieService movieService = (MovieServiceImpl)ctx.getBean("movieServiceImpl");
        중략...

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

    <bean id="movieServiceImpl"
            class="org.anyframe.plugin.core.moviefinder.service.impl.MovieServiceImpl">
        중략...
    </bean>

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

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

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

public class MovieServiceImpl implements MovieService {
    public void setMovieDao(MovieDao movieDao) {
        this.movieDao = movieDao;
    }
    중략... 
}

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

<bean id="coreMovieService"
        class="org.anyframe.plugin.core.moviefinder.service.impl.MovieServiceImpl">
    <property name="movieDao" ref="coreMovieDao" />
</bean>
	
<bean id="coreMovieDao" 
        class="org.anyframe.plugin.core.moviefinder.service.impl.MovieDao">
</bean>

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

public class MovieServiceImpl implements MovieService {
    MovieDao movieDao;
    public MovieServiceImpl(MovieDao movieDao) {
        super(movieDao);
        this.movieDao = movieDao;}
        중략...
}

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

<bean id="coreMovieService" 
    class="org.anyframe.plugin.core.moviefinder.service.impl.MovieServiceImpl">
    <constructor-arg ref="coreMovieDao"/>
</bean>
        
<bean id="coreMovieDao" 
        class="org.anyframe.plugin.core.moviefinder.service.impl.MovieDao">
    중략...
</bean>

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

<bean id="coreMovieService" 
    class="org.anyframe.plugin.core.moviefinder.service.impl.MovieServiceImpl">
    <constructor-arg type="org.anyframe.plugin.core.moviefinder.service.BeanA" ref="beanA"/>
    <constructor-arg type="org.anyframe.plugin.core.moviefinder.service.BeanB" ref="beanB"/>
</bean>

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

<bean id="coreMovieService"
    class="org.anyframe.plugin.core.moviefinder.service.impl.MovieServiceImpl">
    <constructor-arg index="0" ref="beanA" /> 
    <constructor-arg index="1" ref="beanB" /> 
</bean>

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

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

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

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

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

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

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

  • 생성자의 인자 인덱스

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

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

Spring 3.1 버전부터 소개된 c-namespace를 이용하면, 중첩하여 표현된 복잡한 constructor-arg 요소를 축약된 형태의 생성자 인자 속성으로 간단히 나타낼 수 있다.

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:c="http://www.springframework.org/schema/c"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans.xsd">
      
  <bean id="bar" class="x.y.Bar"/>
  <bean id="baz" class="x.y.Baz"/>

  <-- 'traditional' declaration -->      
  <bean id="foo" class="x.y.Foo">
      <constructor-arg ref="bar"/>
      <constructor-arg ref="baz"/>
      <constructor-arg value="foo@bar.com"/>
  </bean>

  <-- 'c-namespace' declaration -->
  <bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com">

</beans>

간혹 생성자의 인자값을 알 수 없는 경우 (일반적으로 디버깅 정보없이 컴파일된 bytecode의 경우), 다음과 같이 대안으로 인자의 순번을 이용할 수 있다.

<-- 'c-namespace' index declaration -->
<bean id="foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz">

참고

XML 문법의 제약사항으로 인하여, XML의 속성명은 숫자로 시작할 수 없다. 따라서 순번을 이용하는 경우에는 _(언더바) 문자를 XML 속성명 앞에 표기하여야 한다.

실제 상황에서, 생성자 분석 메커니즘은 굳이 순번을 이용할 필요가 없을 정도로 상당히 효율적이다. 따라서 설정시 생성자의 순번 대신 인자명을 기술하는 방식을 권장한다.

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="movieService" class="com.foo.SimpleMovieService">
          <ref parent="accountService"/>
      </bean>

  • inner Bean

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

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

  • Collection

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

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

  • Collection 병합

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

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

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

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

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

  • <null> 요소

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

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

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

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

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-core.xml 파일의 일부이다.

<bean id="coreMovieService"
    class="org.anyframe.plugin.core.moviefinder.service.impl.MovieServiceImpl" 
    autowire="byType" depends-on="coreMovieDao">
</bean>

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

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

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

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

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

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

<bean id="coreMovieDao" 
    class="org.anyframe.plugin.core.moviefinder.service.impl.MovieDaoImpl" lazy-init="true"/>
<bean id="coreMovieService" 
        class="org.anyframe.plugin.core.moviefinder.service.impl.MovieServiceImpl"/>

- 모든 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 속성에 정의할 수 있는 값은 다음과 같다.

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

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

<bean id="coreMovieService"
    class="org.anyframe.plugin.core.moviefinder.servicce.impl.MovieServiceImpl" 
    autowire="byType" depends-on="coreMovieDao">
</bean>

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

  • XML 파일 크기 줄어듬

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

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

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

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

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

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

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

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

    [표제목-dependency-check 속성값 목록]
    모드설명
    none[기본 설정] 의존성 확인 안 함. 참조관계의 Bean이 존재하지 않는 경우 Property 설정 안 함
    simplePrimitive Type과 collection을 위해 의존성 확인 수행
    object참조관계의 Bean을 위해 의존성 확인 수행
    allsimple과 object 모드를 모두 수행

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

    <bean id="coreMovieService" 
        class="org.anyframe.plugin.….MovieServiceImpl" dependency-check="object">
        property name="coreMovieDao" ref="coreMovieDao" />
    </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-core.xml 의 일부이다.

<bean id="coreMovieService"
    class="org.anyframe.plugin.core.moviefinder.service.impl.MovieServiceImpl">
    <!-- method injection -->
    <lookup-method name="getMovieDao" bean="coreMovieDao"/> 		
</bean>

<!-- change scope from singleton to prototype (non singleton) -->
<bean id="coreMovieDao" 
        class="org.anyframe.plugin.core.moviefinder.service.impl.MovieDao" scope="prototype"/>

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

public class MovieServiceImpl … {
    public MovieDao getMovieDao(){
        // 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 속성값

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

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


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

<bean id="coreMovieService" 
        class="org.anyframe.plugin.core.moviefinder.service.impl.MovieServiceImpl" 
        scope="singleton”>
    <property name="coreMovieDao" ref="coreMovieDao" />
</bean>
<bean id="coreMovieDao" 
        class="org.anyframe.plugin.core.moviefinder.service.impl.MovieDao”>
    중략...
</bean>

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


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

<bean id="coreMovieService" 
        class="org.anyframe.plugin.core.moviefinder.service.impl.MovieServieImpl"
        scope="prototype”>
    <property name="coreMovieDao" ref="coreMovieDao" />
</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/>와 함께 작성해야 한다.(아래의 예시 참고)

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

    <!-- a HTTP Session-scoped bean exposed as a proxy -->
    <bean id="moviePreferences"
            class="org.anyframe.plugin.core.moviefinder.service.impl.MoviePreferences" 
            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="coreMovieService" 
            class="org.anyframe.plugin.core.moviefinder.service.impl.MovieServiceImpl">
        <!-- a reference to the proxied 'moviePreferences' bean -->
        <property name="moviePreferences" ref="moviePreferences"/>
    </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-core.xml 의 일부이다.

<bean id="coreMovieService"
        class="org.anyframe.plugin.core.moviefinder.service.impl.MovieServiceImpl" 
        init-method="movieInitialize" destroy-method="movieDestroy" parent="parent">
</bean>

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

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


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

<bean id="coreMovieService"
        class="org.anyframe.plguin.core.moviefinder.service.impl.MovieServiceImpl" 
        init-method="movieInitialize" destroy-method="movieDestroy"
        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-core.xml 의 일부이다.

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

<bean id="coreMovieService" 
        class="org.anyframe.plugin.core.moviefinder.service.impl.MovieServiceImpl" 
        init-method="movieInitialize" destroy-method="movieDestroy" 
        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-core.xml 의 속성 정의 부분이다.

    <!-- set file locations --> 
    <bean id="configurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>MovieConfigurer.properties</value>
            </list>
        </property>
    </bean>
     
    <bean id="coreMoiveService" 
            class="org.anyframe.plugin.core.moviefinder.service.impl.MovieServiceImpl">
        <property name="coreMovieDao" ref="coreMovieDao" />
        <!-- set movieTitle value using key name in properties file -->
        <property name="movieTitle" value="${movie.title}"></property>
    </bean>

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

    movie.title=Shrek   

  • 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-moviemgmt</value></list>
    </property>
</bean>

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

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

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

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

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

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

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

  • Built-in Events

    [표제목-Built-in Event 목록]
    이벤트설명
    ContextRefreshedEventApplicationContext가 초기화되거나 갱신(refresh)될 때 발생하는 이벤트 - 여기서 초기화는 모든 Bean이 로드되고 Singleton Bean들은 미리 인스턴스화되며 ApplicationContext는 사용할 준비가 된다는 것을 의미함
    ContextClosedEventApplicationContext의 close()메소드를 사용하여 ApplicationContext가 종료될 때 발생하는 이벤트 - 여기서 종료는 Singleton Bean들이 소멸(destroy)되는 것을 의미함
    RequestHandledEventHTTP 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-core.xml 파일의 일부이다.

    <bean id="movieEventListener" 
        class="org.anyframe.plugin.core.moviefinder.service.impl.MovieEventListener"/>

    다음은 Custom Event인 MovieEvent를 처리하고 있음을 알 수 있다.

    public class movieEventListener implements ApplicationListener {
        public void onApplicationEvent(ApplicationEvent evt) {
            if (evt instanceof MovieEvent) {
                MovieEvent event = (MovieEvent)evt;
                System.out.println("Received in MovieEventListener : " + 
                    event.getMovieMessage());
            }
        }
    }

    다음은 Custom Event인 MovieEvent를 발생시키는 부분이다.

    this.ctx.publishEvent(new MovieEvent(this,"new movie is added successfully."));

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

대부분의 전형적인 어플리케이션 구축 시에는 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-4.0.xsd
            http://www.springframework.org/schema/util 
            http://www.springframework.org/schema/util/spring-util-4.0.xsd
            http://www.springframework.org/schema/jee 
            http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
            http://www.springframework.org/schema/lang 
            http://www.springframework.org/schema/lang/spring-lang-4.0.xsd
            http://www.springframework.org/schema/jms 
            http://www.springframework.org/schema/jms/spring-jms-4.0.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
            http://www.springframework.org/schema/tx 
            http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
            http://www.springframework.org/schema/context 
            http://www.springframework.org/schema/context/spring-context-4.0.xsd">
            <!-- <bean/> definitions here -->
    </beans>

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

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





출처 - http://dev.anyframejava.org/docs/anyframe/plugin/essential/core/1.6.1-SNAPSHOT/reference/html/ch01.html#core_spring_ioc_dependencies










IOC (Inversion of Control)

역제어라는 뜻으로 제어권의 반환을 뜻한다.

기존의 개발자들이  New 연산자, 인터페이스 호출, 팩토리 호출방식으로
객체의 인스턴스를 생성함으로 인스턴스 생성 방법에 대한 제어권을 개발자들이 가지고 있었다.
IOC 란 인스턴스 생성의 제어를  개발자 본인이 아닌 다른 누군가에게 반환 준다는 개념이다.
여기서 말하는 다른 누군가란 EJB, Servlet 등 bean을 관리해 주는 컨테이너이다.
즉 IOC 란 인스턴스의 생성부터 소멸까지의 인스턴스의 생명주기 관리를 내가 아닌 컨테이너가 대신 해준다는 뜻이다.

 

Spring 컨테이너

Spring 컨테이너는 IOC를 지원한다. Spring 컨테이너란 beans 를 관리하고 애플리케이션 중요 부분을 형성한다. 
즉 Spring 컨테이너는 메타데이터(xml 설정)를 통해  bean를 인스턴스화 하고 이를 조합하여 관리하는 역할을 한다. 
컨테이너는 관리되는 bean 들을 의존성 삽입(Dependency Injection)을 통해 IOC 를 지원한다.  


Spring Container 


DI (Dependency Injection)
DI(의존성 삽입) 은 Spring 컨테이너가 IOC 를 지원하는 새로운 형태다.

DI는  클래스 사이의 의존관계를 빈 설정정보를 바탕으로 컨테이너가 자동적으로 연결해 주는 것을 의미한다.
Spring 컨테이너가 지원하는 DI 는 두가지 유형이 있다.

 

- Setter Injection
Setter Method를 명시하여 자동적으로 의존성 삽입이 이루어 지는 유형

 

- Constructor Injection

인자를 가지고 있는 생성자를 호출 할때 의존성 삽입이 이루어 지는 유형


참조 - http://blog.naver.com/jiruchi/10025194984



[출처] Spring IOC 란??|작성자 jiruchi

Posted by linuxism
,