개인적인 Spring Framework 구축을 위한 pom.xml 설정 및 dependency

(xml 안쓰고 Java Base 기반 설정 한다더니.. pom.xml을 사용하고 있다......

아직 Gradle을 사용하지 못하다보니 Meven 기반을 사용했다..)

이번만 xml을 사용하자....!!!


pom.xml 내부에 아래와 같은 코드를 적용해야 web.xml을 사용하지 않고

Java Base Configuration 기반으로 Spring을 구축할 수 있다.

pom.xml 설정

<bulid>

내부에 아래 코드 추가 / web.xml 사용 X, Java Base Config 사용을 위한 코드

<pluginManagement>

<plugins>

<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-war-plugin</artifactId>

<version>2.4</version>

<configuration>

<warSourceDirectory>src/main/webapp</warSourceDirectory>

<warName>적당한 이름 작성</warName>

<failOnMissingWebXml>false</faillOnMissionWebXml>

</configuration>

</plugin>

</plugins>

</pluginManagement>

</bulid>


dependency 리스트

commons-dbcp2

spring-tx

joda-time

joda-convert

jackson-core

jackson-annotation

jackson-databind

jackson-datatype-joda

jackson-dataformat-xml

jackson-module-jaxb-annotaions

gson

logback-core

logback-classic

log4jdbc-log4j2-jdbc4

janino

aspectjweaver

commons-io

httpclient

httpcore

javax.mail-api


+@ 입맛대로 추가하기

(위의 리스트는 내가 기본적으로 필요하다고 느끼는 목록)



'Spring Framework > Spring 설정' 카테고리의 다른 글

Spring Java Base Configuration  (0) 2016.09.02
Spring Framework Java Base 설정  (0) 2016.09.02

Spring Java Base Configuration 설정


Spring 설정 첫 블로그 내용에서 언급했다시피 나는 Spring설정을 Java Base 기반으로 구현할 것이다.


0. SpringFramework Version 설정

  - pom.xml -> 현재 Spring 재단에서 지정한 Current 으로 설정.

     또는 자신이 사용하고자 하는 버전으로 설정

  - 단, 3.0 버전 이상으로 설정해야 한다.

  - Srping 3.0이상 부터 Java Base 기반 설정을 제공한다.

    (http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html

    - 7.2.1 Configuration metadata 참조)


1. 웹 환경에서 부트스트랩할 WebInitailzer 생성(web.xml 의 Java base 버전)
  - Spring은 웹 환경에서 어플리케이션을 부트스트랩 할 수 있는 두 개의 컴포넌트를 제공한다.
  - 이 두 개의 컴포넌트는 어플리케이션 컨텍스트를 부트스트랩하고 설정한다. (DispatcherServlet 과 ContextLoaderListener)
  - implements WebApplicationInitializer 로 구현할 경우 DispatcherServlet과 ContextLoaderListener를 모두 구현해야한다. 
  - 하지만 AbstractAnnotationConfigDispatcherServletInitailizer를 상속받아 구현할 경우 처리가 간단해짐.

 - 해당 클래스는 내부적으로 startUp, registDispatcherServlet을 진행함. (고로 WebApplicationInitializer 을 구현할 필요가 없다)

  - AbstractAnnotationConfigDispatcherServletInitailizer 을 상속 받으면 @Override 되는 메소드는 아래와 같다.

    -> getRootConfigClasses()

      = root-config 관련 파일. (xml 기준 root-context.xml)

    -> getServletConfigClasses()

      = servlet-config 관련 파일. (xml 기준 servlert-context.xml)

    -> getServletMapping()

 - 추가적으로 Filter 셋팅 처리 진행.

  - Filter 셋팅 시 등록할 메소드 구현등...

샘플 소스 : WebAppInitializer.java

import javax.servlet.Filter;


import org.springframework.web.filter.CharacterEncodingFilter;

import org.springframework.web.filter.ShallowEtagHeaderFilter;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;


import com.restfulapi.config.appservlet.AppServletConfig;

import com.restfulapi.config.appservlet.ServletContextConfig;


