본문 바로가기

프로그래밍/Spring

전자정부 프레임워크를 Java Config 방식으로 설정해보자(context-common.xml 변환)

지난 글에서는 AOP 관련 설정인 ContextAspect 클래스를 제작하는 방법에 대해 설명하였다. 이번엔 공통 설정으로 사용된 context-common.xml 환경파일을 Java Config 파일로 바꿔보도록 하자

 

본격적으로 설명하기에 앞서 context-common.xml 에서 설정하는 것이 무엇이 있는지 살펴보고 넘어가자. context-common.xml 에서 설정하는 것은 다음의 것들이다.

 

  • 등록해야 할 Bean 클래스를 scan해서 대상이 되는 클래스를 자동으로 Spring Bean으로 등록하는 <context:component-scan> 태그
  • Sample 프로젝트에서 사용되는 각종 메시지를 관리하는 ReloadableResourceBundleMessageSource 클래스를 Spring Bean으로 등록하는 <bean> 태그
  • EgovAbstractServiceServiceImpl 클래스에서 예외를 발생하지 않고 후처리로직을 실행하는 기능을 수행하는 LeaveaTrace 클래스를 Spring Bean으로 등록하는 <bean> 태그
  • 지정된 클래스 및 메소드의 패턴에서 예외 발생시 이에 대한 예외처리를 하는 handler들을 등록시켜 관리하는 클래스인 DefaultTraceHandleManager 클래스를 Spring Bean으로 등록하는 <bean> 태그
  • AntPathMatcher 클래스를 등록하는 <bean> 태그
  • DefaultTraceaHandleManager 클래스에 등록되는 예외처리 handler인 DefaultTraceHandler를 등록하는 <bean> 태그

 

먼저 context-common.xml에 설정되어 있는 <component-scan> 태그를 보면 다음과 같이 설정되어 있다.

 

<context:component-scan base-package="egovframework">
	<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>

 

Spring을 아는 사람이면 이 태그가 의미하는 것이 무엇인지 알겠지만 설명을 하자면 egovframework 패키지를 기준으로 egovframework 맟 그 하위 패키지를 스캔하면서 Spring Bean으로 등록이 되게끔 설정된 클래스(@Component 어노테이션 또는 @Controller, @Service, @Repository 같이 @Component 어노테이션을 사용해서 만든 어노테이션)이 붙은 클래스는 등록을 하되 Controller 어노테이션이 붙은 클래스는 Spring Bean 등록에서 제외되도록 하는 설정이다. 그러나 전자정부 프레임워크의 Sample 프로젝트는 Root Context와 Servlet Context로 Context를 2개로 나눈 구성을 하고 있으며 context-*.xml 설정 파일들은 Root Context를 구성하는 설정파일이기 때문에 Servlet Context를 구성하는 설정파일인 WEB-INF/config/egovframework/springmvc/dispatcher-servlet.xml 에도 <context:component-scan> 태그가 있다. 다음의 것이 dispatcher-servlet.xml에 설정된 <context:component-scan> 태그이다

 

<context:component-scan base-package="egovframework">
	<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
	<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
	<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>

 

이 <context:component-scan> 태그도 egovframework 및 그 하위 패키지를 스캔하지만 Controller 어노테이션이 붙은 클래스를 등록하고 Service, Repository 어노테이션이 붙은 클래스는 Spring Bean 등록에서 제외되도록 하는 설정이다. 이 2개의 <context:component-scan> 태그의 설정을 최종적으로 정리하면 다음과 같이 요약이 된다.

 

  • Root Context, Servlet Context에 Spring Bean으로 등록되는 클래스는 egovframework 및 그 하위 패키지를 스캔하면서 @Component 어노테이션 또는 @Component 어노테이션을 사용해서 만든 어노테이션이 붙은 클래스를 등록한다.
  • 단 이 Spring Bean으로 등록되는 작업에는 필터링을 거쳐서 작업을 하게 되는데 Root Context는 @Controller 어노테이션이 붙은 클래스만 Spring Bean 등록작업에서 제외가 되며, Servlet Context는 @Contoller 어노테이션이 붙은 클래스는 등록에 포함이 되고, @Service, @Repository 어노테이션이 붙은 클래스는 등록에 제외가 된다.

 

그러면 이 부분을 Java Config로 바꾸는 작업을 진행해보도록 하자. <context:component-scan> 태그와 동일한 작업을 해주는 @ComponentScan 어노테이션을 이용하면 된다. context-common.xml의 <context:component-scan> 태그 내용을 @ComponentScan 어노테이션을 이용해서 바꾸면 다음과 같이 바꿀수 있다.

 

