'FRAMEWORK/SPRING MVC'에 해당되는 글 10건

  1. 2014.09.29 log4j.properties 설정
  2. 2014.09.29 Spring - 뷰 리졸버(View resolver)
  3. 2014.09.29 Spring - Transaction2 (선언적 트랜잭션 처리)
  4. 2014.09.29 Spring - Transaction1 (스프링의 트랜잭션 지원)
  5. 2014.09.29 Spring - POJO
  6. 2014.09.29 Spring - Bean 초기화 및 생명주기
  7. 2014.09.29 Spring - Bean Scope
  8. 2014.09.29 Spring - IoC & DI
  9. 2014.09.29 Spring - AOP 개념 정리
  10. 2014.09.29 Spring MVC 와 DispatcherServlet

log4j.properties 설정

FRAMEWORK/SPRING MVC 2014. 9. 29. 13:37

* 설정 준비

- log4j-1.x.xx.jar를 복사해서 eclipse/workspace/<프로젝트명>/WEB-INF/lib에 붙여넣기

- eclipse/workspace/<프로젝트명>/WEB-INF/src에 log4j.properties를 작성

- eclipse/workspace/<프로젝트명>/WEB-INF/web.xml에 아래 부분 추가

<init-param>

      <param-name>log4j-configuration</param-name>

      <param-value>/WEB-INF/src/log4j.properties</param-value>

</init-param>


-> 굳이 /WEB-INF/src에 log4j.properties를 작성하지 않아도 되는데, 이 properties 파일 위치를 바꿀 경우 web.xml의

param-value의 값도 바꾼 위치에 맞게 설정해준다.


자바 프로그램의 로깅에서 사실상 표준의 위치에 있는 Log4J의 설정 파일은 프로퍼티 형태의 log4j.properties와 XML 형태의 log4j.xml이 있다. 

XML 버전이 조금 늦게 나왔지만 향후에는 XML 설정파일만 지원하게 될 것이라는 얘기도 있지만, 프로퍼티 설정파일이 Java의 deprecated 메소드가 여전히 남아있는 것처럼 아주 없어질 것 같지는 않다.

한 가지 유의할 점은 Log4J가 자동 설정파일을 찾을 때 클래스패스에서 log4j.xml 파일을 먼저 찾고, 이 xml 파일이 없을 경우에만 log4j.properties를 찾는다는 것이다. 


* log4j.properties의 사용 예제

#로그설정(로그레벨 DEBUG ~ FATAL, OFF일경우 로그사용 안함), 사용 로그 이름
log4j.rootLogger=DEBUG, stdout, rolling


#stdout 로그 레벨 설정

log4j.appender.stdout.Threshold=WARN

Log Level
TRACE : 가장 상세한 정보를 나타낼 때 사용한다.
DEBUG : 일반 정보를 상세히 나타낼 때 사용한다.
INFO  : 일반 정보를 나타낼 때 사용한다.
WARN  : 에러는 아니지만 주의할 필요가 있을 때 사용한다.
ERROR : 일반 에러가 일어 났을 때 사용한다.
FATAL : 가장 크리티컬한 에러가 일어 났을 때 사용한다.


  FATAL > ERROR > WARN > INFO > DEBUG > TRACE


#stdout 콘솔 어펜더로 사용
log4j.appender.stdout=org.apache.log4j.ConsoleAppender


Appender

 Appender 설 명
 org.apache.log4j.AsyncAppender 비동기 출력 - 네트워크 전송등 조금 특수한 용도에 사용된다. 로그 이벤트를 queue에 모은후 다른 쓰레드에서 스케줄로 로그를 출력해주는것이다. 다른 Appender와 결합해서 사용한다.
 org.apache.log4j.ConsoleAppender

 stdout, stderr 출력 - Console

 org.apache.log4j.DailyRollingFileAppender 지정한 시간단위로 파일 출력- File
 org.apache.log4j.varia.ExternallyRolledFileAppender 외부 Roller로 출력
 org.apache.log4j.FileAppender

 파일 출력 - File

 org.apache.log4j.jdbc.JDBCAppender 데이터베이스로 출력
 org.apache.log4j.net.JMSAppender JMS로 출력
 org.apache.log4j.lf5.LF5Appender LogFactor5라는 스윙 로그뷰어로 출력
 org.apache.log4j.nt.NTEventLogAppender Windows 이벤트 로그로 출력
 org.apache.log4j.varia.NullAppender 아무것도 안함
 org.apache.log4j.RollingFileAppender 파일 크기단위로 파일 출력 - File
 org.apache.log4j.net.SMTPAppender 메일로 출력
 org.apache.log4j.net.SocketAppender 외부 서버에 Socket으로 출력
 org.apache.log4j.net.SocketHubAppende SocketServer로서 출력
 org.apache.log4j.net.SyslogAppender Unix Syslog로 출력
 org.apache.log4j.net.TelnetAppender#FAE0D4


#stdout 패턴 레이아웃 사용
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout


Layout - 로그 출력을 어떤 형식으로 할지 구체적으로 지정해주는 부분

  org.apache.log4j.SimpleLayout

 기본 레이아웃

  org.apache.log4j.TTCCLayout 

 시간 출력에 특화된 레이아웃

  org.apache.log4j.HTMLLayout

 HTML형식으로 출력. 테이블 형식으로 각각의 로그를 출력한다.

  org.apache.log4j.XMLLayout

 XML형식으로 출력

  org.apache.log4j.PatternLayout

 사용자 마음대로 패턴을 지정하여 출력


#stdout 레이아웃 패턴 정의
log4j.appender.stdout.layout.ConversionPattern=%t> [%d{yyyy-MM-dd HH:mm:ss}] [%c{1}] [%L] [%p] %m %n


Options
%d : 로깅 이벤트가 일어난 날자(date)
%p : 로깅 이벤트의 priority
%t : 로깅 이벤트를 생성한 스레드 이름
%c : 로깅 이벤트의 category
%F : 로깅요청을 일으킨 파일 이름
%L : 로깅요청을 일으킨 파일의 행번호
%x : 로깅이벤트를 발생시킨 스레드에 관련된 내포검사항목  (Nested Diagnostic Context : NDC)을 출력
%C : 로깅요청을 일으킨 호출자의 완전한 클래스이름
%M : 로깅요청을 일으킨 메소드
%m : 메세지
%n : 플랫폼 독립적인 개행문자
%l : 소스코드의 위치정보를 출력한다. %C. %M(%F:%L) 의 축약형


#롤링파일 Appender 사용
log4j.appender.rolling=org.apache.log4j.RollingFileAppender
#롤링파일 로그에 대한 파일명 (경로 설정 가능 함)
log4j.appender.rolling.File=output.log

# Log File 뒤에 날짜 패턴 추가
log4j.appender.rolling.DatePattern='.'yyyyMMdd

# Tomcat Restart시 새로 작성 여부 (true - 기존파일에 추가, false - 새로 작성)
log4j.appender.rolling.Append=true

#로그파일 최대 크기
log4j.appender.rolling.MaxFileSize=1KB
#최대파일이 넘어갔을 경우 백업
log4j.appender.rolling.MaxBackupIndex=1
#롤링파일 패턴 레이아웃 사용
log4j.appender.rolling.layout=org.apache.log4j.PatternLayout
#롤링파일 패턴 정의
log4j.appender.rolling.layout.ConversionPattern=%t> [%d{yyyy-MM-dd HH:mm:ss}] [%c{1}] [%L] [%p] %m %n



:

Spring - 뷰 리졸버(View resolver)

FRAMEWORK/SPRING MVC 2014. 9. 29. 13:36

뷰 리졸빙(View resolving)

사용자에게 결과를 랜더링하여 보여주기 위하여 사용


뷰 리졸버 구현체

 뷰 리졸버

 설명

 BeannameViewResolver

 논리적 뷰 이름과 동일한 ID를 갖는 <bean> 으로 등록된 View의 구현체를 찾는다.

 ContentNegotiatingViewResolver

 요청되는 콘텐츠 형식에 기반을 두어 선택한 하나 이상의 다른 뷰 리졸버에 위임한다.

 FreeMarkerViewResolver

 FreeMarker 기반의 템플릿을 찾는다.

 경로는 논리적 뷰 이름에 접두어와 접미어를 붙여 구성

 InternalResourceViewResolver

 웹 어플리케이션의 WAR 파일 내에 포함된 뷰 템플릿을 찾는다.

 뷰 템플릿의 경로는 논리적 뷰 이름에 접두어와 접미어를 붙여 구성

 JasperReportsViewResolver

 Jasper Reports 리포트 파일로 정의된 뷰를 찾는다.

 경로는 논리적 뷰 이름에 접두어와 접미어를 붙여 구성

 TilesViewResolver

 Tiles 템플릿으로 정의된 뷰를 찾는다.

 템플릿 이름은 논리적 뷰 이름에 접두어와 접미어를 붙여 구성

 UrlBasedViewResolver

 ViewResolver의 구현체로 특별한 맵핑 정보 없이 view 이름을 URL로 사용

 View 이름과 실제 view 자원과의 이름이 같을 때 사용할 수 있다.

 ResourceBundleViewResolver

 ViewResolver 의 구현체로 리소스 파일을 사용합니다.

 views.properties 를 기본 리소스 파일로 사용합니다.

 VelocityLayoutViewResolver

 VelocityViewResolver의 서브클래스로, 스프링의 VelocityLayoutView를 통해 페이지 구성을 지원

 VelocityViewResolver

 Velocity 기반의 뷰를 찾는다.
 경로는 논리적 뷰 이름에 접두어와 접미어를 붙여 구성

 XmlViewResolver

 ViewResolver 의 구현체로 XML파일을 사용합니다.

 /WEB-INF/views.xml 을 기본 설정파일로 사용합니다.

 XsltViewResolver

 XSLT기반의 뷰를 찾는다. 

 XSLT스타일시트의 경로는 논리적 뷰 이름에 접두어와 접미어를 붙여 구성






ViewResolver들의 상위에 있는 AbstractCachingViewResolver 이 클레스가 캐슁 기능을 제공하기 때문에 이 클레스의 하위 클레스들은 엄청난 성능 향상을 맛볼 수 있다. 캐슁 기능을 끄고 싶을 때는 cache 속성을 false로 하면 된다.
런타임시 특정 view를 다시 읽어야 한다면 removeFromCache(String viewName, Locale loc) 메소드를 사용할수 있다.


Resolver 사용 예제

1. UrlBasedViewResolver

ViewResolver 의 구현체로 특별한 맵핑 정보 없이 의미상 view 이름을 URL로 사용

View 이름과 실제 view 자원과의 이름이 같을 때 사용

<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">

    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>

viewResolver는 사용자에게 보여줄 view를 생성할 때, prefix와 suffix를 지정해 줄 수 있다. 

만약 controller에서 넘겨준 modelAndView 값이 index이라면, 위의 정의에 따라 viewResolver는 "/WEB-INF/index.jsp"를 찾는다.


2. InternalResourceViewResolver

UrlBasedViewResolver 를 상속 받았으며 InternalResourceView(Servlet, JSP)를 사용

<bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
  <property name="prefix" value="/WEB-INF/jsp/"/>
  <property name="suffix" value=".jsp"/>
</bean>

UrlBasedViewResolver를 상속받아 사용은 유사하다. 

참고적으로 JSTL을 사용하려면 위의 viewClass를 정의해주면 된다.


3. ResourceBundleViewResolver

ViewResolver 의 구현체로 리소스 파일 사용(views.properties 를 기본 리소스 파일로 사용)

다른 View 리졸버는 항상 논리적인 View 이름을 사용하여 단일한 View 구현 객체를 결정하는 반면, ResourceBundleViewResolver는 사용자의 Locale을 기초로 하여 동일한 논리적인 뷰 이름으로 서로 다른 View 구현 객체를 리턴 할 수 있다.


.properties 의 문법

viewname.class = class-name

viewname.url = view-url


viewReslover 정의

<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
    <property name="basename" value="views"/>       // view.properties에 기술 
    <property name="defaultParentView" value="parentView"/>

    // views.properties 파일에 정의되지 않을 경우에는 parentView 에 정의된 사항을 따른다.
</bean>


views.properties 리소스 파일

bookView.class = org.springframework.web.servlet.view.JstlView
bookView.url = WEB-INF/jsp/book/bookView.jsp

bookEdit.class = org.springframework.web.servlet.view.JstlView
bookEdit.url = WEB-INF/jsp/book/bookEdit.jsp


Controller 작성

public class TestController{

public ModelAndView index(HttpServletRequest request, HttpServletResponse response){

Map model = new HashMap();


return new ModelAndView("bookview", "model", model);

}

}

viewResolver는 사용자에게 보여줄 view를 생성할 때, views.properties에서 정의된 URL을 참조한다.

만약 views.properties파일에 원하는 정보가 없다면 parentView.properties파일을 참조하게 된다.


4. XmlViewResolver

ViewResolver 의 구현체로 XML파일 사용(/WEB-INF/views.xml 을 기본 설정파일로 사용)

BeanNameViewResolver와 마찬가지로  뷰 이름과 동일한 이름을 갖는 빈을 뷰 객체로 사용


<bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>


<bean id="excelViewResolver" class="org.springframework.web.servlet.view.XmlViewResolver">
    <property name="order" value="1"/>
    <property name="location" value="/WEB-INF/simpleviews.xml"/>
</bean>


simpleviews.xml

<beans>
    <bean name="report" class="org.springframework.example.ReportExcelView"/>
</beans>


5. TilesViewResolver

런타임 시 전체 페이지에 조립되는 페이지의 조각을 배치하기 위한 템플릿 프레임워크


Tiles 2를 이용하기 위해서는 UrlBasedViewResolver를 정의한 후 viewClass를 org.springframework.web.servlet.view.tiles2.TilesView로 정의

<bean id="tilesViewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.tiles2.TilesVIew"/>
</bean>


Tiles 매핑 관련 정보가 작성되어 있는 tiles definition 파일의 위치를 정의

TilesViewResolver자체는 타일즈 정의에 대하여 아무런 정보를 가지지 않는다. 대신 해당 정보를 추적하는 TileConfigurer에 의존한다. 따라서 tiles definition의 정보를 아래와 같이 정의한다.

<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles2.TIlesConfigurer">
    <property name="definitions">

        <list>

            <value>/WEB-INF/view.xml</value>

        </list>

</bean>


view.xml

Tiles를 사용하기 위해서는 실제 Controller에서 리턴된 view 이름을 토대로 페이지에 출력해줄 tiles attribute를 정의해주는 tiles definition을 정의

<definition name="template" template="/sample/layouts/main_template.jsp">  // 공통 레이아웃 정의

    <put-attribute name="header" value="/sample/layouts/top.jsp" />

    <put-attribute name="body" value="/sample/layouts/welcome.jsp" />

    <put-attribute name="footer" value="/sample/layouts/left.jsp" />

</definition>

<definition name="home" extends="template">                             // home 타일 정의

    <put-attribute name="body" value="/sample/view/home.jsp" />

</definition>


먼저 Layout을 정의한 jsp 페이지를 정의한다. 

