AES256Crypto 소스

 - AES256 암복호화기 샘플 소스


import java.nio.charset.Charset;

import java.util.Arrays;


import javax.crypto.Cipher;

import javax.crypto.spec.IvParameterSpec;

import javax.crypto.spec.SecretKeySpec;


import org.apache.commons.codec.binary.Base64;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.util.StringUtils;


/**

 * 암호화 처리기 (AES256)

 */

public class AES256Crypto {

Logger logger = LoggerFactory.getLogger(this.getClass());


//DEFAULTS

private static final Charset UTF8 = Charset.forName("UTF-8");

private static final String DEFAULT_CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";

private static byte[] DEFAULT_IV = new byte[16];

static {

Arrays.fill(DEFAULT_IV, (byte)0);

}


//Cipher Init Parameters

String cipherAlgorithm = null;

String secretKey;

byte[] iv;


//Cipher

SecretKeySpec secretSpec = null;

IvParameterSpec ivSpec = null;


public String getSecretKey() {

return secretKey;

}

public void setSecretKey(String secretKey) {

this.secretKey = secretKey;

}


public AES256Crypto() {

this(DEFAULT_CIPHER_ALGORITHM, "", DEFAULT_IV);

}


public AES256Crypto(String secretKey) {

this(DEFAULT_CIPHER_ALGORITHM, secretKey, DEFAULT_IV);

}


public AES256Crypto(String cipherAlgorithm, String secretKey, byte[] iv) {

this.secretKey = secretKey;

this.cipherAlgorithm = cipherAlgorithm;

this.iv = iv;

}


public Cipher getEncryptChiper() throws Exception {

try {

//Init Secret

this.secretSpec = new SecretKeySpec(this.secretKey.getBytes(), "AES");

//Init IV

this.ivSpec = new IvParameterSpec(this.iv);

Cipher cipher = Cipher.getInstance(this.cipherAlgorithm);

cipher.init(Cipher.ENCRYPT_MODE, this.secretSpec, this.ivSpec);

return cipher;

} catch (Exception ex) {

logger.warn("getEncryptChiper FAILED. cipherAlgorithm:{}, secretKey:{}, iv:{} - EXCEPTION: {} - {}",

cipherAlgorithm, secretKey, iv,

ex.getClass().getName(), ex.getMessage());

throw ex;

}

}

public Cipher getDecryptChiper() throws Exception {

try {

//Init Secret

this.secretSpec = new SecretKeySpec(this.secretKey.getBytes(), "AES");

//Init IV

this.ivSpec = new IvParameterSpec(this.iv);

Cipher cipher = Cipher.getInstance(this.cipherAlgorithm);

cipher.init(Cipher.DECRYPT_MODE, this.secretSpec, this.ivSpec);

return cipher;

} catch (Exception ex) {

logger.warn("getDecryptChiper FAILED. cipherAlgorithm:{}, secretKey:{}, iv:{} - EXCEPTION: {} - {}",

cipherAlgorithm, secretKey, iv,

ex.getClass().getName(), ex.getMessage());

throw ex;

}

}


/** 주어진 문자열을 기본(UTF-8)인코딩으로 암호화된 바이트배열 획득 */

public byte[] encryptString(String plainString) {

return encryptString(plainString, UTF8);

}

/** 주어진 문자열을 주어진 인코딩으로 암호화된 바이트배열 획득 */

public byte[] encryptString(String plainString, Charset charset) {

if(StringUtils.isEmpty(plainString)) {

logger.warn("encryptString FAILED. -> plainString is empty.");

return null;

}

byte[] cipherBytes = null;

try {

Cipher cipher = getEncryptChiper();

byte[] plainBytes = plainString.getBytes(charset);

cipherBytes = cipher.doFinal(plainBytes);

} catch (Exception ex) {

logger.warn("encryptString FAILED. plainString: {} - EXCEPTION: {} - {}", plainString,

ex.getClass().getName(), ex.getMessage());

}

return cipherBytes;

}


/** 주어진 문자열을 기본(UTF-8)인코딩으로 암호화된 바이트배열의 Base64인코딩 문자열 획득 */

public String encryptStringToBase64(String plainString) {

return encryptStringToBase64(plainString, UTF8);

}

/** 주어진 문자열을 주어진 인코딩으로 암호화된 바이트배열의 Base64인코딩 문자열 획득 */

public String encryptStringToBase64(String plainString, Charset charset) {

byte[] cipherBytes = encryptString(plainString, charset);

logger.trace("cipherBytes: " + Arrays.toString(cipherBytes));

byte[] cipherBytesBase64Encoded = Base64.encodeBase64(cipherBytes);

logger.trace("cipherBytesBase64Encoded: " + Arrays.toString(cipherBytesBase64Encoded));

return new String(cipherBytesBase64Encoded, charset);

}


/** 주어진 암호화된 바이트배열을 복호화하여 기본(UTF-8)인코딩의 문자열로 획득 */

public String decryptBytes(byte[] chiperBytes) {

return decryptBytes(chiperBytes, UTF8);

}

/** 주어진 암호화된 바이트배열을 복호화하여 주어진 인코딩의 문자열로 획득 */

public String decryptBytes(byte[] chiperBytes, Charset charset) {

if(chiperBytes == null || chiperBytes.length == 0) {

logger.warn("decryptBytes FAILED. -> chiperBytes is empty.");

return null;

}

String plainString = null;

try {

Cipher cipher = getDecryptChiper();

byte[] plainBytes = cipher.doFinal(chiperBytes);

plainString = new String(plainBytes, charset);

} catch (Exception ex) {

logger.warn("decryptBytes FAILED. - EXCEPTION: {} - {}", ex.getClass().getName(), ex.getMessage());

}

return plainString;

}


/** 주어진 암호화된 바이트배열을 Base64Encoding된 문자열을 Base64Decoding과 복호화하여 기본(UTF-8)인코딩 문자열로 획득 */

public String decryptBase64String(String cipherBase64String) {

if(StringUtils.isEmpty(cipherBase64String)) {

logger.warn("decryptBase64String FAILED -> cipherBase64String is empty.");

return null;

}

byte[] cipherBytes = Base64.decodeBase64(cipherBase64String);

String plainString = decryptBytes(cipherBytes);

plainString = StringUtils.trimTrailingWhitespace(plainString);

return plainString;

}

}



