Spring Framework 소스코드 CVS로 다운받기

Spring 소스 SVN으로 다운받기를 시도해본 적이 있는데 오늘 갑자기 그 때 일이 다시 생각납니다. Spring 소스 코드를 CVS로 밖에 제공하지 않더군요. Eclipse에 SVN으로 checkout이나 commit은 종종 해봤지만 CVS는 해본적이 없어서 일단은 SVN repository와 같아 보이는 CVS repository를 열었습니다.
1343883341.bmp
CVS+ 라는 아이콘을 클릭하고 소스코드가 있는 곳의 주소를 넣어줘야 될 것 같습니다.
1058144926.bmp위와 같이 입력하고 finish 를 하면 CVS repository에 다음과 같이 저장소가 하나 잡힌 것을 확인 할 수 있습니다. 아마도 Spring이겠죠. Spring을 체크아웃 받습니다.

1253626790.bmp이 안에 sample 코드도 많이 들어있고 spring 소스파일들은 src 폴더 안에 들어있습니다.

참고 : http://sourceforge.net/cvs/?group_id=73357

SqlMapClientDaoSupport

1181804571.bmp
MySQL에 만들어둔 Member 테이블에 iBatis를 사용해서 SQL을 날리는 Member.xml에 있는 sql의 ID를 호출하는 SqlmapMemberDao가 상속받고 있는 SqlMapClientDaoSupport에 대해서 조사해 봅니다.

