자바 컴파일 버전 Exception

 

컴파일된 앱(ex: lib/jar)이 실행하는 환경의 jdk 버전보다 높은 경우 아래와 같은 exception이 발생. 

Exception in thread "main" java.lang.UnsupportedClassVersionError: ~ : Unsupported major.minor version 52.0

여기서 version 52.0 은 java 8버전을 의미(1.8)

 

 

참고 :

J2SE 8   = Version 52

J2SE 7   = Version 51

J2SE 6.0 = Version 50

J2SE 5.0 = Version 49

JDK  1.4 = Version 48

JDK  1.3 = Version 47

JDK  1.2 = Version 46

JDK  1.1 = Version 45



해결 방법 :

jar/lib 을 본인(서버)의 실행환경(jdk버전)에 맞게 버전을 낮춰주거나,

본인(서버)의 실행환경을 사용할 jar/lib에 맞춰주어야 한다. 

보통 전자의 방법으로 해결..

반응형

스프링프레임워크 log4j2 설정 : Spring Framework + log4j 2 + slf4j + jboss/wildfly(ver 9)

 

*  jdk : 1.7

*  framework : spring

*  was : jboss/wildfly 9

 

#기본개념

slf4j 는 interface, 

log4j 는 구현체

 

slf4j 사용시 소스에서 interface , imple 사용하듯 구현체만 바꾸면 돼서 

log4j 를 나중에 걷어내고 다른 log lib을 사용해도 소스레벨에서 수정할게 없음. 그래서 slf4j 사용.

 

log4j 2는 log4j 1랑 다른거 없이 log4j 2.x 대 버전을 의미.

(log4j 1.x 는 log4j 라고부르고 log4j 2 는 log4j 2.x 를 말하는 듯)

* log4j2 사용시 slf4j는 1.7.x 이상 사용

 

참고 : https://goddaehee.tistory.com/45

(위 고수님의 블로그에 log4j, slf4j, logback 에 관한 내용이 상세히 기술되어 있음)

 

