본문 바로가기
Programming/>> Spring

[Spring] Spring기반 웹 사이트 템플릿 만들기 - 1. Xml설정을 Java Config로 변경

by 니키ᕕ( ᐛ )ᕗ 2017. 7. 9.

기존 프로젝트 소스를 이어서 작업하게 되는데, 기존에는 xml설정으로 했다면 이번에는 Java config로 context설정을 하려고 한다. 물론 소스는 두 방식 다 작성에서 올릴 예정.  

 

일단 내가 context 설정들이 위치하는 디렉토리는 아래와 같이 두었다

 

기존 xml 설정때는 web.xml에서 context 설정 루트를 아래와 같이 지정했었다.

<!-- Xml Config -->
<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>classpath:spring/context-*.xml</param-value>
</context-param>
<!--/ Xml Config -->
<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
	<servlet-name>appServlet</servlet-name>
	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
	<!-- Xml Config -->
	<init-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>spring/context-servlet.xml</param-value>
	</init-param>
	<!--/ Xml Config -->
	<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
	<servlet-name>appServlet</servlet-name>
	<url-pattern>/</url-pattern>
</servlet-mapping>

java config의 방식은 XmlWebApplicationContext 대신 AnnotationConfigWebApplicationContext를 사용하는 ContextLoaderListener를 설정하고 설정 위치는 반드시 콤마나 공백을 구분자로 사용하는 하나 이상의 정규화된 @Configuration 클래스들로 구성되어야 한다. 정규화된 팩키지는 컴포넌트 스캔으로 지정될 수도 있다.  나는 Component 스캔을 할 수 있도록 패키지명으로 지정했다.

<!-- Java Based Config -->
<context-param>
	<param-name>contextClass</param-name>
	<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>spring.web.config.common</param-value>
</context-param>
<!--/ Java Based Config -->
<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
	<servlet-name>appServlet</servlet-name>
	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
	<!-- Java Based Config -->
	<init-param>
		<param-name>contextClass</param-name>
		<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
	</init-param>
	<init-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>spring.web.config.servlet.ServletConfig</param-value>
	</init-param>
	<!--/ Java Based Config -->
	<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
	<servlet-name>appServlet</servlet-name>
	<url-pattern>/</url-pattern>
</servlet-mapping>

 그리고 Context와 Servlet의 위치를 분리했다. 나는 이 둘을 분리한다는 개념을 전자정부프레임워크를 사용하면서 알게 되었다. 사실 그 전에는 web.xml에서 xml 위치를 변경하는 법을 몰랐기 때문에 Spring MVC project starter에서 분리해주는 대로 사용하고 있었다. 

 

 

1. 원래 web.xml에는 ContextLoaderListener(<listener>로 등록)와 DispatcherServlet(<servlet>으로 등록) 2개가 있고, 각각 contextConfigLocation에 따라 설정파일이 있는데, 이 2개가 개별적인 ApplicationContext(Spring의 IoC Container)로 기동된다. 정확하게는 listener가 root WebApplicationContext이가 servlet이 이 root WebApplicationContext를 상속받게된다. 여기서 상속이라는 개념은 class의 extends가 아닌, 상위에 bean들을 참조할 수 있게 parent로 지정되는 방식이다. 그렇기에 이를 분리하지 않고 같은 contextConfigLocation을 사용하게 되면 bean이 중복으로 등록이 될 수 있으므로 이를 방지한다. 

 

2. Spring MVC에서는 DispatcherServlet이 Controller에 클라이언트의 Request요청을 보내고 Controller는 Service단에서 비즈니스 로직을 처리하고 그 결과인 Model과 View를 DispatcherServlet에 리턴한다. 또한, Controller에 Service나 DAO를 Injection하여 Controller에서 이 두 가지를 사용할 수는 있지만 Service와 DAO에는 Controller를 Injection하지 않는 구조로 되어있다. 

 

2는 내가 겉핥기로 알고 있는 사실이고 1은 이 글을 작성하다가 찾은 내용인데, 결국 2의 구조로 Spring MVC가 돌아가는 것은 Spring의 내부적으로 1의 내용으로 되어있기 때문인 것 같다. 왜 분리해서 쓰는지의 명확한 문장을 찾아서 만들어보고 싶었는데 정리가 잘 되지 않아서 이건 일단 여기까지.

 

 

일단 Servlet Context쪽부터 보자면. SpringMVC를 사용하기 위해 설정 클래스 파일에 @EnableWebMvc 어노테이션을 달아주고 WebMvcConfigurerAdapter를 상속한다.

 

1. Component Scan

 Servlet Context에서는 @Controller만 스캔하고 @Service와 @Repository는 스캔 대상에서 제외시킨다.

- Xml

<!-- - The controllers are autodetected POJOs labeled with the @Controller annotation. -->
<context:component-scan base-package="spring.web.app">
	<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>

- Java Config

@Configuration @EnableWebMvc @ComponentScan( basePackages = "spring.web.app", includeFilters = { @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = Controller.class) }, excludeFilters = { @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = Service.class), @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = Repository.class) } ) public class ServletConfig extends WebMvcConfigurerAdapter { }

 