/**

 * Spring Dispatcher Servlet Web Application Initializer (web.xml)

 * @author Choeng SungHyun <public27@naver.com>

 */

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {


    /*

    //WebApplicationInitializer를 직접구현하는 샘플. 대신에 AbstractAnnotationConfigDispatcherServletInitializer를 사용하기로 함

    public class SpringWebAppInitializer implements org.springframework.web.WebApplicationInitializer {

        @Override public void onStartup(ServletContext servletContext) throws ServletException {


            // Create the 'root' Spring application context

            AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();

            rootContext.register(RootContextConfig.class);


            // Manage the lifecycle of the root application context

            servletContext.addListener(new ContextLoaderListener(rootContext));


            // Create the dispatcher servlet's Spring application context

            AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();

            appContext.register(AppServletContextConfig.class);


            // Register and map the dispatcher servlet

            ServletRegistration.Dynamic dispatcher = servletContext.addServlet("appServlet", new DispatcherServlet(appContext));

            dispatcher.setLoadOnStartup(1); dispatcher.addMapping("/");

        }

    }

    */

    

    //AbstractAnnotationConfigDispatcherServletInitializer 내부에서 startUp, registDispatcherServlet을 진행함.

    

    @Override

    protected Class<?>[] getRootConfigClasses() {

        return new Class<?> [] {

            DataContextConfig.class,        //데이터 베이스 관련 Config

            RootContextConfig.class         //root-context.xml

            //, 구분으로 N개의 class가 들어갈 수 있음.

        };

    }


    @Override

    protected Class<?>[] getServletConfigClasses() {

        return new Class<?> [] {

            AppServletConfig.class,

            ServletContextConfig.class

            //, 구분으로 N개의 class가 들어갈 수 있음.

        };

    }


    @Override

    protected String[] getServletMappings() {

        return new String[] { "/" };

    }

    

    @Override

    protected Filter[] getServletFilters() {

        //Dispatcher Servlet 필터를 등록

        return new Filter[] {

                new ShallowEtagHeaderFilter(),

                getCharacterEncodingFilter()

            };

    }

    

    protected CharacterEncodingFilter getCharacterEncodingFilter() {

        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();

        characterEncodingFilter.setEncoding("UTF-8");

        characterEncodingFilter.setForceEncoding(true);

        return characterEncodingFilter;

    }

 

}


2. Config 파일 생성

  - getRootConfigClassess에 들어갈 설정. (RootContextConfig.java & DataBaseContextConfig.java)

  - root-context.xml => RootConfig.java 로 해당 설정 파일은 ApplicationContextAware를 Runtime 프로파일 설정 가능(propertyHolder)

샘플 소스 : RootContextConfig.java

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.BeansException;

import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;

import org.springframework.context.ApplicationContext;

import org.springframework.context.ApplicationContextAware;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.ComponentScan;

import org.springframework.context.annotation.Configuration;

import org.springframework.context.annotation.EnableAspectJAutoProxy;

import org.springframework.context.annotation.FilterType;

import org.springframework.context.annotation.PropertySource;

import org.springframework.stereotype.Controller;

import org.springframework.stereotype.Repository;

import org.springframework.stereotype.Service;

import org.springframework.web.bind.annotation.RestController;


import com.restfulapi.util.CommonUtils;


/**

 * root 설정용 클래스

 * root-context.xml 의 역할을 대신 하거나 보충한다.

 * @author Cheong SungHyun <public27@naver.com>

 */

@Configuration

@EnableAspectJAutoProxy

@ComponentScan(

        basePackages = "com.restfulapi",

        useDefaultFilters = false,

        excludeFilters = {

                @ComponentScan.Filter(type=FilterType.ANNOTATION, classes = Controller.class),

                @ComponentScan.Filter(type=FilterType.ANNOTATION, classes = RestController.class)

        },

        includeFilters = {

                @ComponentScan.Filter(type=FilterType.ANNOTATION, classes = Configuration.class),

                @ComponentScan.Filter(type=FilterType.ANNOTATION, classes = Repository.class),

                @ComponentScan.Filter(type=FilterType.ANNOTATION, classes = Service.class)

        }

)

@PropertySource("classpath:/runtimeEnv/config-${spring.profiles.active}.properties")

public class RootContextConfig implements ApplicationContextAware {

    protected static Logger logger = LoggerFactory.getLogger(RootContextConfig.class);


    protected ApplicationContext applicationContext;

    

    @Override

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

