[toby의스프링] 10장 - IoC 컨테이너와 DI
이번 장의 분량은 꽤 많다. 약 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>
</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>
</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부에서 학습했기 때문에 실제 적용된 applicationContext 파일을 소개하는 선에서 마무리하자. 3
마지막으로, 이 장에서 언급된 개념 중 특히 중요한 항목들을 나열해보자.
1. 스프링의 빈 등록 방법: XML, 빈 자동인식, 자바 코드에서 등록(XML + 자동인식이 자주 쓰인다)
2. 스프링의 빈 의존관계 설정 방법: XML, 애노테이션, 자바 코드에서 등록(XML + 애노테이션이 자주 쓰인다)
3. 스코프: 빈의 존재 범위. 싱글톤, 프로토타입, 기타 스코프(요청request, 세션session, 글로벌세션globalSession, 애플리케이션application : 모두 웹 전용이다)
아.. 졸리다.