@ComponentScan(
	basePackages="egovframework",
	excludeFilters={
		@ComponentScan.Filter(type=FilterType.ANNOTATION, value=Controller.class)
	}
)

 

<context:component-scan> 태그와 비교해보면 어떻게 설정하는지 이해되리라 생각한다. 대신 주의할 점이 있다면 XML에서는 스캔해야 할 패키지 이름을 설정하는 속성명이 base-package 이렇게 -(하이픈)과 단수식 표현이지만 @ComponentScan 어노테이션에서는 이 기능을 하는 속성 이름이 basePackages 이렇게 -을 사용하지 않고 복수식 표현을 하는것(복수식 표현을 하는 이유는 basePackages 속성은 String 배열 타입이기 때문에 복수개의 스캔해야 할 패키지 이름 설정이 가능하기 때문이다. 즉 basePackages={"egovframework","myframework"} 이렇게 설정하면 egovframework 패키지와 myframework 패키지 이렇게 2개의 스캔해야 할 패키지를 지정할 수 있다)과 스캔해야 하거나 또는 스캔에서 제외해야 하는 클래스 또는 어노테이션을 설정할 필터 설정을 할때 @ComponentScan.Filter 어노테이션을 이용해서 지정하는 것 정도이다. Servlet Context를 구성하는 

dispatcher-servlet.xml에 설정되어 있는 <context:component-scan> 태그를 @ComponentScan 어노테이션을 이용해서 바꾸면 다음과 같이 바꿀수 있다. 이 내용도 방금 설명한 내용을 이해했다면 이해하는데 별 문제가 없을 것이다

 

@ComponentScan(
	basePackages="egovframework",
	includeFilters={
		@ComponentScan.Filter(type=FilterType.ANNOTATION, value=Controller.class)	
	},
	excludeFilters={
		@ComponentScan.Filter(type=FilterType.ANNOTATION, value=Service.class)
		, @ComponentScan.Filter(type=FilterType.ANNOTATION, value=Repository.class)
	}
)

 

근데 지금부터는 약간 초치는(?) 얘기를 시작해야겠다. 아직 최종적으로 완성된 상태는 아니지만 일단 완성된 상태라 가정하고 Tomcat에서 Sample 프로젝트를 실행시키면 에러가 발생한다. 실행될 당시의 로그가 너무 길어서 설명에 필요한 것만 추스리면 다음의 내용이 나온다.

 

INFO [org.springframework.web.context.ContextLoader] Root WebApplicationContext: initialization started

INFO [org.springframework.web.context.support.AnnotationConfigWebApplicationContext] Refreshing Root WebApplicationContext: startup date [Sun Mar 06 03:18:30 KST 2016]; root of context hierarchy

INFO [org.springframework.web.context.support.AnnotationConfigWebApplicationContext] Successfully resolved class for [egovframework.example.config.root.RootContext]

INFO [org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactory] Creating embedded database 'testdb'

INFO [org.springframework.jdbc.datasource.init.ScriptUtils] Executing SQL script from class path resource [db/sampledb.sql]

INFO [org.springframework.jdbc.datasource.init.ScriptUtils] Executed SQL script from class path resource [db/sampledb.sql] in 52 ms.

INFO [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping] Mapped "{[/egovSampleList.do],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String egovframework.example.sample.web.EgovSampleController.selectSampleList(egovframework.example.sample.service.SampleDefaultVO,org.springframework.ui.ModelMap) throws java.lang.Exception

INFO [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping] Mapped "{[/updateSampleView.do],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String egovframework.example.sample.web.EgovSampleController.updateSampleView(java.lang.String,egovframework.example.sample.service.SampleDefaultVO,org.springframework.ui.Model) throws java.lang.Exception

INFO [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping] Mapped "{[/deleteSample.do],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String egovframework.example.sample.web.EgovSampleController.deleteSample(egovframework.example.sample.service.SampleVO,egovframework.example.sample.service.SampleDefaultVO,org.springframework.web.bind.support.SessionStatus) throws java.lang.Exception

INFO [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping] Mapped "{[/updateSample.do],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String egovframework.example.sample.web.EgovSampleController.updateSample(egovframework.example.sample.service.SampleDefaultVO,egovframework.example.sample.service.SampleVO,org.springframework.validation.BindingResult,org.springframework.ui.Model,org.springframework.web.bind.support.SessionStatus) throws java.lang.Exception

INFO [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping] Mapped "{[/addSample.do],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String egovframework.example.sample.web.EgovSampleController.addSampleView(egovframework.example.sample.service.SampleDefaultVO,org.springframework.ui.Model) throws java.lang.Exception