[ # log4j 2 및 slf4j maven dependency 설정 ]

log4j-slf4j-impl jar(actifactId) 사용시 1.7.x 이하 version 사용

log4j-slf4j18-impl jar(actifactId) 사용시 1.8.x 이상 version 사용

참고 : https://logging.apache.org/log4j/2.x/log4j-slf4j-impl/index.html

 

[ pom.xml ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<modelVersion>4.0.0</modelVersion>
    <groupId>com.sample</groupId>
    <name>SAMPLE</name>
    <packaging>war</packaging>
    <version>0.1</version>
    <properties>
        <!-- 생략 -->
        <org.slf4j-version>1.7.5</org.slf4j-version>
        <org.log4j-version>2.9.0</org.log4j-version>
    </properties>
 
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${org.springframework-version}</version>
    <exclusions>
        <!-- Exclude Commons Logging in favor of slf4j -->
        <exclusion>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
         </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>${org.slf4j-version}</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>${org.log4j-version}</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>${org.log4j-version}</version>
    <exclusions>
         <!-- Exclude Commons Logging in favor of log4j-core -->
         <exclusion>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
         </exclusion>
    </exclusions>
</dependency>
cs

* spring 의 경우 default 로 jcl(commons-logging)사용하므로 이를 slf4j 로 대체하기 위해 jcl-over-slf4j 사용

* jcl(commons-logging) 대신 slf4j 를 사용하므로 spring-context 에서 commons-logging 제거(exclude)

* jcl-over-slf4j 내부적으로 slf4j-api dependency 를 포함하고 있으므로 slf4j-api 는 별도의 dependency 로 추가 하지 않음

* log4j-core 내에 log4j-api dependency 포함하므로 log4j-slf4j-impl 에서 해당 의존성 제거

 

[ #log4j2.xml 설정 ]

src/main/resources/ 경로에 생성(/WEB-INF/classes/).

해당 위치에 파일을 위치시킬 경우 log4j 가 초기화 될 때 알아서 해당 파일을 읽어들임

 

* src/main/resource/ 밑이 아닌 기타 경로에 해당 파일을 위치시킬 경우

web.xml 에 아래와 같이 추가

참고 : https://okky.kr/article/282263

 

[ web.xml ]

1
2
3
4
5
6
7
8
9
  <context-param>
    <param-name>log4jConfigLocation</param-name>
    <param-value>classpath:~~~/log4j2.xml</param-value>
  </context-param>
  
  <listener>
    <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
  </listener>
 
cs

 

[ log4j2.xml ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>    
    <Appenders>
        <Console name="console_root" target="SYSTEM_OUT">
            <PatternLayout pattern="%d %5p %m%n" />
        </Console>
         <Console name="console_com" target="SYSTEM_OUT">
            <PatternLayout pattern="%d %5p [%c] %m%n" />
        </Console>       
    </Appenders>
    
    
    <Loggers>
         <Logger name="java.sql" level="INFO" additivity="false">
            <AppenderRef ref="console_com" />
        </Logger>
        <Logger name="jdbc.sqltiming" level="INFO" additivity="false">
            <AppenderRef ref="console_com" />
        </Logger>
        <Logger name="egovframework" level="INFO" additivity="false">
            <AppenderRef ref="console_com" />
        </Logger>
        <Logger name="com" level="INFO" additivity="false">
            <AppenderRef ref="console_com" />
        </Logger>
        <Logger name="org.springframework" level="INFO" additivity="false">
            <AppenderRef ref="console_com" />
        </Logger>
        <Root level="INFO">
            <AppenderRef ref="console_root" />
        </Root>
    </Loggers>
</Configuration>
 
cs

 

[* 어펜더 (Appender) ]

어펜더는 로그이벤트를 목적지에 전달하는 역할을 한다.(타겟 목적지에 이벤트 데이터를 쓰는 역할)

어펜더는 Appender interface 를 구현한다.

name 속성을 가지며 이를 통해 로거에서 참조한다.

아래와 같은 어펜더가 존재.

ConsoleAppender : Sysout 을 찍는 용도로 사용되는 어펜더

FileAppender : 로그를 파일로 저장할 용도로 사용되는 어펜더

RollingFileAppender : FileAppender 에 Rollover 기능(파일 백업)을 추가한 어펜더

 

[* 로거 (Logger) ]

로거의 위계구조(hierarchy)에 대한 이해가 필요하다. 여기를 읽어보길 바란다.

아래는 로거 위계구조의 예이다.

com.sample.sampleController 클래스 내에서
protected Logger logger = LoggerFactory.getLogger(this.getClass());
logger.info("샘플로깅!");
과 같이 로그를 출력할 경우,
this.getClass()com.sample.sampleController를 반환하고,
해당 클래스는 com 패키지 하위의 클래스(com.*) 이므로 "com"이란 이름(name)으로 명명된 logger설정을 따른다.

* 만약 23~25line 의 "com" logger 를 제거한다면 com 패키지 하위 소스내(com.sample.sampleController)에서 찍은 로그는 Root 로거의 console_root 어펜더에 의해 출력된다. ("com" logger 있을 경우 : console_com(%d %5p [%c] %m%n) appender 에 의해 출력, 없을 경우 root logger인 console_root(%d %5p %m%n)  에 의해 출력

* 이와 같은 위계구조를 활용하여 라이브러리 내부에서 불필요 혹은 원치않는 로그를 출력하고 있는 경우, 해당 라이브러리 패키지 경로를 별도의 logger 로 잡아 로그 출력을 제거 할 수 있다.

* Root 로거는 매핑되는 logger 를 찾지 못하는 경우 가장 마지막에 사용되는 최상위 로거이며, Root 로거는 반드시 설정해주어야 한다.

 

[* 패턴 (PatternLayout) ]

%d 날짜 년월일시

%c : 클래스명

%5p : 로그레벨(5자리 길이 맞춰서 출력(5자리 이하일경우 공백열로 채워넣음))

%m : 메시지

%n : 줄바꿈

%t : 스레드명

 

[ # jboss 에서 log4j 사용시 필요한 설정 ]

: jboss 내부적으로 slf4j, log4j 라이브러리를 내장하고 있기 때문에

해당 부분을 exclude 하여 충돌요소를 제거

 

[ jboss-deployment-structure.xml ]

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>
<jboss-deployment-structure>
    <deployment>
        <exclusions>
            <module name="org.apache.log4j" />
            <module name="org.apache.commons.logging" />
            <module name="org.slf4j"/>
        </exclusions>
    </deployment>
</jboss-deployment-structure>
cs

참고 : http://blog.naver.com/PostView.nhn?blogId=djawl_2&logNo=220665045226&redirect=Dlog&widgetTypeCall=true
(위 고수님의 블로그에 jboss 환경변수를 이용하여 충돌을 막는 방법도 기재되어 있음)

 

[ # jboss 에서 log4j 2.10 버전 이상 사용시 발생하는 문제 ]

jboss 에서 log4j 2 2.10 이상 버전 사용시 발생하는 warn 로그 :

WARN  [org.jboss.as.server.deployment] (MSC service thread 1-5) WFLYSRV0003: Could not index class META-INF/versions/9/module-info.class at /content/example.war/WEB-INF/lib/log4j-api-2.11.1.jar: java.lang.IllegalStateException: Unknown tag! pos=4 poolCount = 32

서버동작에 문제는 없으나 was 기동시 WARN 로그 찍히는게 보기가 싫으니,  log4j 버전을 살짝 내려서 사용.

참고 : https://issues.jboss.org/browse/JBEAP-15261

(module-info.class 가 log4j 2.10 이상 버전에 존재하므로 해당 경고가 발생)

 

참고 : log4j 2 user guide

반응형

emoji 처리

모바일 앱이 전달하는 채팅내용에 emoji 가 포함되어 API 서버에서 Mysql DB에 해당 채팅내용을 로그성으로 기록하기 위해 insert를 하다 DB에러가 났다.

Exception은 아래와 같이 Incorrect string value ~ 발생.

Warning: #1366 Incorrect string value: '\xF0\x9F\x8D\xB0' for column 'Text' at row ~

 

* emoji 는 모바일(android, ios)에서 사용되는 이모티콘 같이 생긴 문자열로

3byte 문자열인 utf8과 달리 4byte 문자열인 utf8mb4 charset 을 사용한다.

 

 

해결방법 1. 

DB 칼럼의 datatype을 utf8mb4로 수정을 하여 해결

 

해결방법 2.

2-1) emoji 를 정규식으로 거른다 :

android, ios os 버전업이 될 때마다 emoji가 추가되는 듯 하니, 오래 운영될 서비스라면 적합하지 않은 방법인 듯.

2-2) 문자를 제외한 모든 데이터를 정규식으로 거른다 :

허용해줄 모든 문자들을 신중히 남겨놓아야 하기 때문에 번거롭겠지만, 로그성 데이터라면 대충 걸러도 되니 해당 방법을 선택..

1
2
3
4
5
6
public static String replaceEmoji(String text){
 
    String newText = text.replaceAll("[^a-zA-Z0-9ㄱ-힣\\s!@#$%^&|:<>~/';\"`.\\?\\}\\{\\|\\*\\[\\]\\(\\)-_/]""▩");
        
   return newText;
}
cs

 

 

 

반응형

운영, 테스트서버, 개발서버 와 같이 서버가 3개가 구성되어있고,

각각의 서버별로 사용하는 jar가 달라야 할 때, 서버 환경에 맞게 jar를 빌드하여 서버에 배포해보자.

 

* 서버 환경별로 jar 내부적으로 소켓통신을 하는 목적지 ip가 달라 jar가 총 3개 존재했고..

   war 를 어떤 서버에 배포하느냐에 따라 jar를 바꿔서 빌드 후, 서버에 배포해야 했던 상황

** 소스내에서 갖다 쓰는 jar 내부의 메소드 자체를 수정하여 목적지 서버 ip를 파라미터로 받게끔 처리해주는게 베스트..

    하지만 jar 만드신 분이 본인보다 상급자라면 별 수 없다.

 

 

1) pom.xml 에 profiles 설정 및 local repository 설정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<repositories>
      <repository>
         <id>local-repository</id>
         <url>file:${basedir}/repo</url>
      </repository>
       </repositories>
    
    <profiles>
      <profile>
         <id>dev</id>
         <dependencies>
            <dependency>
               <groupId>chat</groupId>
               <artifactId>chatmodule-dev</artifactId>
               <version>0.0.6</version>
            </dependency>
         </dependencies>
      </profile>
      <profile>
         <id>tb</id>
         <dependencies>
            <dependency>
               <groupId>chat</groupId>
               <artifactId>chatmodule-tb</artifactId>
               <version>0.0.6</version>
            </dependency>
         </dependencies>
      </profile>
      <profile>
         <id>live</id>
         <dependencies>
            <dependency>
               <groupId>chat</groupId>
               <artifactId>chatmodule-</artifactId>
               <version>0.0.6</version>
            </dependency>
         </dependencies>
      </profile>
   </profiles>
cs

 

2) 프로젝트 내에 local repository 구성

repo > chat > chatmodule/chatmodule-dev/chatmodule-tb > 0.0.6 > chatmodule-~.jar 와 같이 구성

chatmodule-~.jar : 운영서버 배포시 사용될 jar

chatmodule-tb~.jar : 테스트서버 배포시 사용될 jar

chatmodule-dev~.jar : 개발서버 배포시 사용될 jar

 

 

3) Maven Profile 선택

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Maven > Select Maven Profiles 클릭.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

활성화 시킬 profile(dev/tb/live) 선택

 

 

4) Maven update

 

5) Maven Build (war 파일 생성)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

