먹고 사는 이야기/프로그래밍

[toby의스프링] 10장 - IoC 컨테이너와 DI

Kunner 2011. 11. 8. 22:23

이번 장의 분량은 꽤 많다. 약 140p - 장수로는 70장 - 소설처럼 술술 읽히지 않는 특성 상 글자를 읽는데만 서너시간씩 걸린다.
눈꺼풀은 어찌나 무겁던지.. 

처음 이 책을 들었을 때 철저히 숙독해야겠다고 마음 먹었다.
먹고 살기 위한 공부가 아니라 공부를 위한 공부를 해 보겠다고 말이지.
사실 이미 아는 내용이거나, 실무에서 빈번하게 쓰이지 않는 부분은 그냥 대충 읽고 넘어가도 좋을텐데.. 얼마간은 바보 같은 짓인것 같기도 하다.

이 책은 한번 읽고 따라해보는 스타일이 아니라 원리를 이해하고 계속해 두고 보는 바이블 스타일이다.
그래서 단시간에 읽고 이해하기도 어렵지만 정리하기는 더욱 어려운 것 같다.



이번 장에서는 스프링 컨테이너에서 Bean을 DI하는 법에 대해 말하고 있다.
사실 실무에서 늘 하던거지만, 이렇게 체계적으로 파고 들어 본 적은 없었다.
그저 기계적인 접근(Ctrl + C, Ctrl + V)만이 있었을 뿐.

그래서 이번 장은 참 따분하기도 했고[각주:1], 또 참 흥미로운 내용[각주:2]이기도 했다.
 
70페이지나 되는 방대한 분량을 게시물 하나로 정리하기는 좀 어렵겠고..
하나 하나 세세하게 짚어야 할 이유도 별로 없을 것 같다.


IoC 컨테이너를 통해 애플리케이션이 만들어지는 방식 (본문 p772)



IoC에 대해서는 이미 앞선 1부에서 충분히 이야기 했던 내용이니 이론적인 이야기는 그림을 통해 설명하고 넘어가자.
귀찮아서라기보다, 딱히 더 설명할 것이 없다.

대신 실무에서 빈번하게 사용되는 방식인 XML을 이용한 applicationContext 설정을 가져다 review 하는 방식으로 이 장을 정리하고자 한다.
예제로 제시되는 applicationContext 는 얼마 전에 실제로 개발한 프로젝트에서 사용한 것 중 일부이다.
원본 applicationContext는 분량이 꽤 방대해 그 중 일부를 발췌했다.
따라서 정상 동작하는 소스가 아니므로, 어떻게 하는지만 대충 살펴보기 바란다.


applicationContext.xml


<?xml version="1.0" encoding="UTF-8"?>

<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-2.5.xsd  

        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd

        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">


        <!-- 동적 URL Mapping : 각 url에 오브젝트를 연결한다. -->

<bean id="defaultUrlMapping" 

class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">

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

<property name="interceptors"> //정의된 url에 인터셉트를 적용한다.

                    <list>

                        <ref bean="serviceSessionInterceptor"/> //인터셉터의 예. list 로 복수를 지정할 수 있다.

                        <ref bean="menuNodeInterceptor"/>

                    </list>

                </property>

<property name="mappings">

<props>

<prop key="/serviceName#1.do">serviceController#1</prop> //서블릿(do)에 연결될 오브젝트(Controller)

<prop key="/serviceName#2.do">serviceController#2</prop>
                                ... (연결될 수 만큼) ...
</props> 

</property>
</bean>
 

        <!-- 정적 URL Mapping : 각 url에 staticUrlController를 연결한다. 단순 html 페이지에서 사용 -->

<bean id="staticUrlMapping" 

class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">

<property name="alwaysUseFullPath" value="true" />

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

<property name="interceptors"> //정의된 url에 인터셉트를 적용한다.

                    <list>

                        <ref bean="serviceSessionInterceptor"/> //인터셉터의 예. list 로 복수를 지정할 수 있다.

                        <ref bean="menuNodeInterceptor"/>

                    </list>

                </property>

<property name="mappings">

<props>

<prop key="/serviceName#1.do">staticUrlController</prop> //서블릿(do)에 연결될 staticUrlController

<prop key="/serviceName#2.do">staticUrlController</prop>
                                ... (연결될 수 만큼) ...
</props> 

</property>
</bean>

 

<bean id="staticUrlController" class="org.springframework.web.servlet.mvc.UrlFilenameViewController"/> // 위에서 호출한 정적(static) 페이지를 보여주는 서블릿 컨트롤러


        <!--JSP View Mapping : JSP페이지를 view로 연결 -->

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" 

p:prefix="/WEB-INF/jsp/"

p:suffix=".jsp">

<property name="viewClass"

           value="org.springframework.web.servlet.view.JstlView" />

</bean>

 

        <!--트랜잭션 적용을 위한 AOP 설정 -->

<aop:config>

<aop:pointcut id="defaultServiceOperation"

expression="execution(* classpathRoot.*.service.*Service.*(..))" />

<aop:advisor pointcut-ref="defaultServiceOperation"

advice-ref="defaultTxAdvice" order="2"/>

</aop:config>


<!-- 실제 메소드에 트랜잭션 적용하기 -->

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

<tx:attributes>

<tx:method name="update*" propagation="REQUIRED" rollback-for="Exception"/> //update로 시작하는 이름을 가진 모든 메소드에 적용한다. 이하 동일.

<tx:method name="delete*" propagation="REQUIRED" rollback-for="Exception"/>

<tx:method name="insert*" propagation="REQUIRED" rollback-for="Exception"/>

<tx:method name="process*" propagation="REQUIRED" rollback-for="Exception"/>