해당 main_template.jsp 페이지에서 기본적으로 사용할 페이지 구성 요소(위의 예에선 header, body, footer)들을 정의한 후 다른 view들은 미리 정의된 template이라는 definition을 extends하여 body만 설정하여 사용할 수 있다. 

위의 예에서 home라는 이름의 view가 리턴될 경우 "/sample/view/home.jsp" 페이지의 레이아웃으로 

header에는 "/sample/layouts/top.jsp" body는 "/sample/view/home.jsp", footer는 "/sample/layouts/left.jsp"이 될 것이다. 


JSP에서 tiles 구성 요소를 넣을 때는 아래와 같이 tiles taglib을 정의한 후 <tiles:insertAttribute> 태그를 이용하여 사용한다.


6. 다수의 ViewResolver 설정

하나의 DispatcherServlet은 한 개 이상의 ViewResolver 설정 가능

order 프로퍼티를 이용하여 뷰 이름을 검사할 ViewResolver의 순서를 결정 (order프로퍼티의 값이 작은 것이 우선)

우선순위를 명시하지 않으면 Integer.MAX_VALUE를 order 프로퍼티의 값으로 가진다.

우선순위가 높은 ViewReslover가 null을 리턴하면 그 다음 우선순위를 갖는 ViewResolver에 뷰를 요청


<bean id="excelViewResolver" class="org.springframework.web.servlet.view.XmlViewResolver">
    <property name="order" value="0"/>
    <property name="location" value="/WEB-INF/simpleviews.xml"/>
</bean>


<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedXmlViewResolver">

    <property name="order" value="1"/>

    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>


우선순위 결정 시 주의점

InternalResourceViewResolver는 항상 뷰 이름이 매핑되는 뷰 객체를 리턴하므로 마지막 순위를 갖도록 지정 (null은 리턴하지 않는다.)

VelocityViewResolver와 VelocityLayoutViewResolver의 경우 둘 다 뷰 이름에 매필되는 Velocity 템플릿 파일이 존재 하지 않을 경우 null을 리턴하지 않고 예외를 발생 시킴 (VelocityViewResolver 역시 가장 낮은 순위를 갖도록 한다.)


참조 :

http://whiteship.tistory.com/819

http://linuxp.tistory.com/entry/Spring-Framework%EC%9D%98-ViewResolver

http://dev.anyframejava.org/docs/anyframe/4.1.0/reference/html/ch11.html

http://ingenuity.egloos.com/m/3108539

http://blog.naver.com/PostView.nhn?blogId=apchima&logNo=80159530985

:

Spring - Transaction2 (선언적 트랜잭션 처리)

FRAMEWORK/SPRING MVC 2014. 9. 29. 13:36

선언적 트랜잭션

Transaction Template와 달리 트랜잭션 처리를 코드에서 직접적으로 수행하지 않음

설정 파일이나 어노테이션을 이용하여 트랜잭션의 법위, 롤백 규칙 등을 정의


선언적 트랜잭션 정의 방식

1. <tx:advice> 태그를 이용하여 트랜잭션 처리

2. @Transaction 어노테이션을 이용한 트랜잭션 설정

3. TransactionProxyFactoryBean 태그를 이용한 트랜잭션 처리 

(2.0 이전 빈마다 프록시를 만들어서 사용하였으나 현재는 많이 사용되지 않는다.)


트랜잭션 특성

선언적 트랜잭션은 트랜잭션 특성으로 정의된다.

트랜잭션 특성이란?  특정 메소드에 어떻게 적용되어야 하는지에 대한 정책을 기술한 것

전파방식, 격리수준, 읽기전용힌트, 타임아웃, 롤백규칙으로 정의된다.


<tx:advice> 태그를 이용한 트랜잭션 처리

<tx:advice> 태그를 이용해서 트랜잭션 속성을 정의하기 위해서는 먼저 tx 네임스페이스를 추가.

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    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
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
        p:dataSource-ref="dataSource" />

    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="save*" propagation="REQUIRED" />

            <tx:method name="*" propagation="SUPPORTS" read-only="true" />
        </tx:attributes>
    </tx:advice>
    ...
 </beans>


tx네임스페이스를 추가하여 <tx:advice>태그, <tx:attributes>태그, <tx:method>태그 이용으로 트랜잭션 속성 정의


<tx:advice> 태그

트랜잭션을 적용할 때 사용될 Advisor를 생성

id속성 - 생성 될 트랜잭션 Advisor의 식별 값 입력

transaction-manager속성 - 스프링의 PlatformTransactionManager 빈을 설정


<tx:method> 태그

트랜잭션을 설정할 메소드 및 트랜잭션 속성을 설정

<tx:attributes>태그의 자식 태그로 설정

name 속성 

- 트랜잭션이 적용될 메소드의 이름을 명시.  

- "*"을 사용한 설정이 가능 ("get*" 으로 설정한 경우 이름이 get으로 시작하는 메소드을 의미)

propagation 속성 - 트랜잭션 전파 규칙을 설정

isolation 속성 - 트랜잭션 격리 레벨을 설정

read-only 속성 - 읽기 전용 여부를 설정

no-rollback-for 속성 - 트랜잭션을 롤백하지 않을 예외 타입을 설정

rollback-for 속성 - 트랜잭션을 롤백할 예외 타입을 설정

timeout 속성 - 트랜잭션의 타임 아웃 시간을 초 단위로 설정


트랜잭션 적용

<tx:advice> 태그는 Advisor만 생성하는 것일뿐, 실제로 트랜잭션을 적용하는 것은 아니다.

트랜잭션의 적용은 AOP를 통하여 이루어짐

 <aop:config>
    <aop:advisor 

        pointcut="execution(* *...service.*(..))"

        advice-ref="txAdvice" />
 </aop:config>

- pointcut 어트리뷰트는 AspectJ 포인트 컷 표현식을 이용하여 service의 모든 메소드를 어드바이스 해야 한다고 정의

- 어떤 트랜잭션 특성이 어떤 메소드에 적용되는지는 트랜잭션 어드바이스에 정의되어 있으며, 이는 advice-ref 어트리뷰트에 txAdvice라는 이름으로 참조하고 있다는 것을 의미한다.



1. 전파방식(propagation behavior)

- 호출되는 메소드와 클라이언트에 대하여 트랜잭션의 경계를 정의

  전파방식

 의미

  MANDATORY

 호출 전에 반드시 진행 중인 트랜잭션이 존재해야 한다.
 진행 중인 트랜잭션이 존재하지 않을 경우 예외 발생

  NESTED

 이미 진행 중인 트랜잭션이 존재하면 중첩된 트랜잭션에서 실행되어야 함을 나타낸다.

 중첩된 트랜잭션은 본 트랜잭션과 독립적으로 커밋되거나 롤백될 수 있다. 만약 본 트랜잭션이 없는 상황이라면 이는 PROPAGATION_REQUIRED와 동일하게 작동한다.

 그러나 이 전파방식은 벤더 의존적이며, 지원이 안되는 경우도 많다.

  NEVER

 트랜잭션 진행 상황에서 실행 될 수 없다.

 만약 이미 진행 중인 트랜잭션이 존재하면 예외 발생

  SUPPORTED

 트랜잭션이 없는 상황에서 실행

 만약 진행 중인 트랜잭션이 있다면 해당 메소드가 반환되기 전까지 잠시 중단한다.

  REQUIRED

 트랜잭션 상황에서 실행되어야 한다.

 진행 중인 트랜잭션이 있다면 이 트랜잭션에서 실행된다.

 없는 경우에는 트랜잭션이 새로 시작된다.

  REQUIRED_NEW

 자신만의 트랜잭션 상황에서 실행되어야 한다. 

 이미 진행 중인 트랜잭션이 있으면 그 트랜잭션은 해당 메소드가 반환되기 전에 잠시 중단된다.

  SUPPORTS

 진행 중인 트랜잭션이 없더라도 실행 가능하고, 트랜잭션이 있는 경우에는 이 트랜잭션 상황에서 실행된다.


2. 격리수준(isolation level)

- 한 트랜잭션이 동시 진행 중인 다른 트랜잭션의 영향을 얼마나 받는지 정의

  격리수준

  의미

  DEFAULT

  하부 데이터 저장소의 기본 격리 수준을 이용

  이는 데이터 저장소마다 다르다.

  READ_UNCOMMITTED

  커밋되지 않은 데이터 변경사항을 읽을 수 있다.
  더티읽기, 반복할 수 없는 읽기, 팬텀읽기를 허용

  READ_COMMITTED

  다른 트랜잭션에 커밋된 변경사항만 읽을 수 있다.
  더티읽기는 방지되지만, 반복할 수 없는 읽기, 팬텀읽기는 허용

  REPEATABLE_READ

  같은 데이터 필드는 여러 번 반복해서 읽더라도 동일한 값을 읽도록 한다.

  자신이 변경한 필드는 변경된 값을 읽는다.

  더티읽기와 반복할 수 없는 읽기는 방지되지만, 팬텀읽기는 허용

  SERIALIZABLE

  완전한 ACID를 보장하는 격리 수준
  보통 해당 트랜잭션에 관련된 모든 테이블을 잠그기 때문에 성능이 가장 비효율적인 격리 수준이다.


더티읽기 - 한 트랜잭션에서 다른 트랜잭션에 의해 변경될 수 있지만 아직 변경되지 않은 데이터를 읽게 되는 문제

   - 데이터가 커밋되지 않고 롤백되어 버린다면, 첫 번째 트랜잭션에서 읽은 이 데이터는 유효하지 않은 데이터가 된다.

반복할 수 없는 읽기 - 트랜잭션이 같은 질의를 두 번 이상 수행할 때 서로 다른 데이터를 얻게되는 문제

   - 각 질의 수행 사이에 동시 진행 중인 다른 트랜잭션에서 이 데이터를 변경하는 경우 발생

팬텀읽기 - 반복할 수 없는 읽기와 유사하지만 차이가 있따.

   - 팸텀읽기는 어떤 트랜잭션(T1)이 둘 이상의 데이터 행을 읽은 다음, 동시 진행 중인 다른 트랜잭션(T2)이 추가 행을

     삽입할 때 발생한다. T1에서의 동일한 질의를 다시 수행하면, T1은 이전에는 없던 데이터 행까지 읽게 된다.


3. 읽기전용

- 트랜잭션이 하부의 데이터 저장소에 대해 오직 읽기 작업만 수행하고, 수정등의 작업은 수행하지 않는 경우 사용.

- 읽기전용으로 설정 할 경우 데이터 저장소에서는 트랜잭션의 읽기 전용 특성을 활용한 몇 가지의 최적화를 수행 할 여지가 생긴다. 따라서 트랜잭션을 읽기 전용으로 선언함으로써 데이터 저장소에게 최적화가 적절하다고 판단하고 최적화를 적용할 수 있는 기회를 줄 수 있다.

<tx:advice id="txAdvice" transaction-manager="transactionManager">

<tx:attributes>

<tx:method name="get*" propagation="REQUIRES" read-only="true" />

</tx:attributes>

</tx:advice>

데이터 저장소는 트랜잭션이 시작할 때 읽기 전용 최적화를 수행하므로 새로운 트랜잭션을 시작 할 수 있는 전파방식(REQUIRES, REQUIRES_NEW, NESTED)을 가진 메소드에 선언해야 의미가 있다.


4. 타임아웃

- 트랜잭션이 일정 시간이 지나버리면 롤백하도록 선언하기 위해 사용

- 새로운 트랜잭션을 시작 할 수 있는 전파방식(REQUIRES, REQUIRES_NEW, NESTED)을 가진 메소드에 선언해야 의미가 있다.


5. 롤백규칙

어떤 예외가 발생하였을 때 롤백하고, 어떤 경우에는 롤백하지 않는지 정의

기본적으로 런타임 예외(Runtime Exception)에 대해서는 롤백하고, 검사형 예외(Checked Exception)에 대해서는 롤백하지 않는다.

롤백규칙을 지정해 놓으면 검사형 예외(Checked Exception)에 대해서도 트랜잭션 롤백 처리를 할 수 있다.


rollback-for 속성 - 예외 발생 시 롤백 작업을 수행할 예외 타입을 설정

no-rollback-for 속성 - 예외가 발생하더라도 롤백을 하지 않는 예외 타입을 설정


명시할 예외 타입이 한 개 이상인 경우 각각의 예외는 콤마로 구분

예외 클래스는 완전한 이름을 입력하거나, 패키지 이름을 제외한 클래스 이름만 입력 가능

<tx:method name="get*" read-only="true" rollback-for="Exception"/>

<tx:method name="get*" read-only="true" no-rollback-for="Exception"/>

rollback-for와 no-rollback-for를 동시에 정의하면 가장 상위의 정의가 적용된다.



어노테이션 기반 트랜잭션 설정

@Transactional 어노테이션 사용

메소드나 클래스에 적용

관련 트랜잭션 속성을 설정


 import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;

 public class PlaceOrderServiceAnnotationImpl implements PlaceOrderService {
    @Transactional(propagation=Propagation.REQUIRED, readOnly=true)
    public PurchaseOrderResult order(PurchaseOrderRequest orderRequest)
        throws ItemNotFoundException {

        Item item = itemDao.findById(orderRequest.getItemId());

        if(item == null) {
            throw new ItemNotFoundException(orderRequest.getItemId()):
        }

        PaymentInfo paymentInfo = new PaymentInfo(item.getPrice());
        paymentInfoDao.insert(paymentInfo);
        ...
    }
 }


@Transaction 어노테이션 속성

 속성

 설

 propagation

 트랜잭션 전파 규칙 설정, Propagation 열거형 타입에 값이 정의됨

 기본 값 - Propagation.REQUIRED

 isolation

 트랜잭션 격릴 레벨 설정

 readOnly

 읽기 전용 여부 설정

 rollbackFor

 트랜잭션을 롤백할 예외 타입 설정 ex)rollbackFor={Exception.class}

 noRollbackFor

 트랜잭션을 롤백하지 않을 예외 타입 설정 ex)noRollbackFor={Exception.class}

 timeout

 트랜잭션의 타임아웃을 초단위로 설정


@Transactional 어노테이션이 적용된 스프링 빈에 트랜잭션 적용

<tx:annotation-driven>태그를 설정

 <tx:annotation-driven transaction-manager="transactionManager" />

 <bean id="placeOrderService"
    class="kame.spring.store.domain.PlaceOrderServiceAnnotationImpl"
    ... />


<tx:annoation-driven> 태그 대신 PersistenceAnnotationBeanPostProcessor 클래스를 빈으로 등록해도 된다.

 <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />



참조 : 

http://blog.naver.com/PostView.nhn?blogId=chocolleto&logNo=30087623408&parentCategoryNo=&categoryNo=&viewDate=&isShowPopularPosts=false&from=postView

http://tedwon.com/display/dev/Spring+Transactions

'FRAMEWORK > SPRING MVC' 카테고리의 다른 글

log4j.properties 설정  (0) 2014.09.29
Spring - 뷰 리졸버(View resolver)  (0) 2014.09.29
Spring - Transaction1 (스프링의 트랜잭션 지원)  (0) 2014.09.29
Spring - POJO  (0) 2014.09.29
Spring - Bean 초기화 및 생명주기  (0) 2014.09.29
:

Spring - Transaction1 (스프링의 트랜잭션 지원)

FRAMEWORK/SPRING MVC 2014. 9. 29. 13:35

트랜잭션 (Transaction)

트랜잭션은 현대의 웹 보안에 있어서 매우 중요한 역활을 차지하며 DB와 JAVA언어가 데이터를 주고 받는 과정에 원자성을 부여하는 수단을 일컫는다.


간단하게 설명하자면 !!

어떤 작업 프로세스를 하나로 묶어 실행 중 하나의 작업이라도 실패하면 모두 실패 처리를 해주고, 

전체 작업이 성공하면 성공 처리를 해주는 것이다.



스프링 프레임워크의 트랜잭션 

 - Java Transaction API(JTA), JDBC, Hivernate, Java Persistence API(JPA), Java Data Objects(JDO)같은 여러 가지 트랜잭션 API간에 일관성있는 프로그래밍 모델

 - 선언적인 트랜잭션 관리 지원

 - 프로그래밍적인 트랜잭션 관리에 대해 JTA같은 복잡한 트랜잭션 API보다 간단한 API

 - 스프링의 데이터 접근 추상화와의 뛰어난 통합


스프링의 트랜잭션 기술을 이용하면, 단 몇 줄의 코드만으로 다이나믹 프록시와 AOP라는 기술을 통해 

크게는 인터페이스 단위에서 클래스, 작게는 메서드까지 세분화하여 트랜잭션을 컨트롤 할 수 있다. 

거기다 애노테이션 기술로 @Transactional을 설정하는 것으로도 트랜잭션 설정이 가능하게 해준다.



스프링의 트랜잭션 관리 지원

1. 프로그래밍 처리 방식 

- 코드에서 트랜잭션의 범위를 정교하게 정의 할 수 있다. 

  하지만 트랜잭션이 스프링의 종속적인 코드가 될 가능성이 크다.


2. 선언적인 트랜잭션

- 실제 작업과 트랜잭션 규칙을 분리 할 수 있다. 

- 격리 수준이나 타임아웃같은 추가적인 애트리뷰트를 선언 할 수 있다.


트랜잭션의 동작 원리



1. Caller가 target 메소드를 호출하면 먼저 AOP Proxy가 호출된다.

2. Transaction Advisor는 새로운 트랜잭션을 생성한다.

3. Custom interceptor들이 transaction advisor 전 후로 호출된다.

4. Target 비즈니스 메소드가 호출된다.

5. Transaction Advisor는 커밋 또는 롤백을 결정하고 호출자에게 넘긴다.

6. AOP Proxy는 결과를 받아서 Caller에서 넘긴다


스프링의 PlatformTransactionManager 설정

스프링은 트랜잭션을 직접 관리하지 않고, 스프링에 포함된 일련의 트랜잭션 관리자를 이용한다.




트랜잭션 관리자는 트랜잭션 관리 책임을 JTA나 퍼시스턴스 메커니즘이 제공하는 플랫폼 종속적인 트랜잭션 구현체에 위임한다.


1. JDBC 트랜잭션

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">

<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />

<property name="url" value="jdbc:oracle:thin:@" />

<property name="username" value="" />

<property name="password" value="" />


<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

<property name="dataSource" ref="dataSource" />

</bean>


DataSourceTransactionManager는 내부적으로 DataSource에서 획득하게 될 java.sql.Connection 객체를 이용하여 트랜잭션을 관리한다.

예를 들어, 모든 작업이 성공한 성공적인 트랜잭션은 이 커넥션 객체의 commit()메소드 호출을 통해 Commit되고, 실패한 트랜잭션은 rollback()메소드를 통해 RollBack된다.


2. 하이버네이트 트랜잭션

<bean id="sessionFactory" class="org.springframeworkd.orm.hibernate3.LocalSessionFactoryBean">

<property name="dataSource" ref="dataSource" />

<property name="mappingResources">

.....

</property>

<property name="hibernateProperties">

.....

</property>

</bean>


<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager>

<property name="dataSource" ref="dataSource" />

</bean>


sessionFactory 프로퍼티를 통해서 전달받은 하이버네이트 Session으로부터 하이버네이트 Transaction을 생성한 뒤, Transaction을 이용해서 트랜잭션을 관리.


3. JPA 트랜잭션

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">

.....

</bean>


<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">

<property name="entityManagerFactory" ref="entityManagerFactory" />

</bean>


entityManagerFactory 프로퍼티를 통해 전달받은 EntityManagerFactory를 이용해서 트랜잭션을 관리.


4. JTA 트랜잭션

JTA를 사용한다면 트랜잭션 관리자 정의는 JDBC, Hibernate, JPA나 다른 지원 기술 등 어떤 데이터접근 기술을 사용한지와 상관없이 같을 것이다. 이는 JTA 트랜잭션이 트랜잭션이 적용된 어떤 리소스와도 동작하는 전역 트랜잭션이기 때문이다.



참조 : 

http://demon92.tistory.com/67

http://blog.outsider.ne.kr/869

http://springmvc.egloos.com/498979

http://blog.naver.com/PostView.nhn?blogId=chocolleto&logNo=30087623217&parentCategoryNo=&categoryNo=&viewDate=&isShowPopularPosts=false&from=postView

'FRAMEWORK > SPRING MVC' 카테고리의 다른 글

Spring - 뷰 리졸버(View resolver)  (0) 2014.09.29
Spring - Transaction2 (선언적 트랜잭션 처리)  (0) 2014.09.29
Spring - POJO  (0) 2014.09.29
Spring - Bean 초기화 및 생명주기  (0) 2014.09.29
Spring - Bean Scope  (0) 2014.09.29
:

Spring - POJO

FRAMEWORK/SPRING MVC 2014. 9. 29. 13:34
지난 몇 년간 EJB3를 포함한 많은 자바의 새로운 기술과 제품들은 저마다 POJO 프로그래밍의 지원을 주요 장점으로 내세우며 등장했다. 그러나 POJO 프로그래밍이 과연 무엇이고 어떤 이득을 안겨줄 수 있는지에 대해 명확히 설명하는 것은 사실 쉽지 않다. 이 글에서는 POJO 프로그래밍이 왜 중요한지 살펴보고 그것을 통해 기존의 무겁고 지저분한 코드를 어떻게 깔끔하고 군살 없는 코드로 만들 수 있는지를 알아보자.

 

POJO는 ‘Plain Old Java Object’의 약자이다. 이를 번역하면 ‘평범한 구식 자바 오브젝트(객체)’라고 할 수 있겠다. 도대체 평범하고 구식인 자바 오브젝트가 뭐가 다르고 특별해서 POJO라고 부르는 것일까? 그럼 평범하지 않은 최신의 자바 오브젝트는 또 무엇인가?

POJO란 무엇인가?

POJO를 이해하려면 POJO라는 단어가 만들어진 역사적 배경을 살펴볼 필요가 있다. POJO는 마틴 파울러가 2000년 가을에 열렸던 어느 컨퍼런스의 발표를 준비하면서 처음 만들어낸 말이다. 마틴 파울러는 EJB(Enterprise JavaBean)보다는 단순한 자바 오브젝트에 도메인 로직을 넣어 사용하는 것이 여러 가지 장점이 있는데도 왜 사람들이 그 EJB가 아닌 ‘평범한 자바 오브젝트’를 사용하기를 꺼려하는지에 대해 의문을 가졌다. 그리고 그는 단순한 오브젝트에는 EJB와 같은 그럴듯한 이름이 없어서 그 사용을 주저하는 것이라고 결론 내렸다. 그래서 만든 단어가 POJO라는 용어인 것이다. POJO 기반의 기술을 사용한다고 말하면 왠지 첨단 기술을 사용하는 앞선 개발자인 듯한 인상을 주기 때문이다. 
이 POJO라는 용어를 만들어낸 전략은 매우 성공적이었다. EJB의 문제점과 한계를 느낀 많은 개발자들은 새로운 기술처럼 보이는 POJO 프로그래밍이라는 것에 관심을 가지게 되었고, POJO 기반의 프로그래밍 기술이 EJB의 강력한 대안으로 등장하기 시작했다. POJO 기반의 프레임워크, POJO 애플리케이션을 위한 플랫폼 등이 점점 인기를 끌게 되었고, 결국 POJO가 배제하려고 했던 EJB는 POJO 기반의 기술에 밀려 이제 레거시 기술로 사라질 위기에 처했다. 그렇다면 단지 EJB를 사용하지 않으면 모두 POJO라고 할 수 있을까? 그렇지는 않다. POJO 프로그래밍이라는 개념은 단지 ‘EJB가 아닌 자바’ 이상의 특징을 가지고 있는 프로그래밍 모델이다. POJO 기반의 개발은 생각보다 단순하지 않다. 
POJO를 좀더 이해하려면 EJB의 장단점을 함께 이해해야 한다. 그것은 POJO 프로그래밍이 다시 EJB 시대 이전으로 돌아가자는 것이 아니고, EJB를 넘어 그보다 더 앞으로 나아가자는 것이기 때문이다.

 

EJB와 엔터프라이즈 서비스

자바에서 EJB 기술의 등장은 필연적인 것이었다. 기업의 IT 시스템은 점점 그 중요성이 증대되고 그에 따라 점점 복잡한 기술이 요구되었으며 자바의 기초적인 JDK만으로는 그것을 충족시킬 수 없었다. 서버 기반의 자바 기술인 J2EE(Java2 Enter pise Edition)가 등장했지만 Servlet, JSP 레벨의 최소한의 서버 프로그래밍 인터페이스만 가지고는 복잡한 엔터프라이즈 애플리케이션을 제작하는 데 부담이 적지 않았다.


엔터프라이즈 시스템의 복잡도의 증가 영역

 - 기업업무처리의 IT시스템에 대한 의존도가 높아지면서 시스템이 다뤄야 하는 비즈니스로직 자체가 점차 복잡해진 것 애플리케이션 로직의 복잡도와 상세 기술의 복잡함을 개발자들이 한 번에 다룬다는 것은 쉬운 일이 아니었다. 한 개발자가 보험업무와 관련된 계산 로직을 자바로 어떻게 구현해야 하는지에 집중하면서 동시에 시스템 레벨에서 멀티 DB로 확장 가능한 트랜잭션 처리와 보안 기능을 멀티스레드 세이프하게 만드는 것에 신경 써야 한다면 여간 부담되는 게 아닐 것이다.

 - 많은 사용자의 처리요구를 빠르고 안정적이면서 확장 가능한 형태로 유지하기 위해 필요한 로우레벨의 기술적인 처리 요구 ( 대표적으로 트랜잭션 처리, 상태 관리, 멀티스레딩, 리소스 풀링, 보안 등 )

 

EJB는 이런 문제를 다루기 위해 등장했다. EJB 1.0의 스펙이 제시한 EJB의 비전은 ‘EJB는 애플리케이션 개발을 쉽게 만들어 준다. 애플리케이션 개발자는 로우레벨의 기술들에 관심을 가질 필요도 없다’였다. 애플리케이션 개발자들은 다뤄야 하는 해당 도메인과 비즈니스 로직에만 집중하면 된다는 것이었다. 게다가 EJB는 독립적으로 개발한 컴포넌트들을 서버에 자유롭게 배포하고 서로 연동해 사용하게 하는 컴포넌트 기반의 개발 모델을 제시할 뿐더러, 여러 대의 서버에 분산되어 있는 모듈간의 리모팅 처리도 개발자들이 거의 신경 쓰지 않고 개발할 수 있게 했다. 더 나아가 벤더별로 제각각 발전시켜 혼란에 빠지기 쉬운 자바의 서버 기술을 일관성 있게 구현하게 지원하므로 특정 서버에 종속되지 않고 서버간의 이동성(portability)을 보장해 준다고 약속했다

 

하지만 EJB는 불필요할 만큼 과도한 엔지니어링으로 실패한 대표적인 케이스였다.

EJB에서는 현실에서 1% 미만의 애플리케이션에서만 필요한 멀티 DB를 위한 분산 트랜잭션을 위해 나머지 99%의 애플리케이션도 무거운 JTA 기반의 글로벌 트랜잭션 관리 기능을 사용해야 했다. EJB의 혜택을 얻기 위해 모든 기능이 다 필요하지도 않은 고가의 WAS(CPU 당 몇 백에서 몇 천만 원씩 하는)를 구입해야 했고, 고급 IDE의 도움 없이는 손쉽게 다룰 수 없는 복잡한 설정 파일 속에서 허우적대야 했다. EJB 컴포넌트는 컨테이너 밖에서는 정상적으로 동작할 수 없으므로 개발자들은 끝도 없이 반복되는 수정-빌드-배포-테스트의 지루한 과정으로 많은 시간을 낭비해야 했고, 간단한 기능에 대해서조차 자동화된 테스트를 만드는 것은 거의 불가능에 가까웠다. 테스트는 서버에 배치 후에 대부분 수동으로 해야 했고, 느린 배포 작업 탓에 그나마도 자주 반복되기 힘들게 만들었다. 특별한 경우가 아니라면 그다지 장점이 없는 EJB의 원격분산 모델은 성능을 떨어뜨리고 서버의 복잡도만 증가시켰다. 가장 최악의 문제점은 EJB 스펙을 따르는 비즈니스 오브젝트들은 객체지향적인 특징과 장점을 포기해야 했다는 것이다. EJB빈은 상속과 다형성 등의 혜택을 제대로 누릴 수 없었다. 간단한 기능 하나를 위해서도 많은 인터페이스와 EJB 의존적인 상속 등을 사용해야 했다. EJB 옹호자들 사이에서조차 외면 받아온 엔티티빈에 대해 말하는 것은 시간 낭비일 것이다.

 

그럼에도 EJB가 계속 사용되었던 이유는,

엔터프라이즈 애플리케이션에서 반드시 필요로 하는 주요한 엔터프라이즈 서비스들을 애플리케이션 코드와 분리해서 독립적인 서비스로 사용할 수 있게 만들어줬다는 점이다. 비록 불완전하고 불필요한 복잡도가 남아있긴 했지만 선언적인 트랜잭션 관리(Declarative Transaction Management)나 역할 기반의 보안(Role based Security) 기능들을 제공했다. 비즈니스 오브젝트를 배포하고 관리하는 컨테이너를 제공하고 기본적인 스레드 관리, 인스턴스/리소스 풀링을 제공하는 등의 것들이다. 한편으로는 ‘개발자들이 로우레벨의 기술적인 문제에 신경 쓰지 않고 비즈니스 로직에 충실히 개발하게 함으로써 애플리케이션 개발을 손쉽게 만들어 준다’는 처음 약속을 어느 정도 지켰다고 볼 수 있다. 하지만 EJB의 문제는 앞서 지적한 것처럼 한편으로는 애플리케이션 개발의 복잡도를 제거하면서 다른 한편으로는 더 많은 문제와 복잡성을 가지고 왔다는 것이다.

 