profile(dev/tb/live) 선택

 

 

6) war 파일 및 jar 확인

빌드가 성공적으로 끝났으면,

workspace/프로젝트/war파일명/target/WEB-INF/lib/

경로로 이동하여 지정한 profile 의 jar가 잘 들어갔는지 확인.

 

 

위와 같이 profile 을 사용하여 환경별 jar 를 동적으로 사용하는게 아닌,

단순히 로컬(프로젝트 내)에 존재하는 jar 1개를 사용하고자 할 경우

local repository 를 잡을 필요 없이 아래와 같이 scope 를 system 으로 주고 프로젝트 내에서 jar 를 가져다 사용할 수 있다.

1
2
3
4
5
6
7
<dependency>
    <groupId>chat</groupId>
    <artifactId>mobile</artifactId>
    <version>0.0.1</version>
    <scope>system</scope>
    <systemPath>${basedir}/src/main/webapp/WEB-INF/lib/chat_0.0.1.jar</systemPath>
</dependency>
cs

 

반응형

기존에 interceptor 로 로그를 출력하도록 소스를 짜놓았지만 리소스를 너무 잡아먹는 듯 하여

AOP에서 로그를 출력하도록 바꾸었다.

 

설명은 나중에 채워넣겠다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
package ;
 
import java.util.HashMap;
import java.util.Map;
 
import javax.servlet.http.HttpServletRequest;
 
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.ModelAndView;
 
 
@Component
@Aspect
public class ControllerAOP {
 
    protected static final Logger logger = LoggerFactory.getLogger(ControllerAOP.class);
 
    @Around("execution(public org.springframework.web.servlet.ModelAndView com.sample.bmp.mobile..*Controller.*(..))")
    public ModelAndView around(ProceedingJoinPoint pjp) {
        ModelAndView mav = null;
 
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
 
        Object inputParam = null;
        for (Object obj : pjp.getArgs()) {
            if (obj instanceof Map) {
                inputParam = obj;
            }
        }
 
        long start = System.currentTimeMillis();
 
        String controller = (String) pjp.getTarget().getClass().getSimpleName();
 
        String path = request.getRequestURI();
        String addr = request.getRemoteAddr();
        int port = request.getRemotePort();
 
        logger.info("##########################################################################");
        logger.info("# REQUEST | CONTROLLER = {} | METHOD = {} | REMOTEADDR = {} | PORT = {} | IN_PARAMS = {}",
                controller, path, addr, port, inputParam == null ? "": inputParam);
        logger.info("##########################################################################");
 
        Map<String, Object> result = null;
        
        try {
            mav = (ModelAndView) pjp.proceed();
            result = mav.getModel();
        } catch (APIException e) {
            mav = new ModelAndView("jsonView");
            result = new HashMap<String, Object>();
//            e.printStackTrace();
            result.put("result_cd", e.getErrCode());
            result.put("result_msg", e.getMessage());
 
            mav.addAllObjects(result);
        } catch (Throwable e) {
            mav = new ModelAndView("jsonView");
            result = new HashMap<String, Object>();
//            e.printStackTrace();
            result.put("result_cd"0);
            result.put("result_msg""");
 
            mav.addAllObjects(result);
        } finally {
            String success = "fail";
            
            if (result.containsKey("result_cd")) {
                try {
                    if ("1".equals(result.get("result_cd").toString())) {
                        success = "success";
                    }
                } catch (ClassCastException | NumberFormatException e) {
                    logger.debug("SessionInterceptor error == {}", e);
                }
            }
            
 
            long end = System.currentTimeMillis();
            logger.info("##########################################################################");
            logger.info("# RESPONSE | CONTROLLER = {} | METHOD = {} | RESULT = {} | REMOTEADDR = {} | PORT = {} | TIME = {} ms | IN_PARAMS = {} | OUT_PARAMS = {}",
                    controller, path, success, addr, port, end - start,
                    inputParam == null ? "" : inputParam, 
                    result == null ? "" : result.toString());
            logger.info("##########################################################################");
 
        }
 
        return mav;
    }
 
}
cs
반응형