<tx:method name="perform*" propagation="REQUIRED" rollback-for="Exception"/>

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

<tx:method name="set*" propagation="REQUIRED" read-only="true" />

<tx:method name="find*" propagation="REQUIRED" read-only="true"/>

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

</tx:attributes>

</tx:advice> 

 

<!-- Bean을 등록하고 해당 Bean에 DI 하기 -->

<bean id="codeService" class="mest.eks.common.service.CodeService">

<property name="zipCodeService" ref="zipCodeService"/> // 아래 오렌지색 처리된 zipCodeService를 참조함

</bean>

<bean id="zipCodeService" class="projectName.common.service.ZipCodeService">

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

</bean>


<!-- DB 연결 설정 -->

<bean id="nativeJdbcExtractor" class="org.springframework.jdbc.support.nativejdbc.SimpleNativeJdbcExtractor" lazy-init="true"/>


<bean id="oracleLobHandler" class="org.springframework.jdbc.support.lob.OracleLobHandler" lazy-init="true">

  <property name="nativeJdbcExtractor"><ref local="nativeJdbcExtractor"/></property>

  </bean>

<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean" >

<property name="configLocation">

<value>/WEB-INF/config/sqlmap-config.xml</value> //iBatis Sql Mapping 연결설정

</property>

<property name="dataSource">

<ref bean="dataSource" />

</property>

<property name="lobHandler" ref="oracleLobHandler" />

</bean>

<!-- jndi -->

<bean id="jnditemplate" class="org.springframework.jndi.JndiTemplate" >

  <property name="environment">

      <props>

        <prop key="java.naming.factory.initial">jeus.jndi.JNSContextFactory</prop>

        <prop key="java.naming.provider.url">localhost:9736</prop>

      </props>

  </property>

</bean>

<!-- Jeus Server use ConnectionPoolingDataSource -->

<bean id="transactionManager"

class="org.springframework.jdbc.datasource.DataSourceTransactionManager"

p:dataSource-ref="dataSource" />

<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">

<property name="jndiName" value="JNDI_NAME"/>

<property name="jndiTemplate" ref="jnditemplate"/>

</bean> 

</beans>



sqlmap-config.xml

<?xml version="1.0" encoding="UTF-8" ?>


<!DOCTYPE sqlMapConfig

PUBLIC "-//ibatis.apache.org//DTD SQL Map Config 2.0//EN"

"http://ibatis.apache.org/dtd/sql-map-config-2.dtd">


<sqlMapConfig>

<settings enhancementEnabled="true" useStatementNamespaces="true" />


<!-- common sqlMap -->

<sqlMap resource="xmlfile.xml" />

... (필요한 수 만큼 반복) ... 

</sqlMapConfig> 




xmlfile.xml

<?xml version="1.0" encoding="UTF-8"?>


<!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"

    "http://ibatis.apache.org/dtd/sql-map-2.dtd">

<sqlMap namespace="BoardFile">

<typeAlias alias="fileVo" type="projectName.model.FileVo"/>

<typeAlias alias="map" type="java.util.Map"/>

<resultMap id="_resultMap" class="fileVo" >

<result column="FILE_NM" property="fileNm" />

<result column="FILE_PATH" property="filePath" />

</resultMap>

<select id="fileInfo" resultMap="_resultMap" parameterClass="fileVo">

SELECT *

FROM

TABLE_NAME

WHERE

SEQ = #seq#

<isNotEmpty property="requestParameter"> //넘겨 받은 값에 의한 판별

AND MENU_CD = #requestParameter#

</isNotEmpty>

</select>

</sqlMap> 



applicationContext 를 설명할 때, 단순히 기술 방식만 언급하는게 아니라 이를 클래스나 메소드에서 어떻게 활용하는지를 함께 언급해야 할 것이다.


위에서 보여준 그림을 다시 보면서 얘기하자면..

IoC 컨테이너를 통해 애플리케이션이 만들어지는 방식 (본문 p772)



위에서 잔뜩 나열한 xml 파일들은 모두 메타정보리소스에 해당하는 내용들이다. 이를 스프링의 메타정보리더가 읽어들여 설정메타정보로 바꾼다. 이 정보가 IoC 컨테이너가 되려면 POJO 클래스와 함께 사용되어야 한다. 즉, applicationContext에서 설정된 내용들을 POJO 클래스에서 불러와 활용해야 한다.

하지만 POJO 클래스에서 메타정보를 활용하는 방법에 대해서는 이미 1부에서 학습했기 때문에[각주:3] 실제 적용된 applicationContext 파일을 소개하는 선에서 마무리하자.



마지막으로, 이 장에서 언급된 개념 중 특히 중요한 항목들을 나열해보자.


1. 스프링의 빈 등록 방법: XML, 빈 자동인식, 자바 코드에서 등록(XML + 자동인식이 자주 쓰인다)

2. 스프링의 빈 의존관계 설정 방법: XML, 애노테이션, 자바 코드에서 등록(XML + 애노테이션이 자주 쓰인다)

3. 스코프: 빈의 존재 범위. 싱글톤, 프로토타입, 기타 스코프(요청request, 세션session, 글로벌세션globalSession, 애플리케이션application : 모두 웹 전용이다)
 




아.. 졸리다.


  1. 대부분 아는 내용이었으니까 [본문으로]
  2. 스프링의 작동 원리에 대해 이야기하고 있었으므로 [본문으로]
  3. 더구나 자바 클래스 파일의 작성법을 다룰 수는 없지 않은가? 이것은 어디까지나 스프링에 대한 학습서 내용을 간추린 것이니까. ㅎㅎ;; [본문으로]