- Logback이란?


  log4j는 Ceki Gulcu라는 개발자가 최초로 만듦.

  Ceki Gulcu가 log4j를 통해서 더 나은 Logger에 대한 깊은 프로젝트를 시작하여 탄생한 것이 SLF4J와 Logback 이다.

  SLF4J는 로깅 구현제라기 보다는 Logging Facaed.

    -> SLF4J의 API를 사용하면, 구현체의 종류와 상관없이 "일관된 로깅 코드"가 작성가능 하며 여러 Logging 구현체들을 변경이 최소한의 수정으로 가능하다.


  http://logback.qos.ch/ 홈페이지 처음 이런 말을 한다.

  Logback is intended as a successor to the popular log4j project, picking up where log4j leaves off.

  즉. logback은 log4j 후계자다.



- 왜 Logback을 사용해야 하는가?

(https://beyondj2ee.wordpress.com/2012/11/09/logback-%EC%82%AC%EC%9A%A9%ED%95%B4%EC%95%BC-%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0-reasons-to-prefer-logback-over-log4j/ 내용 참고하여 재 작성)


  1) faster implementation

    -> logback은 오랫동안 검증된 log4j 아키텍쳐를 기반으로 재작성 됨.

    -> 특히 성능은 약 10배정도 개선, 메모리 점유도 보다 적게 사용.


  2) extensive battery of tests

    -> log4j시절 부터 오랜기간 동안 광범위한 테스트가 진행되었고 logback은 더 많은 테스트의 반복과정을 수행.

   - 높은 퀄리티 보장.


  3) logback-classic speaks SLF4J natively

    -> logback은 3가지 컴포넌트로 구성

      1) logback-core :  핵심 코어 컴포넌트

      2) logback-classic : slf4j에서 사용가능하도록 만든 플러그인 컴포넌트

      3) logback-access :  HTTP 요청에 대한 강력한 디버깅 기능 제공하는 컴포넌트


  4) extensive documentation

    -> 레퍼런스 매뉴얼 제공. 지속적이면서 상세한 메뉴얼 제공


  5) configuration files in XML or Groovy

    -> logging 설정에 대한 syntax는 기본적으로 XML 형태

    -> Grooby syntax도 지원.


  6) automatic reloading of configuration files


  7) graceful recovery form I/O failures


  8) automatic removal of old log archives


  9) automatic compression of archived log files


  10) prudent mode

    -> 하나의 서버에서 "다수의 JVM"이 기동 중일 경우 "하나의 파일"에 로그 수집하고자 할때 사용하는 기능.


  11) Lillith

    -> 현재 발생하는 로그 이벤트에 대한 상태 정보를 볼 수 있도록 지원하는 뷰어


  12) conditional processing of configuration files.


  13) filters

    -> 로깅을 남길지, 말지 행들링 하는 기능.


  14) sifting Appender

    -> filter기능과 유사하지만 다른 기능 제공.

    -> 로그 파일을 특정 주제별로 분류

        (ex. HTTP Session별, SQL 명령어 별, PG결제 별 등등)


  15) stack traces with packaging data

    -> Exception 발생 시 참조했던 외부 라이브러리 버전 출력 기능.


  16) logback-access, i.e HTTP-access logging wih brains, is an integral part of logback

    -> HTTP 디버깅 제공.