Mybatis like 조건시 문자열 처리

 

 

특정 문자열을 포함한 값을 가져오는 쿼리는

select * from where title like '%대상문자열%'

과 같이 쿼리를 작성 및 사용하는데,

이를 mybatis 에서 사용시 like 뒷 부분을 어떻게 처리해야 할지 난해하다.
*
like '%#{key}%' 와 같이 작성시
아래와 같은 에러로그를 볼 수 있다.

19:16:37,814 INFO  [stdout] (default task-3) ### SQL: SELECT COUNT(*)      FROM banword     WHERE ban_word like '%?%'

 

19:16:37,814 INFO  [stdout] (default task-3) ### Cause: java.sql.SQLException: Parameter index out of range (1 > number of parameters, which is 0).

19:16:37,814 INFO  [stdout] (default task-3) ; SQL []; Parameter index out of range (1 > number of parameters, which is 0).; nested exception is java.sql.SQLException: Parameter index out of range (1 > number of parameters, which is 0)., result_cd=0} | RESULT = fail | REMOTEADDR = 127.0.0.1 | PORT = 58841 | TIME = 350 ms

 

해결 :

 

[MySQL]
title like CONCAT('%',#{key},'%')

[Oracle]
title like '%' ||  #{key} || '%'

[MSSQL]
title like '%' + #{key} + '%' 


 

반응형

'back > Mybatis,Ibatis' 카테고리의 다른 글

[mybatis] xml에서 java method 사용  (0) 2020.08.24
[Mybatis] 동적쿼리 (if test) 문자열처리  (4) 2020.03.27
[Ibatis] dtd 경로 문제  (1) 2019.12.30
[Mybatis] $과 # 차이  (0) 2019.12.05

Exception 클래스의 구현과 AOP 설정을 이용한 예외처리

 

API 서버 개발을 하며 케이스별 에러코드와 에러메시지를 response 값으로 던져주어야 했다.

각각의 API(Controller)에 try catch 로 exception 을 잡아 상황별 에러코드 및 에러메시지를 던지기엔 API 갯수가 너무 많았고, 비효율적(노가다)이라 생각하여

enum과 customized Exception class, AOP 설정을 통해 해결했다.

 

1. ENUM 클래스 작성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public static enum APIResult {
        SUCCESS (200"success"),
        FAILURE (999"fail"),  //기타 에러
        E_PARAM_NULL(101"input parameter is null"),  // 필수값 누락 에러
        E_FORMAT(102"input parameter data format error"), // 파싱 에러
        E_NETWORK(104"network error"), //네트워크 에러
        
        private int code;
        private String message;
        
        private APIResult(int _code, String _message) {
            this.code = _code;
            this.message = _message;
        }
        
        public static APIResult search(int code) {
            for (APIResult status : APIResult.values()) {
                if(status.getCode() == code) {
                    return status;
                }
            }
            return null;
        }
      
        public int getCode() {
            return code;
        }
 
        public void setCode(int code) {
            this.code = code;
        }
 
        public String getMessage() {
            return message;
        }
 
        public void setMessage(String message) {
            this.message = message;
        }
}
cs
예외 케이스별로 던져줄 Error Code와 Error Message를 Enum 클래스로 작성.
 

2. customized Exception class 작성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package ;
 
public class APIException extends RuntimeException {
 
    private int ERR_CODE = APIResult.FAILURE.getCode();
 
    public APIException()  {
        super();
    }
 
    public APIException(String errDesc) {
        super(errDesc);
    }
 
    public APIException(String errDesc, int code) {
        super(errDesc);
        this.ERR_CODE = code;
    }
 
    public APIException(String errDesc, ConstantsAPI.APIResult e){
        super(errDesc);
        this.ERR_CODE = e.getCode(); 
    }  
 
    public APIException(ConstantsAPI.APIResult e){
        super(e.getMessage());
        this.ERR_CODE = e.getCode(); 
    }
 
    public String getErrorDesc() {
        return this.getMessage();
    }
 
    public int getErrCode() {
        return this.ERR_CODE;
    }  
 
}
 
 
cs

RuntimeException 을 상속받는 Exception 클래스를 작성한다.

생성자 오버로딩을 하여 Exception Message 및 Exception Code를 받을 수 있게 한다.

* Enum 클래스를 통으로 받는 생성자 또한 만들어준다.

ex)

APIException(ConstantsAPI.APIResult e){

//생략

}

 
3. Controller 에서 try catch 처리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@RequestMapping(value = "/sample", method={RequestMethod.POST}, produces = "application/json; charset=UTF-8")
public ModelAndView sample(HttpServletRequest request, HttpServletResponse response, @RequestBody Map<String, Object> inParam) throws Exception {
    
    Map<String, Object> result = new HashMap<String, Object>();
    ModelAndView mav = new ModelAndView("jsonView");
    
    try{
        // inParam 유효성 체크
        CommUtil.paramVaildate(new String[] {"tokenId"}, inParam);
        
        sampleService.sampleMethod(inParam);
        
        result.put("result_cd", APIResult.SUCCESS.getCode());
        result.put("result_msg""");
    } catch (APIException ae) {
        result.put("result_cd", ae.getErrCode());
        result.put("result_msg", ae.getMessage());
    } catch (Exception e){
        result.put("result_cd", APIResult.FAILURE.getCode());
        result.put("result_msg", e.getMessage());
    }
    
    mav.addAllObjects(result);
    
    return mav;
}
 
