제1부스프링핵심개념 스프링이제공하는기능은매우다양하지만핵심을파고들어가보면스프링의주요기능은종속객체주입 (DI: Dependency Injection) 과애스펙트지향프로그래밍 (AOP: Aspect-Oriented Programming) 으로귀결된다. 1장 스프링속으로 에서는 DI와 AOP의기본적인개념을살펴보고, 이두기능이애플리케이션객체간의결합도를줄이는데어떻게기여하는지알아본다. 2장 빈와이어링 에서는 DI를이용해애플리케이션객체간의결합도를줄이기위해스프링의 XML 기반설정을사용하는방법에대해좀더깊이있게살펴본다. 여기서애플리케이션객체를정의하고의존객체와연결하는방법을배운다. XML이스프링을설정할수있는유일한방법은아니다. 2장에서배운내용에이어 3장 XML 설정최소화 에서는최소한의 XML( 또는전혀 XML을사용하지않아도 ) 애플리케이션객체연결을가능하게하는스프링의몇가지새로운기능을살펴본다. 4장 애스펙트지향스프링 에서는스프링의 AOP 기능을이용해시스템전반에걸친서비스 ( 보안이나감사등 ) 와그서비스를받는객체간의결합도를줄이는방법을살펴본다. 이장은스프링 AOP를이용해선언적트랜잭션과보안을제공하는방법을배우게될 6장과 9장의기반이되는장이다.
1 장스프링속으로 1장에서다룰내용스프링의코어모듈살펴보기애플리케이션객체간의결합도줄이기 AOP를이용해횡단관심사관리하기스프링의빈컨테이너 모든것은빈 (bean) 에서시작됐다. 1996년, 자바 (Java) 라는프로그래밍언어는아직참신하고, 흥미진진하고, 전도유망한언어에불과했다. 애플릿을이용해풍부하고동적인웹애플리케이션을만드는데매료되어자바진영으로모여들었던많은개발자는얼마지나지않아자바언어의가능성이저글링하는만화캐릭터를그려내는정도에그치지않는다는것을알게됐다. 이전의다른언어와달리자바는개별컴포넌트로이뤄진복잡한애플리케이션을만들수있게해줬다. 애플릿에이끌려자바를찾아왔던개발자들은컴포넌트에매료되어남게됐다. 그해 12월, 썬마이크로시스템즈가자바를위한소프트웨어컴포넌트모델을정의한자바빈즈 (JavaBeans) 1.00-A 명세를발표했다. 이명세는자바객체를재사용이가능하게하고단순한자바객체를이용해복잡한애플리케이션을쉽게구성할수있게해주는일련의코딩정책을정의한것이었다. 자바빈즈는이처럼재사용할수있는애플리케이션컴포넌트를정의하는범용수단으로사용하려는의도로만들어졌지만, 실제로는주로사용자인터페이스위젯을만드는데사용되고있었다. 실질적인 작업을하기에는너무단순해보였던것이다. 엔터프라이즈개발자들은그이상의것을원했다. 3
4 1 장스프링속으로 첨단애플리케이션은자바빈즈명세에서직접적으로제공하지않는트랜잭션지원이나보안, 분산컴퓨팅등의서비스를필요로하는경우가많다. 그래서 1998년썬 (Sun) 은엔터프라이즈자바빈즈 (EJB: Enterprise JavaBeans) 명세 1.0을발표했다. 이명세는자바컴포넌트개념을서버측으로확장해엔터프라이즈서비스를제공한것이었지만, 원래자바빈즈명세가가졌던간결함을그대로유지하지는못했다. 사실 EJB는이름말고는자바빈즈명세와유사한점이전혀없다. EJB를기반으로한많은애플리케이션이성공적으로구축되어왔지만, EJB는엔터프라이즈애플리케이션개발을간소화하겠다는본래의취지에전혀부합하지못했다. EJB의선언적프로그래밍모델이트랜잭션이나보안같은개발의기반구조를상당부분간소화해준것은사실이지만, 배포디스크립터 (deployment descriptor) 나홈인터페이스, 리모트인터페이스, 로컬인터페이스등의코드때문에개발이복잡하고어려워진다. 시간이지나면서점점더많은개발자가 EJB에환멸을느끼게됐다. 결과적으로최근에는 EJB의인기가시들해지고개발자는좀더쉬운방법을찾아나섰다. 이렇게해서이제자바컴포넌트개발은다시처음으로돌아왔다. 애스펙트지향프로그래밍 (AOP: Aspect-Oriented Programming) 과종속객체주입 (DI: Dependency Injection) 역주1 같은새로운프로그래밍기법들덕분에이전에는 EJB로만가능했던것들을자바빈으로도할수있게됐다. 사용하기복잡한 EJB를쓰지않아도평범한자바객체 (POJO: Plain-Old Java Object) 로도 EJB 같은선언적프로그래밍모델이가능해진것이다. 간단한자바빈으로도충분하다면더이상불편한 EJB 컴포넌트를사용할필요가없다. 1) 이렇게되자, 당연한일이지만 EJB도 POJO 지향프로그래밍모델을사용할수있도록변화했다. DI와 AOP 개념을채택하면서 EJB는예전보다훨씬간결해졌다. 그러나대부분의개발자들이느끼기에는변화의폭이너무작고느렸다. EJB 3 명세가발표됐을때 POJO 기반의다른개발프레임워크들은이미자바커뮤니티에서사실상의표준으로자리잡고있었다. 우리가이책을통해살펴볼스프링프레임워크는바로이러한가벼운 POJO 기반개발진영을 역주 1 종속객체주입, 즉 DI(Dependency Injection) 는스프링에서가장기본이되면서도매우중요한의미를갖는다. 기존에가장많이쓰이던표현은 의존성주입 이었다. 하지만이표현은 없는의존성을만들어주입한다 는오해를일으키고, 이때문에 DI의이해가어려웠다고생각한다. DI는없는의존성을주입하는것이아니라의존성은이미존재하되, 실제객체가필요로하는종속객체를주입하는것을의미한다. 따라서 의존성주입 보다뜻이명확하도록 종속객체주입 이라는표현을사용하겠다. 또이책에서는원어의억지스러운번역보다는쉽고자연스럽게받아들일수있는번역을택했음을미리밝혀둔다.
1.1 자바개발간소화 5 진두지휘하고있다. 이장에서는우선스프링이어떤것인지전체적인감을잡고이책의나머지부분을이해하는데필요한정보를얻을수있도록스프링프레임워크를개략적으로소개할것이다. 이장을읽고나면스프링이어떤종류의문제를해결해주는지파악할수있을것이다. 중요한것부터먼저하는게일의순서가아닐까! 자, 이제스프링의모든것을알아보자. 1.1 자바개발간소화 스프링 (Spring) 은로드존슨 (Rod Johnson) 이그의책 Expert One-on-One: J2EE Design and Development ( Expert One-on-One J2EE 설계와개발, 정보문화사 ) 를통해소개한오픈소스프레임워크로서엔터프라이즈애플리케이션개발의복잡함을겨냥해만들어졌다. 스프링은 EJB로만할수있었던작업을평범한자바빈을사용해서할수있게해준다. 스프링이서버측개발에만유용한것은아니다. 간소함, 테스트의용이함, 낮은결합도라는견지에서보면모든자바애플리케이션이스프링의덕을볼수있다. 다른이름의빈 (bean) 스프링에서는애플리케이션컴포넌트를가리켜 빈 (bean) 과 자바빈 (JavaBean) 이라는용어를자유롭게사용하고있지만, 이것이꼭자바빈규약 (JavaBeans specification) 전체를문자그대로지켜야한다는의미는아니다. POJO(Plain-Old Java Object, 평범한자바객체 ) 라면어떤것이나스프링의컴포넌트가될수있다. 이책에서는느슨한수준의자바빈정의를사용하며, POJO 와동의어라생각해도괜찮다. 이책을통해스프링의다양한내용을살펴보겠다. 하지만스프링이제공하는거의모든기본사상은몇가지기초적인개념으로귀결되며, 이는스프링의기본임무인 자바개발간소화 에모든초점을맞춘다. 이것이핵심키워드다! 특정기능을간소화하는프레임워크는많이존재하다. 하지만스프링의목적은자바개발을폭넓게간소화하는데있다. 이내용은더많은설명이필요해보인다. 그럼어떻게자바개발을간소화할수있을까? 자바복잡도에대한공격을지원하기위해스프링은네가지주요전략을채택했다.
6 1 장스프링속으로 POJO를이용한가볍고 (lightweight) 비침투적인 (non-invasive) 개발역주2 2) DI와인터페이스지향 (interface orientation) 을통한느슨한결합도 (loose coupling) 애스펙트와공통규약을통한선언적프로그래밍애스펙트와템플릿을통한상투적인코드축소스프링이수행하는거의모든작업은이네가지전략중하나이상에속한다. 앞으로스프링이자바개발을간소화하는방법에대한구체적인예제를살펴보면서이와같은사상을확장해나가겠다. 먼저스프링이 POJO 지향개발을통해어떻게침투적인개발을최소화할수있는지부터알아보자. 1.1.1 POJO 의힘 오랜기간자바개발경험이있다면인터페이스의구현이나클래스의확장을강요하는프레임워크를본적이있으며, 심지어이런프레임워크로작업한경험도있으리라생각된다. 전형적인예가바로 EJB 2 시대의무상태세션빈 (stateless session bean) 이다. 코드 1.1의간단한 HelloWorldBean에서보듯이 EJB 2 명세의요구사항은다소무겁다. 코드 1.1 EJB 2.1 은침략적인 API 로, 일반적으로필요하지도않은메소드를강제로구현하게한다. package com.habuma.ejb.session; import javax.ejb.sessionbean; import javax.ejb.sessioncontext; public class HelloWorldBean implements SessionBean { public void ejbactivate() { 왜이런메소드가필요할까? public void ejbpassivate() { public void ejbpassivate() { public void ejbremove() { 역주 2 침투적이란 EJB 와같이특정기술을적용했을때그기술과관련된코드나규약등이등장하는경우를말한다. 꼭필요한기능이아니지만특정기술을사용한다는이유로특정클래스, 인터페이스, API 등의코드가등장할경우를말하며, 이는개발의복잡성을가중시킬수있다.
1.1 자바개발간소화 7 public void setsessioncontext(sessioncontext ctx) { public String sayhello() { EJB 의핵심비즈니스로직 return "Hello World"; public void ejbcreate() { SessionBean 인터페이스는여러생명주기콜백메소드 ( 이러한메소드는 ejb 로시작한다 ) 구현으로 EJB의생명주기에연결시킨다. 하지만필요하지않음에도불구하고 SessionBean 인터페이스가 EJB의생명주기에강제로연결시킨다는말이더정확한표현이다. 실제로 HelloWorld Bean의대부분의코드는전적으로프레임워크를위한용도다. 묻고싶다. 누가누구를위해일하는가? 하지만 EJB 2만침략적으로다가온건아니다. 다른인기있는프레임워크인스트러츠 (Struts), 웹워크 (WebWork), 그리고태피스트리 (Tapestry) 의초기버전등도간단한자바클래스가아니라자신만의프레임워크를강요했다. 이런무거운 (heavyweight) 프레임워크는개발자에게불필요한코드가흩어져있는클래스작성을강요하고, 프레임워크에묶어두고, 테스트수행도어려웠다. 반면에스프링은 API를이용하여애플리케이션코드의분산을가능한막는다. 스프링은스프링에특화된인터페이스구현이나스프링에특화된클래스확장을거의요구하지않는다. 스프링기반애플리케이션의클래스에는스프링이사용한다는표시도거의없다. 최악의경우, 클래스에스프링의애너테이션 (annotation) 이붙지만그렇지않은경우엔 POJO다. 설명을위해코드 1.1의 HelloWorldBean 클래스를스프링이관리하는빈으로함수를다시작성하면코드 1.2와같다.
8 1 장스프링속으로 코드 1.2 스프링은 HelloWorldBean 에불합리한요구를하지않는다. package com.habuma.spring; public class HelloWorldBean { public String sayhello() { 이것이실제필요한전부다. return "Hello World"; 더나아지지않았는가? 정신없던생명주기메소드는모두사라졌다. 실제로 HelloWorldBean 은구현하지도, 확장하지도않았고, 심지어스프링 API에서어떤것도임포트하지않았다. HelloWorldBean은군살이없고, 평범하며, 모든구문은 POJO다. 간단한형태에도불구하고 POJO는매우강력할수있다. 스프링이 POJO에힘을불어넣는방법중하나는 DI를활용한조립이다. 그럼, DI를통해애플리케이션객체상호간의결합도를낮추는방법을알아보자. 1.1.2 종속객체주입 종속객체주입 (DI: Dependency Injection) 이라는문구가다소위협적으로들려서복잡한프로그래밍기술의개념이나디자인패턴이떠오를수있다. 하지만 DI는생각만큼복잡하지않다. 사실프로젝트에 DI를적용해보면코드가훨씬더간단해지고, 이해하기쉬우며, 테스트하기도쉬워진다. HelloWorld 예제보다복잡한실제애플리케이션에서는두개이상의클래스가서로협력하여비즈니스로직을수행한다. 이때각객체는협력하는객체에대한레퍼런스 ( 즉, 종속객체 ) 를얻을책임을진다. 그결과, 결합도가높아지고테스트하기힘든코드가만들어지기쉽다. 예를들어, 코드 1.3에있는 Knight 클래스를생각해보자.
1.1 자바개발간소화 9 코드 1.3 DamselRescuingKnight 는 RescueDamselQuest 만떠날수있다. package com.springinaction.knights; public class DamselRescuingKnight implements Knight { private RescueDamselQuest quest; public DamselRescuingKnight() { quest = new RescueDamselQuest(); RescueDamselQuest 에강하게결합된다. public void embarkonquest() throws QuestException { quest.embark(); 보다시피 DamselRescuingKnight는생성자안에자신의원정 (Quest) 인 RescueDamselQuest 를생성한다. 이것은 DamselRescuingKnight가 RescueDamselQuest에강하게결합되도록하며, 기사의원정출정목록을심각하게제한한다. 도움이필요한여성이있는곳에기사도있다. 하지만용을물리쳐야하거나원탁역주3 이필요하다면, 또한순회하려면 이런 그냥앉아만있어야한다. 3) 게다가 DamselRescuingKnight에대한단위테스트를작성하기도몹시어렵다. 단위테스트에서는기사의 embarkonquest() 가호출될때 quest의 embark() 메소드의호출을확인하고싶어한다. 하지만여기서는이를수행하는명확한방법이없다. 안타깝게도 DamselRescuing Knight는테스트하지못한채로남아있게된다. 결합도는머리가둘달린짐승이다. 결합도가높은코드는한편으로테스트와재활용이어렵고, 이해하기도어려우며, 오류를하나수정하면다른오류가발생하는경향이있다. 반면에전혀결합이없는코드는아무것도할수없다. 무언가쓸만한일을하려면클래스들끼리어떻게든서로알고있어야한다. 결합은필요하지만주의해서관리해야한다. 반면에 DI를이용하면객체는시스템에서각객체를조율하는제3자에의해생성시점에종속객체 (dependency) 가부여된다. 객체는종속객체를생성하거나얻지않는다. 즉, 종속객체는종속객체가필요한객체에주입된다. 역주 3 원탁 (Round Table) 은영국의전설적통치자아서왕과그기사들이앉았던둥근탁자다.
10 1 장스프링속으로 이의미를설명하기위해코드 1.4에있는 BraveKnight를살펴보자. 이클래스는용감할뿐만아니라발생할어떤종류의원정도떠날수있다. 코드 1.4 BraveKnight 는부여된어떤 Quest 도충분히감당할정도로유연하다. package com.springinaction.knights; public class BraveKnight implements Knight { private Quest quest; public BraveKnight(Quest quest) { this.quest = quest; Quest 가주입된다. public void embarkonquest() throws QuestException { quest.embark(); 보다시피 DamselRescuingKnight와달리 BraveKnight는자신의원정 (Quest) 을생성하지않는다. 대신에생성시점에생성자인자로원정이부여된다. 이와같은종류의종속객체주입을생성자주입 (constructor injection) 이라고한다. 게다가부여된원정은모든원정이구현하는인터페이스인 Quest 타입으로제공된다. 따라서 BraveKnight는 RescueDamselQuest, SlayDragonQuest, MakeRoundTableRounderQuest, 또는부여된다른 Quest 구현을떠날수있다. 여기서요점은 BraveKnight가 Quest의특정구현체에결합되지않는다는사실이다. Quest 인터페이스를구현하기만하면기사에게어떤종류의원정을떠나도록요청하든문제가되지않는다. 이것이바로 DI의주요이점인 느슨한결합도 (loose coupling) 다. 어떤객체가자신이필요로하는종속객체를인터페이스를통해서만알고있다면 ( 구현클래스나인스턴스화되는방법이아니라 ) 사용하는객체쪽에는아무런변경없이종속객체를다른구현체로바꿀수있다. 실제종속객체를바꾸는가장일반적인방법중하나는테스트동안의모조구현체이용이다. 강한결합도 (tight coupling) 로인해 DamselRescuingKnight를적절히테스트할수없었지만, 코드 1.1과같이 Quest의모조구현체를제공하여 BraveKnight를쉽게테스트할수있다.
1.1 자바개발간소화 11 코드 1.5 BraveKnight 테스트를위하여모조 Quest 를주입한다. package com.springinaction.knights; import static org.mockito.mockito.*; import org.junit.test; public class BraveKnightTest { @Test public void knightshouldembarkonquest() throws QuestException { Quest mockquest = mock(quest.class); 모조 Quest 생성 BraveKnight knight = new BraveKnight(mockQuest); mockquest 주입 knight.embarkonquest(); verify(mockquest, times(1)).embark(); 여기서는 Quest 인터페이스의모조구현체를만들기위해 Mockito 로알려진모조객체프레임워크를사용했다. 모조객체가생겼으면 BraveKnight의새로운인스턴스를생성하고, 생성자를통해모조 Quest를주입한다. embarkonquest() 메소드를호출한후에는 Mockito에게 Quest의 embark() 메소드가정확히한번호출됐는지확인하도록한다. 기사에게원정임무주입 이렇게해서 BraveKnight 클래스는모든원정임무를부여받을수있게됐다. 그러면이제 BraveKnight 클래스에어떤 Quest를부여할지를어떻게지정할수있을까? 애플리케이션컴포넌트간의관계를정하는것을와이어링 (wiring) 이라고한다. 스프링에서컴포넌트를와이어링하는방법은여러가지가있그림 1.1 종속객체주입은객체가스스로종속객지만가장일반적인방법은 XML을이용하는체를획득하는것과는반대로객체에종속객체가부여되는것을의미한다. 방법이다. 코드 1.6은 BraveKnight에게 Slay DragonQuest를부여하는간단한스프링설정파일인 knights.xml이다.
12 1 장스프링속으로 코드 1.6 스프링의 XML 설정을이용하여 BraveKnight 에게 SlayDragonQuest 를주입하기 <?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-3.0.xsd"> <bean id="knight" class="com.springinaction.knights.braveknight"> <constructor-arg ref="quest" /> 원정빈주입 </bean> <bean id="quest" class="com.springinaction.knights.slaydragonquest" /> SlayDragonQuest 생성 </beans> 이예제는빈과빈을와이어링하는간단한예를보여준다. 지금은자세한내용에신경쓰지않아도된다. 자세한내용은 2장에서빈을와이어링하는다른방법들과함께살펴볼예정이다. BraveKnight와 Quest 사이의관계를정의했으니이제이 XML 설정파일을로드하여애플리케이션을구동해볼차례다. 실행해보기 스프링애플리케이션에서애플리케이션컨텍스트 (application context) 는빈에관한정의들을바탕으로빈들을엮어준다. 스프링애플리케이션컨텍스트는애플리케이션을구성하는객체의생성과와이어링을전적으로책임진다. 스프링에는애플리케이션의여러구현체가존재하며, 각각의주요차이점은설정을로드하는방법에있을뿐이다. knights.xml에서는빈들이 XML 파일에선언되어있으므로애플리케이션컨텍스트로 Class PathXmlApplicationContext를사용하면좋다. 이스프링컨텍스트구현체는애플리케이션의클래스패스에있는하나이상의 XML 파일에서스프링컨텍스트를로드한다. 코드 1.7에있는 main() 메소드는 ClassPathXmlApplicationContext를사용해 knights.xml을로드하고 Knight에대한레퍼런스를얻는다.
1.1 자바개발간소화 13 코드 1.7 KnightMain.java 는기사를포함하는스프링컨텍스트를로드한다. package com.springinaction.knights; import org.springframework.context.applicationcontext; import org.springframework.context.support.classpathxmlapplicationcontext; public class KnightMain { public static void main(string[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("knights.xml"); 스프링컨텍스트로드 Knight knight = (Knight) context.getbean("knight"); 기사빈얻기 knight.embarkonquest(); 기사사용 여기서 main() 메소드는 knights.xml 파일을기반으로스프링애플리케이션컨텍스트를생성한다. 그런다음 ID가 knight인빈을조회하기위해팩토리로애플리케이션컨텍스트를사용한다. Knight 객체에대한레퍼런스를얻은후에간단히 embarkonquest() 메소드를호출해기사를주어진원정의길로떠나보낸다. 이클래스는기사가어떤유형의 Quest를떠나는지에대해서는아무것도알지못한다. BraveKnight를다룬다는사실도알지못한다. knights.xml 파일만이어떤구현체인지확실히알고있다. 지금까지 DI를간단히살펴봤다. 앞으로도이책을통해 DI에대한더많은내용을알아보겠다. 하지만 DI를더자세히알아보고싶다면 DI를상세히다루는단지 R. 프라사나 (Dhanji R. Prasanna) 의 Dependency Injection 을읽어보기바란다. 하지만지금은또다른스프링의자바간소화전략인 애스펙트 (Aspect) 를통한선언적프로그래밍 을알아보자. 1.1.3 애스펙트적용 DI가소프트웨어컴포넌트의결합도를낮춰준다면, 애스펙트지향프로그래밍은애플리케이션전체에걸쳐사용되는기능을재사용할수있는컴포넌트에담을수있게해준다. 애스펙트지향프로그래밍은소프트웨어시스템내부의관심사들을서로분리 (separation of
14 1 장스프링속으로 concerns) 하는기술이라고설명할수있다. 시스템은보통특정한기능을책임지는여러개의컴포넌트로구성된다. 그러나각컴포넌트는대체로본연의특정한기능외에로깅이나트랜잭션관리, 보안등의시스템서비스도수행해야하는경우가많다. 이러한시스템서비스는시스템의여러컴포넌트에관련되는경향이있기때문에횡단관심사 (cross-cutting concerns) 라고한다. 이러한관심사가여러컴포넌트에퍼지게되면코드는다음두가지차원에서복잡해진다. 시스템전반에걸친관심사를구현하는코드가여러컴포넌트에서중복되어나타난다. 이때문제는이관심사의구현을변경해야하는경우여러컴포넌트를모두변경해야한다는것이다. 이관심사를별도의모듈로추상화해서각컴포넌트에서하나의메소드만호출할수있도록만든다고하더라도, 여전히이메소드가여러컴포넌트에서중복되어나타나는문제는동일하다. 컴포넌트의코드가본연의기능과관련없는코드로인해지저분해진다. 주소록에주소를등록하는메소드는보안상태가유지됐는지아닌지에는신경쓸필요없이주소를등록하는방법에만관여하는것이좋다. 그림 1.2는위에설명한복잡성을그림으로나타낸것이다. 왼쪽에있는비즈니스객체들은시스템서비스와매우긴밀하게관련을맺고있다. 각객체가본연의책임을수행할뿐만아니라로깅과보안, 트랜잭션컨텍스트에대해서도알아야한다. AOP는시스템서비스를모듈화해서컴포넌트에선언적으로적용할수있게해준다. AOP를이용하면시스템서비스에대해서는전혀알지못하면서응집도가높고본연의관심사에집중하는컴포넌트를만들수있다. 다시말해애스펙트는 POJO를말그대로평범하게해준다. 그림 1.3에서묘사하고있는것처럼애스펙트를애플리케이션의여러컴포넌트를덮는담요처럼생각하면개념을잡는데도움이될것이다. 이그림에서애플리케이션의핵심은비즈니스기능을구현하는모듈들로구성되어있고, AOP를이용해서이핵심애플리케이션위에추가적인기능을여러겹으로덮고있다. 이때핵심기능을구현하는모듈에는아무런변화도가하지않고추가적인기능을선언적으로적용할수있다. 이개념은매우강력한개념으로서, 보안, 트랜잭션, 로깅등을처리하느라고애플리케이션의핵심비즈니스로직이지저분해지는것을막아준다. 기사예제로다시돌아가, 기본적인스프링애스펙트를추가해보면서스프링에서애스펙트를적용하는방법을구체적으로살펴보자.
1.1 자바개발간소화 15 그림 1.2 시스템관련관심사가주관심사가아닌모듈들에로깅이나보안등시스템전반에걸친관심사에대한호출들이여기저기흩어져있다. 그림 1.3 AOP 를이용하면시스템전반에걸친관심사가관련컴포넌트를감싼다. 이렇게함으로써애플리케이션의컴포넌트들이본연의비즈니스기능에집중할수있다. AOP 실습음유시인으로알려진음악을좋아하는이야기꾼에의해기사의업적이노래로기록되기때문 에음유시인의서비스를이용하여 BraveKnight의출정과복귀를기록하고싶다고가정해보자. 사용할 Minstrel 클래스는코드 1.8과같다.
16 1 장스프링속으로 코드 1.8 Minstrel 은중세시대의음악을좋아하는로깅시스템이다. package com.springinaction.knights; public class Minstrel { public void singbeforequest() { 원정전에호출됨 System.out.println("Fa la la; The knight is so brave!"); public void singafterquest() { 원정후에호출됨 System.out.println( "Tee hee he; The brave knight did embark on a quest!"); 보다시피 Minstrel은두개의메소드가있는간단한클래스다. singbeforequest() 메소드는기사가원정을떠나기전에호출되며, singafterquest() 메소드는기사가원정을마친후에호출된다. 코드에서작업하기위해서는간단해야한다. 따라서 Minstrel을사용하기위해서 BraveKnight를적절히수정하자. 첫번째시도는다음과같다. 코드 1.9 Minstrel 메소드를호출해야하는 BraveKnight package com.springinaction.knights; public class BraveKnight implements Knight { private Quest quest; private Minstrel minstrel; public BraveKnight(Quest quest, Minstrel minstrel) { this.quest = quest; this.minstrel = minstrel; public void embarkonquest() throws QuestException { minstrel.singbeforequest(); 기사가자체적인음유시인을관리해야할까? quest.embark(); quest.embark(); minstrel.singafterquest(
1.1 자바개발간소화 17 기교를부려봤다. 하지만일부는여기서적절해보이지않는다. 정말로기사의관심범위내에서음유시인을관리해야할까? 음유시인은기사의요청없이도그의일을수행한다. 궁극적으로음유시인의일은기사의업적에대해노래하는것이다. 왜기사가음유시인에게그의작업을수행하도록상기시켜줘야하는가? 게다가기사는음유시인에대해알아야하므로, 강제로 Minstrel을 BraveKnight에주입 (inject) 한다. 이는 BraveKnight의코드를복잡하게만들뿐만아니라음유시인이없는기사를원하는경우당황스러워진다. 만일 Minstrel이 null이라면? 이와같은상황을다루기위해몇가지 null 체크로직을도입해야할까? 간단한 BraveKnight 클래스가점차복잡해지기시작하고 nullminstrel 시나리오까지처리해야한다면그복잡함은더해진다. 하지만 AOP를사용하면기사의원정에대해노래할음유시인을선언할수있으며, 기사는 Minstrel 메소드를직접처리하는일에서해방된다. Minstrel을애스펙트로바꾸려면스프링설정파일의하나로선언하기만하면된다. 여기서는 knights.xml 파일을수정하여 Minstrel을애스펙트로선언했다. 코드 1.10 Minstrel 을애스펙트로선언하기 <?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:aop="http://www.springframework.org/schema/aop" xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <bean id="knight" class="com.springinaction.knights.braveknight"> <constructor-arg ref="quest" /> </bean> <bean id="quest" class="com.springinaction.knights.slaydragonquest" /> <bean id="minstrel" class="com.springinaction.knights.minstrel" /> Minstrel 빈선언 <aop:config> <aop:aspect ref="minstrel">
18 1 장스프링속으로 <aop:pointcut id="embark" expression="execution(* *.embarkonquest(..))" /> 포인트컷정의 <aop:before pointcut-ref="embark" method="singbeforequest"/> 비포어드바이스선언 <aop:after pointcut-ref="embark" method="singafterquest"/> 애프터어드바이스선언 </aop:aspect> </aop:config> </beans> 여기서스프링의 aop 설정네임스페이스를사용하여 Minstrel 빈이애스펙트라고선언한다. 먼저 Minstrel을빈으로선언해야한다. 그런다음 <aop:aspect> 엘리먼트에서빈을참조한다. 애스펙트를더정의해보자면, embarkonquest() 메소드가실행되기전에 Minstrel의 singbeforequest() 메소드가호출되어야한다고선언한다 (<aop:before> 를이용하여 ). 이것을비포어드바이스 (before advice) 라부른다. 그리고 embarkonquest() 메소드가실행된후에 singafterquest() 메소드가호출되어야한다고선언한다 (<aop:after> 를이용하여 ). 이것을애프터어드바이스 (after advice) 라부른다. 양쪽경우모두 pointcut-ref 애트리뷰트는 embark 라는이름의포인트컷 (pointcut) 을참조한다. 이포인트컷은앞에있는 <pointcut> 엘리먼트에어드바이스가적용될위치를선택하는 expression 애트리뷰트와함께정의돼있다. expression 구문은 AspectJ의포인트컷표현식언어다. AspectJ나 AspectJ 포인트컷표현식의작성방법을자세히모른다고해도걱정할필요는없다. 4장에서스프링의 AOP에대해더자세히논의할것이다. 지금은스프링이 BraveKnight 가원정을떠나기전과후에 Minstrel의 singbeforequest() 와 singafterquest() 메소드를호출한다는정도만알아도충분하다. 이렇게해서몇줄안되는 XML 설정으로 Minstrel을스프링애스펙트로바꿨다. 지금완전히이해가되지않더라도걱정할필요는없다. 4장에서다양한스프링 AOP 예제를통해충분히이해할수있는시간을갖게된다. 지금은이예제에서다음에설명하는두가지점만분명하게알고넘어가면된다.
1.1 자바개발간소화 19 첫번째는 Minstrel이여전히 POJO라는점이다. Minstrel이애스펙트로사용될것임을나타내는내용은 Minstrel에전혀포함되어있지않다. 스프링컨텍스트에서선언적으로애스펙트가된다. 두번째이면서가장중요한점은 BraveKnight가 Minstrel을명시적으로호출하지않아도 Minstrel이 BraveKnight에적용될수있다는사실이다. 실제로 BraveKnight는 Minstrel 의존재를전혀인식하지못한다. 또한중요한것은 Minstrel을애스펙트로바꾸기위해몇가지스프링마법을사용했지만, 먼저 <bean> 으로선언돼야한다는사실이다. 여기서의핵심은종속객체주입같은다른스프링빈과함께수행할수있는스프링애스펙트를이용하면무엇이든가능하다는점이다. 기사에대해노래하기위해애스펙트를사용하는일도즐거울수있지만, 스프링의 AOP는더실용적인목적으로사용된다. 나중에보겠지만스프링 AOP는선언적트랜잭션 (6장) 과보안 (9 장 ) 등의서비스를제공하기위해도입한다. 하지만지금은스프링이자바개발을간소화시켜주는또하나의방법을알아보자. 1.1.4 템플릿을이용한상투적인코드제거 코드를작성하다가이전에이미작성했던코드같다고느껴본적이있는가? 이것은데자뷰 (déjà vu) 가아니다. 이것이바로상투적인코드다. 즉, 공통작업이나간단한작업을위해반복적으로작성해야하는코드다. 안타깝게도자바 API에는상투적인코드가많이포함돼있다. 상투적코드의대표적인예는데이터베이스에서데이터를조회하는 JDBC 작업이다. 예를들어, 이전에 JDBC로작업한적이있어도다음에도유사한코드를작성해야하는경우가있다. 코드 1.11 JDBC 등상당수의자바 API에는상투적인코드작성이많이포함돼있다. public Employee getemployeebyid(long id) { Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null; try { conn = datasource.getconnection();
20 1 장스프링속으로 stmt = conn.preparestatement( "select id, firstname, lastname, salary from " + "employee where id=?"); 직원조회 stmt.setlong(1, id); rs = stmt.executequery(); Employee employee = null; if (rs.next()) { employee = new Employee(); employee.setid(rs.getlong("id")); 데이터로부터객체생성 employee.setfirstname(rs.getstring("firstname")); employee.setlastname(rs.getstring("lastname")); employee.setsalary(rs.getbigdecimal("salary")); return employee; catch (SQLException e) { 여기서무엇을수행해야하지? finally { if(rs!= null) { 정리작업 try { rs.close(); catch(sqlexception e) { if(stmt!= null) { try { stmt.close(); catch(sqlexception e) { if(conn!= null) { try { conn.close(); catch(sqlexception e) { return null;
1.1 자바개발간소화 21 보다시피 JDBC 코드는직원의이름과급여를데이터베이스에서조회한다. 하지만조회하는부분을찾으려면열심히살펴봐야한다. 그이유는몇줄안되는직원조회코드가 JDBC 형식의더미에묻혀버렸기때문이다. 먼저커넥션 (connection) 을생성하고, 그런다음에질의객체 (statement) 를생성하며, 마지막으로결과를조회한다. 그리고예외를던지더라도 (throw) 수행할수있는작업이많지않음에도불구하고, JDBC의분노를달래기위해검사형예외 (checked exception) 인 SQLException을잡아야 (catch) 한다. 마지막으로모든작업을완료한후에는커넥션과질의객체, 그리고결과집합을닫는정리작업을수행해야한다. 이또한분노를일으킬수있다. 따라서여기서도별도로 SQLException을잡아야한다. 코드 1.11에서가장주목할부분은거의모든 JDBC 작업을위해작성했던코드와완벽히동일하다는사실이다. 직원조회를위해수행하는작업은극히일부분이다. 대부분은 JDBC의상투적인코드다. 상투적인코드작업에 JDBC만있는것은아니다. 많은기능은종종유사한상투적인코드를요구한다. JMS, JNDI, 그리고 REST 서비스의소비에는수많은공통적인반복코드가포함돼있다. 스프링은템플릿에상투적인코드를캡슐화하여반복적인코드를제거하는방법을찾는다. 스프링의 JdbcTemplate은전통적인 JDBC에서필요한모든형식없이도데이터베이스작업을수행할수있게한다. 예를들어, 스프링의 SimpleJdbcTemplate(JdbcTemplate의서브클래스로자바 5 기능을활용한다 ) 을사용하면 JDBC API가요구하는작업이아니라직원데이터를조회하는작업에초점을맞추도록 getemployeebyid() 메소드를재작성할수있다. 업데이트된 getemployeebyid() 메소드는코드 1.12와같다.
22 1 장스프링속으로 코드 1.12 템플릿은코드가작업에바로집중할수있게한다. public Employee getemployeebyid(long id) { return jdbctemplate.queryforobject( "select id, firstname, lastname, salary " + SQL 쿼리 "from employee where id=?", new RowMapper<Employee>() { public Employee maprow(resultset rs, int rownum) throws SQLException { 결과를객체에매핑 Employee employee = new Employee(); employee.setid(rs.getlong("id")); employee.setfirstname(rs.getstring("firstname")); employee.setlastname(rs.getstring("lastname")); employee.setsalary(rs.getbigdecimal("salary")); return employee;, id); 쿼리파라미터지정 보다시피새롭게수정된 getemployeebyid() 는매우간단하면서도실제로데이터베이스에서직원을조회하는작업에초점을맞춘다. 템플릿의 queryforobject() 메소드에는 SQL 쿼리, RowMapper( 결과집합데이터를도메인객체에매핑 ), 그리고쿼리파라미터가부여될수있다. getemployeebyid() 에서는이전에보였던 JDBC의상투적인코드가보이지않는다. 이것은모두템플릿내부에서처리된다. 지금까지 POJO 지향개발, DI, AOP, 그리고템플릿을이용하여자바개발의복잡성을공격하는방법을살펴봤다. 또한 XML기반설정파일에서빈과애스펙트를설정하는방법도살펴봤다. 그런데이러한파일을어떻게로드할수있을까? 그리고어디에로드될까? 이제부터애플리케이션의빈이위치하는스프링컨테이너를살펴보자. 1.2 빈을담는그릇, 컨테이너 스프링애플리케이션에서는스프링컨테이너 (container) 에서객체가태어나고, 자라고, 소멸한다. 그림 1.4와같이스프링컨테이너는객체를생성하고, 서로엮어주고, 이들의전체생명주기 ( 생성에서소멸까지 ) 를관리한다.
1.2 빈을담는그릇, 컨테이너 23 스프링컨테이너가어떤객체를생성하고구성해서서로엮어줘야하는지알수있게설정하는방법은다음장에서설명하겠다. 우선은객체들의삶의터전인스프링컨테이너자체부터이해하는것이더중요하다. 스프링컨테이너에대한이해를토대로객체가어떻게관리되는지를이해해야한다. 스프링컨테이너는스프링프레임워크의핵심부에위치한다. 스프링컨테이너는종그림 1.4 스프링애플리케이션에서객체는스프링컨속객체주입을이용해서애플리케이션을테이너내에서태어나고, 더불어살아간다. 구성하는컴포넌트를관리하며, 협력컴포넌트간연관관계의형성도여기에서이뤄진다. 이러한짐을컨테이너에덜어버린객체들은더명확하고이해하기쉬우며, 재사용을촉진하고, 단위테스트가용이해진다. 스프링컨테이너는여러가지가있다. 스프링에는여러컨테이너구현체가존재하며, 이들은크게두가지로분류된다. 첫번째부류는빈팩토리 (org.springframework.beans.factory. BeanFactory 인터페이스에의해정의된다 ) 로, 이는 DI에대한기본적인지원을제공하는가장단순한컨테이너다. 두번째부류는애플리케이션컨텍스트 (org.springframework.context. ApplicationContext 인터페이스에의해정의된다 ) 로, 빈팩토리를확장해프로퍼티파일에텍스트메시지를읽고해당이벤트리스너에대한애플리케이션이벤트발행같은애플리케이션프레임워크서비스를제공하는컨테이너다. 스프링으로작업할때빈팩토리나애플리케이션컨텍스트중에아무거나사용해도상관없지만, 빈팩토리는대부분의애플리케이션에대하여지나치게저수준의기능을제공한다. 따라서빈팩토리보다는애플리케이션컨텍스트를더선호한다. 따라서앞으로는애플리케이션컨텍스트에초점을맞춰작업할예정이며, 빈팩토리에대해서는많은시간을할애하지않겠다. 1.2.1 또하나의컨테이너, 애플리케이션컨텍스트 스프링에는다양한종류의애플리케이션텍스트가있다. 그중에서가장많이접하게될세가지는다음과같다.
24 1 장스프링속으로 ClassPathXmlApplicationContext - 클래스패스에위치한 XML 파일에서컨텍스트정의내용을로드한다. FileSystemXmlApplicationContext - 파일시스템에서, 즉파일경로로지정된 XML 파일에서컨텍스트정의내용을로드한다. XmlWebApplicationContext - 웹애플리케이션에포함된 XML 파일에서컨텍스트정의내용을로드한다. XmlWebApplicationContext는 7장에서웹기반스프링애플리케이션을설명할때살펴보기로한다. 지금은간단히 FileSystemXmlApplicationContext를이용해서파일시스템에서애플리케이션컨텍스트를로드하거나, ClassPathXmlApplicationContext를이용해서클래스패스에서애플리케이션컨텍스트를로드하자. 파일시스템이나클래스패스에서애플리케이션컨텍스트를로드하는방법은빈팩토리때와유사하다. 다음은 FileSystemXmlApplicationContext를로드하는코드다. ApplicationContext context = new FileSystemXmlApplicationContext("c:/foo.xml"); 클래스패스에서로드하는 ClassPathXmlApplicationContext 도다를바없다. ApplicationContext context = new ClassPathXmlApplicationContext("foo.xml"); 둘사이에차이가있다면 FileSystemXmlApplicationContext의경우 foo.xml을파일시스템에서지정된경로 (c:/fool.xml) 로찾고, ClassPathXmlApplicationContext는클래스패스에포함된모든경로 (JAR 파일포함 ) 에서찾으려한다는점이다. 애플리케이션컨텍스트를얻은다음켄텍스트의 getbean() 메소드를호출하여스프링컨테이너에서빈을조회할수있다. 이제스프링컨테이너를생성하는기초방법을익혔으니컨테이너내부빈의생명주기를좀더살펴보자. 1.2.2 빈의일생 보통의자바애플리케이션에서빈의생명주기는매우단순하다. 자바의 new 키워드를이용하거나역직렬화 (deserialization) 를통해빈을인스턴스화하고이를바로사용한다. 빈이더이상
1.2 빈을담는그릇, 컨테이너 25 사용되지않으면가비지컬렉션 (garbage collection) 후보가되어언젠가는메모리덩어리가됐다가허공속으로사라질것이다. 반면에스프링컨테이너내에서빈의생명주기는좀더정교하다. 빈이생성될때스프링이제공하는커스터마이징기회를이용하려면스프링의빈생명주기를이해해두는것이좋다. 그림 1.5는 BeanFactory 컨테이너내에서빈이갖는구동 (startup) 생명주기를보여준다. 그림 1.5 빈은스프링컨테이너에서탄생과소멸사이에몇단계의과정을거치게된다. 각단계는스프링이빈을관리하는방법을제어할수있는기회이기도하다. 보다시피빈팩토리는빈이사용가능한상태가되기전에몇가지준비 (setup) 과정을수행한다. 그림 1.5의각과정을상세히설명하면다음과같다. 1. 스프링이빈을인스턴스화한다. 2. 스프링이값과빈의레퍼런스를빈의프로퍼티에주입한다. 3. 빈이 BeanNameAware를구현하면, 스프링이빈의 ID를 setbeanname() 메소드에넘긴다. 4. 빈이 BeanFactoryAware를구현하면, setbeanfactory() 메소드를호출하여빈팩토리자체를넘긴다. 5. 빈이 ApplicationContextAware를구현하면, 스프링이 setapplicationcontext() 메소드를호출하고본애플리케이션컨텍스트 (enclosing application context) 에대한참조를넘긴다.
26 1 장스프링속으로 6. 빈이 BeanPostProcessor 인터페이스를구현하면, 스프링은 postprocessbeforeinitiali zation() 메소드를호출한다. 7. 빈이 InitializingBean 인터페이스를구현하면, 스프링은 afterpropertiesset() 메소드를호출한다. 마찬가지로빈이 init-method와함께선언됐으면, 지정한초기화메소드가호출된다. 8. 빈이 BeanPostProcessor를구현하면, 스프링은 postprocessafterinitialization() 메소드를호출한다. 9. 이상태가되면빈은애플리케이션에서사용할준비가된것이며, 애플리케이션컨텍스트가소멸될때까지애플리케이션컨텍스트에남아있다. 10. 빈이 DisposableBean 인터페이스를구현하면, 스프링은 destroy() 메소드를호출한다. 마찬가지로빈이 destroy-method와함께선언됐으면, 지정된메소드가호출된다. 이제스프링컨테이너가어떻게생성되고로드되는지알게됐지만, 우리는아직컨테이너에아무것도넣지않았기때문에별쓸모가없다. 스프링 DI를활용하려면컨테이너에애플리케이션객체를넣고서로연결해줘야한다. 빈와이어링 (wiring) 은 2장에서좀더상세히살펴보겠다. 그에앞서, 스프링프레임워크가무엇으로구성되어있고최신버전의스프링이제공하는기능이무엇인지현대스프링현황을조사해보자. 1.3 스프링현황 앞서살펴봤듯이, 스프링프레임워크는 DI, AOP, 그리고상투적인코드축소를통해자바개발을간소화하는데초점을맞춘다. 이모두스프링이수행하는작업이며, 유용하다. 하지만스프링에는눈에보이는것보다더많은기능이존재한다. 스프링프레임워크안에서스프링이자바개발을쉽게할수있는여러가지방법을찾을수있다. 하지만스프링프레임워크자체를넘어서웹서비스, OSGi, 플래시 (Flash), 그리고심지어.NET 등의영역으로스프링을확장하여코어프레임워크를구축하려는프로젝트의더큰생태계가있다. 먼저코어스프링프레임워크가어떤도움을주는지자세히분석해보자. 그런다음, 안목을넓혀서더큰스프링포트폴리오의다른구성요소를검토하도록하자.
1.3 스프링현황 27 1.3.1 스프링모듈 스프링프레임워크는여러개의개별모듈로구성되어있다. 스프링프레임워크를다운로드하여압축을풀면그림 1.6과같이 dist 디렉터리에서 20개의다른 JAR 파일을볼수있다. 스프링을구성하는 20개의 JAR 파일은그림 1.7에보이는여섯개의기능카테고리중하나에속한다. 그림 1.6 JAR 파일에는스프링프레임워크배포파일이존재한다. 이모듈들은전체적으로보면엔터프라이즈애플리케이션개발에필요한모든것을제공한다. 그러나애플리케이션을만들때항상이스프링프레임워크모듈전체를이용해야하는것은아니다. 스프링프레임워크는다른프레임워크나라이브러리와쉽게통합될수있으므로원하는기능이스프링에없어도직접개발할필요없이다른프레임워크나라이브러리를사용할수있다. 스프링모듈들이전체적인구조하에서각각어떤기능을담당하는지하나씩살펴보기로하자.
28 1 장스프링속으로 웹과리모팅 그림 1.7 스프링프레임워크는여섯개의잘정의된모듈로구성돼있다. 코어스프링컨테이너스프링프레임워크의핵심은스프링애플리케이션에서빈의생성, 설정, 그리고처리방법을관리하는컨테이너다. 이모듈내에서 DI를제공하는스프링빈팩토리를확인할수있다. 빈팩토리를구성하다보면여러가지스프링의애플리케이션컨텍스트구현체가보이는데, 각각은스프링을구성하는다른방법을제공한다. 빈팩토리와애플리케이션컨텍스트외에도이모듈은이메일, JNDI 액세스, EJB 통합, 그리고스케줄링등의다양한엔터프라이즈서비스도제공한다. 보다시피모든스프링의모듈은코어컨테이너위에구축된다. 애플리케이션구성시암묵적으로코어컨테이너클래스를사용한다. 코어모듈은스프링의 DI를살펴보는 2장을시작으로이책의전반에걸쳐다룬다. 스프링의 AOP 모듈스프링은 AOP 모듈을통해애스펙트지향프로그래밍을풍부하게지원한다. AOP 모듈은스프링애플리케이션에서애스펙트를개발할수있는기반이되는것으로서, DI처럼애플리케이션객체간의결합도를낮추는데기여한다. AOP는주로애플리케이션전체에걸친관심사 ( 트랜잭션이나보안등 ) 와각객체간의결합도를낮추는데이용된다.
1.3 스프링현황 29 스프링의 AOP 지원에대해서는 4 장에서자세히살펴본다. 데이터액세스와통합 JDBC를이용해작업하다보면커넥션을얻어오고, 질의객체를생성하고, 결과집합을처리하 고, 커넥션을닫는코드를수없이반복하게된다. 스프링의 JDBC와 DAO(Data Access Objects, 데이터액세스객체 ) 모듈은이렇게반복되는코드를추상화하므로이모듈을이용하면데이터베이스관련코드를깔끔하고간단하게만들수있고, 데이터베이스리소스를닫지않아서발생할수있는문제를예방할수있다. 또한이모듈에는여러종류의데이터베이스서버가제공하는오류메시지위에의미있는예외계층이추가되어있다. 이모듈을이용하면더이상암호문같은 SQL 오류메시지를이해하려고머리를싸매지않아도된다! 스프링의 ORM 모듈은 JDBC보다객체관계매핑 (ORM: Object-Relational Mapping) 도구를선호하는사람들을위한것이다. 스프링의 ORM 모듈은 DAO 모듈위에올라가서 ORM 솔루션용 DAO를만드는편리한방법을제공한다. 스프링은고유한 ORM 솔루션을구현하지않고, 하이버네이트, 자바퍼시스턴스 API, 자바데이터객체 (JDO), ibatis SQL Maps 등널리사용되는 ORM 프레임워크와의연결고리를제공한다. 스프링의트랜잭션관리기능은 JDBC 뿐만아니라이 ORM 프레임워크들도지원한다. 5장에서스프링데이터액세스를살펴볼때스프링의템플릿기반 JDBC 추상화가어떻게 JDBC 코드를크게간소화하는지알아본다. 이모듈은또한메시징을통해다른애플리케이션과의비동기식통합을위하여 JMS(Java Message Service) 를이용한스프링추상화를포함한다. 그리고스프링 3.0 현재, 이모듈은원래스프링웹서비스 (Spring Web Services) 프로젝트의일부분이었던객체 -XML 매핑 (OXM: Object-XML Mapping) 을포함한다. 또한이모듈은스프링의 AOP 모듈을이용하여스프링애플리케이션에서객체들의트랜잭션관리서비스를제공한다. 스프링의트랜잭션지원기능은 6장에서자세히살펴본다. 웹과리모팅 MVC(Model-View-Controller) 패러다임은사용자인터페이스가애플리케이션로직과분리되는웹애플리케이션을만드는경우에일반적으로사용되는접근방식이다. 자바의유명한 MVC 프레임워크가운데에는아파치스트럿츠 (Apache Struts), JSF, 웹워크 (WebWork), 태피
30 1 장스프링속으로 스트리 (Tapestry) 등이있다. 스프링이다양한유명 MVC 프레임워크와잘통합되기는하지만, 웹과리모팅모듈에는애플리케이션의웹계층에서결합도를낮추는스프링의기술을잘반영하는 MVC 프레임워크가별도로만들어져있다. 이프레임워크는두가지형태로구성되어있다. 하나는기존웹애플리케이션을위한서블릿기반프레임워크이고, 다른하나는자바포틀릿 (portlet) API에대한개발을위한포틀릿기반애플리케이션이다. 사용자접촉 (user-facing) 웹애플리케이션뿐만아니라이모듈은다른애플리케이션과상호작용하는애플리케이션을개발하기위한다양한리모팅옵션도제공한다. 스프링의리모팅기능에는 RMI(Remote Method Invocation), Hessian, Burlap, JAX-WS, 그리고스프링의 HTTP 호출자등이있다. 7장에서스프링의 MVC 프레임워크를살펴본다음, 10장에서스프링의리모팅기능을확인해보겠다. 테스팅개발자가작성하는테스트의중요성을인식하여스프링은스프링애플리케이션테스트에전념하는모듈을제공한다. 이모듈안에서 JNDI, 서블릿, 그리고포틀릿과동작하는코드의단위테스트작성을위한모조객체구현을확인할수있다. 통합테스트의경우, 이모듈은스프링애플리케이션컨텍스트에서빈을로드하고이컨텍스트에있는빈과의작업을지원한다. 스프링의테스팅모듈은 4장에서처음다룬다. 그런다음 5장과 6장에서스프링데이터액세스와트랜잭션을테스트하는방법을살펴보면서배운내용을넓혀가겠다. 1.3.2 스프링포트폴리오 스프링은눈에보이는게다가아니다. 실제로스프링프레임워크다운로드에있는것보다더많은기능이존재한다. 만일코어스프링프레임워크에서멈춘다면, 다양한스프링포트폴리오가제공하는풍부한잠재력을놓치게된다. 전체스프링포트폴리오에는코어스프링프레임워크와서로연관되어구축된다양한프레임워크와라이브러리가있다. 전체스프링포트폴리오는자바개발의모든측면에서스프링프로그래밍모델을제공해준다.
1.3 스프링현황 31 스프링포트폴리오가제공하는모든기능을설명하려면책몇권의분량이필요하다. 따라서여기서는코어스프링프레임워크의범위를넘어선일부내용을포함하여스프링포트폴리오의몇가지주요부분위주로살펴보겠다. 스프링웹플로스프링웹플로 (Spring Web Flow) 는스프링의코어 MVC 프레임워크를기반으로목표를향해사용자를안내하는 ( 마법사나장바구니를떠올리면된다 ) 대화형, 흐름기반웹애플리케이션구축을지원한다. 스프링웹플로는 8장에서자세히논의하겠으며, 스프링웹플로에대한더많은정보는홈페이지 http://www.springsource.org/webflow에서확인할수있다. 스프링웹서비스코어스프링프레임워크는웹서비스로스프링빈을선언적으로배포할수있지만, 이러한서비스는틀림없이구조적으로열악한구현우선 (contract-last) 모델역주4 을기반으로한다. 서비스에대한규약은빈의인터페이스에서결정된다. 스프링웹서비스 (Spring Web Services) 는규약우선 (contract-first) 웹서비스모델을제공하는데, 여기서서비스규약을충족하기위해서서비스구현체가작성된다. 4) 이책에서스프링웹서비스에대해논의하지는않겠다. 하지만더많은정보는스프링웹서비스의홈페이지인 http://static.springsource.org/spring-ws/sites/2.0에서확인할수있다. 스프링시큐리티보안은많은애플리케이션의핵심요소다. 스프링 AOP를이용하여구현된스프링시큐리티 (Spring Security) 는스프링기반애플리케이션에선언적보안메커니즘을제공한다. 애플리케이션에스프링시큐리티를추가하는방법은 9장에서살펴보겠다. 더많은정보는스프링시큐리티의홈페이지 http://static.springsource.org/spring-security/site에서확인할수있다. 스프링인티그레이션 많은엔터프라이즈애플리케이션은다른엔터프라이즈애플리케이션과상호작용해야한다. 스 역주 4 구현우선 (contract-last) 모델은자바소스코드를먼저작성한후에 WSDL 을자동생성하여구현하는방식 (code-first 라고도함 ) 이며, 규약우선 (contract-first) 모델은 WSDL 파일을먼저작성한후자바소스코드를작성하는방식이다.
32 1 장스프링속으로 프링인티그레이션 (Spring Integration) 은몇가지공통적인통합패턴의구현체를스프링의선언적방식으로제공한다. 이책에서는스프링인티그레이션은다루지않는다. 하지만스프링인티그레이션에대한더많은정보는마크피셔 (Mark Fisher), 요나스파트너 (Jonas Partner), 마리우스보고예비치 (Marius Bogoevici), 그리고이바인펄드 (Iwein Fuld) 가쓴 Spring Integration in Action 을읽어보거나스프링인티그레이션홈페이지인 http://www.springsource.org/spring-integration 을살펴보기바란다. 스프링배치데이터의일괄작업이필요하다면배치처리만큼좋은방법은없다. 배치애플리케이션을개발한다면스프링배치 (Spring Batch) 를이용해스프링의강건한 POJO 지향개발모델을활용할수있다. 스프링배치는이책의범위밖이다. 하지만티에리템플라 (Thierry Templier) 와아르노꼬골르느 (Arnaud Cogoluègnes) 가쓴 Spring Batch in Action 에서많은정보를얻을수있다. 또한스프링배치홈페이지 http://static.springsource.org/spring-batch에서스프링배치에대해배울수있다. 스프링소셜소셜네트워크는인터넷에서떠오르는트렌드로, 페이스북이나트위터등의소셜네트워크사이트와의통합기능을갖춘애플리케이션도계속증가하는추세다. 관심있는분야라면스프링의소셜네트워크확장기능인스프링소셜 (Spring Social) 을살펴보기바란다. 스프링소셜은비교적최신분야이며이책에서별도로다루지는않지만, 보다많은정보는 http://www.springsource.org/spring-social에서확인가능하다. 스프링모바일모바일애플리케이션은소프트웨어개발의또하나의중요한분야다. 많은사용자들이선호하는단말이스마트폰과태블릿기기로이동하고있다. 스프링모바일 (Spring Mobile) 은모바일웹애플리케이션개발을지원하는스프링의새로운확장기능이다. 스프링모바일과관련된프로젝트로는스프링안드로이드 (Spring Android) 프로젝트가있다.
1.3 스프링현황 33 이새로운프로젝트는스프링프레임워크가제공하는단순함을안드로이드용네이티브애플리케이션개발에적용하는데그목적이있다. 처음부터이프로젝트는안드로이드애플리케이션내에서사용할수있는스프링의 RestTemplate 버전을제공한다 (RestTemplate은 11장에서다룬다 ). 이프로젝트역시새로운분야이고이책의범위를벗어나지만, 더다양한내용은 http://www. springsource.org/spring-mobile과 http://www.springsource.org/spring-android에서배울수있다. 스프링 DM 스프링 DM(Spring Dynamic Module) 은 OSGi의동적모듈화방식과스프링의선언적 DI가조화를이룬다. 스프링 DM을이용하면, OSGi 프레임워크내에서서비스를선언적으로발행하고소비하는별개의높은응집도와낮은결합도의모듈로구성된애플리케이션을구축할수있다. 주목할점은 OSGi 세계에서의엄청난충격으로인해선언적 OSGi 서비스에대한스프링 DM 모델은 OSGi Blueprint Container 로 OSGi 명세자체가공식화됐다. 또한 SpringSource는스프링 DM을 Gemini 패밀리의일부로이클립스프로젝트로전환했으며, 지금은 Gemini Blueprint로알려져있다. 스프링 LDAP DI와 AOP 외에도스프링프레임워크를통해적용되는또다른공통기법은 JDBC 쿼리또는 JMS 메시징같이불필요하게복잡한작업에대해템플릿기반의추상화를만드는것이다. 스프링 LDAP은 LDAP에스프링스타일의템플릿기반액세스를제공하며, 일반적으로 LDAP 작업에포함된단순반복적인코드를제거한다. 스프링 LDAP에관한더많은정보는 http://www.springsource.org/ldap 에서확인가능하다. 스프링리치클라이언트웹기반애플리케이션이전통적인데스크톱애플리케이션에서주목을빼앗아간듯보인다. 하지만아직스윙 (Swing) 애플리케이션을개발한다면스윙에스프링의힘을제공하는풍부한애플리케이션툴킷인스프링리치클라이언트 (Spring Rich Client) 를확인해보기바란다.
34 1 장스프링속으로 스프링.NET.NET 프로젝트에서도 DI와 AOP를포기할필요가없다. 스프링.NET(Spring.NET) 은.NET 플랫폼에대해서도스프링의느슨한결합과애스펙트지향기능을동일하게제공한다. 핵심적인 DI와 AOP 기능외에도스프링.NET은 ADO.NET, NHibernate, ASP.NET, 그리고 MSMQ 작업에대한모듈을포함해.NET 개발을단순화하기위한몇가지모듈이있다. 스프링.NET에관한더많은정보는 http://www.springframework.net에서확인할수있다. 스프링-플렉스어도비 (Adobe) 의플렉스 (Flex) 와에어 (AIR) 는리치인터넷애플리케이션개발을위한강력한기능을제공한다. 이러한리치 UI가서버측자바코드와상호작용해야할경우 BlazeDS로알려진리모팅과메시징기법을이용할수있다. 스프링- 플렉스 (Spring-Flex) 통합패키지는플렉스와에어애플리케이션이 BlazeDS를이용하여서버측스프링빈과통신할수있게한다. 또한스프링루 (Spring Roo) 를위한부가기능이있으며, 플렉스애플리케이션의신속한 (rapid) 애플리케이션개발을가능하게한다. 스프링- 플렉스의탐구는 http://www.springsource.org/spring-flex에서시작할수있으며, 스프링액션스크립트 (Spring ActionScript) 는 http://www.springactionscript.org에서살펴보기바란다. 액션스크립트에서스프링프레임워크의다양한혜택을제공한다. 스프링루점점더많은개발자들이스프링을이용하여작업함에따라스프링과관련프레임워크에관한공통용어들과모범사례 (best practice) 가등장하고있다. 동시에애플리케이션개발작업을단순하게하는스크립트기반개발모델과함께루비온레일스 (Ruby on Rails) 와그레일스 (Grails) 등의프레임워크가떠오르고있다. 스프링루 (Spring Roo) 는상호작용적인도구환경을제공하여스프링애플리케이션의신속한개발을가능하게하며, 지난몇년간확인된모범사례를통합했다. 루와다른신속한애플리케이션개발프레임워크와의차이점은스프링프레임워크를사용하여자바코드를만든다는점이다. 결과물은많은기업개발조직에낯선언어로코딩된별도의프레임워크가아니라하늘에한점부끄럼없는스프링애플리케이션이다. 스프링루에관한더많은정보는 http://www.springsource.org/roo에서확인할수있다.
1.4 스프링의새로운기능 35 스프링확장지금까지설명한모든프로젝트외에도 http://www.springsource.org/extensions에커뮤니티주도의스프링확장모음이있다. 여기에포함된유용한기능은다음과같다. 파이썬 (Python) 언어를위한스프링구현체블롭 (Blob) 저장 db4o와 CouchDB 퍼시스턴스스프링기반워크플로 (workflow) 관리라이브러리스프링시큐리티를위한커버로스 (Kerberos) 와 SAML 확장 1.4 스프링의새로운기능 이책의 2판을작성한후거의 3년이흘렀고, 그기간동안많은일들이벌어졌다. 스프링프레임워크에는두번의중요한릴리즈가있었는데, 각각은애플리케이션개발을쉽게하는새로운기능과개선을가져왔다. 그리고스프링포트폴리오의몇가지다른멤버도큰변화를겪었다. 이책은이와같은변화를다룬다. 하지만지금은스프링에서새로워진기능이무엇인지부터간단히살펴보자. 1.4.1 스프링 2.5 에서새로워진기능 2007년 11월, 스프링프레임워크의 2.5 버전이릴리즈됐다. 스프링 2.5의의미는애너테이션기반개발이채택됐다는데있다. 스프링 2.5 이전에는 XML 기반설정이표준이었다. 하지만스프링 2.5에서도입된다양한방식의애너테이션활용은스프링설정에필요한 XML의양을급격히줄였다. @Autowired 애너테이션을통한애너테이션기반 DI와 @Qualifier를이용한세분화된오토와이어링 (auto-wiring) 제어생명주기메소드를위한 @PostConstruct와 @PreDestroy뿐만아니라명명된리소스의 DI를위한 @Resource를포함한 JSR-250 애너테이션지원 @Component 애너테이션 ( 또는다양한스테레오타입애너테이션중하나 ) 을적용한스프링컴포넌트자동탐지 (auto-detection)
36 1 장스프링속으로 스프링웹개발을크게간소화시키는완전히새로운애너테이션기반스프링 MVC 프로그래밍모델 JUnit 4와애너테이션기반의새로운통합테스트프레임워크스프링 2.5의애너테이션은중요한얘기지만다음과같은내용도더있다. JDBC 4.0, JTA 1.1, JavaMail 1.4, 그리고 JAX-WS 2.0를포함한전체자바 6와자바 EE 5 지원이름으로스프링빈에애스펙트를위빙 (weaving) 하는새로운빈-이름포인트컷표현식 AspectJ 로딩시점위빙을위한지원기능내장애플리케이션컨텍스트세부정보설정을위한 context 네임스페이스와메시지드리븐 (message-driven) 빈설정을위한 jms 네임스페이스를포함한새로운 XML 설정네임스페이스 SqlJdbcTemplate에서의명명된파라미터지원이책을통해이와같은새로운스프링기능을살펴본다. 1.4.2 스프링 3.0 에서새로워진기능 스프링 2.5의훌륭한기능들로인해뒤이은스프링 3.0에서가능한기능이무엇인지상상하기쉽지않았다. 하지만 3.0 릴리즈와함께스프링은애너테이션기반사상의유지와다양한새로운기술로한단계도약했다. XML, JSON, RSS, 또는다른적절한응답을이용하여 REST 스타일 URL에응답하는스프링 MVC 컨트롤러를포함한스프링 MVC의전체규모 REST 지원. 스프링 3.0의새로운 REST 지원은 11장에서살펴본다. 다른빈과시스템속성을포함하여다양한소스로부터값의주입을가능하게하여스프링 DI를새로운수준으로제공하는새로운표현식언어 (expression language). 스프링의표현식언어는 2장에서자세히살펴본다. 쿠키와요청헤더에서각각값을가져오는 @CookieValue와 @RequestHeader를포함한스프링 MVC를위한새로운애너테이션. 7장에서스프링 MVC를살펴보면서이러한애너테이션의사용법을알아본다. 스프링 MVC의쉬운설정을위한새로운 XML 네임스페이스 JSR-303(Bean Validation) 애너테이션을이용한선언적유효성검증지원
1.4 스프링의새로운기능 37 새로운 JSR-330 DI 명세지원비동기식과스케줄링된메소드의애너테이션지향선언 XML이없이스프링설정이가능한새로운애너테이션기반의설정모델. 이새로운설정방식은 2장에서살펴본다. 객체-XML(OXM) 매핑기능이스프링웹서비스 (Spring Web Services) 프로젝트에서코어스프링프레임워크로이동스프링 3.0의새로운기능만큼이나스프링 3.0에없는기능이무엇인지도파악하는것도중요하다. 특히, 스프링 3.0으로시작하려면이제는자바 5 이상이필요하다. 자바 1.4는더이상스프링에서지원하지않는다. 1.4.3 스프링포트폴리오에서새로워진기능 코어스프링프레임워크외에도스프링을기반으로하는프로젝트에는흥미로운새로운움직임이있다. 모든세부적인변경사항을다루기에지면이부족하지만중요하다고생각되는몇가지항목을살펴보면다음과같다. 스프링웹플로 2.0(Spring Web Flow 2.0) 은대화형웹애플리케이션생성을더쉽게만드는간편해진플로정의스카마와함께릴리즈됐다. 스프링웹플로 2.0과함께스프링자바스크립트 (Spring JavaScript) 와스프링페이스 (Spring Faces) 가생겼다. 스프링자바스크립트는동적인동작을이용하여웹페이지의점진적향상을가능하게하는자바스크립트라이브러리다. 스프링페이스는스프링 MVC 와스프링웹플로내에서뷰기술로 JSF의사용을가능하게한다. 예전의아씨지시큐리티 (Acegi Security) 프레임워크는완벽히정비되어스프링시큐리티 2.0(Spring Security 2.0) 으로릴리즈됐다. 이러한새로운구현에서스프링시큐리티는애플리케이션보안을설정하는데필요한 XML의양을극적으로줄이는새로운설정스키마를제공한다. 이책을쓰는시점에도스프링시큐리티는계속발전하고있다. 스프링시큐리티 3.0이최근에릴리즈됐는데, 보안제약조건을선언하는스프링의새로운표현식언어를활용하여선언적방식의보안적용을더간소화한다. 보다시피스프링은활동적이며, 계속해서진화하는프로젝트다. 엔터프라이즈자바애플리케이션개발을더용이하게만드는것을목표로새로운무엇이항상존재한다.
38 1 장스프링속으로 1.5 요약 지금까지스프링을이용해어떤일을할수있는지알아봤다. 스프링은엔터프라이즈자바개발자의작업을쉽게하고, 결합도가낮은코드를만드는것을목적으로한다. 여기서핵심은 DI와 AOP다. 1장에서는스프링의 DI에대해간단히살펴봤다. DI는객체들이상호간의종속관계나구체적인구현방법을알필요가없도록애플리케이션객체들을연결하는방법이다. 종속객체를필요로하는객체가직접종속객체를얻어오는것이아니라, 종속객체를필요로하는객체에종속객체가부여되는방식이다. 종속객체를필요로하는객체는인터페이스를통해서주입된객체만알기때문에결합도가낮아진다. DI에이어스프링의 AOP 지원에대해서도간단히살펴봤다. AOP를이용하면일반적으로애플리케이션의여러곳에흩어져있는로직을한곳으로, 즉애스펙트로모을수있다. 각애스펙트는스프링이빈들을와이어링할때런타임에연결되어효과적으로빈에새로운기능을부여한다. DI와 AOP는스프링의중추에해당한다. 스프링프레임워크를잘사용하려면이두가지주요기능을잘이해하고사용해야한다. 이장에서는스프링의 DI와 AOP 기능의기본적인사항들을알아봤지만, 뒤에나오는여러장을통해 DI와 AOP를자세히알아보겠다. 그럼이제잔소리는그만접어두고 2장으로이동하여스프링에서 DI를이용해객체들을연결하는방법을알아보자.