Spring Framework 프로젝트 생성 시

slf4j, log4j dependency가 자동 적용되어 있는 것 같다.

해당 라이브러리가 적용된 것을 확인 후 아래와 같이 진행.

(logback-core.jar, logback-classic, log4jdbc-log4j2-jdbc4.jar 등 적용

http://logback.qos.ch/ 참고)



아래와 같이 Logback 적용


1. src/main/resources

  -> logback.xml 추가

      (환경 별 logback 설정 파일을 호출하기 위한 proxy 파일이라고 보면 됨.)

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE xml>

<configuration scan="true" scanPeriod="5 seconds" debug="true">

<if condition='isDefined("spring.profiles.active")'>

<then>

<include resource="runtimeEnv/logback-${spring.profiles.active}.xml" />

</then>

</if>

</configuration> 



2. src/main/java/runtimeEnv 패키지 생성

  - logback.shared.xml 생성

  - 공통 환경 변수 설정, Appender 설정.

    (실제 logback 환경별 설정 - appender설정, rolling 설정 등)

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE xml>

<included scan="true" scanPeriod="2 seconds">


<!-- 공통 환경변수 설정 -->

<property name="APP_NAME" value="프로젝트 이름"/>

<property name="LOG_FILENAME" value="로그 파일 명" />

<property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} %-5level: %c{50}.%method\\(\\) (%file:%line\\) - %msg%n" />

<property name="LOG_CHARSET" value="UTF-8" />


<!-- Appenders 설정 -->

<!-- CONSOLE: 콘솔로 출력 -->

<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">

<encoder>

<pattern>${APP_NAME} ${LOG_PATTERN}</pattern>

<charset>${LOG_CHARSET}</charset>

</encoder>

</appender>


<!-- FILE: CURRENT 현재 인스턴스 시작부터 로깅 (개발용. 실서버에서는 일자별로깅의 오늘자 로그 조회) -->

<appender name="FILE" class="ch.qos.logback.core.FileAppender">

<file>${FILE_LOG_FOLDER}/${LOG_FILENAME}.CURRENT.log</file>

<append>false</append>