결국 EJB는 형편없는 생산성과 느린 성능, 불필요한 기술적인 복잡도, 벤더 사이의 알력으로 과도하게 높아진 스펙 등으로 인해 자바의 엔터프라이즈 개발에 대한 불신을 가중시켰다. 마침내 마틴 파울러를 비롯한 많은 오피니언 리더들은 EJB와 같은 잘못 설계된 과도한 기술을 피하고, 객체지향 원리에 따라 만들어진 자바 언어의 기본에 충실하게 비즈니스 로직을 구현하는 일명 POJO 방식으로 돌아서야 한다고 지적하고 나섰다. POJO 방식의 개발은 EJB가 잃어버린 소중한 가치인 객체지향적인 설계와 자동화된 테스트의 편의성, 개발생산성 등을 회복시켜 줄 수 있는 길이기 때문이다.

 

결국 POJO를 정리하자면,

1. 특정 규약(contract)에 종속되지 않는다.

Java 언어와 꼭 필요한 API 외에 종속되지 않는다. EJB2 의 EntityBean 상속이나 Struts 1 의 ActionForm 상속등 규약에 종속되지 않아야 한다.

2. 특정 환경에 종속되지 않는다.

EJB3 의 JNDI 서버 서비스의 의존이나 특정 벤더의 서버나 기업프레임워크안에서만 동작 가능한 코드가 아니다.

3. 객체지향원리에 충실해야 한다.

특정 기술규약과 환경에 종속되지 않은 Java Object 가 모두 POJO라 할수는 없다. 객체지향 개념이 녹아있지 않은 것만 POJO 이다.

 

POJO를 사용하는 이유

1. 코드의 간결함

비즈니스 로직과 특정 환경/low 레벨 종속적인 코드를 분리하므로 단순하다.

2. 자동화 테스트에 유리

환경 종속적인 코드는 자동화 테스트가 어렵지만, POJO 는 테스트가 매우 유연하다.

3. 객체지향적 설계의 자유로운 사용

특정 규약 종속적인 객체의 경우 특정 상속을 미리 지정해서, 단일상속만 제공하는 JAVA 언어로서는 더이상 객체지향적 설계를 하는데 제약을 가져오는 경우가 있으나, POJO 는 아무런 규약이나 규제가 없으므로 OO 설계에 매우 자유롭다.

 

 

POJO 프레임워크

앞에서 강조한 것처럼 EJB를 사용하지 말고 POJO를 쓰자는 것이 EJB 이전의 방식으로 돌아가는 것을 의미한다면 또 다른 문제가 발생할 수밖에 없다. 여전히 복잡한 로우레벨의 API를 이용해 코드를 작성해야 하고, 많은 기술적인 문제를 애플리케이션 코드에 그대로 노출시켜 개발해야 한다면 기껏 POJO로의 복귀 덕분에 얻은 많은 장점들을 놓칠 수밖에 없다. 그래서 등장한 것이 바로 POJO 기반의 프레임워크이다.

 

POJO 프레임워크 - POJO를 이용한 애플리케이션 개발이 가진 특징과 장점을 그대로 살리면서 EJB에서 제공하는 엔터프라이즈 서비스와 기술을 그대로 사용할 수 있도록 도와주는 프레임워크, 나아가 이는 기존의 EJB에서보다 훨씬 더 세련되고 나은 방법

많은 POJO 프레임워크가 있지만 그 중에서 가장 대표적인 것을 꼽으라면 하이버네이트와 스프링을 들 수 있다.


하이버네이트 - EJB의 엔티티빈이 제시했던 컨테이너가 관리하는 퍼시스턴스 기술(Container Managed Persistence)과 오브젝트-관계형 DB 매핑(Object Relational Mapping) 기술을 순수한 POJO를 이용해 사용할 수 있게 하는 POJO 기반의 퍼시스턴스 프레임워크

하이버네이트는 사실 엔티티빈이 제공했던 것과는 비교할 수 없을 만큼 더 편리하고 세련된 방식으로 퍼시스턴스 기능을 제공한다. 굳이 컨테이너 위에서 동작시킬 필요도 없고, 이상적인 매핑을 위해 포기했던 많은 기능과 성능을 객체지향 DB에 적합하게 최적화한 기능을 통해 JDBC API를 직접 사용해 개발하는 것 못지않은 성능과 복잡한 퍼시스턴스 로직을 개발 가능하게 해줬다. 가장 중요한 점은 하이버네이트가 사용하는 POJO 엔티티들은 EJB의 엔티티빈과 달리 객체지향적인 다양한 설계와 구현이 가능하다는 점이다. 엔티티의 상속, 다형성, 밸류 오브젝트, 사용자정의 타입 등을 어떠한 기술적인 손해 없이도 그대로 퍼시스턴스 매핑용 오브젝트로 사용할 수 있게 한다.


스프링 - 세션빈이 제공하던 중요한 엔터프라이즈 서비스들을 POJO 기반으로 만든 비즈니스 오브젝트에서 사용할 수 있게 한다. 대표적인 것이 선언적인 트랜잭션 서비스와 보안이다. 또한 EJB와 마찬가지로 오브젝트 컨테이너를 제공해서 인스턴스의 라이프사이클을 관리하고 필요에 따라 스레딩, 풀링 및 서비스 인젝션 등의 기능을 제공한다. 또한 OOP를 더 OOP답게 사용할 수 있게 하는 AOP 기술을 적용해서 POJO 개발을 더 쉽게 만든다.

 

POJO 프로그래밍을 손쉽게   있도록 지원하는 세가지 가능기술(Enabling technology)

(1) IoC/DI (Inversion of Control / Dependency Injection)

IoC/DI  스프링의 가장 기본의 되는 기술이자 스프링의 핵심 개발원칙이다나머지 두가지 기술인 AOP  PSA IoC/DI 바탕을 두고 있으며, 3대기술은 아니지만자주 등장하는 템플릿/콜백 패턴의 적용도 IoC/DI 핵심원리다.


(2) AOP (Aspect Oriented Programming)
핵심 관심사를 분리하여 프로그래매 모듈화를 향상시키는 프로그래밍 스타일이다. AOP는 객체를 핵심 관심사와 횡단 관심사로 분리하고, 횡단 관심사를 관점(Aspect)라는 모듈로 정의하고, 핵심 관심사와 엮어서 처리할 수 있는 방법을 제공한다. 스프링은 자체적으로 프록시 기반의 AOP를 지원하며, 로깅, 트랜잭션, 보안 이런것들이 전반적으로 처리하는데 사용됨.

(3) PSA (Portable Service Abstraction)
인터페이스가 다른 다양한 구현을 같은 방식으로 사용하도록 중간에 인터페이스 어댑터 역할을 해주는 레이어를 하나 추가하는 방법


POJO vs. 짝퉁 POJO

POJO 프로그래밍의 진정한 가치는 자바의 객체지향적인 특징을 살려 비즈니스 로직에 충실한 개발이 가능하도록 하는 것이다. 그러면서 복잡한 요구조건을 가진 엔터프라이즈 개발의 필요조건을 충족시킬 수 있도록 POJO 기반의 프레임워크를 적절히 사용하는 것이 요구된다. 문제는 단지 POJO 프레임워크로 잘 알려진 제품을 사용하기만 하면 자동으로 POJO 개발을 하고 있다고 생각하는 경우가 많다는 것이다.


스프링과 하이버네이트가 한창 인기를 끌기 시작할 때 EJB 기반으로 구현된 애플리케이션에 스프링과 하이버네이트를 적용했다고 해서 주목받던 어느 오픈소스 제품이 있었다. POJO 기반 서버 애플리케이션의 좋은 샘플이라고 생각한 필자와 동료들은 그 소스를 가져다가 한번 살펴봤는데, 정작 그 소스를 보고는 놀라지 않을 수 없었다. EJB를 제거하고 POJO 기반의 프레임워크를 적용했다고는 하지만 사라진 것은 단지 EJB 관련 클래스와 인터페이스 정도이고 그 외에 EJB로 개발할 때 가졌던 모든 코드와 스타일을 그대로 가져다가 억지로 POJO 프레임워크에 구겨 넣은 것을 발견했기 때문이다. 그들이 말하는 POJO 프레임워크 기반의 개발이란 단지 POJO 프레임워크 위에서 동작하기만 하면 그 오브젝트의 설계와 구현은 어찌되든 상관없는 형태였다. 이는 자바가 처음 등장했을 때 자바로 개발한다고 하면서, 실제로는 C에서 쓰던 절차적인 방식(객체지향적인 모든 특성 무시)으로 구현했던 경우와 다를 게 없었다.


필자는 그런 POJO 애플리케이션을 짝퉁 POJO라고 부르고 싶다. 결과적으로 그런 개발은 POJO 프로그래밍이 주는 장점을 제대로 살릴 수 없고 오히려 배제되어야 할 또 다른 복잡함을 낳기 때문이다. <리스트 1>은 최근에 어느 프로젝트에서 스프링을 이용해 개발했다고 소개한 코드를 간략하게 옮겨놓은 것이다.

 

<리스트 1> 잘못된 POJO 기반 코드의 예

class MyService {
    private MyDAO myDAO;
    private XXDao xxDAO;
     
    public MyVo foo(UrVO urVo) throws MyException {
        Connection con = null; 
        try {
            con = DBUtil.getConnection();
            con.setAutoCommit(false);

            if (xxDAO.check(urVo))
                return myDAO.foo(con, urVo);

            else
                return myDAO.boo(con, urVo);
        }
        catch (MyException e) {
            con.rollback();
            throw e;
        }
        catch (Exception e) {
            throw new MyException(e, "XXX00001");
            con.rollback();
        }
        finally {
            con.commit();
            DBUtil.close(con);
        }
    }
    
}


class MyDAO { 
    public void foo(Connection con, UrVO urVO) throws MyException {
        PreparedStatement pstmt = null;
        try {
            pstmt = new LoggablePreparedStatement(con, QueryFactory.getInstance().getQuery("FOO"));
            pstmt.setInt(1, urVO.getSeq());
            if (pstmt.executeUpdate() != 1) {
                throw new MyException("SVM00009");
            }
        } catch (MyException e) {
            throw kble;
        } catch (Exception e) {
            throw new MyException(e);
        } finally {
            DBUtil.close(pstmt);
        }
    }
    
}

 

이 코드는 물론 스프링의 IoC/DI 컨테이너에 올라가서 빈(bean)으로 등록되어 Setter 인젝션 방식으로 사용되기는 할 것이다. 그럼 과연 이 코드가 POJO라고 말할 수 있을까? 물론 EJB를 사용하지 않았고 특정 환경에 종속적인 것처럼 보이지도 않는다. 또한 POJO 프레임워크의 대표격인 스프링 프레임워크를 사용해 만들어졌다. 하지만 이 코드는 POJO 프로그래밍의 장점을 거의 살리지 못한 레거시 코드일 뿐이다.

 

필자는 POJO 기반의 코드인지 아닌지를 확인하기 위해 다음의 중요한 두 가지 기준을 적용한다.
첫째는 객체지향적인 설계원칙에 충실하도록 개발되어 있는지 여부이다. POJO의 자바 오브젝트라는 것은 단지 자바 언어 문법을 지켜 만들었다는 뜻이 아니다. 객체지향 언어로서의 자바 오브젝트의 특징을 가지고 있는지가 중요하다. POJO 코드가 객체지향적인 원리에 따라 설계되어 있지 않고 더 나아가 적절하게 리팩토링하는 것도 어려운 구조라면 POJO로 개발했다는 것이 주는 가치는 거의 없다고 본다. EJB를 사용하지 않으므로 오히려 코드가 <리스트 1> 같은 식으로 바뀌었다면 그것은 POJO 개발이라고 볼 수 없다. 끊임없이 반복적으로 등장하는 템플릿 코드와 테스트하기 힘든 구조, 확장이나 재활용의 어려움 등이 코드에 그대로 남아있다면 EJB의 문제점을 여전히 안고 있는 것이다. 
둘째는 테스트 코드 개발의 용이성이나 테스트 코드를 잘 작성했는지의 여부이다. EJB를 버리고 POJO 기반으로 개발한다고 하면서도 여전히 수정-빌드-배포-테스트라는 방식을 탈피하지 못하고 있다면 EJB로 개발했던 시절과 대체 무엇이 다를까? 잘 만들어진 POJO 애플리케이션은 자동화된 테스트 코드 작성이 편리하다. 코드 작성이 편리하면 좀더 자주 꼼꼼하게 만들게 되고 반복적으로 실행할 수 있으므로 코드 검증과 품질 향상에 유리하다. 또한 잘 만들어진 테스트 코드베이스가 있다면 리팩토링할 여유가 생겨 POJO 코드를 더 나은 설계구조로 변경할 가능성도 높아진다.

 

POJO로의 변환

<리스트 1>의 코드를 스프링의 POJO 프로그래밍 모델에 따라 수정해 보자. DAO 코드의 가장 큰 문제점은 JDBC의 오래된 코드 스타일을 그대로 사용하고 있다는 것이다. 따라서 try/catch/ finally의 전형적인 템플릿 코드가 실제 DAO 로직보다 더 많은 라인을 차지하고 있다. 또 Query를 불러오는 부분이 싱글톤을 사용하고 있다. 그나마 JDBC 코드를 간략하게 작성하도록 돕는 유틸리티 클래스를 사용했지만 정적 메소드를 이용했다. 싱글톤과 정적메소드는 테스트 코드를 만드는 데 가장 큰 장애물이다. 테스트를 쉽게 하기 위해 모의객체(mock object)로 변환하는 것이 불가능하기 때문이다. 객체지향적인 설계방식을 따른다면 Query를 가져오는 기능을 인터페이스로 만들어 사용하게 하고 이를 구현한 오브젝트를 DAO에서 참조할 수 있도록 하는 것이 바람직하다. 또한 템플릿 스타일의 과도한 코드를 제거하고 반복적인 JDBC 워크플로우를 제거하려면 콜백 방식으로 구현하는 게 좋다. 그러면 DAO의 데이터액세스 로직과 JDBC 처리 워크플로우를 구분할 수 있다. <리스트 2>는 이렇게 스프링을 이용한 POJO 스타일로 수정된 코드이다.

 

<리스트 2> 수정된 MyDAO

public class MyDAO {
    private DataSource dataSource;
    private SimpleJdbcTemplate jdbcTemplate;
    private QueryFactory queryFactory; 
    // setter methods
    public void foo(UrlVo urlVo) {
        jdbcTemplate.update(queryFactory.getQuery("FOO"), urlVo.getSeq());
    }
}

 

MyDAO는 DataSource, QueryFactory라는 협력객체를 인터페이스를 통해 액세스하므로 해당 오브젝트의 구현에 상관없이 동작할 수 있다. DataSource가 개발용에서 테스트용으로 또 실제 운영서버로 바뀐다고 하더라도 DAO는 그 부분에 신경 쓰지 않고 클래스의 목적인 데이터로직을 구현하는 데만 충실하게 만들어져 동작할 수 있게 되었다. 테스트를 위해 실제 DB가 아닌 Fake DB나 Embedded DB를 적용한다면 그 DB 설정을 돌려주는 적절한 DataSource를 MyDAO가 사용하도록 스프링을 통해 설정하면 그만이다.