        this.applicationContext = applicationContext;

    }

    

    // Runtime 프로파일 설정 가능(propertyHolder)

    @Bean

    public PropertyPlaceholderConfigurer propertyPlaceholderConfigurer() {

        PropertyPlaceholderConfigurer configurer = new PropertyPlaceholderConfigurer();

        String activeProfile = CommonUtils.getActiveProfile();

        logger.debug("ACTIVE SPRING PROFILE => {}", activeProfile);

        configurer.setLocations(

                this.applicationContext.getResource("classpath:/runtimeEnv/config-" + activeProfile + ".properties")

        );

        return configurer;

    }

}

 

 

  - Data Base 설정 파일.

샘플 소스 : DataContextConfig.java

import javax.sql.DataSource;


import org.apache.commons.dbcp2.BasicDataSource;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.transaction.annotation.EnableTransactionManagement;


/**

 * Database Config

 * @author Cheong SungHyun <public27@naver.com>

 */

@Configuration

@EnableTransactionManagement

public class DataContextConfig {


    @Value("${jdbc.driverClass}")

    private String jdbcDriverClass;

    

    @Value("${jdbc.url}")

    private String jdbcUrl;

    

    @Value("${jdbc.username}")

    private String jdbcUsername;

    

    @Value("${jdbc.password}")

    private String jdbcPassword;

    

    @Bean(name = "dataSource")

    public DataSource getDataSource() {

        BasicDataSource dataSource = new BasicDataSource();

        

        //로그 출력

        dataSource.setDriverClassName(jdbcDriverClass);

        dataSource.setUrl(jdbcUrl);

        dataSource.setUsername(jdbcUsername);

        dataSource.setPassword(jdbcPassword);

        

        return dataSource;

    }

}


  - getServletConfigClasses에 들어갈 설정
  - servlet-context.xml => ServletContextConfig.java 로 작성.
    -> 해당 설정 파일은 WebMvcConfigurerAdapter를 상속받아서 작성.
       해당 설정 파일은 WebMvcConfigurerAdapter와 WebConfigurationSupport 두가지 클래스 중 하나를 상속받아 구현 가능.
       WebMvcConfigurerAdapter의 경우 Spring의 기본설정으로 구현 가능.
       WebConfigurationSupport의 경우 커스텀하게 구현이 가능.(단, 전면 재구성을 위해선 @EnableWebMvc 어노테이션 제거 후 구현)
    -> 기본 구현 + 리소스 핸들러 설정, ViewResolver 등록, Interceptor 설정, ConfigureContentNegotiation 설정, Formatter 설정, HttpMessageConverter 설정등 작성 가능하다.

샘플 소스 : ServletContextConfig.java


import java.nio.charset.Charset;

import java.util.List;

import java.util.concurrent.TimeUnit;


import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.context.annotation.ComponentScan;

import org.springframework.context.annotation.Configuration;

import org.springframework.context.annotation.EnableAspectJAutoProxy;

import org.springframework.context.annotation.FilterType;

import org.springframework.format.FormatterRegistry;

import org.springframework.http.CacheControl;

import org.springframework.http.MediaType;

import org.springframework.http.converter.ByteArrayHttpMessageConverter;

import org.springframework.http.converter.FormHttpMessageConverter;

import org.springframework.http.converter.HttpMessageConverter;

import org.springframework.http.converter.ResourceHttpMessageConverter;

import org.springframework.http.converter.StringHttpMessageConverter;

import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;

import org.springframework.stereotype.Controller;

import org.springframework.stereotype.Repository;

import org.springframework.stereotype.Service;

import org.springframework.web.bind.annotation.RestController;

import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;

import org.springframework.web.servlet.config.annotation.EnableWebMvc;

import org.springframework.web.servlet.config.annotation.InterceptorRegistry;

import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;

import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;

import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;

import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;

import org.springframework.web.servlet.view.json.MappingJackson2JsonView;

import org.springframework.web.servlet.view.xml.MappingJackson2XmlView;


import com.restfulapi.framework.converter.StringToJodaDateTimeConverter;

import com.restfulapi.framework.converter.StringToJodaLocalDateTimeConverter;

import com.restfulapi.util.ApiInterceptor;


/**

 * servlet 설정용 클래스

 * servlet-context.xml 의 역할을 대신 하거나 보충한다.

 * @author Cheong SungHyun <public27@naver.com>

 */

@Configuration

@EnableAspectJAutoProxy     //ASPECT 사용을 위함.

//extends WebMvcConfigurationSupport - 커스텀하게 구현

//extedns WebMvcConfigurerAdapter    - 기본설정으로 구현

@EnableWebMvc   //Spring 내부적으로 WebMvcConfigurationSupport 클래스를 환경설정 클래스로 등록하여 Spring MVC 환경을 구성하게됨.