INFO [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping] Mapped "{[/addSample.do],methods=[POST],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String egovframework.example.sample.web.EgovSampleController.addSample(egovframework.example.sample.service.SampleDefaultVO,egovframework.example.sample.service.SampleVO,org.springframework.validation.BindingResult,org.springframework.ui.Model,org.springframework.web.bind.support.SessionStatus) throws java.lang.Exception

INFO [org.springframework.web.servlet.handler.SimpleUrlHandlerMapping] Mapped URL path [/cmmn/validator.do] onto handler of type [class org.springframework.web.servlet.mvc.ParameterizableViewController]

INFO [org.springframework.web.context.ContextLoader] Root WebApplicationContext: initialization completed in 4558 ms

3월 06, 2016 3:18:34 오전 org.apache.catalina.core.ApplicationContext log

정보: Initializing Spring FrameworkServlet 'action'

INFO [org.springframework.web.servlet.DispatcherServlet] FrameworkServlet 'action': initialization started

INFO [org.springframework.web.context.support.AnnotationConfigWebApplicationContext] Refreshing WebApplicationContext for namespace 'action-servlet': startup date [Sun Mar 06 03:18:34 KST 2016]; parent: Root WebApplicationContext

INFO [org.springframework.web.context.support.AnnotationConfigWebApplicationContext] Successfully resolved class for [egovframework.example.config.servlet.ServletContext]

INFO [org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactory] Creating embedded database 'testdb'

INFO [org.springframework.jdbc.datasource.init.ScriptUtils] Executing SQL script from class path resource [db/sampledb.sql]

WARN [org.springframework.web.context.support.AnnotationConfigWebApplicationContext] Exception encountered during context initialization - cancelling refresh attempt

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'mapperConfigurer' defined in class path resource [egovframework/example/config/root/ContextMapper.class]: Unsatisfied dependency expressed through constructor argument with index 0 of type [javax.sql.DataSource]: : Error creating bean with name 'sqlSession' defined in class path resource [egovframework/example/config/root/ContextMapper.class]: Unsatisfied dependency expressed through constructor argument with index 0 of type [javax.sql.DataSource]: : Error creating bean with name 'dataSource' defined in class path resource [egovframework/example/config/root/ContextDataSource.class]: Instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanDefinitionStoreException: Factory method [public javax.sql.DataSource egovframework.example.config.root.ContextDataSource.dataSource()] threw exception...

 

블로그에서 에러 로그를 보여주는 것이 좀 어려워서 보기 힘들겠지만 로그에 대해 설명을 해야 @ComponentScan 어노테이션 설정시 주의할 점을 설명할 수 있어서 그러니 이해 부탁한다. 맨 위에서부터 한 줄씩 보면 Root Context 초기화 작업이 시작되고 'testdb' 라는 이름의 임베디드 데이터베이스를 생성한 뒤 SQL 스크립트인 db/sampledb.sql을 실행시킨다. 이 부분은 나중에 Database 관련 설정시에 나올 내용이기때문에 일단은 임베디드 데이터베이스를 생성시킨다는 정도로만 넘어가자.

 

문제는 그 다음이다. 그 다음으로 나오는 내용(진한 파란색 글씨로 강조된 내용)이 RequestMappingHandlerMapping 클래스가 여러개의 URL을 매핑하는 로그가 나온다. 여기서 먼가 이상하다는 느낌을 받은 사람이 있다면 난 프로그래밍 감이 좋다고 말해주고 싶다. 이전에 우리가 설정한 Root Comtext의 @ComponentScan 어노테이션 설정을 보면 @Controller 어노테이션은 스캔 대상에서 제외가 되게끔 설정했다. 근데 실행하면 Root Context가 올라갈때 @Controller 어노테이션이 스캔이 되면서 RequestMappingHandlerMapping 클래스가 동작해서 URL 매핑 작업이 발생하고 있다. 즉 @Controller 어노테이션 클래스가 스캔이 되어버린것이다.

 