<encoder>

<pattern>${LOG_PATTERN}</pattern>

<charset>${LOG_CHARSET}</charset>

</encoder>

</appender>


<!-- ROLLING: 일자별 로깅 (지난파일 압축하여 N개유지) -->

<appender name="ROLLING" class="ch.qos.logback.core.rolling.RollingFileAppender">

<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">

<fileNamePattern>${FILE_LOG_FOLDER}/${LOG_FILENAME}.%d{yyyyMMdd}.log.zip</fileNamePattern>

<maxHistory>31</maxHistory>

<cleanHistoryOnStart>true</cleanHistoryOnStart>

</rollingPolicy>

<append>true</append>

<encoder>

<pattern>${LOG_PATTERN}</pattern>

<charset>${LOG_CHARSET}</charset>

</encoder>

</appender>


<!-- ROLLING: 일자별 로깅 - WARN LEVEL Threshold (지난파일 압축하여 N개유지) -->

<appender name="ROLLING_WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">

<filter class="ch.qos.logback.classic.filter.ThresholdFilter">

<level>WARN</level>

</filter>

<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">

<fileNamePattern>${FILE_LOG_FOLDER}/${LOG_FILENAME}.%d{yyyyMMdd}.WARN.log.zip</fileNamePattern>

<maxHistory>31</maxHistory>

<cleanHistoryOnStart>true</cleanHistoryOnStart>

</rollingPolicy>

<append>true</append>

<encoder>

<pattern>${LOG_PATTERN}</pattern>

<charset>${LOG_CHARSET}</charset>

</encoder>

</appender>


</included>



3. logbak-환경.xml

  - 환경은 local, dev, stage, qa, real, production 등 입맛대로 사용.

  - 단, spring.profiles.active 값과 일치해야함.

    (개별 logging 정책, log 레벨 설정 등)

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE xml>

<included scan="true" scanPeriod="2 seconds">


<!-- 환경변수 설정 -->

<property name="FILE_LOG_FOLDER" value="로그 저장 경로" />


<!-- 공용파일 -->

<include resource="runtimeEnv/logback.shared.xml" />


<!-- 개별 LOGGING 정책 설정 -->

<logger name="XXXXXXXX" level="DEBUG" />


<logger name="framework.log.ControllerLoggingAspect" level="DEBUG" />


<logger name="jdbc.sqlonly" level="ERROR" />

<logger name="jdbc.sqltiming" level="INFO" />

<logger name="jdbc.audit" level="ERROR" />

<logger name="jdbc.resultset" level="ERROR" />

<logger name="jdbc.resultsettable" level="INFO" />

<logger name="jdbc.connection" level="ERROR" />


<logger name="org.mybatis" level="INFO" />


    <logger name="org.apache.commons.digester" level="INFO"/>

<logger name="org.apache.http" level="INFO" />


<logger name="org.springframework" level="INFO" />


<!-- ROOT LEVEL LOGGING 정책 설정 -->

<root level="DEBUG">

<appender-ref ref="CONSOLE" />

<appender-ref ref="FILE" />

<appender-ref ref="ROLLING" />

<appender-ref ref="ROLLING_WARN" />

</root>


</included>



위 3번까지의 설정 방법은 나만의 방법이며

모든 개발자들에게 통용되는 방법은 아니다.

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

 

}

 


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

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

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

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


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

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

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


아직 Git사용이 가능해지면

내가 구현할 Spring Framework 기본 구축 환경 소스를 공개할 예정이다.

(아직 Git을 사용한 적이 없고, 그리고 소스 역시 아직 공개할 만한 수준이 아니다....)


소스 공개가 언제가 될지 모르지만

중간에 멈추지 않고 블로그 활동을 지속하길 바라고 있다.


일단 간단하게 RestFul한 심플한 환경 구축을 해놨지만

다시 블로그를 작성하면서 새롭게 만들어볼 예정이다.


현재 한국에서 개발 및 운영되는 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