cs
9라인 CommUtil.paramValidate(..); : API 서버의 Controller 이기 때문에 필수값이 제대로 들어왔는지 우선 확인
11라인 sampleService.samplemethod(..); : 서비스 호출(서비스 내부적으로 파싱 및 기타 에러가 나는 경우 try catch로 예외를 잡아 Controller 쪽으로 예외를 미룸..  (생략)
catch문으로 예외를 잡아 error code 및 error message result에 담아 return..

 

* input parameter 유효성 검사

 

향상된 for문을 적절히 사용.

Map에 키가 담겨있지 않거나, 빈 값인 경우 구현해 놓았던 Custom Exception 클래스를 throw.

이때, 위에 작성해 놓은 enum을 활용.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static boolean paramValidate(String[] params, Map<String, Object> inParam) {
        
        boolean _result = true;
        
        try {
            for (String item : params) {
                if(!inParam.containsKey(item)) {
                    throw new APIException(String.format("%s not exist", item), APIResult.E_PARAM_NULL);
                }
                if(String.valueOf(inParam.get(item)).isEmpty) {
                    throw new APIException(String.format("%s is null", item), APIResult.E_PARAM_NULL);
                }
            }
        } catch(APIException e) {
            throw e;
        } catch(Exception e) {
            throw new APIException(APIResult.FAILURE);
        }
        
        return _result;
    }
}
 
cs

 

여기까지만 해도 괜찮지만 Controller 마다 아래의 코드가 들어가는게 마음에 들지 않았다..