또 JdbcTemplate이라는 JDBC 워크플로우를 제공하는 템플릿 기반의 Helper 클래스를 사용해서 반복적으로 등장하는 JDBC의 try/catch/finally 작업을 DAO에서 제외하고 순수하게 데이터 처리와 관련된 핵심 로직만 사용하도록 정리했다. 결과적으로 코드는 깔끔해지고 여러 클래스에 거쳐 나타나는 지저분한 중복은 제거되었다. 또한 DAO 오브젝트의 역할을 넘어선 것들은 의존하는 오브젝트로 분리하고 그 구현에 종속되지 않도록 인터페이스를 사용하게 했으므로 객체지향적인 설계에 충실한 오브젝트의 설계와 구현에 가깝도록 만들어졌다. 

이번에는 MyService를 살펴보자. MyService의 가장 큰 문제는 무엇인가?

그것은 MyService는 비즈니스 로직을 구현한 클래스임에도 불구하고, 실제로 코드 안에는 그와 상관없는 데이터액세스와 관련된 코드들이 덕지덕지 붙어 있다. MyService에 나타난 커넥션과 트랜잭션 처리 코드들은 어찌 보면 어쩔 수 없는 선택으로 생각할 수도 있다. DAO를 분리해 놓은 상태로, MyService에서 여러 개의 DAO를 호출해 처리하는 결과를 하나의 커넥션과 트랜잭션으로 묶으려면 MyService 레벨에서 처리해야 하기 때문이다. 그렇지 않고 DAO 메소드 단위로 커넥션과 트랜잭션을 다루면 더 심각한 결과를 가져온다. 하지만 결과적으로 MyService는 DB 처리 코드와 로직 코드가 짬뽕되어 있는 지저분한 코드가 되었고, DB 관련 코드와 구현에 종속이 되었으므로 MyService는 객체지향적인 장점을 살려 재활용되거나 발전하는 데 극히 제한을 받게 된다. 만일 트랜잭션 처리가 JDBC 방식에서 JTA로 바뀌었다면? MyService처럼 구현한 모든 서비스/비즈니스 로직 코드를 일일이 수정해야 하는 중노동에 시달려야 한다. 하나의 클래스에 여러 가지 레이어의 기술이 짬뽕되고 책임이 중복되어 나타나며 한 가지 기능을 수정하기 위해 수많은 클래스를 수정해야 한다면 이것이 과연 POJO를 지지하는 사람들이 기대했던 바로 그 POJO 프로그래밍의 장점이라고 할 수 있을까?

 

<리스트 3> 수정된 MyService

class MyService {
    private MyDAO myDAO;
    private XxDAO xxDAO;
    // setter methods
    public void foo(UrVo urVo) {
        if (xxDAO.check(urVo))
            return myDAO.foo(con, urVo);
        else
            return myDAO.boo(con, urVo);
    }
    
}

 

MyService를 순수한 비즈니스 로직에 충실한 POJO 기반으로 변경하려면 스프링과 같은 POJO 프레임워크의 도움이 절실히 필요하다. 겉으로 드러나는 DB 커넥션, 예외처리, 트랜잭션과 관련된 부분을 비즈니스 로직을 구현한 MyService에서 분리하기 위해서는 AOP 기술의 도움이 필요하기 때문이다. <리스트 3>은 스프링의 AOP로 선언적 트랜잭션 관리 기능을 적용해 변경한 MyService 코드이다.


<리스트 1>에서 보았던 MyService의 모든 지저분한 DB와 트랜잭션 처리 블록이 모두 제거되었다. 남은 것은 MyService의 비즈니스 로직에 충실한 코드들뿐이다. 필요에 따라 적절한 DAO를 호출해서 퍼시스턴스 관련된 기능을 DAO를 통해 처리하는 것만 있다. 이는 객체지향적인 설계 모델에 충실하게 만들어져 있다.
한 가지 남은 문제는 테스트 유용성인데, MyService의 메소드를 테스트하기 위해서는 MyDAO, XxDAO가 필요하다. DAO는 DB를 역시 필요로 하므로 MyService를 테스트하기 위해 그 메소드를 호출하면 2개의 DAO 코드와 DB까지 동작이 필요하기 때문이다. DB까지 연동하는 테스트는 그 데이터 준비도 만만치 않거니와 시간도 많이 걸린다. 따라서 테스트를 만들고 자주 수행하는 데 어려움을 준다.
이를 위해 MyDAO를 다시 생각해 볼 필요가 있다. MySer vice 입장에서 MyDAO는 오브젝트를 장기간 보존하고 그것을 다시 조회하도록 돕는 퍼시스턴스 기능의 필요에 따라 사용하는 대상이다. 그 구현이 어떻게 되는지는 중요하지 않을 뿐더러 퍼시스턴스 구현이 필요하면 바뀔 수도 있다고 가정하면 지금과 같은 MyDAO의 클래스를 직접 액세스하는 구조는 바람직하지 않다. 전략패턴(Strategy Pattern)의 개념을 적용해서 MyDAO를 인터페이스와 구현으로 분리하고 MyService는 MyDAO 인터페이스를 사용하도록 수정하면 이 문제를 모두 해결할 수 있다. 먼저 MyDAO 인터페이스를 정의한다.

 

interface My DAO {
    void foo(UrVo);
}

그리고 이를 구현하는 클래스를 만든다.

class MyJdbcDAO implements MyDAO {
    
}

 

이제 MyService는 MyDAO라는 인터페이스에 대해 프로그래밍하는 구조로 바뀌었다. 이것의 장점은 MyDAO의 구현이 언제든지 교체 가능하다는 것이다. 구현한 알고리즘을 통째로 교환해 사용할 수 있게 하는 것이 전략패턴의 특징이다. DAO 구현이 필요에 따라 JDBC가 아닌 하이버네이트나 다른 종류의 퍼시스턴스여야 한다면 언제든지 MyDAO 인터페이스를 구현해 만들면 된다. MyService는 구현이 바뀌는 것에 전혀 영향 받지 않는다.
MyService의 로직이 복잡해 구현하면서 자주 테스트가 필요하다면 MyDAO 인터페이스를 구현한 간단한 테스트용 DAO 클래스를 만들어 MyService가 그것을 사용하도록 만든다. 이는 MyService와 MyDAO의 관계를 느슨하게 만들어 주었기 때문에 모두 가능한 것이다. 여기서 나온 MyService와 MyDAO는 아직 개선의 여지가 남아 있다. 하지만 일단 여기까지만 살펴보자. 

여기서 사용한 기법들은 모두 객체지향의 기본 설계원리와 패턴에 등장하는 것들이지 어떤 최신 기법들이 아니다. 물론 AOP라는 새로운 기술을 사용하지만, 그것이 추구하는 것은 또 다른 프로그래밍 모델이 아닌 OOP에 더 충실한 자바 코드를 만드는 데 도움을 주는 것일 뿐이다. 
앞에서 살펴본 것처럼 POJO 기반의 프레임워크를 가져다 사용하기만 했다고 해서 POJO 프로그래밍이 되고 그 유익을 누리는 것은 결코 아니다. POJO가 지향하는 자바의 객체지향적인 설계의 기본에 충실하도록 POJO 프레임워크의 도움을 받아 구현하는 것이 진정한 POJO 프로그래밍이다. 결과적으로 이렇게 만들어진 POJO 기반의 코드는 EJB나 그 이전의 자바개발 방법을 따라 만든 어떤 코드보다 더 간결하고 깔끔해진다. 이것이 객체지향 언어인 자바를 사용하면서 누려야 하는 진정한 혜택이 아닐까.

 

풍성한 도메인 모델

이제는 POJO 개발의 조금 다른 영역을 생각해 보자. POJO의 자바 오브젝트가 지닌 기본 특징은 하나의 오브젝트 안에 상태(State)와 행위(Behavior)를 모두 가지고 있는 것이다. 쉽게 말해 인스턴스 변수와 로직을 가진 메소드를 가지고 있다는 의미이다. 문제는 자바의 그런 특성이 EJB에 와서 이상한 오브젝트 형태로 왜곡된 것이다. 빈약한 오브젝트(anemic object)라고 불리는 것이 바로 그것으로, 로직이 없고 상태만 가진 오브젝트들이 다수 사용되었다. 그러면 그 로직에 해당하는 행위는 어디로 갔을까? 그것은 절차적인(procedural) 스타일로 작성된 서비스 레이어의 메소드로 들어갔다. 결과적으로 서비스 오브젝트는 과도한 로직이 트랜잭션 스크립트 형태로 반복되어 길게 등장하면서 갈수록 비대해졌고, 도메인 모델을 구현한 오브젝트는 빈약한 오브젝트로 오로지 상태 인스턴스 변수만 가진 전혀 객체지향 언어 답지 않은 결과만 남게 되었다. 
이러한 스타일은 EJB뿐만 아니라 그 이후에 등장한 POJO 기반의 프로그래밍에서도 쉽게 찾아볼 수 있다. <리스트 4>와 <리스트 5>는 각각 은행의 계좌를 이체하는 서비스의 메소드와 계좌를 구현한 도메인 모델이다.

 

<리스트 4> 계좌이체 서비스 클래스

public class MoneyTransferServiceProceduralImpl implements MoneyTransferService {
    private AccountDAO accountDAO;
    private BankingTransactionDAO bankingTransactionDAO;

    public BankingTransaction transfer(String fromAccountId, String toAccountId, double amount) {
        Account fromAccount = accountDAO.findAccount(fromAccountId);
        Account toAccount = accountDAO.findAccount(toAccountId);
        assert amount > 0;
        double newBalance = fromAccount.getBalance() - amount;
        switch (fromAccount.getOverdraftPolicy()) {
            case Account.NEVER:
                if (newBalance < 0)
                    throw new MoneyTransferException("In sufficient funds");
                break;
            case Account.ALLOWED:
                Calendar then = Calendar.getInstance();
                then.setTime(fromAccount.getDateOpened());
                Calendar now = Calendar.getInstance();
                double yearsOpened = now.get(Calendar.YEAR) - then.get(Calendar.YEAR);
                int monthsOpened = now.get(Calendar.MONTH) - then.get(Calendar.MONTH);
                if (monthsOpened < 0) {
                    yearsOpened--;
                    monthsOpened += 12;
                }
                yearsOpened = yearsOpened + (monthsOpened / 12.0);
                if (yearsOpened < fromAccount.getRequiredYearsOpen() || newBalance < fromAccount.getLimit())
                    throw new MoneyTransferException("Limit exceeded");
                break;
            default:
                throw new MoneyTransferException("Unknown overdraft type: " + fromAccount.getOverdraftPolicy());
        }
        fromAccount.setBalance(newBalance);
        toAccount.setBalance(toAccount.getBalance() + amount);
        TransferTransaction txn = new TransferTransaction(fromAccount, toAccount, amount, new Date());
        bankingTransactionDAO.addTransaction(txn);
        return txn;
    }
}

 

<리스트 5> Account 도메인 클래스

public class Account {
    public static final int NEVER = 1;
    public static final int ALLOWED = 2;

    private int id;
    private double balance;
    private int overdraftPoicy;
    private String accountId;
    private Date dateOpened;
    private double requiredYearsOpen;
    private double limit;
    // getters/setters
} 

 

MoneyTransferServiceProceduralImpl의 transfer 메소드는 길고 복잡하다. 자세히 살펴보면 사실 Account라는 오브젝트에 속해야 하는 로직들이 이체서비스의 서비스 로직에 빠져나와 있음을 알 수 있다. 반면에 Account 오브젝트에는 행위(메소드)는 없고 상태(필드)만 남아 있다. 만일 여기서 사용된 Account에 마땅히 들어가야 할 내용이 다른 곳에서 또 필요하다면? 그때는 복잡한 로직 코드가 여기저기 중복될 수밖에 없다. 
그럼 객체지향 기술의 장점을 충분히 누릴 수 있도록 이 코드를 POJO답게 수정해 보자. 가장 간단한 방법은 Account 오브젝트가 가지고 있어야 하는 행위를 Account로 옮기는 것이다. <리스트 6>과 <리스트 7>은 각각의 코드를 수정한 것이다.


<리스트 6> 수정된 계좌이체 서비스 클래스

public class MoneyTransferServiceProceduralImpl implements MoneyTransferService {
    …
    public BankingTransaction transfer(String fromAccountId, String toAccountId, double amount) {
        Account fromAccount = accountDAO.findAccount(fromAccountId);
        Account toAccount = accountDAO.findAccount(toAccountId);
        assert amount > 0;

        fromAccount.debit(amount);
        toAccount.credit(amount);

        TransferTransaction txn = new TransferTransaction(fromAccount, toAccount, amount, new Date());
        bankingTransactionDAO.addTransaction(txn);
        return txn;
    }
}

 

<리스트 7> 수정된 Account 오브젝트

class Account {

// fields

// getters/setters

 

public void debit(Account fromAccount, double amount) {

double newBalance = getBalance() ? amount;

switch (getOverdraftPolicy()) {

...

}

setBalance(newBalance);

}

public void credit(Account toAccount, double amount) {

setBalance(getBalance() + amount);

}

}

 

이체서비스 오브젝트 안의 한 메소드에 지저분하게 들어 있던 이체 로직은 그 로직이 있어야 할 Account 안에 절절한 메소드 형태로 옮겨졌다. 서비스 메소드에서는 그 오브젝트의 메소드를 호출해 서비스 로직을 수행하는 간단하고 명확한 코드로 역시 변경되었다. 이제 Account 오브젝트는 여러 로직과 계층에서 자신의 로직을 분산해 복사할 것 없이, 오브젝트 그대로 재활용되어 사용되는 객체지향의 혜택을 누릴 것이다. 또한 POJO이므로 Account와 MoneyTransferServiceProceduralImpl 모두 쉽게 테스트할 수 있다. 이렇게 객체지향 원리에 충실하게 도메인 모델을 만드는 것을 풍성한 도메인 모델(Rich Domain Model)이라고 한다.

 

진정한 POJO 프로그래밍 추구하기