스프링 레퍼런스 12.5
를 참고하면 다음과 같은 표를 볼 수 있습니다.
1363766640.bmp
저는 iBATIS 2,0을 사용하기에 DAO 클래인 SqlmapMemberDao가 SqlMapClientDaoSupport 클래스를 상위 클래스로 상속받도록 했습니다.
1072221328.bmp
그리고 SqlMapClientDaoSupport에서 사용되는 SqlMapClient 객체를 DI(dependency injection)하기 위해 daocontext-member.xml에 다음과 같이 코딩합니다.
1361370266.bmpsqlMapClient 객체는 사용할 SqlMap을 다음과 같이 sql-map-config.xml 에 코딩해 둡니다.
1122443480.bmp여기서 사용하기로 한 Member.xml 파일을 보면 다음과 같습니다.
[#M_ more.. | less.. |
<?xml version=”1.0″ encoding=”UTF-8″?>
<!DOCTYPE sqlMap
PUBLIC “-//iBATIS.com//DTD SQL Map 2.0//EN” “http://www.ibatis.com/dtd/sql-map-2.dtd”>

<sqlMap namespace=”Member”>

   <!– alias –>
   <typeAlias alias=”member” type=”net.webapp2.member.domain.Member” />

   <!– result map –>
   <resultMap id=”memberMap” class=”member”>
       <result property=”id” column=”id” />
       <result property=”name” column=”name” />
       <result property=”messengerId” column=”messengerId” />
       <result property=”email” column=”email” />
       <result property=”blugAddress” column=”blugAddress” />
       <result property=”phone” column=”phone” />
   </resultMap>

   <!– insert –>
   <insert id=”insert” parameterClass=”member”>
       INSERT INTO Member(name, email
       <isNotNull property=”messengerId”>, messengerId</isNotNull>
       <isNotNull property=”phone”>, phone</isNotNull>
       <isNotNull property=”blugAddress”>, blugAddress </isNotNull>)
       VALUES(#name#, #email#
       <isNotNull property=”messengerId”>, #messengerId#</isNotNull>
       <isNotNull property=”phone”>, #phone#</isNotNull>
       <isNotNull property=”blugAddress”>, #blugAddress#</isNotNull>);
   </insert>

   <!– select –>
   <select id=”count” resultClass=”int”>
       SELECT COUNT(id)
       FROM Member;
   </select>

   <select id=”getMember” resultMap=”memberMap”>
       SELECT id, name, messengerId, email, blugAddress, phone
       FROM Member
       WHERE id = #id#;
   </select>

   <select id=”getByName” resultMap=”memberMap”>
       SELECT id, name, messengerId, email, blugAddress, phone
       FROM Member
       WHERE name = #name#;
   </select>

   <select id=”getAllMember” resultMap=”memberMap”>
       SELECT id, name, messengerId, email, blugAddress, phone
       FROM Member;
   </select>

   <!– update –>
   <update id=”update” parameterClass=”member”>
       UPDATE Member
       <dynamic prepend=”SET”>
           <isNotNull property=”name” prepend=”,”>
           name = #name#
           </isNotNull>
           <isNotNull property=”messengerId” prepend=”,”>
           messengerId = #messengerId#
           </isNotNull>
           <isNotNull property=”email” prepend=”,”>
           email = #email#
           </isNotNull>
           <isNotNull property=”blugAddress” prepend=”,”>
           blugAddress = #blugAddress#
           </isNotNull>
           <isNotNull property=”phone” prepend=”,”>
           blugAddress = #blugAddress#
           </isNotNull>
       </dynamic>
       WHERE id = #id#;
   </update>

   <!– delete –>
   <delete id=”deleteById” parameterClass=”int”>
       DELETE from Member WHERE id = #id#
   </delete>

</sqlMap>
_M#]
이렇게 설정파일에 해당하는 iBatis Sql Map 2.0 과 JDBC인 datasource를 SqlMapClientFactoryBean 타입의 객체인 sqlMapClient의 속성으로 DI 시킵니다.
[#M_ more.. | less.. |
<?xml version=”1.0″ encoding=”UTF-8″?>
<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.xsd”>

   <!– DataSource –>
   <!– MySQL –>
   <bean id=”dataSource”
       class=”org.apache.commons.dbcp.BasicDataSource”
       destroy-method=”close”>
       <property name=”driverClassName” value=”com.mysql.jdbc.Driver” />
       <property name=”url” value=”jdbc:mysql://localhost:3306/adressbook?autoReconnect=true” />
       <property name=”username” value=”keesun” />
       <property name=”password” value=”keesun” />
   </bean>

   <!– SqlMap setup for iBATIS Database Layer –>
   <bean id=”sqlMapClient”
       class=”org.springframework.orm.ibatis.SqlMapClientFactoryBean”>
       <property name=”configLocation”
           value=”file:conf/sql-map-config.xml” />
       <property name=”dataSource” ref=”dataSource” />
   </bean>

   <!– Transaction Manager –>
   <bean id=”transactionManager”
       class=”org.springframework.jdbc.datasource.DataSourceTransactionManager”>
       <property name=”dataSource” ref=”dataSource” />
   </bean>

</beans>
_M#]
글로 정리하다 보니까 이제 눈에 조금 보이네요.

SqlMapClientDaoSupport라는 클래스에서 SqlMapClient 객체를 사용하기 때문에 이것을 와이어링 해주는데.. SqlMapClieint는 org.springframework.orm.ibatis.SqlMapClientFactoryBean 이 타입이며 SqlMapClientFactoryBean을 정의 할때는 configLocation 이것과 dataSource 를 와이어링 해줘야 한다.

configLocation은 이곳에서 사용할 iBATIS sql map에 대한 정보를 가지고 있는 sql-map-config.xml 파일로 지정해주고 sql-map-config.xml에는 member.xml파일이 지정되어 있고 member.xml은 iBATIS를 사용하고 있다.

datasource는 db connection객체를 생성하기 위한 네 가지 정보에 값을 setting한다.

BeanFactory 인터페이스 살펴보기

먼저 org.springframework.beans.factory.BeanFactory가 책임지는 것은 무엇들이 있는지 살펴 보겠습니다.

BeanFactoryAPI를 살펴보겠습니다. 일반적으로BeanFactory XML 같은configuration data 안에 저장된 Bean에 대한 정의들을 읽어 들이고, org.springframwork.beans 패키지를 사용하여 Bean을설정합니다.

BeanFactory 인터페이스가 가지고 있는 필드 입니다.

Field Summary

static String

FACTORY_BEAN_PREFIX
         Used to dereference a FactoryBean and distinguish it from beans created by the FactoryBean.

BeanFactory 인터페이스 안에 들어있는 메소드들은 다음과 같습니다.

Method Summary

boolean

containsBean(String name)
         Does this bean factory contain a bean definition with the given name?

String[]

getAliases(String name)
         Return the aliases for the given bean name, if defined.

Object

getBean(String name)
         Return an instance, which may be shared or independent, of the given bean name.

Object

getBean(String name, Class requiredType)
         Return an instance (possibly shared or independent) of the given bean name.

Class

getType(String name)
         Determine the type of the bean with the given name.

boolean

isSingleton(String name)
         Is this bean a singleton?

메소드들은 대강 어떤 일을 책임을 지고 있는지 알겠는데 맨 위에 있는 필드 하나가 잘 모르겠네요.

org.springframework.beans.factory.BeanFactory
public static final String FACTORY_BEAN_PREFIX "&"

Used to dereference a FactoryBean and distinguish it from beans created by the FactoryBean. For example, if the bean named myEjb is a FactoryBean, getting &myEjb will return the factory, not the instance returned by the factory.

dereference라는 단어는 처음보네요 ^^;; reference는 수도없이 봐왔는데. FactoryBean과 FactoryBean에 의해 생성된 bean을 구분하기 위해 사용한다고 하는군요. 예를 든 것을 보면 빈 이름이 myEjb이면 FactoryBean이고 &myEjb는 factory에 의해 반환되는 객체가 아니라 factory를 반환한다.

지쟈스… 무슨 말인지… Someboby PLZ HELP~

Spring ApplicationContexts

Spring ApplicationContexts

Spring 에플리케이션의 심장과 정신에 해당하는 ApplicationContext 안에서 실제 DI를 수행합니다.

ApplicationContext는 BeanFactory를 특화시킨 것입니다.(ApplcationContext가 BeanFacotory를 상속받았죠.) BeanFactory는 Spring에서 사용되는 객체들의 레지스트리입니다. BeanFactory는 bean의 생성과 그들 간의 종속성 주입, 그리고 bean lookup(찾아 주기)를 담당합니다.

ApplicationContext는 이러한 BeanFactory의 기능에 부가적인 기능을 추가시킨 것입니다. 보통 BeanFactory대신에  이것을 사용한답니다. 웹 에플리케이션의 경우 웹에 특화된 WebApplicationContext를 사용합니다. ApplicationContext는 BeanFactoryPostProcessors를 가지고 초기화를 거친 후에 BeanFactory를 자동으로 처리할 수 있다고 합니다.(뭐가 어떻게 돌아간다는 건지는 잘 모르겠네요–;) 이밖에도 메시지를 internationalication(국제화)하는 기능, loosely coupled(약하게 결합된) 생산자와 소비자를 위한 event-routing mechanism, ApplicationContextAware같은 생명주기 인터페이스를 지원합니다.

ApplicationContext는 주로 XML 파일을 사용하여 bean들을 설정합니다. 매우 간단한 applicationContext.xml파일을 보겠습니다.

위 설정 파일에서 두 개의 bean을 등록한 것을 볼 수 있습니다. 이 bean들을 생성하고 관리하는 것이 ApplicationContext가 할일 입니다. 저 위의 <property> 속성을 보시면 Dependency Injection을 정의해 둔 것을 볼 수 있습니다. cashRegister bean에서 priceMatrix 레퍼런스가 priceMatrixBean을 사용하도록 정의한 것입니다.

위 설정 파일 없이도 그러한 것은 다음 순수 java code로도 가능합니다.

The Return of the POJO

이전 글과 이번 글에 걸쳐 DI와 ApplicationContext에 대해 배웠습니다. 이 두 개념은 Spring의 핵심 개념입니다. 이 것들을 에플리케이션(심지어 웹 에플리케이션 마저)을 Plain Old Java Object(POJO)로 개발 하기 위한 도구 입니다. 이것이 가능하기 때문에 강력한 Object Oriented 개념을 웹 에플리케이션 개발에 사용할 수 있습니다. 프레임웤에 특화된 코드를 작성하기 보다 비즈니스 로직에만 집중하여 개발하기 때문입니다. 이 책에서는 앞으로 도메인 객체를 먼저 작성하고 그런 다음 프레임웤을 사용하여 그것들을 웹 에플리케이션으로 향상시도록 할 것입니다.

여기까지 1장을 모두 요약하자면

  • Dependency Injection은 Inversion Of Control의 한 종류이다. 이것은 코드에 종속성을 주입하는 원리이다. 이것을 사용하면 객체 생성 또는 그것을 위치시키는 일을 프레임웍에게 전가 시키기 때문에 코드 자체의 테스트가 용이해진다.
  • ApplicationContext는 Spring의 주요 객체 레지스트리와 통합 포인트다. 주로 XML파일을 통해서 bean과 그들의 종속성을 설정한다. 많은 기능이 있지만 그 중에 객체 생성과 DI가 핵심 기능이다.
  • 마지막으로 POJO 스타일로 개발이 가능하다는 것이다. 따라서 객체 지향 개발 기술의 사용이 가능하며 POJO로 개발 된 시스템은 테스트가 용이하고 유연하다. 또한 개발자들이 프레임웤을 어떻게 다루는지 보다는 문제 영역과 비즈니스 로직에 집중하여 개발이 가능해진다.

Inversion of Control

Inversion of Control

Dependency Injection(의존성 주입)과 혼용되어 사용되는 것을 종종 보았는데 이 글을 읽어보니 어느정도 명확해 지네요. IOC가 보다 광범위한 의미이고 이 것을 표현하는 여러가지 방법이 있습니다. DI도 그중에 하나라고 하네요. 이번 장에서 말하고 있기로는 AOP와 DI가 IOC의 일부라고 합니다.

먼저 간략히 정의를 살펴보면 IOC는 braod range of techniques that allow an object to become a passive participant in the system. IOC를 사용하면 객체의 몇몇 특성이나 시점(aspect)에 대한 제어권을 프레임웍이나 환경에게 넘기게 됩니다. 그러한 제어권에는 객체의 생성이나 의존하는 객체들의 임명 등이 있습니다.

AOP

먼저 AOP를 사용하게 되는 경우를 살펴보겠습니다.

여기 보시면 주황색 부분의 코드(인증 관련 코드)가 매번 중복되어 나타나는 것을 볼 수 있습니다.
사실 인증은 메소드 본래의 작업이 아닌 부가적인 작업에 불과함에도 본래 코드보다 많은 부분을 차지 할 뿐만 아니라 계속해서 반복되어 나타나고 있습니다.

이 코드를 시스템 뒤딴으로 빼려면 Spring의 AOP(Aspect-Oriented Programming)를 사용하서 간단히 해결할 수 있습니다. Aspects are concerns of the application that apply themselves across the entire system. Aspect란 전체 시스템에서 통용되어 사용되는 에플리케이션과의 관계라고 할 수 있나요…흠; 어렵네요. 위 예제에서는 SecurityManager가 시스템에서 통용되는 aspect의 하나의 예라고 할 수 있겠네요. 그리고 통용된다는 증거로 모든 BankAccount의 모든 메소드에서 hasPermission 메소드를 호출하고 있습니다. 이런 비슷한 aspect로는 logging, auditing, transaction management가 있습니다. 이 중에 auditing은 뭔지 잘 모르겠네요.


Spring AoP는 이러한 aspect들을 runtime 때 또는 compile 때 도메인 모델(여기선 BankAccount)에 끼워넣습니다.(보통 weaving 한다고 합니다.) 이 말은 BankAccount에서 위와 같이 주황색 부분의 코드를 전부 지워도 AOP 프레임웤이 이 전과 똑같이 동작하도록 도와준다는 것입니다.

Spring은 기본적으로 proxy-based AOP(프록시 기반 AOP)를 사용합니다. 이 말은 원래 목표가 되는 객체(targer object)를 – 여기서는 BankAccount가 되겠죠- Aspect를 적용하기 위해서 Proxy로 감싸서 목표 객체 대신에 사용한다는 것입니다.  다음의 그림을 보시면 이해가 될 것입니다.

여기서 잠깐 Spring은 프록시가 아닌 컴파일 할 때 끼워넣는 AspectJ를 지원한다는 점. 그리고 이게 단순한 프록시를 사용하는 솔루션 보다 더 많은 기능을 가지고 있다고 합니다.


BankTeller가 colseOut을 호출 하면 이것은 BankAccount의 메소드를 호출하는 것이 아니라 BankAccount의 프록시를 호출하게 되고 이 프록시는 weaving된 aspect에 따라 3번 작업을 하고 그리고 본래의 작업인 4번을 BankAccount를 이용하여 처리합니다.

여기까지 정리

  • IOC는 제어권을 프레임웤에 넘기는 포괄적인 개념이다.
  • 여기서 제워권이란 새로운 객체의 생성, 트랜잭션, 보안에 대한 제어들을 이야기 한다.
  • AOP는 IOC를 실현하는 하나의 기술이다.
  • DI또한 IOC를 실현하는 하나의 기술이다.

Dependency Injection

DI는 Spring 프레임웤의 핵심 기술입니다. DI is a technique that framework use to wire together an application. 프레임웤은 에플리케이션 간의 종속성을 연결하는 작업을 하여 에플리케이션에 있는 객체 생성을 하는 코드를 제거하는 일을 합니다.

DI와 Service Locator pattern을 비교할 수 있는데.. 그 둘을 비교하기 위해 간단한 예제를 보겠습니다. 이 예제는 쇼핑카트에 있는 물품들을 계산을 하는데 DB로 부터 그 물품들의 가격을 가져와서 합산하는 프로그램입니다. 따라서 먼저 간단한 인터페이스를 하나 작성을 합니다. 이 인터페이스는 CashRegister로써 쇼핑 카트를 매개변수로 받아서 그 안에 있는 물품을 계산하는 calculateTotalPrice 메소드가 있습니다. 그리고 PriceMatrix 인터페이스에는 lookup 메소드가 있어서 원하는 물품의 가격을 반환해주는 일을 합니다.


그리고 CashRegister를 구현한 CashRegisterImpl을 봅시다.


어쩌면 이렇게 한 것이 매우 자연스럽지만 다음과 같은 세가지 중요한 문제가 있습니다.
먼저 가장 중요한 것으로 인터페이스가 아닌 특정 구현에 의존한다는 것입니다. 이 말은 H.F. Degisn Pattern 1장에 나오는 디자인 원칙이며 위 코드는 그것을 어겼습니다. 특정 구현에 의존하게 되면 왜 안좋은지에 대한 것은 위에서 확인하시기 바랍니다.
둘째 PriceMatrixImpl 객체가 CashRegisterImpl을 생성 할 때 마다 매번 생성된다는 것입니다. 이것은 문제가 있지요. 가격표는 하나만 있으면 되는데 매번 계산할 때 마다 새로운 가격표를 생성한다는 것은 자원을 낭비하는 일입니다.
마지막으로 첫번째 발생한 일에 딸려 오는 문제로써 테스트하기가 힘들다는 것입니다. CashRegister를 테스트하기 위해서는 지금 PriceMatrixImpl까지 제대로 구현으르 해야 테스트가 가능합니다. 그리고 단위 테스트는 아예 할 수가 없는 상황이네요.

그럼 PriceMatrix없이 테스트가 가능하기나 하냔 말인가가 궁금한데요. 저도 잘 모르는 부분인데 Mock이라는 기술을 사용하면 단위 테스트가 가능하다고 합니다.

Service Locator를 사용해 봅시다.

Service Locator Pattern은 객체를 생성하여 그 레퍼런스를 얻어내는 과정을 숨기는 것을 말합니다. 클라이언트를 객체가 언제, 어디서 어떻게 생성되는지 모르도록 하는 패턴입니다. 클라이언트를 보호하고 코드 중복을 줄이기 위해서 이 패턴이 만들어졌다고 합니다. 보통 static 메소드를 사용하여 요구된 객체에 대한 하나의 객체를 반환해 줍니다. (싱글턴 패턴하고 다른게 뭐죠? 첨에는 static 팩토리인가.. 생각했다가 오직 하나의 클래스에 대한 하나의 객체만 리턴 해주면 이거 싱글턴 아닌가.. 하는 생각이 들었습니다.)


이렇게 함으로써 첫번째 문제(특정 구현에 의존하던 문제)와 두번째 문제(매번 새로운 객체를 생성하던 문제)가 해결되었습니다. 하지만 세번째 단위 테스트를 하려면 Mock 객체를 사용해야 하는데 Mock객체를 끼워넣을 방법이 없습니다. 왜내면 저 위의 주황색 부분이 test할 때는 Mock 객체를 넘겨주고 실제 사용할 때는 PriceMatrix객체를 주도록 바꾸기가 어렵기 때문입니다. 이런것을 효율적으로 하려면 클라이언트 코드에서 자원을 가져오거나 생성하는 활동에 전혀 참여하면 안됩니다. 그냥 자원이 클라이언트에 주어져야 합니다.

Dependency Injection을 사용해 봅시다.

Service Locator를 사용하는 대신에 프레임웤이 PriceMatrix type 객체의 레퍼런스를 CashRegisterImpl에 제공해 줍니다.  객체 생성과 객체 연결(location)에 대한 책임이 클래스에서 프레임웤으로 뒤집어졌습니다. 이러한 것을 DI라고 하며 두 가지 방법이 흔히 사용됩니다.

Spring AOP의 도움을 받는 method-based injection이라고 불리는 세번째 방법도 있으나 복잡하고 잘 쓰이질 않아서 여기서 다루진 않는답니다.

처음으로 볼 DI의 한 종류로는 constructor-based injection이 있습니다. 이 것은 객체가 생성될 때 종속성을 주입하는 방법입니다.


이렇게 하면 된거죠. 하지만 이 클래스는 Hollywood Principle을 따르고 있는데요. 할리우드 원칙이란 “내가 전화할테니 전화하지 마!”라는 원칙입니다. 다시 이 경우에 대입해 본다면 “내가 줄테니 자원을 달라고 요청하지마!” 라고 할 수 있겠습니다.

다음은 Setter-based injection입니다.


PriceMatrix type의 객체 생성 시기가 좀더 유연해 졌습니다. 생성자 기반과 세터 기반 중에 어떤 것을 사용할 지는 사용자의 선택입니다. 물론 어떤 경우를 선택하느냐에 따라 실제 종속성을 주입할 때 따르게 되는 지침에 해당하는 XML 파일의 내용이 약간 바뀔 것입니다.
이 것은 생성자 기반 종속성 주입 시에 사용할 XML 설정 파일의 일부이며
이 것은 세터 기반 종속성 주입 시에 사용할 XML 설정 파일의 일부입니다.

첫번째 문제-특정 구현에 의존했었던-는 해결된 듯 합니다.
두번째 문제-매번 새로운 객체를 생성했던-도 역시 bean이 기본적으로 싱클턴 객체로 생성되기 때문에 해결 된 듯합니다.
그럼 남은것은 세번째 문제-단위 테스트를 할 수 없었던-것인데요.
이렇게 하면 Service Locator에서 해결 못했던 Mock을 사용한 단위 test를 어떻게 할 수 있을까요?

처음 본거라 저도 잘 모르겠지만 다음과 같이 Mock을 사용한다고 합니다.

여기까지 정리하면..

  • DI는 종속성을 필요로 하는 코드 없이 여러 클래스들을 묶는데 사용하는 기술입니다.
  • 클라이언트는 프레임웤에게 종속성의 생명주기 관리를 넘겼습니다.
  • 이렇게 함으로써 클라이언트는 테스트하기 용이해 집니다.