1
2
3
4
5
6
7
8
9
try{
 
catch (APIException ae) {
    result.put("result_cd", ae.getErrCode());
    result.put("result_msg", ae.getMessage());        
catch (Exception e){
    result.put("result_cd", APIResult.FAILURE.getCode());
    result.put("result_msg", e.getMessage());
}
cs

 

 

6줄 남짓의 코드를 지우기 위해 

Exception을 공통(일괄?)처리 할 수 있는 @ControllerAdvice 및 @Exceptionhandler 를 사용하여 Exception 처리를 해보았으나..

Exception이 발생하는 경우, log를 찍기 위해 구현해 놓았던 interceptor 가 제대로 동작하지 않는 문제점이 발생.

 

구글링을 해보니, Exception 발생시 interceptor의 postHandle은 타지 않는단다..

 

아래는 구글링으로 찾은 내용..

 

From the javadoc of HandlerInterceptor.postHandle

Intercept the execution of a handler. Called after HandlerAdapter actually invoked the handler, but before the DispatcherServlet renders the view. Can expose additional model objects to the view via the given ModelAndViewDispatcherServlet processes a handler in an execution chain, consisting of any number of interceptors, with the handler itself at the end. With this method, each interceptor can post-process an execution, getting applied in inverse order of the execution chain.

Arguably the MethodArgumentNotValidException is thrown before the method is actually called, it is called in preparing the actual method call. 

Actually the postHandle is only executed after successful execution/invocation of the method.  In case of an exception only the preHandle and afterCompletion methods are called.

>> postHandle은 정상적인 메소드 실행에서만 작동. 예외시 preHandle, afterCompletion 만 호출됨.

 

(출처 : https://stackoverflow.com/questions/20551888/controlleradvice-methods-seem-to-bypass-interceptors)

 

그래서 AOP를 사용해보았다.

 

 

4. AOP 사용

4-1) pom.xml 에 aop 사용을 위한 설정 추가

 

property에 아래의 내용 추가

1
<org.aspectj-version>1.6.10</org.aspectj-version>
cs

 

dependency 추가

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>${org.aspectj-version}</version>
</dependency>
 
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>${org.aspectj-version}</version>
</dependency>
 
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjtools</artifactId>
    <version>${org.aspectj-version}</version>
</dependency>
cs

 

4-2) SampleAOP 작성

@Around(pointCut)을 선언

@Around("execution(public org.springframework.web.servlet.ModelAndView com.sample.*Controller.*(..))")

설명 >> public 접근제한자이면서, org.springframework.web.servlet.ModelAndView 리턴타입이면서, 

            com.sample. 밑의 Controller로 끝나는 모든 Class 밑의 모든 메소드(인자값 상관없이)가 실행될 때, 해당 함수(around(..))를 전, 후로 실행...

 Controller(pjp.proceed()) 에서 예외 발생시 AOP에서 error code 및 error message를 넣어 return

 

* advice : @Around, @Before, @After 등..  해당 어노테이션이 붙은 코드의 실행 시점을 의미

* pointCut : 정규식을 사용하여 어떤 joinPoint 를 사용할지 지정

* joinPoint : advice가 적용될 수 있는 메소드(함수)

 

* xml 빈 등록 잘못 할 경우 aop 를 두번 타는 문제가 발생할 수 있으니 주의.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package ;
 
import java.util.HashMap;
import java.util.Map;
 
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
 
@Component
@Aspect
public class SampleAOP {
    
    @Around("execution(public org.springframework.web.servlet.ModelAndView com.sample.*Controller.*(..))")
    public ModelAndView around(ProceedingJoinPoint pjp) {
        ModelAndView mav = null;
        
        try {
              mav = (ModelAndView) pjp.proceed();
        } catch(APIException e){
            mav = new ModelAndView("jsonView");
            e.printStackTrace();
            Map<String, Object> result = new HashMap<String, Object>();
 
            result.put("result_cd", e.getErrCode());
            result.put("result_msg", e.getMessage());
            
            mav.addAllObjects(result);
        } catch (Throwable e) {
            mav = new ModelAndView("jsonView");
            e.printStackTrace();
            Map<String, Object> result = new HashMap<String, Object>();
 
            result.put("result_cd"0);
            result.put("result_msg", e.getMessage());
            
            mav.addAllObjects(result);
        }
        return mav;
    }
}
 
cs

 

 

5. Controller 에서 try catch 처리 수정

 

예외처리를 AOP에서 일괄적으로 관리하니 기존의 try catch 를 지워준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 @RequestMapping(value = "/sample", method={RequestMethod.POST}, produces = "application/json; charset=UTF-8")
    public ModelAndView sample(HttpServletRequest request, HttpServletResponse response, @RequestBody Map<String, Object> inParam) throws Exception {
        
        Map<String, Object> result = new HashMap<String, Object>();
        ModelAndView mav = new ModelAndView("jsonView");
        
        // inParam 유효성 체크
        CommUtil.paramVaildate(new String[] {"tokenId"}, inParam);
            
        sampleService.sampleMethod(inParam);
            
        result.put("result_cd", APIResult.SUCCESS.getCode());
        result.put("result_msg""");
        
        mav.addAllObjects(result);
        
        return mav;
    }
 
cs

 

AOP 를 어설프게나마 사용해 보았다는 걸로 만족...

AOP 는 추후 더 공부해서 개념, 설정, 구현 부분을 따로 포스팅 해보겠다.

 

 

 

* 제 경우 기존 프로젝트에 aop 설정이 부분적으로 되어있었습니다.

  위의 설정 및 소스를 그대로 따라할 경우 aop 동작이 제대로 안될 수도 있습니다.

 

반응형

HttpUrlConnection을 이용한 외부서버 통신

프로젝트 내에서 외부 library를 사용하는데 제한이 조금 있어서 Spring이 지원하는 RestTemplate 같은 건 사용할 수 없었고, 대신 java.net.HttpUrlConnection 으로 외부 서버와 통신할 수 밖에 없었다..

 

makeParams(..) :

map에 담겨온 파라미터들을 get방식 뒤에 붙이는 url?key=value 형식으로 만드는 역할을 하는 메소드.

makeJsonParams(..) :

map에 담겨온 파라미터들을 { "key" : "value" } 와 같이 json 포맷으로 만들어 주는 메소드

httpUrlConnection(..) :

실제 외부와 connection 을 하는 메소드.

header 정보를 담아 호출해야하는 경우, json 형식으로 파라미터를 넘겨야 하는 경우 등 상황에 따라 호출하는 데이터 형식 및 호출 방식이 달라지기 때문에 오버로딩하여 구현

 

소스는 아래와 같다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package ;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.stereotype.Component;
import com.google.gson.Gson;
/** @author ljpyo */
@Component("httpUtil")
public class HttpUtil {
    
    private static final Logger logger = LoggerFactory.getLogger(HttpUtil.class);
    public static final String POST = "POST";
    public static final String GET = "GET";
    public static final String DELETE = "DELETE";
    
    private String makeParams(Map<String, Object> params){
        String param = null;
        StringBuffer sb = new StringBuffer();
        
        if(params != null){
           for ( String key : params.keySet() ){
               logger.info(" key : " + key + " / value : " + params.get(key));
               sb.append(key).append("=").append((params.get(key)==null?"":params.get(key)).toString().trim()).append("&");
           }
        }
        param = sb.toString().substring(0, sb.toString().length()-1);
        return param;
    }
    
    private String makeJsonParams(Map<String, Object> params){
        String json = "";
        if(params != null){
            json = new Gson().toJson(params);
        }
        return json;
    }
    
    public String httpUrlConnection(String getpost, String targetUrl, Map<String, Object> params) throws Exception {
       String returnText = this.httpUrlConnection(getpost, targetUrl, params, nullfalse);
       return returnText;
    }
    
    public String httpUrlConnection(String getpost, String targetUrl, Map<String, Object> params, boolean isJson) throws Exception {
        String returnText = this.httpUrlConnection(getpost, targetUrl, params, null, isJson);
        return returnText;
     }
    
    public String httpUrlConnection(String getpost, String targetUrl, Map<String ,Object> params, Map<String, Object> header, boolean isJson) throws Exception {
       URL url = null;
       HttpURLConnection conn = null;
       
       String jsonData = "";
       BufferedReader br = null;
       StringBuffer sb = null;
       String returnText = "";
       JSONObject jobj = null;
       
       String postParams = "";
       
       try{
           
           if(getpost.equalsIgnoreCase(POST) || getpost.equalsIgnoreCase(DELETE)){
               url = new URL(targetUrl);
           } else if(getpost.equalsIgnoreCase(GET)){
               url = new URL(targetUrl + ((params!=null)?"?"+makeParams(params):""));
           }
           logger.info("request url : " + url);
           conn = (HttpURLConnection) url.openConnection();
//         conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
//         conn.setRequestProperty("Accept", "application/json");
           
           if(header != null){
               conn.setRequestProperty(header.get("headerKey").toString(), header.get("headerValue").toString());
               logger.info("header : " + header.get("headerKey").toString() + "  /  headerValue : " +header.get("headerValue").toString());
           }
           
           if(isJson){
               conn.setRequestProperty("Content-Type""application/json");
           }
           
           conn.setRequestMethod(getpost);
           conn.setConnectTimeout(5000);
           conn.setReadTimeout(5000);
           conn.setDoOutput(true);
           
           if(getpost.equalsIgnoreCase(POST) || getpost.equalsIgnoreCase(DELETE)){
               if(params != null){
                   if(isJson){
                       postParams = makeJsonParams(params);
                   } else {
                       postParams = makeParams(params);
                   }
                   logger.info("isJson : " + isJson);
                   logger.info("postParam.toString()  : " + postParams);
                   logger.info("post param : " + postParams.getBytes("UTF-8").toString());
                   conn.getOutputStream().flush();
               }
           } 
           
           br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
           
           sb = new StringBuffer();
           
           while((jsonData = br.readLine()) != null){
               sb.append(jsonData);
           }
           
           returnText = sb.toString();
           
           try{
               jobj = new JSONObject(returnText);
               if! jobj.has("responseCode") ){
                   jobj.put("responseCode", conn.getResponseCode());
               }
           } catch (JSONException e){
               jobj = new JSONObject();
               jobj.put("responseCode", conn.getResponseCode());
           }
           
       } catch (IOException e){
           logger.debug("exception in httpurlconnection ! ", e);
           throw new APIException("exception in httpurlconnection !");
       } finally {
           try {
               if (br != null) br.close();
           } catch(Exception e){
               logger.warn("finally..br.close()", e);
           }
           br = null;
           try {
           if(conn!=null)
               conn.disconnect();
           } catch(Exception e){
               logger.warn("finally..conn.disconnect()", e);
           }
           conn = null;
       }
       return jobj != null ? jobj.toString() : null;
    }
    
}
 