그리고 그 다음을 보면 Servlet Context 초기화 작업이 시작된다는 내용이 나온다. 여기서도 이상한 내용이 나오게 되는데 진한 검은색 글씨로 강조된 밑줄 그어진 내용을 보자. 이 부분을 보면 'testdb' 라는 이름의 임베디드 데이터베이스를 생성한 뒤 SQL 스크립트인 db/sampledb.sql을 실행시킨다. 어? 이 부분은 Root Context 초기화 작업할 때 작업한 내용인데 왜 Servlet Context 올라갈 때 이 작업을 다시하지? 아무튼 로그를 보면 이러한 작업을 하면서 오류가 발생하게 된다. 로그에 대한 설명은 이 정도로 마치도록 하고 이제 이 로그를 보여준 이유를 이해하리하 생각한다. 즉 우리가 설정한 @ComponentScan 어노테이션이 정상적인 동작을 하지 않는다는 것이다. 즉 Root Context에서는 Servlet Context에서 작업해야 할 @Controller 어노테이션을 스캔하는 작업이 발생되어지고 있고, Servlet Context에서는 Root Context에서 작업했던 Database 생성작업을 다시 작업하고 있다. 왜 이런 상황이 벌어지고 있는가? 지금부터 이런 상황이 벌어지는 원인에 대해 알아보도록 하자.

 

이 원인에 대해 이해할려면 <context:component-scan> 태그 또는 @ComponentScan 어노테이션에 있는 하나의 속성에 대한 이해와 @Configuration 어노테이션에 대한 이해가 필요하다. 먼저 <context:component-scan> 태그 또는 @Component 어노테이션에 대한 이해부터 얘기하도록 하자. <context:compontnt-scan> 태그에는 use-default-filters 라는 속성이 있다(@ComponentScan 어노테이션으로는 같은 기능의 속성으로 useDefaultFilters 라는 속성이 있다) 이 속성이 하는 역할은 클래스를 스캔하면서 @Component, @Controller, @Service, @Repository 어노테이션이 붙은 클래스가 발견되면 자동으로 Spring Bean으로 등록하게 하는 기능이다. 그리고 이 속성은 기본값이 true이기 때문에 <context:component-scan> 태그 또는 @ComponentScan 어노테이션에서 사용을 하지 않더라도 기본적으로 이 기능이 사용된다. 그래서 전자정부 프레임워크 Sample 프로젝트의 context-common.xml에 있는 Root Context의 <context:component-scan> 태그 설정을 보면 use-default-filters 속성을 사용하지 않았기 때문에 @Component, @Controller, @Service, @Reposiroty 어노테이션이 붙은 클래스가 모두 Spring Bean으로 등록시킬려고 하지만 <context:exclude-filter> 태그에서 @Controller 어노테이션을 설정했기 때문에 @Controller 어노테이션을 제외한 나머지 3개의 어노테이션이 붙은 클래스를 Spring Bean으로 등록하며, dispatcher-servlet.xml에 설정된 Servlet Context의 <component:component-scan> 태그 설정을 보면 use-default-filters 속성을 사용하지 않았기 때문에 위에서 언급한 4개의 어노테이션이 붙은 클래스를 등록시킬려고 하지만 <context:include-filter> 태그와 <context:excude-filter> 태그를 통해 최종적으로는 @Component, @Controller 어노테이션이 붙은 클래스가 Spring Bean 으로 등록되고 @Service, @Repository 어노테이션이 붙은 클래스는 Spring Bean 등록에서 제외된다.

 

여기까지만 읽어보면 별 문제가 없어 보인다. 일반적으로 Root Context에는 @Service, @Repository 어노테이션이 붙은 클래스가 Spring Bean으로 등록되고, Servlet Context에는 @Controller 어노테이션이 붙은 클래스를 Spring Bean으로 등록하기 때문에 이 설정이 특별히 문제가 있다고 볼 수가 없다. 정말 이 원칙에 충실히 잘 따른 설정이다. 근데 왜 이러한 문제가 발생하는 것일까? 우리는 여기에서 한가지 특정 어노테이션이 붙은 클래스가 등록된다는 것을 망각한 것이 이러한 문제를 발생시키고 있다. 바로 @Component 어노테이션이다. Sample 프로젝트에서는 직접적으로 @Component 어노테이션이 붙은 클래스를 사용하고 있지 않기 때문에 @Component 어노테이션이 붙은 클래스가 등록될 상황이 존재하지 않는다. 그러나 지금의 이 문제가 발생하는 원인은 @Component 어노테이션이 붙은 클래스가 등록이 되어서 이 문제가 발생한 것이다. 그럼 어디에 @Component 어노테이션이 붙은 클래스가 있는 것인가? 바로 @Configuration 어노테이션이다.

 

@Configuration 어노테이션의 소스를 보면 왜 이런 상황이 발생하는지를 알 수 있다. 다음의 소스는 @Configuration 어노테이션의 소스이다.

 

package org.springframework.context.annotation;

import java.lang.annotation.Documented;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {

	String value() default "";

}

 