//몇몇 구성에 대해서 커스터마이징을 처리. -> WebMvcConfigurerAdapter을 사용

//전면 재구성을 위해서는 EnableWebMvc어노테이션 제거 후 WebMvcConfigurationSupport를 상속받아 재구성

@ComponentScan (

        basePackages = "com.restfulapi"

       ,useDefaultFilters = false   // 기본 스캔 전략을 종료. 중요함.

       ,includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Controller.class),

                          @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = RestController.class)

       }  //Controller만 스캔

       ,excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class),

                          @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Repository.class),

                          @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Service.class)

       } //Configuration은 스캔대상에서 제외. Config는 명시적으로 지정

)

public class ServletContextConfig extends WebMvcConfigurerAdapter {

    

    protected static Logger logger = LoggerFactory.getLogger(ServletContextConfig.class);

    

    /**

     * 리소스 핸들러 설정

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

     */

    @Override

    public void addResourceHandlers(ResourceHandlerRegistry registry) {

        registry.addResourceHandler("/css/**").addResourceLocations("/css/")

                .setCacheControl(CacheControl.maxAge(10, TimeUnit.SECONDS).mustRevalidate());

        registry.addResourceHandler("/lib/**").addResourceLocations("/lib/")

                .setCacheControl(CacheControl.maxAge(10, TimeUnit.SECONDS).mustRevalidate());

        registry.addResourceHandler("/web/**").addResourceLocations("/webroot/")

                .setCacheControl(CacheControl.noStore());   //prevent cache

                //.setCachePeriod(0);     //0: prevent cache

    }

    

    /**

     * ViewResolver 등록

     */

    @Override

    public void configureViewResolvers(ViewResolverRegistry registry) {

        MappingJackson2JsonView jsonView = new MappingJackson2JsonView();

        jsonView.setPrettyPrint(true);

        

        MappingJackson2XmlView xmlView = new MappingJackson2XmlView();

        xmlView.setPrettyPrint(true);

        

        registry.enableContentNegotiation(

                jsonView

               ,xmlView

        );

        registry.jsp("/WEB-INF/views/", ".jsp");

        super.configureViewResolvers(registry);

    }

    

    /**

     * Interceptor 설정

     */

    @Override

    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(new ApiInterceptor());

    }

    

    /**

     * Configure content negotiation options.

     */

    @Override

    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {

        //Restful 한 서비스에서 APPLICATION_JSON_VALUE로 지정하여도 충분하나

        //IE9에서 JSON을 iframe transport로 전송 시 파일로 저장하려는 버그 발생으로 인해 아래와 같이 선언.

        MediaType API_PRODUCES_MEDIATYPE = MediaType.valueOf(MediaType.TEXT_HTML_VALUE+";charset=utf-8");

        

        configurer.ignoreAcceptHeader(false)                //HttpRequest Header의 Accept 무시 여부

                  .favorPathExtension(true)                    //프로퍼티 값을 보고 URL의 확장자에서 리턴 포맷을 결정 여부

                  .ignoreUnknownPathExtensions(true)       //모든 미디어 유형으로 해결할 수없는 경로 확장자를 가진 요청을 무시할지 여부

                  .favorParameter(true)                         //URL 호출 시 특정 파라미터로 리턴포맷 전달 허용 여부 ex)a.do?format=json

                  .mediaType("json", MediaType.APPLICATION_JSON_UTF8)   //UTF-8 기반 JSON 타입 선언

                  .defaultContentType(API_PRODUCES_MEDIATYPE);

    }

    

    /**

     * Add Converters and Formatters in addition to the ones registered by default.

     * This implementation is empty.

     */

    @Override

    public void addFormatters(FormatterRegistry registry) {

        super.addFormatters(registry);

        registry.addConverter(new StringToJodaDateTimeConverter());

        registry.addConverter(new StringToJodaLocalDateTimeConverter());

    }

    

    /**

     * {@link HttpMessageConverter}를 설정

     * 설정시 기본 컨버터 미사용. {@link #addDefaultHttpMessageConverters(List)}

     * {@link RequestMappingHandlerAdapter}, {@link ExceptionHandlerExceptionResolver}에서 사용.

     */

    @Override

    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {

        converters.add(new ByteArrayHttpMessageConverter());   //바이트 배열 읽기/쓰기. 모든 미디어 타입(*/*)에서 읽고 application/octect-stream으로 쓴다.

        converters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")){{

            setWriteAcceptCharset(true);

        }});                                                            //모든 미디어 타입(*/*)을 String으로 읽는다. text/plain에 대한 String을 쓴다.

        converters.add(new ResourceHttpMessageConverter()); //Resource를 읽고 쓰기.

        converters.add(new FormHttpMessageConverter());      //application/x-www-form-urlencoded. Form data is read from and written into a MultiValueMap.

        converters.add(new MappingJackson2HttpMessageConverter());

        //converters.add(new MappingJackson2XmlHttpMessageConverter());

    }

}


  - AppServletContextConfig.java 는 Spring 관련 컴포넌트가 아닌 서비스에서 소비하는 컴포넌트에 대한 설정파일로 작성.

  - ContorllerLoggingAspect, ObjectMapper 설정, Cache 설정(EHCache), JsonMaker설정, TCP Connection Beans, Factory 설정 등.