2. Resource mapping

 js, css, img같은 리소스 파일의 URL Mapping

- Xml

<resources mapping="/resources/**" location="/resources/" />

- Java Config

/* Resources */
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
	registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}

 

 

3. Tiles

 Tiles에 정리해놓은 것은 이 포스트를 참조하면 되겠다. ViewResolver의 숫자는 우선순위를 뜻하고 어떤 방법으로 View를 구성할건지에 따라 달라진다. 일반적으로 BeanNameViewResolver를 사용하는 원인인 JsonView로 해놓으면 JsonView bean을 찾아 View를 생성하고 여기에서 없으면 UrlBasedViewResolver로 가서 Tiles 규칙에 따른 View를 생성한다. Tiles 규칙에도 해당되지 않는 이름이라면 InternalResourceViewResolver에 설정되어있는 경로로 가서 jsp파일을 찾고 이 마저도 없으면 View가 없어서 Exception이 발생한다. 404가 뜨던가 503이 뜨던가... 

- Xml

<beans:bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
	<beans:property name="order" value="0" />
</beans:bean>

<beans:bean class="org.springframework.web.servlet.view.UrlBasedViewResolver">
	<beans:property name="viewClass" value="org.springframework.web.servlet.view.tiles3.TilesView"/>
	<beans:property name="order" value="1" />
</beans:bean>

<beans:bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
	<beans:property name="definitions">
		<beans:value>/WEB-INF/tiles/tiles.xml</beans:value>
	</beans:property>
</beans:bean>

<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
	<beans:property name="prefix" value="/WEB-INF/views/" />
	<beans:property name="suffix" value=".jsp" />
	<beans:property name="order" value="2" />
</beans:bean>

- Java Config

/* ViewResolver*/
@Bean
public BeanNameViewResolver beanNameViewResolver() {
	BeanNameViewResolver resolver = new BeanNameViewResolver();
	resolver.setOrder(0);
	return resolver;
}
	/* Tiles */
@Bean
public UrlBasedViewResolver urlBasedViewResolver() {
	UrlBasedViewResolver resolver = new UrlBasedViewResolver();
	resolver.setViewClass(TilesView.class);
	resolver.setOrder(1);
	return resolver;
}
@Bean
public TilesConfigurer tilesConfigurer() {
	TilesConfigurer tilesConfigurer = new TilesConfigurer();
	tilesConfigurer.setDefinitions(new String[] { "/WEB-INF/tiles/tiles.xml" });
	tilesConfigurer.setCheckRefresh(true);
	return tilesConfigurer;
}
	/* Tiles */
@Bean
public InternalResourceViewResolver internalResourceViewResolver() {
	InternalResourceViewResolver resolver = new InternalResourceViewResolver();
	resolver.setPrefix("/WEB-INF/views/");
	resolver.setSuffix(".jsp");
	resolver.setOrder(2);
	return resolver;
}

 

Common Context

 서비스 로직에서는 @Controller는 스캔하지 않고 @Service와 @Repository를 스캔한다.

- Xml

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

- Java Config

@Configuration
@ComponentScan(
	basePackages = "spring.web.app", 
	excludeFilters = {
		@Filter(type = FilterType.ASSIGNABLE_TYPE, classes = Controller.class)
	}, 
	includeFilters = { 
		@Filter(type = FilterType.ASSIGNABLE_TYPE, classes = Service.class),
		@Filter(type = FilterType.ASSIGNABLE_TYPE, classes = Repository.class)
	}
)
public class CommonConfig {
	
}

 

1. Messagesource

다국어 지원이나 validation용 메세지를 한곳에서 관리하기 편하게하려고 사용. 메세지 관리는 *.properties 파일을 사용한다.

- Xml

<!-- MessageSource -->
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
	<property name="basename" value="classpath:messages/message" />
	<property name="defaultEncoding" value="UTF-8" />
</bean>

- Java Config

@Bean
public ReloadableResourceBundleMessageSource messageSource() {
	ReloadableResourceBundleMessageSource msg = new ReloadableResourceBundleMessageSource();
	msg.setBasename("classpath:messages/message");
	msg.setDefaultEncoding("UTF-8");
	return msg;
}

 

2. Properties

 properties 파일로부터 값을 읽어와 설정값을 지정할 때 사용. xml방식에서는 한번에 list로 적어주는데 Java Config는 bean을 올린 후, 해당 properties파일 경로를 IMPORT하는 방식으로 어노테이션값을 지정한다.

- Xml

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
	<property name="locations">
		<list>
			<value>classpath:properties/database.properties</value>
		</list>
	</property>
</bean>

- Java Config

@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
    return new PropertySourcesPlaceholderConfigurer();
}
@PropertySource("classpath:properties/database.properties")
public class DataSourceConfig {}

 

3. Datasource

- 이건 다음 포스팅으로

 

 

Context 분리개념

- https://blog.outsider.ne.kr/785

- https://open.egovframe.go.kr/cop/bbs/selectBoardArticle.do?bbsId=BBSMSTR_000000000013&nttId=8427

댓글