이 글의 주제는 다이어트 POJO 프로그래밍이다. 필자처럼 체중이 많이 나가는 사람에게 다이어트는 매우 중요한 도전이자 과제이다. 다이어트가 왜 중요한가? 단지 체중을 줄여 날씬한 몸매를 만드는 것이 목적일까? 
그렇지 않다. 다이어트가 중요한 이유는 그것이 건강을 추구하기 때문이다. 다이어트를 통해 궁극적으로 얻고자 하는 것은 건강이다. 마찬가지로 POJO를 잘 사용하면 군더더기 없는 최소한의 코드로 이전과 동일한 결과를 낼 수 있지만, 더 중요한 의미는 더 건강한 코드를 만드는 데 있다.
단지 어느 순간에 상태가 좋다고 해서 건강하다고 할 수는 없다. 건강이란 외부의 스트레스와 변화에도 흔들리지 않고 견고하게 버틸 수 있는 힘인데, 이렇게 건강한 코드를 만들기 위해서는 반드시 자동화된 테스트 코드를 개발해야 한다. 잘 만들어진 테스트 코드는 지속적인 변화에 유연하게 대응할 수 있도록 도와준다. 변화를 두려워하지 않게 만들고 코드의 구조를 개선하기 위한 리팩토링도 안심하고 진행할 수 있게 지원한다. 또한 POJO로 잘 설계된 코드는 테스트 코드를 쉽게 만들 수 있도록 해준다. 이런 선순환이 지속되면 POJO 프로그래밍은 더 성숙된 결과로 나아가게 될 것이다. 그래서 필자는 POJO 프로그래밍의 꽃은 테스트 코드 작성이라고 생각한다. 
2005년에 프랑스의 온라인 세무시스템은 큰 변화를 겪었다. 기존에 EJB와 J2EE 기반으로 구현된 기존 시스템을 스프링과 하이버네이트를 사용하는 POJO 방식으로 전환한 것이다. 단 3개월 만에 시스템의 구조를 변환하면서 동시에 50개의 새로운 유즈케이스와 100개의 새로운 화면, 150개의 기존 화면을 전환시켰다. 
그 결과는 매우 성공적이었다. 손쉽게 작성할 수 있는 테스트와 간단한 패키징, 배포작업이 용이해졌고 지저분한 템플릿 코드를 제거하면서 생산성이 극대화되었다. 또한 기술적인 리스크가 줄고 일관성 유지가 쉬워졌으며 풍성한 도메인 모델을 적용하고 모든 레이어에 걸처 테스트를 작성하게 되어 큰 폭의 품질 향상이 이뤄졌다.
EJB나 무거운 기존 기술의 굴레에서 벗어나 객체지향 기술의 혜택을 가득 누릴 수 있는 즐거운 POJO 프로그래밍에 한번 도전해 보자. 혹시 지금 POJO 프레임워크를 사용해 개발하고 있다면 과연 자신이 만든 코드가 POJO의 원칙에 맞게 작성되었는지 살펴보자. 그리고 마지막에는 반드시 테스트를 작성하라. 그것이 POJO를 POJO답게 쓰게 하고 그 가치를 누리도록 도와줄 것이다.

 

원본 : http://cafe.daum.net/playtrade/9l2D/9 

http://lefthand29.blogspot.com/2012/02/pojo-plain-old-java-object-programming.html#!/2012/02/pojo-plain-old-java-object-programming.html

:

Spring - Bean 초기화 및 생명주기

FRAMEWORK/SPRING MVC 2014. 9. 29. 13:34

BeanFactory & ApplicationContext

bean factory - 스프링의 IoC 담당하는 핵심 컨테이너

빈을 등록/생성/조회/반환/관리한다보통은 bean factory  바로 사용하지 않고 이를 확장한 application context  이용한다. BeanFactory  bean factory  구현하는interface 이다. (getBean()등의 메소드가 정의되어 있음)

application context - bean factory 확장한 IoC 컨테이너

빈의 등록/생성/조회/반환/관리의 기능은 bean factory  같지만여기에 spring 각종 부가 서비스를 추가로 제공한다. ApplicationContext application context 구현해야 하는 interface이여, BeanFactory  상속한다

 


ApplicationContext가 추가적으로 지원하는 기능
1. MessageSource를 지원하여 애플리케이션에서 필요한 메시지들을 관리하는 것이 가능
2. Spring 프레임워크에서 관리하는 POJO 빈들 중 ApplicationListener를 구현하여 설정파일에 등록할 경우 등록된 리스너에게 이벤트를 전달하는 것이 가능

즉, ApplicationContext를 이용할 경우 애플리케이션내에 간단한 이벤트 기능을 구현하는 것이 가능

3. 애플리케이션내에서는 파일, URL과 같은 다양한 자원(Resource)에 접근할 필요가 있다.

ResourceLoader를 지원함으로 다양한 하위레벨의 자원을 Spring 프레임워크의 Resource 인스턴스로 생성하는 것이 가능

※ ApplicationContext를 직접 사용하는 방법을 알아둘 경우 테스트 프로그램을 작성할 때 유용하게 사용할 수 있다.

 

 

Spring 프레임워크에서 빈의 생명주기



 

POJO 초기화 과정
1. Spring 프레임워크는 먼저 빈 설정파일의 POJO 빈을 인스턴스화하지 않은 상태로 빈 설정 파일 정보를 초기화한다.

   빈 설정파일의 정보를 초기화하면서 XML DTD에 대하여 유효한지를 체크한다. 
2. 빈 인스턴스를 생성하면서 의존관계에 있는 빈이 존재하는지의 여부를 체크하여 의존 관계에 있는 빈이 존재하지

   않는다면 빈의 초기화가 실패한다. 
3. 의존관계에 있는 빈의 체크가 완료되면 setter 메써드를 호출하거나(Setter Injection의 경우), 생성자의 인자

   (Constructor Injection의 경우)로 실질적인 값을 추가하거나 다른 빈에 대한 레퍼런스(reference)를 전달한다. 
4. 생성한 빈이 BeanNameAware 인스턴스이면 setBeanName() 메소드를 호출한다. 
5. 생성한 빈이 BeanFactoryAware 인스턴스이면 setBeanFactory() 메소드를 호출한다. 
6. 생성한 빈이 ApplicationContextAware 인스턴스이면 setApplicationContext() 메소드를 호출한다.

   이 과정은 ApplicationContext를 이용하여 초기할 때만 실행된다. 
7. 생성한 빈이 InitializingBean 인스턴스이면 afterProperties() 메소드를 호출한다. 
8. 생성한 빈의 설정파일에 init-method가 설정되어 있다면 init-method에 해당하는 메소드를 호출한다

 

종료 메소드를 호출하는 과정
1. 생성한 빈이 DisposableBean 인스턴스이면 destory() 메소드를 호출한다. 
2. 생성한 빈의 설정파일에 destroy-method가 설정되어 있다면 destroy-method에 해당하는 메소드를 호출한다. 

POJO 빈에서 빈 설정 정보에 접근하기
POJO 빈에서 BeanNameAware, BeanFactoryAware, ApplicationContextAware 인터페이스 중 필요한 인터페이스를 상속하고 인터페이스의 메소드를 구현하면 된다.

 

빈의 생명주기 제어하기
Spring IoC 컨테이너의 Bean 초기화 및 소멸 작업의 커스트마이징을 하고싶을때 Spring의 초기화 콜백 메서드와 소멸 콜백 메서드를 인식하게하여 사용하면 되는데 그 방법으로는 아래 3가지 방법이 있다.

 
1. InitializingBean 과 DisposableBean 생명주기 인터페이스를 구현하고 초기화와 소멸작업을 위한

   afterPropertiesSet()과 destroy() 메소드를 구현.

2. Bean선언시(xml) init-method와 destroy-method 속성을 설정해 콜백 메소드의 이름을 지정한다.
3. Spring 2.5이후부터는 JSR-250(Common Annotations for the Java Plaform)에 정의되어 있는  

   @PostConstruct 와 @PreDestroy 생명주기 어노테이션을 사용하여 초기화 및 소멸 콜백 메서드를 적용할

   수 있다. 이 콜백 메소드를 호출하기 위해서는 IoC컨테이너에 CommonAnnotationBeanPostProcessor

   인스턴스가 등록돼 있어야한다.

 


빈 생명주기 제어의 간단한 예제
- Cashier Class는 장바구니의 Product들을 계산하는데 사용되며, 계산할 때마다 시각과 수량을 텍스트 파일에 기록한다. 이때 파일을 기록하기전 파일객체를 초기화 하고, 파일 작성후 소멸(close)시킨다.

 

Cashier Class

package com.apress.springrecipes.shop;


import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Date;

public class Cashier{
private String name;
private String path;
private BufferedWriter writer;

public void setBeanName(String beanName) {
this.name = beanName;
}

public String getPath() {
return path;
}

public void setPath(String path) {
this.path = path;
}

public void openFile() throws IOException {
File logFile = new File(path, name + ".txt");
writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(logFile, true)));
}

public void checkout(ShoppingCart cart) throws IOException {
double total = 0;
for (Product product : cart.getItems()) { total += product.getPrice(); }
writer.write(new Date() + "\t" + total + "\r\n");
writer.flush();
}

public void closeFile() throws IOException {
writer.close();
}
}

 

Bean Config

<beans ...>

...

<bean id=cashier1" class="com.apress.springrecipes.shop.Cashier">

<property name="name" value="casher1" />

<property name="name" value="casher1" />

</bean>

</beans>

 

 

1. InitializingBean과 DisposableBean 인터페이스 구현

...

import org.springframework.beans.factory.InitializingBean;

import org.springframework.beans.factory.DisposableBean;


public class Cashier implements InitializingBeanDisposableBean{

...

public void afterPropertiesSet() throws Exception {

openFile();

}

 

public void destroy() throws Exception {

closeFile();

}

}

 

위와 같이 스프링에 종속된 인터페이스를 구현하면 빈이 스프링에 종속되므로 스프링 IoC 컨테이너 외부에서 재사용하지 못한다.

 

 

2. init-method와 destroy-method 속성 설정

<bean id="cashier1" class="com.apress.springrecipes.shop.Cashier" init-method="openFiledestroy-method="closeFile">

<property name="name" value="cashier1" />

<property name="path" value="c:/cashier" />

</bean>

 

 

3. @PostConstruct와 @PreDestroy 어노테이션 적용

package com.apress.springrecipes.shop;

...

import javax.annotation.PostConstruct;

import javax.annotation.PreDestroy;


public class Cashier {

...

@PostConstruct

public void openFile() throws IOException {

File logFile = new File(path, name + ".txt");

writer = new BufferedWriter(new OutputStreamWriter(

new FileOutputStream(logFile, true)));

}

...

@PreDestroy

public void closeFile() throws IOException {

writer.close();

}

}

그리고 CommonAnnotationBeanPostProcessor 인스턴스를 IoC 컨테이너에 추가하면 된다.

 

<beans ...>

<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor" />

<bean id="cashier1" class="com.apress.springrecipes.shop.Cashier">

<property name="name" value="cashier1" />

<property name="path" value="c:/cashier" />

</bean>

</beans>

 

 

 

참조 :  http://www.javajigi.net/pages/viewpage.action?pageId=2621452http://ultteky.egloos.com/10795411

'FRAMEWORK > SPRING MVC' 카테고리의 다른 글

Spring - Transaction1 (스프링의 트랜잭션 지원)  (0) 2014.09.29
Spring - POJO  (0) 2014.09.29
Spring - Bean Scope  (0) 2014.09.29
Spring - IoC & DI  (0) 2014.09.29
Spring - AOP 개념 정리  (0) 2014.09.29
:

Spring - Bean Scope

FRAMEWORK/SPRING MVC 2014. 9. 29. 13:33

Bean Scope

Bean의 정의는 실제 bean 객체를 생성하는 방식을 정의하는 것이다.

Class와 마찬가지로 하나의 Bean 정의에 해당하는 다수의 객체가 생성될 수 있다.

Bean 정의를 통해 객체에 다양한 종속성 및 설정값을 주입할 수 있을 뿐 아니라, 객체의 범위(scope)를 정의할 수 있다. Spring Framework는 5가지 scope을 제공한다.

(그 중 3가지는 web-aware ApplicationContext를 사용할 때 이용할 수 있다).

 

 Scope

 설명

 singleton

 하나의 Bean 정의에 대하여 Spring IoC Container 내에 단 하나의 객체만 존재

 prototype

 하나의 Bean 정의에 대하여 다수의 객체가 존재 할 수 있다.

 request

 하나의 Bean 정의에 대하여 하나의 HTTP request의 생명주기 안에 단 하나의 객체만 존재

 즉, 각각의 HTTP Request는 자신만의 객체를 가진다.

 Web-aware String ApplicaiontContext 안에서만 유효하다.

 session

 하나의 Bean 정의에 대해서 하나의 HTTP Session의 생명주기 안에 단 하나의 객체만 존재

 Web-aware String ApplicaiontContext 안에서만 유효하다.

 global

 session

 하나의 Bean 정의에 대하여 하나의 global HTTP Session의 생명주기 안에 단 하나의 객체

 일반적으로 portlet context 안에서 유효하다

 Web-aware String ApplicaiontContext 안에서만 유효하다.

 

 

[ Singleton Scope ]

Bean이 singleton인 경우, 단지 하나의 공유 객체만 관리된다. Singleton은 Spring의 기본 scope이다.

Singleton은 객체가 하나만 생성되는 싱글톤 객체에 DI를 하게되면 DI 된 객체도 한 번 밖에 세팅되지 않기 때문에Dependency Injection (이후 DI)을 사용할 수 없다.

<bean id="beanTest" class="test.BeanTest/> // default가 singleton이기에 생략 가능
<bean id="beanTest" class="test.BeanTest" scope="singleton"/>
<bean id="beanTest" class="test.BeanTest" singleton="true"/>

 

Singleton Scope의 간단한 예제

BeanTest.java

package test;
public class BeanTest {    
    public BeanTest(){}
}


ExamMain.java

package test.;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class ExamMain {
    public static void main(String[] args) {
        ApplicationContext context = new FileSystemXmlApplicationContext("/src/test/exam06.xml");
        BeanTest beanTest1 = (BeanTest)context.getBean("beanTest");
        BeanTest beanTest2 = (BeanTest)context.getBean("beanTest");
        BeanTest beanTest3 = (BeanTest)context.getBean("beanTest");
  
        System.out.println("beanTest1 : " + beanTest1);
        System.out.println("beanTest2 : " + beanTest2);
        System.out.println("beanTest3 : " + beanTest3);
    }
}

 

exam06.xml

<?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">

    <bean id="beanTest" class="test.BeanTest" scope="singleton" />
</beans>

 

결과 화면



 

위 소스의, exam06.xml 설정 파일을 보면 빈을 생성할 때 scope="singleton" 속성으로 지정되어 있다.

Singleton으로 생성되어 있기 때문에 getBean()으로 각각 호출한 beanTest1~3이 모두 동일한 빈 객체를 참조하고 있음을 알수 있다.

 



 

Singleton 패턴과 스프링의 Singleton 차이

http://toby.epril.com/?p=849 <= 참조 (깔끔하게 정리를 하고 싶은데,,,정리가 안된다,,,;;)

 

 

[ Prototype Scope ]

Singleton이 아닌 prototype scope의 형태로 정의된 bean은 필요한 매 순간 새로운 bean 객체가 생성된다

<bean id="beanTest" class="test.BeanTest" scope="prototype"/>

<bean id="beanTest" class="test.BeanTest" singleton="false"/>

 

Prototype scope을 사용할 때 염두에 두고 있어야 할 것이 있다.

Spring은 prototype bean의 전체 생명주기를 관리하지 않는다 - container는 객체화하고, 값을 설정하고, 다른 prototype 객체와 조립하여 Client에게 전달 한 후 더 이상 prototype 객체를 관리하지 않는다.

즉, scope에 관계없이 초기화(initialization) 생명주기 callback 메소드가 호출되는 반면에, prototype의 경우 파괴(destruction) 생명주기 callback 메소드는 호출되지 않는다. 이것은 client 코드가 prototype 객체를 clean up하고 prototype 객체가 들고 있던 리소스를 해제하는 책임을 가진다는 것을 의미한다.

 

Prototype Scope의 간단한 예제

exam06.xml