cs

 

처음엔 GET POST만 짜놓으면 될 줄 알았는데 나중에 DELETE 방식 요청을 추가적으로 구현해야했다.

POST 처럼 날리면 될 줄 알았더니 DELETE 호출방식엔 outputstream을 사용할 수 없다는 예외가 발생하여 애 좀 먹었다.. (https://developyo.tistory.com/8 참고..) 

 

1년도 안된 신입이 짠 유틸을 꽤 큰 프로젝트에서 공통으로 사용하고 있으니 불안해 죽겠다.

아직 큰 문제 없는 걸 보니 그냥 잘 돌아가고 있는듯...

 

일단 본 프로젝트에선 connectionTimeout , readTimeout Exception이 발생했을 때 재시도(retry) 없이 Customizing한 Exception을 내뱉으며 접속을 종료 시키지만

공부할 겸 retry 기능을 넣어봐야겠다.

 

추후 retry 기능을 넣어 재포스팅하겠다.

 

 

* RETRY (재시도) 설정 추가

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package ;
 
@Component("httpUtil")
public class HttpUtil {
    
    public String httpUrlConnection(~) throws Exception {
               
       //재시도 추가 190220
       for(int i=0; i < (retry<1?1:retry); i++){
               
           try{
               //파라미터 세팅
               //호출 
               //생략~               
 
               if(conn.getResponseCode() != HttpStatus.OK){
                    //응답코드가 실패인 경우 Exception 고의로 발생(catch 에서 continue 로 처리)
                    throw new CustomException();    //customized exception 사용
              }  
              //성공은 for문 나감
              break;
             
              //응답 값 파싱
           } catch (SocketTimeoutException ste){
               errMsg = ste.getMessage();
               logger.debug(errMsg);
           } catch (CustomExceptione ce){
               errMsg = ce.getMessage();
               logger.debug(errMsg);
           } catch (Exception e){
               
           } finally {
               //자원 해제
               try {
                   if (br != null) br.close();
               } catch(Exception e){
                   logger.warn("finally..br.close()", e);
               }
               br = null;
               try {
               if(conn!=null)
                   conn.disconnect();
               } catch(Exception e){
                   logger.warn("finally..conn.disconnect()", e);
               }
               conn = null;
           }
       }
       
       if(jobj!=null){
           return jobj.toString();
       } else {
           throw new APIException(errMsg, ConstantsAPI.APIResult.E_NETWORK.getCode());
       }
    }
}
cs

 

호출받는 쪽에 connect가 오래걸려 connectTimeOut 이 나거나, connect는 되었으나 내부 처리가 오래걸려 readTimeOut이 발생한 경우 특정 횟수(n)만큼 재시도를 하도록 소스를 조금 수정했다.

(보통 한번 안되면 몇 번을 다시 시도해도 안되는 것 같지만...)

 

retry 횟수를 파라미터로 받고,

for문을 돌린다.

timeout 예외 및 응답코드가 200이 아닌 경우(CustomException)가 발생하면( SocketTimeoutException ) catch 문에서 Error 를 먹어버리고 for문이 retry 횟수만큼 이어서 진행된다.

응답코드가 200인 경우 break; 구문을 타게 되어 for문이 종료한다.

* 예외가 발생하지 않을 경우 return 하고 해당 메소드는 끝남.

 

 

요청하고 있는 외부 API 에서 HttpStatus 를 제멋대로 (보통 200을 사용) 담아 리턴하고 있어서 소스 전체를 올리진 못했다. 

(결과코드를 일일히 매핑해야했기 때문에.. 소스가 요상함)

 

여튼 for문 써서 connection 객체로부터 getResponseCode()로 연결상태 확인 하여 처리하면 된다.

반응형

java.net.HttpUrlConnection 을 사용한 GET/POST 방식 호출 함수를 작성 후,

호출 방식(httpMethod) 만 DELETE 방식으로 바꿔서 함수를 동작시킬시

HTTP method DELETE doesn't support output ~

과 같은 exception 이 발생한다.

 

https://bugs.openjdk.java.net/browse/JDK-7157360

위의 URL에 기재된 jdk bug 리포트에 따르면..

When using HttpURLConnection, if I set the request method to "DELETE" and attempt to get to the output stream to write the entity body, I get:

Exception in thread "main" java.net.ProtocolException: HTTP method DELETE doesn't support output

at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLConnection.java:1004)

As it turns out, sun.net.www.protocol.http.HttpURLConnection explicitly denies access to the output stream if the request

method is DELETE.

>> httpurlconnection 을 사용하여, DELETE request 메소드로 사용하고 (.setRequestMethod("DELETE") 의미) 바디를 쓰기위해 output stream 을 가

    져오면, HTTP 메소드 DELETE는 output 을 지원하지 않는다는 exception이 발생한다.

>> sun.net.www.protocol.http.HttpURLConnection 은 request 메소드가 DELETE라면 output stream 접근을 막는다.