@Configuration 어노테이션의 소스를 보면 여기에 @Component 어노테이션이 붙어있다. 바로 이것땜에 이런 현상이 발생하는 것이다. 즉 @Configuration 어노테이션이 내부적으로 @Component 어노테이션을 가지고 있기 때문에 Context의 Spring Bean으로 등록이 되는 것이다. 사실 이 현상까지는 자연스러운 현상이다. 이전 글의 설명에서 언급했지만 이 @Configuration 어노테이션이 붙은 클래스의 내부 동작을 보면 내부적으로 Spring Bean 들이 Injection이 되고 있다. Spring Bean이 Injection이 된다는 것은 Injection 대상이 되는 @Configuration 어노테이션이 붙은 클래스도 Spring Bean으로 등록된다는 의미이다(Spring DI가 적용되는 것은 Spring Bean으로 등록된 클래스에서 가능하다). 때문에 @Configuration 내부에 @Component 어노테이션이 붙어 있는것은 매우 자연스러운 것이다.

 

자 그러면 이 부분을 좀더 디테일하게 보도록 하자. 이전 글에 작성했는 ContextAspect 클래스 소스를 보면 이 클래스의 package는 egovframework.example.config.root 이다. 그리고 앞으로 설명할 Root Context 관련 Java Config 클래스들의 package는 egovframework.example.config.root로 설정할 것이다. 또 Servlet Context 관련 Java Config 클래스들의 package 또한 egovframework.example.config.servlet 이다. 이런 내용에 비추어 보면 <context-component-scan> 태그의 base-package 속성 또는 @ComponentScan 어노테이션의 basePackages 속성에 egovframework로 설정했기 때문에 이 Java Config 클래스 또한 스캔되는 클래스의 대상이 된다. 머 여기까지도 좋다. 그럼 여기까지 이해는 됐을테니 다음 설명하는 내용을 보도록 하자.

 

egovframework 패키지의 자손 패키지에 있는 클래스를 대상으로 Root Context 또는 Servlet Context를 구성하는 클래스들을 스캔하게 될텐데 이 Java Config 클래스들이 모두 양쪽에 스캔이 된다. 즉 내가 Root Context 환경설정을 하기 위해 만든 Java Config 파일들이 Root Context 뿐만 아니라 Servlet Context에서도 스캔이 되며, Servlet Context 환경설정을 하기 위해 만든 Java Config 파일 또한 Root Context에서도 스캔이 되는 것이다. 바로 이점 때문에 위에서 Root Context를 초기화하는 과정에서 Servlet Context를 구성하는 Java Config 파일들이 스캔되어버리기 때문에 나중에 설명하게 될 RequestMappingHandlerMapping 클래스가 URL 매핑작업을 하게 되는 것이다. 또 Servlet Context를 초기화하는 과정에서 Root Context를 구성하는 Java Config 파일들이 스캔되어버리기 때문에 Root Context에서 작업하게 되는 임베디드 데이터베이스를 생성하는 식의 DataSource 생성 작업을 진행하는 것이다.

 

만약 Spring Context를 이렇게 Root Context, Servlet Context 이렇게 이분화시키지 말고 단일화 시켰다면 문제가 발생하지 않는다. 그러나 이렇게 이분화가 되었기 때문에 같은 기능을 하는 Bean들이 각각 두군데의 Context에 존재하게 되는 것이다. 이렇게 스텝이 꼬이는 증상을 막을려면 어떻게 해야 하는가? 다음의 방법을 따르도록 한다.

 

  • <context:component-scan> 태그의 use-default-filters 속성 또는 @ComponentScan 어노테이션의 useDefaultFilters 속성을 false로 지정한 뒤 Spring Bean으로 등록해야 할 특정 어노테이션이 붙은 클래스 또는 제외해야 할 특정 어노테이션이 붙은 클래스를 <context:component-scan> 태그의 <context:include-filter> 태그와 <context:exclude-filter> 태그 또는 @ComponentScan 어노테이션의 includeFilters 속성과 excludeFilters 속성을 통해 지정한다
  • 위의 방법이 번거롭다면 use-default-filters 속성 또는 useDefaultFilters 속성을 애초에 추가해서 지정하지 말고 <context:exclude-filter> 태그 또는 excludeFilters 속성에 @Configuration 어노테이션 클래스를 지정한다

 