샘플 소스 : AppServletContextConfig.java


import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;


import com.restfulapi.framework.log.ControllerLoggingAspect;


/**

 * Spring 관련 컴포넌트가 아닌 서비스에서 소비하는 컴포넌트에 대한 설정

 * @author Cheong SungHyun <public27@naver.com>

 */

@Configuration

public class AppServletConfig {


    // Controller Logging Aspect

    @Bean

    public ControllerLoggingAspect controllerLoggingAspect() {

        return new ControllerLoggingAspect();

    }

    

//    /** MappingJackson ObjectMapper override */

//    @Bean(name="objectMapper")

//    public ObjectMapper objectMapper() {

//        return null;

//    }

//    

//    /** MappingJackson XmlMapper override */

//    @Bean(name="xmlMapper")

//    public ObjectMapper xmlMapper() {

//        return null;

//    }

//    


    // Cache 설정 가능 (ehCache)

    // JsonMaker 설정 가능(Gson등)

    // TCP Connection beans, factory 설정 가능 (커넥션 풀 사이즈 설정, 연결풀 팩토리 설정, 기본 연결 정보 설정, 연결풀 객체 생성 등)   

 

}

 


위 샘플 소스는 참고용 소스이며

중간 중간 유틸성 함수들의 경우 따로 구현해야 한다.

즉, 해당 블로그의 샘플 소스는 완벽하지 않다는 것이다.

(물론 몇 유틸성 함수만 구현하면 정상 작동하는 소스이다.)


샘플 소스는 참고만 하고 자신만의 설정을 구현해야 한다.

오타가 있을 수 있으며 혹 잘못 작성된 내용도 존재할 수 있다.

내 블로그는 완벽하지 못하다.


현재 한국에서 개발 및 운영되는 Spring Framework 기반의 프로젝트는

대부분 XML 설정 기반인 것으로 알고 있다.

(물론 아닐 수도 있지만...)


전자정부프레임워크를 필두로 XML 기반이 대한민국을 뒤덮고 있는 것으로 알고 있다.

내 블로그에서는 XML기반 설정에서 벗어나 Java Base 기반 설정으로 Spring 환경을 구성하는 방식을 추구한다.


Spring의 단점 중 하나는 설정의 어려움이라고 생각하며

이 어려움에는 XML 기반 설정이 큰 몫을 차지한다고 생각한다.

(이 어려움이 Spring을 시작하는 개발자들에게 첫 고비라고 생각한다.)


Java Base 기반 설정은 XML 기반 설정보다 가독성도 뛰어나며 유지보수가 유리하다는 장점이 있다.

보통 Spring을 시작하기 전에 기본 Java 문법은 익히고 왔을테니 아무래도 거부감이 덜하다.

이러한 Java Base 기반 설정은 Spring의 단점을 해결하기 위한 하나의 방법이 될 수 있다.


Spring 재단에서도 이러한 단점을 알고 있었고

이 단점을 해결하기 위해서 일까

Spring 재단에서 역시 XML설정을 최소화하고 Java Base 설정에 힘을 실어주고 있다.

(Spring Boot 프로젝트는 default -> Java Base 기반 설정으로 알고 있다.)


Spring 재단에서도 XML -> Java Base 기반으로 변화하고 있기 떄문에

나 역시 XML 사용을 자제하고 Java Base 기반으로 Spring Framework를 구축하고

Spring에 대한 API들을 하나씩 정복해나가려고 한다.

+ Recent posts