한줄로 정리하자면 jdk 1.7에선 http DELETE 방식에 OutputStream을 지원하지 않는다(Exception이 발생한다).

 

해결책 1.

jdk 버전을 1.8로 올려주면 된다. (참고)

>> jdk1.8버전에 수정된 버그인 듯 하나, 직접 실험해보진 않았다.. (프로젝트 자체가 jdk1.7이었고 다른 방법을 강구해야 했다)

 

해결책 2.

조건문을 걸어 파라미터가 존재하지 않을 경우 OutputStream을 사용하지 않는다.

DELETE 방식의 호출인 경우 파라미터가 없어야 정상으로 알고 있다.

(restful api 에서의 delete 요청은 uri 에 파라미터(key)를 넘긴다)

 

[SAMPLE]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if(getpost.equalsIgnoreCase(POST) || getpost.equalsIgnoreCase(DELETE)){
   if(params != null){
       if(isJson){
           postParams = makeJsonParams(params);
       } else {
           postParams = makeParams(params);
       }
       logger.info("isJson : " + isJson);
       logger.info("postParam.toString()  : " + postParams);
       conn.getOutputStream().write(postParams.getBytes("UTF-8"));
       conn.getOutputStream().flush();
   }
 
cs

(참고 : https://developyo.tistory.com/10?category=688588)

* 결과값은 GET/POST 와 같은 다른 HttpMethod과 상관없이 InputStream을 가져와서 읽어주면 된다.

 

 

쉽게 해결한 듯 보이나 실제론 2시간 가까이 삽질했다.. 

 

반응형

보통 개발환경은 최소 2개 이상으로 나뉜다.

local, dev(개발), oper(운영) 등등..

 

was(웹어플리케이션서버)에 .war(컴파일된 소스)를 deploy(반영/배포) 할 때마다 환경(호출해야하는 이미지서버 도메인주소 등)이 달라지므로 디플로이하는 환경에 맞춰 읽어들여야 할 property 파일들을 달리 처리 해야 한다. (local일땐 local.property를, 개발일땐 dev.property를 ... )

이를 매번 반영할 때마다 환경에 맞게 필요없는 프로퍼티 파일들을 제거한 후 deploy 하기도 번거롭고 

deploy 해놓고 vi (리눅스 visual editor) 로 property 불러오는 스프링 context.xml 을 수정하기도 번거롭다.

 

이런 번거로운 수고를 덜기 위해, 동적으로 프로퍼티를 적용하는 방법은 아래와 같이 몇가지가 있다.

 

1. 서버(WAS)의 ip 값을 기준으로 property 를 분기처리 하는 방법

was 에서 ip를 얻어올 땐 java.net.InetAddress클래스의 getHostAddress() 사용.

가져온 ip주소와 하드코딩 해놓은 was ip 비교하여 property 파일을 분기처리.

서버정보가 바뀔 경우 소스레벨에서의 수정이 필요하므로 그닥 좋은 방법 같진 않다..

[SAMPLE CODE]

1
2
3
4
5
6
InetAddress iAddr = InetAddress.getLocalHost(); 
String adr = iAddr.getHostAddress();
String prefix = ""
if(adr.indexOf("127.0.0.1"> -1){ 
prefix = "local" 
else if (//... 생략
cs

 

2. 서버(WAS)의 환경 변수를 기준으로 property를 분기처리 하는 방법

2-1. was서버 설정

1) 보통 was 실행하는 쉘 스크립트인 start.sh 을 vi 로 들여다 보면 JAVA_OPTS 을 선언한 부분을 찾을 수 있다.

    없다면 start.sh 내에서 호출하는 다른 쉘을 vi 로 또 한 번 들여다보자. 

    찾았다면 JAVA_OPTS="$JAVA_OPTS -Dspring.profiles.active=dev" 와 같이 한 줄을 추가한다.

    못 찾겠으면 

./start.sh -Dspring.profiles.active=dev &

    와 같이 서버구동용 쉘스크립트에 위와 같은 옵션을 주어 실행하면

    위와 같이 할 경우, spring.profiles.active 라는 key 에 dev 라는 value를 갖는 환경변수를 설정한 셈이 된다.

 

2) 스프링 property 설정 수정

1
<util:properties id="properties" location="classpath:sample/properties/conf_#{systemProperties['spring.profiles.active']}.properties" />
cs

이와 같이 설정시

sample/properties/conf_환경변수.properties 프로퍼티를 주입하게 된다.

(환경변수는 로컬 vm arguments(2-1)와 서버별 was 환경변수로 지정한 spring.profiles.active 값)

별다른 코드를 짜지 않아도 위와 같은 설정만으로 서버 환경별 property 를 동적으로 주입받게 되는 것이다.

 

2-2. 로컬설정

사용중인 서버(jboss) 더블클릭 > Open launch configuration >  VM arguments 에

-Dspring.profiles.active=local 추가 (톰캣도 같다)

※ 만약 로컬 환경에서 사용 할 프로퍼티 파일명을 sample/properties/conf_.properties 와 같이 사용할 경우 환경변수를 잡아 줄 필요가 없다.

※ 환경변수(JAVA_OPTS)로 선언할 땐 -Dspring ~ 과 같이 D를 붙여주고 그 외(소스)에는 D를 빼주어야 한다는 걸 주의

 

[사용예]

서버 환경 별로 사용하는 js 가 달라지는 경우 (서버 환경 별로 js 내의 목적지 ip 가 달라지거나 하는 경우)

@Controller :

1
2
@Value("#{systemProperties['spring.profiles.active'].js})"
private String jsFileNm;
cs

JSP :

1
<script src="/resources/js/${jsFileNm}" />
cs

와 같이 사용하여 서버 환경 별로 달라지는 js 파일명을 동적으로 가져다 사용할 수 있다.

 

 

반응형

+ Recent posts