use-default-filters 속성을 false로 설정하면 @Component, @Controller, @Service, @Repository 어노테이션이 붙은 클래스를 자동으로 Spring Bean으로 등록하지 않기 때문에 Spring Bean으로 등록해야 할 어노테이션이 붙은 클래스, 또는 Spring Bean 등록에서 제외시켜야 할 어노테이션이 붙은 클래스를 일일이 지정해야 한다. 여기에서 각 Context 별로 알맞게 @Controller, @Service, @Repository 어노테이션을 지정해주면 된다. 이게 까다롭다면 애초에 use-default-filters(어노테이션에서는 useDefaultFilters) 속성을 넣지 말고(넣지 않으면 default로 true 값이 될 것이다) 이 상태에서 <context:exclude-filters> 또는 excludeFilters 속성에 @Configuration 어노테이션을 명시함으로써 @Configuration 어노테이션이 붙은 클래스는 Spring Bean으로 등록되는 것을 제외시키도록 한다. 이렇게 얘기하면 @Configuration 어노테이션이 붙은 클래스는 Spring Bean으로 등록되지 않는것 처럼 보이지만 그러나 이 방법으로 설정해도 @Configuration 어노테이션이 붙은 클래스는 최종적으로는 Spring Bean으로 등록이 된다. 어 왜 등록이 되지? 등록되는 시점이 다르기 때문이다. 나중에 최종적인 설정 관련 글에서 설명하겠지만 @Configuration 어노테이션이 붙은 클래스는 component scan 과정에서 등록되는 것이 아니라 Context Loader 또는 DispatcherServlet에서 이를 등록하기 때문이다. 

 

위의 과정으로 component scan 방법을 바꾼뒤에 실행시켜 보면 로그가 다음과 같이 해당 컨텍스트 별로 잘 분리되어 나온다. 물론 에러도 발생하지 않는다.

 

INFO [org.springframework.web.context.ContextLoader] Root WebApplicationContext: initialization started

INFO [org.springframework.web.context.support.AnnotationConfigWebApplicationContext] Refreshing Root WebApplicationContext: startup date [Tue Mar 08 17:19:35 KST 2016]; root of context hierarchy

INFO [org.springframework.web.context.support.AnnotationConfigWebApplicationContext] Successfully resolved class for [egovframework.example.config.root.RootContext]

INFO [org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactory] Creating embedded database 'testdb'

INFO [org.springframework.jdbc.datasource.init.ScriptUtils] Executing SQL script from class path resource [db/sampledb.sql]

INFO [org.springframework.jdbc.datasource.init.ScriptUtils] Executed SQL script from class path resource [db/sampledb.sql] in 38 ms.

INFO [org.springframework.web.context.ContextLoader] Root WebApplicationContext: initialization completed in 3351 ms

3월 08, 2016 5:19:38 오후 org.apache.catalina.core.ApplicationContext log

정보: Initializing Spring FrameworkServlet 'action'

INFO [org.springframework.web.servlet.DispatcherServlet] FrameworkServlet 'action': initialization started

INFO [org.springframework.web.context.support.AnnotationConfigWebApplicationContext] Refreshing WebApplicationContext for namespace 'action-servlet': startup date [Tue Mar 08 17:19:38 KST 2016]; parent: Root WebApplicationContext

INFO [org.springframework.web.context.support.AnnotationConfigWebApplicationContext] Successfully resolved class for [egovframework.example.config.servlet.ServletContext]

INFO [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping] Mapped "{[/egovSampleList.do],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String egovframework.example.sample.web.EgovSampleController.selectSampleList(egovframework.example.sample.service.SampleDefaultVO,org.springframework.ui.ModelMap) throws java.lang.Exception

INFO [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping] Mapped "{[/updateSampleView.do],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String egovframework.example.sample.web.EgovSampleController.updateSampleView(java.lang.String,egovframework.example.sample.service.SampleDefaultVO,org.springframework.ui.Model) throws java.lang.Exception

INFO [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping] Mapped "{[/updateSample.do],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String egovframework.example.sample.web.EgovSampleController.updateSample(egovframework.example.sample.service.SampleDefaultVO,egovframework.example.sample.service.SampleVO,org.springframework.validation.BindingResult,org.springframework.ui.Model,org.springframework.web.bind.support.SessionStatus) throws java.lang.Exception

INFO [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping] Mapped "{[/deleteSample.do],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String egovframework.example.sample.web.EgovSampleController.deleteSample(egovframework.example.sample.service.SampleVO,egovframework.example.sample.service.SampleDefaultVO,org.springframework.web.bind.support.SessionStatus) throws java.lang.Exception

INFO [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping] Mapped "{[/addSample.do],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String egovframework.example.sample.web.EgovSampleController.addSampleView(egovframework.example.sample.service.SampleDefaultVO,org.springframework.ui.Model) throws java.lang.Exception

INFO [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping] Mapped "{[/addSample.do],methods=[POST],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String egovframework.example.sample.web.EgovSampleController.addSample(egovframework.example.sample.service.SampleDefaultVO,egovframework.example.sample.service.SampleVO,org.springframework.validation.BindingResult,org.springframework.ui.Model,org.springframework.web.bind.support.SessionStatus) throws java.lang.Exception