<?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">

    <bean id="beanTest" class="test.BeanTest" scope="prototype" />
</beans>

 

결과화면



 

위의 scope속성을 위와 같이 prototype으로 수정해주면 빈을 사용할 때마다 빈을 새로 생성하기 때문에 서로 다른 객체를 참조하고 있음을 알 수 있다.

 



 

 

[ 기타 Scope ]

requestsessionglobal session scope들은 반드시 web-based 어플리케이션에서 사용할 수 있다.

기본 Web 설정(Initial web configuration)
requestsessionglobal session scope을 사용하기 위해서는 추가적인 설정이 필요하다.

 

추가 설정은 사용할 Servlet 환경에 따라 달라진다.만약 Spring Web MVC 안에서 bean에 접근할 경우, 즉 Spring DispatcherServlet 또는 DispatcherPortlet에서 처리되는 요청인 경우, 별도의 추가 설정은 필요없다.

DispatcherServlet과 DispatcherPortlet은 이미 모든 관련있는 상태를 제공한다.

 

만약 Servlet 2.4+ web container를 사용하고, JSF나 Struts 등과 같이 Spring의 DispatcherServlet의 외부에서 요청을 처리하는 경우, 다음javax.servlet.ServletRequestListener를 'web.xml' 파일에 추가해야 한다.

<web-app>
     ... 
    <listener>
        <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
    </listener>
    ...
</web-app>

 

만약 다른 오래된 web container(Servlet 2.3)를 사용한다면, 제공되는 javax.servlet.Filter 구현체를 사용해야 한다. (filter mapping은 web 어플리케이션 설정에 따라 달라질 수 있으므로, 적절히 수정해야 한다.)

<web-app>
    ...
    <filter>
        <filter-name>requestContextFilter</filter-name> 
        <filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
    </filter>
    <filter-mapping> 
        <filter-name>requestContextFilter</filter-name> 
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    ...
</web-app>

 

 

Request scope

<bean id="beanTest" class="test.BeanTest" scope="request"/>

 

위 정의에 따라, Spring container는 모든 HTTP request에 대해서 'loginActionbean 정의에 대한 LoginAction 객체를 생성할 것이다. 즉, 'loginActionbean은 HTTPrequest 수준에 한정된다(scoped). 요청에 대한 처리가 완료되었을 때, 한정된(scoped) bean도 폐기된다.

 

 

session scope

<bean id="beanTest" class="test.BeanTest" scope="session"/>

 

위 정의에 따라, Spring container는 하나의 HTTP Session 일생동안 'userPreferencesbean 정의에 대한 UserPreferences 객체를 생성할 것이다. 즉, 'userPreferences'bean은 HTTP Session 수준에 한정된다(scoped). HTTP Session이 폐기될 때, 한정된(scoped) bean로 폐기된다

 

 

global session scope

<bean id="beanTest" class="test.BeanTest" scope="globalSession"/>

 

global session scope은 HTTP Session scope과 비슷하지만 단지 portlet-based web 어플리케이션에서만 사용할 수 있다. Portlet 명세(specifications)는 global Session을 하나의 portlet web 어플리케이션을 구성하는 여러 portlet들 모두가 공유하는 것으로 정의하고 있다. global session scope으로 설정된 bean은 global portlet Session의 일생에 한정된다

 

 

 

 

 

참조목록

http://snoopy81.tistory.com/170

http://openframework.or.kr/framework_reference/spring/ver2.x/html/beans.html

http://www.egovframe.org/wiki/doku.php?id=egovframework:rte:fdl:ioc_container:bean_scope

'FRAMEWORK > SPRING MVC' 카테고리의 다른 글

Spring - POJO  (0) 2014.09.29
Spring - Bean 초기화 및 생명주기  (0) 2014.09.29
Spring - IoC & DI  (0) 2014.09.29
Spring - AOP 개념 정리  (0) 2014.09.29
Spring MVC 와 DispatcherServlet  (0) 2014.09.29
:

Spring - IoC & DI

FRAMEWORK/SPRING MVC 2014. 9. 29. 13:31

IoC(Inversion of Control - 제어의 역전)란?

IoC란 간단하게 말하여 프로그램의 제어 흐름 구조가 바뀌는 것이다.

일반적으로, main() 같은 프로그램이 시작되는 지점에서 다음에 사용할 오브젝트를 결정, 생성하고, 만들어진 오브젝트 내의 메소드를 호출하는 작업을 반복한다. 이런 구조에서 각 오브젝트느 프로그램 흐름을 결정하거나 사용할 오브젝트를 구성하는 작업에 능동적으로 참여한다.

즉, 모든 종류의 작업을 사용하는 쪽에서 제어하는 구조이다.

 

이에 반하여 IoC는 제어 흐름의 개념을 거꾸로 뒤집는다. 오브젝트는 자신이 사용할 오브젝트를 스스로 생성하거나 선택하지 않는다. 그리고 자신이 어떻게 만들어지고 어디서 사용되는지 알 수 없다. 모든 제어 권한을 자신이 아닌 다른 대상에게 위임하는 것이다. 프로그램의 시작을 담당하는 main()같은 엔트리 포인트를 제외하면 모든 오브젝트는 이런 방식으로 위임받은 제어 권한을 갖는 특별한 오브젝트에 의해 결정되고 만들어지는 것이다.

 

위의 내용을 토대로 IoC를 요약하여 말하자면 아래와 같이 정의할 수 있다.

- 작업을 수행하는 쪽에서 Object 를 생성하는 제어 흐름의 개념을 거꾸로 뒤집는다.
- IoC 에서는 Object 가 자신이 사용할 Object 를 생성하거나 선택하지 않는다.
- 또한 Object 는 자신이 어떻게 생성되고 어떻게 사용되는지 알 수 없다.
- 모든 Object 는 제어 권한을 위임받는 특별한 Object 에 의해서 만들어 지고 사용된다. 

 

 

