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 설정 가능 (커넥션 풀 사이즈 설정, 연결풀 팩토리 설정, 기본 연결 정보 설정, 연결풀 객체 생성 등)   

 

}

 


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

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

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

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


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

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

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


+ Recent posts