INFO [org.springframework.web.servlet.handler.SimpleUrlHandlerMapping] Mapped URL path [/cmmn/validator.do] onto handler of type [class org.springframework.web.servlet.mvc.ParameterizableViewController]

INFO [org.springframework.web.servlet.DispatcherServlet] FrameworkServlet 'action': initialization completed in 768 ms

 

위에 적어놓은 로그와 동일하게 진한 파란색 글씨로 강조된 내용과 진한 검은색 글씨로 밑줄을 그어 표현했으며 이 부분이 나타난 위치와 위에 적어놓은 로그와 비교해보면 다르다는 것을 알 수 있다.

 

<context:component-scan> 태그를 제외한 나머지 부분은 <bean> 태그를 사용하는 것이기 때문에 이를 Java Config로 바꾸는 부분은 XML 태그와 Java Config 방법을 한번씩 보여주는 것으로 마무리 짓겠다. 지금까지의 내용을 잘 따라왔다면 이렇게 변환하는 것에 대해 이해를 할 수 있으리라 생각한다.

 

● ReloadableResourceBundleMessageSource 클래스(XML)

<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
	<property name="basenames">
		<list>
			<value>classpath:/egovframework/message/message-common</value>
			<value>classpath:/egovframework/rte/fdl/idgnr/messages/idgnr</value>
			<value>classpath:/egovframework/rte/fdl/property/messages/properties</value>
		</list>
	</property>
	<property name="cacheSeconds">
		<value>60</value>
	</property>
</bean>

 

● ReloadableResourceBundleMessageSource 클래스(Java Config)

@Bean
public ReloadableResourceBundleMessageSource messageSource(){
	ReloadableResourceBundleMessageSource rrbms = new ReloadableResourceBundleMessageSource();
	rrbms.setBasenames(new String[]{"classpath:egovframework/message/message-common"
			, "classpath:egovframework/rte/fdl/idgnr/messages/idgnr"
			, "classpath:egovframework/rte/fdl/property/messages/properties"});
	rrbms.setCacheSeconds(60);
	return rrbms;
}

 

● LeaveaTrace 클래스(XML)

<bean id="leaveaTrace" class="egovframework.rte.fdl.cmmn.trace.LeaveaTrace">
	<property name="traceHandlerServices">
		<list>
			<ref bean="traceHandlerService" />
		</list>
	</property>
</bean>

 

● LeaveaTrace 클래스(Java Config)

@Bean
public LeaveaTrace leaveaTrace(DefaultTraceHandleManager traceHandlerService){
	LeaveaTrace leaveaTrace = new LeaveaTrace();
	leaveaTrace.setTraceHandlerServices(new TraceHandlerService[]{traceHandlerService});
	return leaveaTrace;
}

 

● DefaultTraceHandleManager 클래스(XML)

<bean id="traceHandlerService" class="egovframework.rte.fdl.cmmn.trace.manager.DefaultTraceHandleManager">
	<property name="reqExpMatcher">
		<ref bean="antPathMater" />
	</property>
	<property name="patterns">
		<list>
			<value>*</value>
		</list>
	</property>
	<property name="handlers">
		<list>
			<ref bean="defaultTraceHandler" />
		</list>
	</property>
</bean>

 

● DefaultTraceHandleManager 클래스(Java Config)

@Bean
public DefaultTraceHandleManager traceHandlerService(AntPathMatcher antPathMater, DefaultTraceHandler defaultTraceHandler){
	DefaultTraceHandleManager defaultTraceHandleManager = new DefaultTraceHandleManager();
	defaultTraceHandleManager.setReqExpMatcher(antPathMater);
	defaultTraceHandleManager.setPatterns(new String[]{"*"});
	defaultTraceHandleManager.setHandlers(new TraceHandler[]{defaultTraceHandler});
	return defaultTraceHandleManager;
}

 

● AntPathMatcher 클래스(XML)

<bean id="antPathMater" class="org.springframework.util.AntPathMatcher" />

 

● AntPathMatcher 클래스(Java)

@Bean
public AntPathMatcher antPathMater(){
	AntPathMatcher antPathMatcher = new AntPathMatcher();
	return antPathMatcher;
}

 

● DefaultTraceHandler 클래스(XML)

<bean id="defaultTraceHandler" class="egovframework.rte.fdl.cmmn.trace.handler.DefaultTraceHandler" />

 

● DefaultTraceHandler 클래스(Java Config)