IoC 구현 방법

  • DL (Dependency Lookup)  - 의존성 검색
            저장소에 저장되어 있는 빈(Bean)에 접근하기 위하여 개발자들이 컨테이너에서 
    제공하는 API 를 이용하여 
            사용하고자 하는 빈(Bean) 을 Lookup 하는 것
  • DI (Dependency Injection) - 의존성 주입
            각 계층 사이, 각 클래스 사이에 필요로 하는 의존 관계를 컨테이너가 자동으로 연결해주는것
            각 클래스 사이의 의존 관계를 빈 설정(Bean Definition) 정보를 바탕으로 컨테이너가 자동으로 연결해 주는 것
            DL 사용 시 컨테이너 종속성이 증가하여, 이를 줄이기 위하여 DI를 사용
        Setter Injection - 인자가 없는 생성자나 인자가 없는 static factory 메소드가 bean을 인스턴스화 하기 위하여 
                              호출된 후 bean의 setter 메소드를 호출하여 실체화하는 방법
                              - 객체를 생성 후 의존성 삽입 방식이기에 구현시에 좀더 유연하게 사용할 수 있다.
                              - 세터를 통하여 필요한 값이 할당되기 전까지 객체를 사용할 수 없다.
                              - Spring 프레임워크의 빈 설정 파일에서 property 사용
  • public class SimpleMovieister{

    private MovieFinder movieFinder;  

    public void setMovieFinder(MovieFinder movieFinder){

    this.movieFinder = movieFinder;

    }

    }


        Constructor Injection - 생성자를 이용하여 클래스 사이의 의존 관계를 연결
                              - 생성자에 파라미터를 지정함으로 생성하고자하는 객체가 필요로 하는 것을 명확하게 알 수 있다.
                              - Setter메소드를 제공하지 않음으로 간단하게 필드를 불변 값으로 지정이 가능하다.
                              - 생성자의 파라미터가 많을 경우 코드가 복잡해 보일 수 있다.
                              - 조립기 입장에서는 생성의 순서를 지켜야 하기에 상당히 불편하다.
                              - Spring 프레임워크의 빈 설정 파일에서 constructor-arg 사용

    public class SimpleMovieister{

    private MovieFinder movieFinder;

    public SimpleMovieLister(MovieFinder movieFinder){

    this.movieFinder = movieFinder;

    }

    }

     

        Method Injection - Singleton 인스턴스와 Non Singleton 인스턴스의 의존 관계를 연결 시킬 필요가 있을 
                              경우 사용하지만, 많이 사용하지는 않는다.

     

     

    의존성 주입(DI)의 간단한 예제

     

    Setter Injection의 예제

    <bean id="exampleBean" class="examples.ExampleBean">
        <property name="beanOne"><ref bean="anotherExampleBean"/></property>
        <!-- setter injection using the neater 'ref' attribute -->
        <property name="beanTwo" ref="yetAnotherBean"/>
        <property name="integerProperty" value="1"/>
    </bean>

    <bean id="anotherExampleBean" class="examples.AnotherBean"/>
    <bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

     

    public class ExampleBean {
        private AnotherBean beanOne;
        private YetAnotherBean beanTwo;
        private int i;

        public void setBeanOne(AnotherBean beanOne)
            this.beanOne = beanOne;

        public void setBeanTwo(YetAnotherBean beanTwo)
            this.beanTwo = beanTwo;

        public void setIntegerProperty(int i)
            this.i = i;
    }

     

     

    Constructor Injection의 예제

    <bean id="exampleBean" class="examples.ExampleBean">
        <constructor-arg><ref bean="anotherExampleBean"/></constructor-arg>
        <constructor-arg ref="yetAnotherBean"/>    
               // <constructor-arg><ref bean="yetAnotherBean"/></constructor-arg> 위와 동일
        <constructor-arg type="int" value="1"/>
    </bean>

    <bean id="anotherExampleBean" class="examples.AnotherBean"/>
    <bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

     

    public class ExampleBean {
        private AnotherBean beanOne;
        private YetAnotherBean beanTwo;
        private int i;
        
        public ExampleBean(
            AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
            this.beanOne = anotherBean;
            this.beanTwo = yetAnotherBean;
            this.i = i;
        }
    }

     

     

    IoC 용어 정리

    bean - 스프링에서 제어권을 가지고 직접 만들고 관계를 부여하는 오브젝트

    자바빈, EJB 빈과 비슷한 오브젝트 단위의 애플리케이션 컴포넌트를 말한다하지만 스프링을 사용하는 애플리케이션에서 만들어지는 모든 오브젝트가 빈은아니다스프링의 빈은 스프링 컨테이너가 생성과 관계설정사용등을 제어해주는 오브젝트를 가리킨다.


    bean factory - 스프링의 IoC 담당하는 핵심 컨테이너

    빈을 등록/생성/조회/반환/관리한다보통은 bean factory  바로 사용하지 않고 이를 확장한 application context  이용한다. BeanFactory  bean factory  구현하는interface 이다. (getBean()등의 메소드가 정의되어 있음)

     

    application context - bean factory 확장한 IoC 컨테이너

    빈의 등록/생성/조회/반환/관리의 기능은 bean factory  같지만여기에 spring 각종 부가 서비스를 추가로 제공한다. ApplicationContext application context 구현해야 하는 interface이여, BeanFactory  상속한다.

     

    configuration metadata (설정정보/설정 메타정보)

    application context 혹은 bean factory  IoC 적용하기 위해 사용하는 메타정보

    스프링의 설정정보는 컨테이너에 어떤 기능을 세팅하거나 조정하는 경우에도 사용하지만 주로 bean  생성/구성하는 용도로 사용된다.

     

    container (IoC container)

    IoC 방식으로 bean 관리한다는 의미에서 bean factory  application context  가리킨다
    (spring container = application context) 
    application context
      자체로 ApplicationContext 인터페이스를 구현한 오브젝트를 가리키기도 하는데하나의 애플리케이션에 보통 여러개의 ApplicationContext Object 만들어진다이를 통칭해서 strping container라고 부를  있다.

    간단하게, 객체를 관리하는 컨테이너이다, 컨테이너에 객체를 담아 두고, 필요할 때에 컨테이너로부터 객체를 가져와 사용할 수 있도록 하고 있다.

     

    spring framework - IoC container, application context  포함해서 spring 제공하는 모든 기능을 통칭한다.

     

     

    클래스 호출방식

    일반적인 클래스 호출



    클래스내에 선언과 구현이 한몸이기때문에 다양한 형태로 변화가 불가능하다.

     

    인터페이스를 이용한 클래스 호출



    클래스를 인터페이스와 구현클래스로 분리를 하였다.
    구현클래스 교체가 용이하여 다양한 형태로 변화가 가능
    하지만 구현클래스 교체시 호출 클래스의 소스를 수정해야한다.

     

    팩토리패턴을 이용한 클래스 호출방식



    팩토리방식은 팩토리가 구현클래스를 생성하므로 클래스는 팩토리를 호출하는 코드로 충분하다.
    구현클래스 변경시 호출클래스에는 영향을 미치지 않고, 팩토리만 수정하면 된다.

    하지만 클래스에 팩토리를 호출하는 소스가 들어가야한다. 그것 자체가 팩토리에 의존함을 의미한다

     

    IoC를 이용한 클래스 호출 방식



    팩토리패턴의 장점을 더하여 어떠한것에도 의존하지 않는 형태로 구성이 가능하다.
    실행 시점에 클래스간의 관계가 형성이 된다. 
    즉 의존성이 삽입된다는 의미로 IoC를 DI(Dependency Injection) 라는 표현으로 사용한다.

     

     

    참조목록

    http://openframework.or.kr/framework_reference/spring/ver2.x/html/beans.html
    http://lefthand29.blogspot.com/2012/02/ioc-inversion-of-control-spring-ioc_22.html#!/2012/02/ioc-inversion-of-control-spring-ioc_22.html

    http://blog.daum.net/kitepc/557
    http://www.javajigi.net/pages/viewpage.action?pageId=5614#SpringIoC%EA%B0%9C%EB%85%90%EB%B0%8F%ED%99%9C%EC%9A%A9%EB%B0%A9%EC%95%88-1.IoC%EC%9D%98%EA%B0%9C%EB%85%90
    http://code.google.com/p/developerhaus/wiki/TobySpring_10#10.2_IoC/DI를_위한_빈_설정_메타정보_작성

    http://trypsr.springnote.com/pages/251122

    'FRAMEWORK > SPRING MVC' 카테고리의 다른 글

    Spring - POJO  (0) 2014.09.29
    Spring - Bean 초기화 및 생명주기  (0) 2014.09.29
    Spring - Bean Scope  (0) 2014.09.29
    Spring - AOP 개념 정리  (0) 2014.09.29
    Spring MVC 와 DispatcherServlet  (0) 2014.09.29
    :

    Spring - AOP 개념 정리

    FRAMEWORK/SPRING MVC 2014. 9. 29. 13:30

    AOP (Aspect Oriented Programming)

    - 기능을 핵심 비지니스 로직과 공통 모듈로 구분하고, 핵심 로직에 영향을 미치지 않고 사이사이에 공통 모듈을 효과적으로 잘 끼워넣도록 하는 개발 방법이다.

    공통 모듈(보안 인증, 로깅 같은 요소등)을 만든 후에 코드 밖에서 이 모듈을 비지니스 로직에 삽입하는 것이 바로 AOP 적인 개발이다. 코드 밖에서 설정된다는 것이 핵심이다.

     

    AOP가 사용되는 경우

    1) 간단한 메소드 성능 검사
    개발 도중 특히 DB에 다량의 데이터를 넣고 빼는 등의 배치 작업에 대하여 시간을 측정해보고 쿼리를 개선하는 작업은 매우 의미가 있다. 이 경우 매번 해당 메소드 처음과 끝에 System.currentTimeMills();를 사용하거나, 스프링이 제공하는 StopWatch코드를 사용하기는 매우 번거롭다.
    이런 경우 해당 작업을 하는 코드를 밖에서 설정하고 해당 부분을 사용하는 편이 편리하다.

    2) 트랜잭션 처리
    트랜잭션의 경우 비지니스 로직의 전후에 설정된다. 하지만 매번 사용하는 트랜잭션 (try~catch부분)의 코드는 번거롭고, 소스를 더욱 복잡하게 보여준다.

    3) 예외 반환
    스프링에는 DataAccessException이라는 매우 잘 정의되어 있는 예외 계층 구조가 있다. 예전 하이버네이트 예외들은 몇 개 없었고 그나마도 Uncatched Exception이 아니였다. 이렇게 구조가 별로 안 좋은 예외들이 발생했을 때, 그걸 잡아서 잘 정의되어 있는 예외 계층 구조로 변환해서 다시 던지는 애스팩트는 제 3의 프레임워크를 사용할 때, 본인의 프레임워크나 애플리케이션에서 별도의 예외 계층 구조로 변환하고 싶을 때 유용하다.

    4) 아키텍처 검증

    5) 기타
    - 하이버네티스와 JDBC를 같이 사용할 경우, DB 동기화 문제 해결
    - 멀티쓰레드 Safety 관련하여 작업해야 하는 경우, 메소드들에 일괄적으로 락을 설정하는 애스팩트
    - 데드락 등으로 인한 PessimisticLockingFailureException등의 예외를 만났을 때 재시도하는 애스팩트
    - 로깅, 인증, 권한 등

     

    AOP의 구성요소
    조인포인트(joinPoint) - 횡단 관심 모듈의 기능이 삽입되어 동작할 수 있는 실행 가능한 특정위치
    ex) 메쏘드가 호출되는 부분 또는 리턴되는 시점, 필드를 액세스하는 부분, 인스턴스가 만들어지는 지점, 예외가 던져지는 시점, 예외 핸들러가 동작하는 위치, 클래스가 초기화되는 곳 등이 대표적인 조인포인트가 될 수 있다. 각각의 조인포인트들은 그 안에 횡단 관심의 기능이 AOP에 의해 자동으로 추가되어져서 동작할 수 있는 후보지가 되는 것이다.

    포인트컷(pointCut) - 어떤 클래스의 어느 조인포인트를 사용할 것인지를 결정하는 선택 기능
    AOP가 항상 모든 모듈의 모든 조인포인트를 사용할 것이 아니기 때문에 필요에 따라 사용해야 할 모듈의 특정 조인포인트를 지정할 필요가 있다. 일종의 조인포인트 선정 룰과 같은 개념이다. 
    AOP에서는 포인트컷을 수행할 수 있는 다양한 접근 방법을 제공한다. AspectJ에서는 와일드카드를 이용한 메쏘드 시그니처를 사용한다.


    어드바이스(advise) 또는 인터셉터(intercepter)
    어드바이스 - 각 조인포인트에 삽입되어져 동작할 수 있는 코드
      주로 메소드 단위로 구성된 어드바이스는 포인트컷에 의해 결정된 모듈의 조인포인트에서 호출되어 사용된다. 
      일반적으로 독립적인 클래스 등으로 구현된 횡단 관심 모듈을 조인포인트의 정보를 참조해서 이용하는 방식으로 작성된다.
    인터셉터 - 인터셉터 체인 방식의 AOP 툴에서 사용하는 용어로 주로 한 개의 invoke 메소드를 가지는 어드바이스

    어드바이스(advise)의 종류
    Before advice : 메서드 실행전에 적용되는 실행
    After returning advice : 메서드가 정상적으로 실행된 후에 실행  (예외를 던지는 상황은 정상적인 상황에서 제외)
    After throwing advice : 예외를 발생시킬 때 적용되는 Advice를 정의 (catch와 비슷)
    Around advice : 메서드 호출 이전, 이후, 예외 발생 등 모든 시점에서 적용 가능한 Advice를 정의


    위빙(weaving) 또는 크로스컷팅(crossCutting)
    위빙 - 포인트컷에 의해서 결정된 조인포인트에 지정된 어드바이스를 삽입하는 과정 (다른 말로 크로스컷팅)
    위빙은 AOP가 기존의 핵심 관심 모듈의 코드에 전혀 영향을 주지 않으면서 필요한 횡단 관심 기능을 추가할 수 있게 해주는 핵심적인 처리과정이다. 위빙을 처리하는 방법은 후처리기를 통한 코드생성 기술을 통한 방법부터 특별한 컴파일러 사용하는 것, 이미 생성된 클래스의 정적인 바이트코드의 변환 또는 실행 중 클래스로더를 통한 실시간 바이트코드 변환 그리고 다이내믹 프록시를 통한 방법까지 매우 다양하다.


    인트로덕션(Introduction) 또는 인터타입 선언
    인트로덕션 - 정적인 방식의 AOP 기술
    동적인 AOP 방식을 사용하면 코드의 조인포인트에 어드바이스를 적용해서 핵심관심 코드의 동작 방식을 변경할 수 있다. 
    인트로덕션은 이에 반해서 기존의 클래스와 인터페이스에 필요한 메소드나 필드를 추가해서 사용할 수 있게 해주는 방법
    OOP에서 말하는 오브젝트의 상속이나 확장과는 다른 방식으로 어드바이스 또는 애스팩트를 이용해서 기존 클래스에 없는 인터페이스 등을 다이내믹하게 구현해 줄 수 있다.


    애스팩트(aspect) 또는 어드바이저
    애스팩트 - 포인트컷(어디에서) + 어드바이스(무엇을 할 것인지) + (필요에 따라 인트로덕션도 포함)
    AspectJ와 같은 자바 언어를 확장한 AOP에서는 마치 자바의 클래스처럼 애스팩트를 코드로 작성할 수 있다. AOP 툴의 종류에 따라서 어드바이스와 포인트컷을 각각 일반 자바 클래스로 작성하고 이를 결합한 어드바이저 클래스를 만들어서 사용하는 방법도 있다.




    Primary(Core) Concern - 비지니스 로직을 구현한 부분
    Cross-Cutting Concern - 보안, 인증, 로그 등과 같은 부가적인 기능으로서 시스템 전반에 산재되어 사용되는 기능
    Code - Primary(Core) concern을 구현해 놓은 코드를 이야기

    위의 이미지는 AOP에 대하여 간단하게 정리한 이미지이다. (그냥 이해한 대로 정리한거라..;;)
    설명을 하자면 기존의 코드는 Primary Concern과 Cross-Cutting Concern이 같이 하나의 프로그램으로 구현되어졌다.
    당연히 비지니스 로직과 상관없는 코드들이 여기저기 산재해 있게 되었기에 가독성과 유지보수성이 좋지 않았다.
    하지만 AOP는 Primary Concern과 Cross-Cutting Concern이 별도로 코드로 구현이 되고, 최종적인 프로그램은 이 (Code와 Advise)을 연결해주는 설정 정보인 Point-Cut을 이용하여 Weaving되어 완성하게 되는 것이다. 

     

    AOP의 설정 구조는 보통 아래와 같이 구성되어 있다.

    <aop:config> 
        <aop:pointcut /> : pointcut 설정
        <aop:aspect> : aspect를 설정
            <aop:before /> : method 실행 전
            <aop:after-returning /> : method 정상 실행 후
            <aop:after-throwing /> : method 예외 발생 시
            <aop:after /> : method 실행 후 (예외 발생 예부 상관 없음)
            <aop:around /> : 모든 시점 적용 가능
        </aop:aspect>
    </aop:config> 

     

    참고 : http://whiteship.tistory.com/1960http://www.zdnet.co.kr/news/news_view.asp?artice_id=00000039147106&type=det 

    'FRAMEWORK > SPRING MVC' 카테고리의 다른 글

    Spring - POJO  (0) 2014.09.29
    Spring - Bean 초기화 및 생명주기  (0) 2014.09.29
    Spring - Bean Scope  (0) 2014.09.29
    Spring - IoC & DI  (0) 2014.09.29
    Spring MVC 와 DispatcherServlet  (0) 2014.09.29
    :

    Spring MVC 와 DispatcherServlet

    FRAMEWORK/SPRING MVC 2014. 9. 29. 13:28

    Spring MVC 는 다른 MVC 프레임워크와 동일하게 앞단에 프론트 컨트롤러(DispatcherServler)를 두고있다.

    DispatcherServlet - web.xml 에 정의한 서블릿
                                    MVC아키텍처로 구성된 프레젠테이션 계층을 만들 수 있도록 설계되어 있다.

    [ Spring MVC의 흐름 ]



     

    1) DispatcherServlet의 HTTP 요청 접수

    자바 서버의 서블릿 컨테이너는 HTTP 프로토콜을 통해 들어오는 요청이 스프링의 DIspatcherServlet에 할당된 것이라면 HTTP 요청 정보를 DIspatcherServlet에 전달.

    web.xml에는 DIspatcherServlet이 전달받을 URL의 패턴이 정의되어 있다.

    DIspatcherServlet는 공통적으로 진행해야 하는 파라미터조작, 한글 디코딩 같은 전처리 작업이 등록되어 있다면 이를 먼저 수행한다.

    web.xml의 설정 (서블릿을 하나 등록하고 해당 서블릿에 매핑을 시켜주는것이 전부이다.)

    <web-app>
    <servlet>
    <servlet-name>example</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
    <servlet-name>example</servlet-name>
    <url-pattern>*.form</url-pattern>
    </servlet-mapping>
    </web-app>

     

    2) DIspatcherServlet에서 컨트롤러로 HTTP 요청 위임

    DIspatcherServlet는 URL이나 파라미터 정보, HTTP 명령등을 참고하여 해당 컨트롤러에 작업을 위임.

    DIspatcherServlet는 핸들러 매핑전략을 이용하여 컨트롤러를 선정

    핸들러 - 컨트롤러를 웹의 요청을 다루는(handle) 오브젝트.

    핸들러 매핑전략 - 사용자 요청을 기준으로 어떤 핸들러에게 작업을 위임할 지 결정해주는 것

    어댑터 - DIspatcherServlet은 컨트롤러가 어떤 메소드를 가졌고 어떤 인터페이스를 구현하였는지 알지 못한다.
            그래서 사용 어댑터는 자신이 담당하는 컨트롤러에 맞는 호출방법을 이용하여 컨트롤러에 작업 요청을 보내고 결과를 
            돌려받아 DIspatcherServlet에게 다시 돌려주는 기능을 한다.



     

    3) 컨트롤러의 모델 생성과 정보 등록

    MVC패턴의 장점은 정보를 담고 있는 모델과 정보를 어떻게 뿌려줄지를 알고 있는 뷰가 분리된다는 점이다.

    컨트롤러의 역활 : 사용자의 요청을 해석
                            실제 비지니스 로직을 수행하도록 서비스 계층 오브젝트에게 작업을 위임
                            모델(맵에 담긴 정보 - 이름과 그에 대응되는 값의 쌍으로 정보를 만드는 것) 생성
                            뷰 선택

    컨트롤러는 모델과 뷰 두 가지 정보는 반드시 DispatcherServlet에 전달해야 한다.

     

    4) 컨트롤러의 결과 리턴 : 모델 , 뷰

    컨트롤러에게서 뷰의 논리적인 이름을 리턴받아, (DispatcherServlet가) 뷰 리졸버를 이용하여 뷰 오브젝트를 생성.

    JSP파일로 만들어진 뷰 템플릿과 JstlView클래스로 만들어진 뷰 오브젝트가 결합하여 HTML을 생성한다.

    ModelAndView 오브젝트 - DispatcherServlet이 최종적으로 컨트롤러로부터 돌려받는 오브젝트 
                                            (모델, 뷰 두가지 정보를 담고 있다.)

     

    5) DispatcherServlet의 뷰 호출과 6) 모델 참조

    DispatcherServlet은 컨트롤러로부터 받은 모델을 뷰오브젝트에 전달 및 클라이언트에게 돌려줄 최종 결과물을 생성 요청

    동적으로 생성되는 부분은 모델을 내용을 참고를 내용을 채운다.

    ex) 컨트롤러가 모델에 name이라는 이름으로 'Spring'을 담았다면 아래와 같이 처리된다.

    <div> 이름 : ${name} <div> -> <div> 이름 : Spring </div>

    HttpsServletResponse오브젝트 - 뷰 작업을 통한 최종 결과물을 담는 오브젝트.

     

    7) HTTP응답 돌려주기

    DispatcherServlet은 등록된 후처리기가 있는지 확인하고 있으면 후속작업을 진행한 후 뷰가 만들어준 HttpServletResponse에 담긴 최종 결과를 서블릿 컨테이너에게 돌려준다.

    서블릿 컨테이너는 HttpServletResponse에 담긴 정보를 HTTP응답으로 만들어 사용자게에 전달해주고 작업을 종료한다.

     

     

    [ 다양한 DispatcherServlet의 DI 가능 전략 ]

    HandlerMapping

    URL과 요청 정보를 기준으로 어떤 핸들러 오브젝트, 즉 컨트롤러를 사용할 것인지 결정하는 로직 담당

    DispatcherServlet은 하나의 이상의 핸들러 매핑을 가질 수 있다.

    Default - BeanNameUrlHandlerMapping, DefaultAnnotationHandlerMapping

     

    HandlerAdapter

    핸들러 매핑으로 선택한 컨트롤러/핸들러를 DispatcherServlet이 호출할 때 사용하는 어뎁터

     

    HandlerExceptionResolver

    예외가 발생하였을 경우 처리하는 로직을 담당

    예외가 발생하였을 때 예외의 종류에 따라 DispatcherServlet을 통해 처리되는 작업의 경우, DispatcherServlet에 등록된 HandlerExceptionResolver 중에서 발생한 예외에 적합한 것을 찾아 예외처리를 위임한다.

     

    ViewResolver

    컨트롤러가 리턴한 뷰이름을 참고하여 적절한 뷰 오브젝트를 찾아주는 로직을 가진 전략 오브젝트

    ViewResolving(뷰 리졸빙) - http://isstory83.tistory.com/117 


    LocaleResolver

    지역(Locale)정보를 결정해주는 전략

    디폴트인 AcceptHandlerLocaleResolver는 HTTP 헤더 정보를 이용하여 지역정보를 설정한다.

    'FRAMEWORK > SPRING MVC' 카테고리의 다른 글

    Spring - POJO  (0) 2014.09.29
    Spring - Bean 초기화 및 생명주기  (0) 2014.09.29
    Spring - Bean Scope  (0) 2014.09.29
    Spring - IoC & DI  (0) 2014.09.29
    Spring - AOP 개념 정리  (0) 2014.09.29
    :