@Bean
public DefaultTraceHandler defaultTraceHandler(){
	DefaultTraceHandler defaultTraceHandler = new DefaultTraceHandler();
	return defaultTraceHandler;
}

 

마지막으로 지금까지 설명한 모든 내용이 적용된 context-common.xml을 Java Config 방식으로 변환한 ContextCommon 클래스의 전체 소스를 올리는 것으로 이번 글을 마무리 짓겠다. 

 

package egovframework.example.config.root;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.util.AntPathMatcher;

import egovframework.rte.fdl.cmmn.trace.LeaveaTrace;
import egovframework.rte.fdl.cmmn.trace.handler.DefaultTraceHandler;
import egovframework.rte.fdl.cmmn.trace.handler.TraceHandler;
import egovframework.rte.fdl.cmmn.trace.manager.DefaultTraceHandleManager;
import egovframework.rte.fdl.cmmn.trace.manager.TraceHandlerService;

@Configuration
@ComponentScan(
	basePackages="egovframework",
	includeFilters={
		@ComponentScan.Filter(type=FilterType.ANNOTATION, value=Service.class)
		, @ComponentScan.Filter(type=FilterType.ANNOTATION, value=Repository.class)
	},
	excludeFilters={	
		@ComponentScan.Filter(type=FilterType.ANNOTATION, value=Controller.class)
		, @ComponentScan.Filter(type=FilterType.ANNOTATION, value=Configuration.class)
	}
)
public class ContextCommon {

	@Bean
	public ReloadableResourceBundleMessageSource messageSource(){
		ReloadableResourceBundleMessageSource rrbms = new ReloadableResourceBundleMessageSource();
		rrbms.setBasenames(new String[]{"classpath:egovframework/message/message-common"
				, "classpath:egovframework/rte/fdl/idgnr/messages/idgnr"
				, "classpath:egovframework/rte/fdl/property/messages/properties"});
		rrbms.setCacheSeconds(60);
		return rrbms;
	}

	@Bean
	public LeaveaTrace leaveaTrace(DefaultTraceHandleManager traceHandlerService){
		LeaveaTrace leaveaTrace = new LeaveaTrace();
		leaveaTrace.setTraceHandlerServices(new TraceHandlerService[]{traceHandlerService});
		return leaveaTrace;
	}

	@Bean
	public DefaultTraceHandleManager traceHandlerService(AntPathMatcher antPathMater, DefaultTraceHandler defaultTraceHandler){
		DefaultTraceHandleManager defaultTraceHandleManager = new DefaultTraceHandleManager();
		defaultTraceHandleManager.setReqExpMatcher(antPathMater);
		defaultTraceHandleManager.setPatterns(new String[]{"*"});
		defaultTraceHandleManager.setHandlers(new TraceHandler[]{defaultTraceHandler});
		return defaultTraceHandleManager;
	}
	
	@Bean
	public AntPathMatcher antPathMater(){
		AntPathMatcher antPathMatcher = new AntPathMatcher();
		return antPathMatcher;
	}
	
	@Bean
	public DefaultTraceHandler defaultTraceHandler(){
		DefaultTraceHandler defaultTraceHandler = new DefaultTraceHandler();
		return defaultTraceHandler;
	}
}

 

 

목     차

 1. 전자정부 프레임워크를 Java Config 방식으로 설정해보자(환경구축)

 2. 전자정부 프레임워크를 Java Config 방식으로 설정해보자(기초)

 3. 전자정부 프레임워크를 Java Config 방식으로 설정해보자(context-aspect.xml 변환)

 4. 전자정부 프레임워크를 Java Config 방식으로 설정해보자(context-common.xml 변환)

 5. 전자정부 프레임워크를 Java Config 방식으로 설정해보자(context-datasource.xml 변환)

 6. 전자정부 프레임워크를 Java Config 방식으로 설정해보자(context-sqlMap.xml, context-mapper.xml 변환)

 7. 전자정부 프레임워크를 Java Config 방식으로 설정해보자(context-transaction.xml 변환)

 8. 전자정부 프레임워크를 Java Config 방식으로 설정해보자(context-idgen.xml, context-properties.xml, context-validator.xml 변환)

 9. 작성된 Java Config 클래스 파일들을 실제 등록해보자(RootContext 클래스 제작)

 10. 전자정부 프레임워크를 Java Config 방식으로 설정해보자(dispatcher-servlet.xml 변환) - 1

 11. 전자정부 프레임워크를 Java Config 방식으로 설정해보자(dispatcher-servlet.xml 변환) - 2

 12. 전자정부 프레임워크를 Java Config 방식으로 설정해보자(dispatcher-servlet.xml 변환) - 3