그동안 이직하고 적응하느라 바빴다는 핑계로.. 글을 쓰지 않다가 정말 오랜만에 글을 하나 쓴다. 별로 대단히 길게 쓸 글은 아니고, 최근 개인 프로젝트를 시작하며 그동안 손에서 놓아두었던 java 개발을 다시 하려고 프로젝트 셋팅을 진행하다가 삽질한 경험이다.

swagger가 어느덧 3.0 버전이 나왔다길래 사용해볼까 싶어서 셋팅 하다 보니 뭔가 생각대로 되지 않아 짧게 셋팅하는 방법을 적어본다. 셋팅하는 방법은 #링크 를 참조 했다.

🌿 dependency 추가

필자는 gradle만 사용하므로 gradle 버전만 작성해놓겠다.

1
2
3
4
5
dependencies {
// ...
implementation "io.springfox:springfox-boot-starter:3.0.0"
// ...
}

swagger 3.0 오면서 편해졌다. springfox-boot-starter 하나만 추가 하면 얘가 필요한 dependency를 다 들고 있기 때문에 신경 쓸게 적다.

⚙ 설정파일 추가

사실 이렇게만 해도 spring boot app 구동 시에 자동으로 뜨긴 한다.
그렇지만 간단한 설정을 추가해 보자.

적당한 곳에 config 관련 패키지를 추가하고 SwaggerConfig.java 파일을 추가하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.deliwind.grams.configrations;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;

@Configuration
public class SwaggerConfig {

@Bean
public Docket api() {
return new Docket(DocumentationType.OAS_30) // open api spec 3.0
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
}
}

🏃🏻‍♀️ Runtime

그리고 서버를 띄운 후 http://localhost:8080/swagger-ui/ 로 접속해보면 swagger-ui 화면을 볼 수 있다.

주의할 것은 swagger 2.0과 달리 기본 swagger-ui 접속 Url이 http://localhost:8080/swagger-ui.html이 아닌 http://localhost:8080/swagger-ui/로 바뀌었다는 점이다. 이 점을 몰라서 “왜 작동이 안되지? “하면서 한참 헤맸었다.

이직한 곳에서의 주 백엔드 개발 언어가 node.js 에 nest.js를 사용하는 환경이라 좀 생소하다. 나에게 맞지 않는 옷을 입은 느낌이 자꾸 든다. 다양한 경험을 위해 이직한 만큼 이곳에서 또 충분히 성장한 후 언젠가 다시 java로 돌아갈 때가 올거라고 믿기에 자바를 계속 갈고 닦기 위해 개인 프로젝트를 자바로 진행하려 한다.
화이팅~

👻 References

Comment and share

원래 Enum에 관련된 글도 이 전 블로그에서 작성했던 적이 있었다. 한번 글들을 날려먹고 두번째 작성하는 글이므로,
이 전에 작성했던 글만큼의 정성을 들일 수 있을까…..?

일단 Java Enum에 대해서는 내가 지금부터 쓰는 글보다 잘 써 놓은 글이 있으니 먼저 읽어 보는것도 좋을 것 같다.

enum을 사용하는 이유는 여러 가지가 있으나, 가장 큰 이유는 DB의 코드값과 달리 IDE 또는 컴파일 단계에서 지원을 받을수 있다는게 가장 큰 이유일 것이다. 자동완성도 지원하며 매직넘버와 달리 오타를 냈다면 컴파일 단계에서 잡아준다. 그말이 그말이잖..

장점, 장점을 보자

컴파일 단계에서 지원을 받을 수 있다

IDE에서는 자동완성도 되고, 오타가 났을 경우에 런타임 에러가 아닌 컴파일 에러를 내므로 버그를 만들 확률이 줄어든다.(어디까지나 완벽히 없어지는건 아니다. 버그 장인은 도구를 탓하지 않는다 )

리팩토링 또는 수정이 쉽다

값이 추가 되더라도 enum값 하나만 추가 하면 된다. 혹시 enum이 내포하고 있는 값이 달라진다면 enum만 수정하면 된다.

그 외에도

기본적으로 enum은 선언시 불변객체로 생성되므로 여러번 할당하더라도 새로 object를 생성하는 비용 부담이 덜하고, String과는 달리 equals를 사용하여 비교할 필요가 없다. == 를 사용하여 비교해도 잘 작동한다.

그렇다면 단점은?

배포 문제에서 자유롭지 못하다

컴파일 단계에서 작동해서 IDE의 지원을 받을 수 있다는 건, 반대로 배포 문제에서 자유롭지 못하다는 뜻이기도 하다. 간단한 값 추가가 있다고 하더라도 DB에서 사용하는 코드와는 달리 배포를 해야 한다. 요즘은 CI/CD가 워낙 잘 돼있는데다가 무중단 배포 환경도 손쉽게 구축하여 사용하는 추세라 크게 문제가 안된다고 할 수 있지만, 그렇다고 하더라도 분명 DB에서 손쉽게 추가하는 것에 비하면 부담이 되는 부분은 사실이다.

만약 DB에서 일원화된 persistant 영역에서 관리하는 코드값이라고 하면 코드만 추가 해주면 끝이지만, 여러 서비스에서 사용하는 값이라면 각 서비스 별로 챙겨서 배포해야 하며, 그러다 보면 누락하는 서비스가 있을수도 있다. (그게 우리 서비스도 이랬던 적이 있더랬..) 또한 이런식으로 누락하는 서비스에서 발생하는 오류는 단순히 DB 코드를 추가했다가 생기는 오류보다 더 크리티컬 하다.

enum이 적극적으로 쓰이지 않던 때에는 거의 모든 상태값들을 코드 테이블을 하나 만들어놓고 다 때려 박았다. 사실 배포 문제에서 자유롭지 못한 문제가 있지만, 생각보다 우리가 코드라고 생각하는 아이들은 사실은 코드가 아닌 경우가 많다. 자주 바뀌는 값과 바뀌지 않는 값을 잘 구분해서 자주 바뀌지 않는 녀석들을 enum으로 선언해서 사용하자.

숫자만 넣어서 만들수는 없다

망치를 든 사람에게는 모든게 못으로 보인다고, enum의 맛을 알게 되면 모든 코드를 enum으로 바꾸고 싶은 욕망에 사로잡히게 된다. 그러나 이렇게 enum으로 하나둘씩 바꾸다 보면 숫자로만 돼있는 코드들은 enum으로 만들 수 없다는 것을 깨닿게 된다.
enum의 상태값들은 java의 필드명 정의하는 규칙과 같이 숫자로만 구성할 수는 없다.

사실 대부분 enum 상태값으로 정의하고자 하는 속성은 숫자가 아닌 다른 뜻을 갖고 있는 경우가 많이 있지만 진짜 숫자만 있는 경우라면? 대체할 만한 단어가 없다면??? 그럼 그냥 다른 문자를 붙이는 수밖에 없다.

1
2
3
enum Numbers {
_1, _2, _3;
}

별로 맘에는 안든다. 그렇지만 범위를 제한할 수 없는 int 필드보다는 낫다.

1
2
3
enum Numbers {
1ST, 2ND, 3RD;
}

뭐 이런것도 나쁘지는 않다.

좀 더 고급지게 사용해보자

자바의 enum은 다른 언어의 enum에 비해서 좀 더 활용도가 높다. 사실 java에서 enum은 하나의 완전한 클래스로 취급 되는데 이 때문에 enum도 인스터스 변수를 가질수도 있고, interface를 implements 하도록 하거나 abstract로 선언할 수도 있으며 method나 static method를 만들수도 있다.

아래 예제를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enum Sequences{
FIRST(1),
SECOND(2),
THIRD(3);

private int order;

Sequences(int order) {
this.order = order;
}

public int getOrder() {
return order;
}
}

이렇게 단순하게 사용하는 경우는 별로 없지만(심지어 java는 ordinal() 이라는 기본 메소드를 제공한다. 그러나 ordinal은 가급적 사용하지 않는것이 좋다. 이유는 후술) 코드에서 보이는 바와 같이 클래스의 모습과 크게 다르지 않다. 더욱이 자바의 enum은 이런 변수를 하나만 사용 가능한 것이 아니라 여러개가 사용 가능하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
enum Sequences{
FIRST(1, "1st"),
SECOND(2, "2nd"),
THIRD(3, "3rd");

private int order;
private String shorten;

Sequences(int order, String shorten) {
this.order = order;
this.shorten = shorten;
}

public int getOrder() {
return order;
}

public String getShorten() {
return shorten;
}
}

참고로 FIRST(1, "1st") 이렇게 enum을 선언하는 구문은 생성자 메소드를 호출하는 방식이기 때문에 생성자를 어떻게 만드느냐에 따라 다른 방식으로도 사용 가능하다. 또한 enum은 static 영역에 저장되기 때문에 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
enum Media {
MOVIE("horror, comedy, sf, family"),
MUSIC("dance, balad, rock, classic");

private Set<String> genreSet;

Media(String genreString) {
this.genreSet = Arrays.asList(genreString.split(","))
.stream()
.map(String::trim)
.collect(Collectors.toSet());
}

public boolean contains(String genre) {
return this.genreSet.contains(genre);
}

public static Media determine(String genre) {
return Arrays.asList(Media.values()).stream()
.filter(media -> media.contains(genre))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("there is no matched genre"));
}

}

물론 아래 코드처럼 생성자에서 array를 바로 만들어서 받을수도 있겠으나 내 취향은 위 코드가 더 맞아서 주로 이렇게 쓴다. (무엇보다 set의 contains 시간 복잡도가 list보다 더 유리하다)

1
2
3
4
5
6
7
8
9
10
11
enum Media {

MOVIE(Arrays.asList("horror", "comedy", "sf", "family")),
MUSIC(Arrays.asList("dance", "comedy", "sf", "family"));

private List<String> genreSet;

Media(List<String> genreSet) {
this.genreSet = genreSet;
}
...

또한 enum에서 변수로 익명함수를 담고 있을 수도 있기 때문에 이를 사용하면 더욱더 활용도가 높아진다. 간단한 사칙연산을 수행하는 enum이다.
(Lombok도 잘 먹혀서, 아래부터는 lombok을 활용해 constructor를 만들어 보겠다.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@AllArgsConstructor
enum Calc {
ADD("+", (a, b) -> a + b),
SUBTRACT("-", (a, b) -> a - b),
TIMES("*", (a, b) -> a * b),
MOD("/", (a, b) -> a / b);

@Getter
private String character;

private BiFunction<Integer, Integer, Integer> calculation;

public int calc(int a, int b) {
return calculation.apply(a, b);
}
}

아래는 위와 같은 일을 하는 코드를 abstract 메소드를 활용하여 만들어 본 코드이다. (java7)
이렇게도 사용 가능하니 적절히 필요에 따라 원하는 방법대로 사용하면 좋다.

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
@Getter
@AllArgsConstructor
public enum Calc {
ADD("+"){
@Override
public int calc(int a, int b){
return a + b;
}
},
SUBTRACT("−"){
@Override
public int calc(int a, int b){
return a - b;
}
},
TIMES("×"){
@Override
public int calc(int a, int b) {
return a * b;
}
},
MOD("÷"){
@Override
public int calc(int a, int b){
return a / b;
}
};

private String character;

public abstract int calc(int a, int b);
}

이런식으로 enum에 디자인 패턴을 얹어 사용하면 코드들이 단순 명료해 지는것을 느낄 수 있을 것이다.

ordinal()을 사용하지 않아야 하나?

아까 잠깐 언급하고 넘어간 ordinal() 메소드에 대해 이야기 해보자. enum은 순서를 가진 열거형이고, 그 순서값을 int로 반환하는 ordinal()이라는 메소드를 제공해준다. 그러나 ordinal을 사용하게 되면 나중에 유지보수에 문제가 발생하게 된다. (이 부분은 effective java item 35를 보면 자세히 나온다)

중간에 새로운 enum 요소가 끼어 드는 경우 새로운 요소 뒤에 위치한 enum들의 연산은 모두 망가지게 된다.

따라서 ordinal을 사용하는 것보다 차라리 위에서 만든 Sequence enum처럼 인스턴스 필드를 사용하여 int value를 담아두고 이를 return하는 getter를 사용하는것이 더 좋다.


내가 java 에서 enum을 사용하는 방법은 이정도이다. 물론 더 다양 하게 사용하는 방법도 있다. (enum 내부에 또 enum을 정의해 사용한다던지, 좀 다른 이야기지만 EnumMap을 사용하는 방법도 있고) 그러나 이정도만 알아도 java enum 초보 티는 좀 벗었다고 할 수 있지 않을까?

끗.

Comment and share

사실 이 이야기는 Effective Java 6장 - 불필요한 객체 생성을 피하라 에 나오는 아주 기초적인 이야기 이다. 그러나 습관이 되지 않아 생각없이 개발하다보면 어느새 이 규칙을 어겨서 어김없이 리팩토링 대상이 되곤 하는데다가, 막상 개발하면서 쓰려다 보면 사용 방법이 잘 생각나지 않아 메모할 겸 짧은 글을 작성 해본다.
(요즘 좀 정신이 나가 있어서 공부는 꾸준히 하기는 한다만, 공부하는 양은 좀 줄었고, 진도는 좀처럼 나가지가 않는다. 에라이)

일반적으로 Java에서 흔히 사용할 수 있는 정규 표현식의 예를 보자. (Junit5)

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
public class Utils {
public static boolean hasOnlyNumbers(String s) {
return s.matches("[\\d]*");
}
}

@DisplayName("Utils 테스트")
class UtilsTest {
@Test
@DisplayName("숫자 외의 문자가 있을 경우 false를 return 해야 한다")
void should_return_false_when_has_other_characters() {
String numbersWithCharacter = "1a2s3d4f5g6";
boolean expected = false;

boolean result = Utils.hasOnlyNumbers(numbersWithCharacter);

assertEquals(expected, result);
}
@Test
@DisplayName("숫자외의 문자가 없을 경우 true를 return 해야 한다")
void should_return_true_when_has_only_numbers() {
String numbersWithCharacter = "123456789";
boolean expected = true;

boolean result = Utils.hasOnlyNumbers(numbersWithCharacter);

assertEquals(expected, result);
}
}

오케이, 원하는 바를 손쉽게 얻었다. 그러나 지금 얻은 결과는 성능상 좋지 못하다. 사실 일반적인 상황에서 String의 matches 메소드를 사용하는게 엄청나게 성능에 문제를 주는 경우는 별로 없으나, 습관이 중요한데다가, 실제로 이런 코드들이 쌓여 문제를 발생하는 경우도 있으니 좋은 습관을 들이는 게 좋다.
이펙티브 자바는 String.matches를 사용하는 것보다 더 고급지고어렵고 성능이 좋은 방법을 알고 있다.

Pattern 클래스를 사용해 보자

바로 Pattern클래스를 사용해 같은 일을 더 성능 좋게 사용할 수 있다.
사실 위 예제의 코드는 메소드가 실행 될때마다 내부적으로 Pattern 인스턴스를 만들고 사용 후 버리는 작업을 수행하게 되는데, 이 Pattern을 final static 객체로 미리 선언해두고 사용하게 되면 메소드가 호출될 때마다 새로 생성하고 버리는 작업이 없어지게 된다.
아래 코드를 보자

1
2
3
4
5
6
public class Utils {
private static final Pattern ONLY_NUMBERS_PATTERN = Pattern.compile("[\\d]*");
public static boolean hasOnlyNumbers(String s) {
return ONLY_NUMBERS_PATTERN.matcher(s).matches();
}
}

테스트를 잘 통과하는 것을 볼 수 있다. 자, 그럼 얼마만큼의 성능 차이가 날까?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@DisplayName("Utils 테스트")
class UtilsTest {

final int times = 10000000;

@Test
@DisplayName("리팩토링 후")
void test_refactor() {
final String numbersWithCharacter = "1a2s3d4f5g6";
for(int i=0; i < times; i++){
Utils.hasOnlyNumbersRefactor(numbersWithCharacter);
}

}
@Test
@DisplayName("리팩토링 전")
void test_before_refactor() {
final String numbersWithCharacter = "1a2s3d4f5g6";
for(int i=0; i < times; i++){
Utils.hasOnlyNumbersOriginal(numbersWithCharacter);
}
}
}

컴퓨팅 성능마다, JVM 셋팅마다 다르겠지만, 메소드 수행을 1000만회 진행 했을때 꽤나 차이가 나는 것을 눈으로 볼 수 있었다.
(2.35초 -> 0.83초)

이펙티브 자바의 아이템6 에는 이 외에도 다른 팁들도 있으나 일단 오늘은 정규 표현식 부분만 정리했다.

정규 표현식을 사용할때는 기억하자.

  1. static final Pattern 으로 불변객체를 생성해서 사용하자
  2. Pattern p = Pattern.compile(“정규식”);
  3. p.matcher(string).matches();

Comment and share

Spring boot 에서는 application.yml (또는 application.properties) 파일의 내용을 빈의 변수에 바인딩하도록 도와주는 어노테이션이 있다.
@Value("${property.value}") 뭐 이런식으로 사용하는데 변수가 늘어남에 따라 어노테이션이 많아져서 가독성을 해치는 경우가 있고, 일일히 지정해주기가 귀찮을 경우 쓸 수 있는 방법인 @ConfigurationProperties 어노테이션에 대해서 간단히 적어 보려 한다.

사용방법

@ConfigurationProperties 는 클래스에 선언하는 어노테이션이다.
간단히 아래 예시와 같이 사용한다.

먼저 gradle(또는 maven 빌드 툴)에 아래 dependency를 설정한다.

1
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'

yml에 필요한 값들을 설정하고

1
2
3
4
user:
conf:
property1: "value1"
property2: "value2"

값들을 사용하고자 하는 클래스에 @ConfigurationProperties 어노테이션을 추가한다. 여기서 prefix도 설정할 수 있다. (user.conf는 예시를 위해 작성해 놓은 것이고, prefix는 다른 값으로도 사용할 수 있다.)
주의할 점은 @Value 어노테이션을 활용해 값을 바인딩 하는것과 다르게, 반드시 setter 메소드를 필요로 한다.
@ConfigurationProperties는 setter 메소드를 사용해 설정값을 바인딩 하기 때문이다. 여기서는 클래스에 @Setter 롬복 어노테이션을 사용해 한번에 정의했다.

1
2
3
4
5
6
@Setter
@ConfigurationProperties(prefix = "user.conf")
public class SomeClass {
private String property1;
private String property2;
}

Comment and share

Message broker

요즘 회사에서 새로운 시스템이 개발될 때, 레거시 시스템과의 연동이 많아지면서 Message broker 도입의 필요성에 대해 이야기 하고 있습니다. (조금 늦었지만..)

메세지 브로커 선택 시 어떤 브로커를 선택 하느냐 또한 중요한데요, 하자면 저희 회사에서는 Amazon MQ를 선택하게 되었습니다.

Amazon MQ는 지난 2018년7월부터 서울 리전에서도 사용 가능한 AWS 서비스 중 하나인데요, 내부적으로는 Apache Active MQ를 사용하도록 구성되어있습니다.

-Amazon MQ 장점

-구성이 쉽다 (유지보수 쉬움)

-가용성 (failover 지원)

-준수한 성능(22k건/초) (은 딱히 장점은 아닌듯 합니다)

-Amazon MQ 단점

-Rabbit MQ나 Apache Kafka에 비해서 상대적으로 떨어지는 인지도(레퍼런스가 덜 풍부함)

-Spring-cloud-stream 을 바로 사용할수 없음

서비스 선택 시 상세한 고려 사항은 https://stackshare.io/stackups/activemq-vs-kafka-vs-rabbitmq를 참고하셔도 됩니다.

Amazon MQ 선택시 마지막까지 고민하게 만들었던 부분은 spring-cloud-stream을 사용하여 구현할 수 없다는 점이었는데요, Spring-cloud-stream
을 사용하여 서비스를 구현하면 간단한 어노테이션 추가만으로도 구현이 가능했던 점이 아주 큰 장점이었거든요.

But, 찾아보니 Amazon MQ도 비슷한 난이도로 구현할 수 있는 방법이 있어 이를 구현하는 방법을 정리 차원에서 작성하고자 합니다. start!


Amazon console에서 Active MQ를 설정하는 방법은 건너뛰도록 하겠습니다.

  1. spring boot을 사용하여 Project를 생성

저는 spring-boot-devtools 정도만 추가 했습니다.(필요에 따라서는 lombok등도 추가 해주시면 됩니다..)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.2</version>
<scope>provided</scope>
</dependency>
  1. Maven pom.xml에 디펜던시 추가

activemq 관련 디펜던시를 추가해줍니다.

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
  1. application.properties에 설청값 셋팅
1
2
3
spring.activemq.broker-url=<broker-url>
spring.activemq.user=<user id>
spring.activemq.password=<password>
  1. 전달할 메세지 형태 클래스 Message.java를 추가 합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.io.Serializable;

public class Message implements Serializable {

private static final long serialVersionUID = -1163890830946122942L;

private String id;
private String name;

public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
  1. 메세지를 생성해서 MQ에 보낼 producer 클래스와 소비하게 될 consumer 클래스를 추가 합니다.

Consumer.java

1
2
3
4
5
6
7
8
9
10
11
12
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;

@Component
public class Consumer {

@JmsListener(destination = "sample.queue")
public void receiveQueue(Message message) {
System.out.println(message.getId() + ", "+ message.getName());
}

}

Producer.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import javax.jms.Queue;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.stereotype.Component;

@Component
public class Producer {

@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;

@Autowired
private Queue queue;

public void send(Message message) {
jmsMessagingTemplate.convertAndSend(queue, message);
}

}
  1. @SpringBootApplication클래스에 Bean 추가
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@SpringBootApplication
public class JmstestApplication {

@Bean
public Queue queue() {
return new ActiveMQQueue("sample.queue");
}

public static void main(String[] args) {
SpringApplication.run(JmstestApplication.class, args);
}
}


  1. 마지막으로 test 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
    import org.junit.Rule;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.boot.test.rule.OutputCapture;
    import org.springframework.test.context.junit4.SpringRunner;

    import static org.assertj.core.api.Assertions.assertThat;

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class JmstestApplicationTests {

    @Rule
    public OutputCapture outputCapture = new OutputCapture();

    @Autowired
    private Producer producer;

    @Test
    public void sendSimpleMessage() throws InterruptedException {

    //given
    Message msg = new Message();
    msg.setId("1");
    msg.setName("a message from JH");

    //when
    producer.send(msg);


    //then
    Thread.sleep(1000L);
    assertThat(outputCapture.toString().contains("1, a message from JH")).isTrue();
    }

    }
  2. 테스트 클래스를 실행해봅시다. 실행 전에 실행 환경 변수에 SERIALIZABLE_PACKAGES 설정값을 추가 해 주어야
    오류 메세지를 만나지 않고 pojo를 전송하고 받을 수 있게 됩니다. 보안 상 신뢰할 수 있는 패키지를 환경설정값에 넣어야만 하도록 되어있기 때문입니다.

    1
    -Dorg.apache.activemq.SERIALIZABLE_PACKAGES="<패키지 경로>"
  3. 테스트 클래스 실행 결과

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    . ____ _ __ _ _
    /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
    ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
    \\/ ___)| |_)| | | | | || (_| | ) ) ) )
    ' |____| .__|_| |_|_| |_\__, | / / / /
    =========|_|==============|___/=/_/_/_/
    :: Spring Boot :: (v2.0.5.RELEASE)

    2018-10-14 16:33:24.224 INFO 54635 --- [ main] c..jmstest.JmstestApplicationTests : Starting JmstestApplicationTests on TF-Mac-023ui-MacBook-Pro.local with PID 54635 (started by jeonghunKim in /Users/tf-mac-023/IdeaProjects/jmstest)
    2018-10-14 16:33:24.226 INFO 54635 --- [ main] c..jmstest.JmstestApplicationTests : No active profile set, falling back to default profiles: default
    2018-10-14 16:33:24.387 INFO 54635 --- [ main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@6c1a5b54: startup date [Sun Oct 14 16:33:24 KST 2018]; root of context hierarchy
    2018-10-14 16:33:26.012 INFO 54635 --- [ main] o.s.c.support.DefaultLifecycleProcessor : Starting beans in phase 2147483647
    2018-10-14 16:33:27.233 INFO 54635 --- [ActiveMQ Task-1] o.a.a.t.failover.FailoverTransport : Successfully connected to ssl://
    2018-10-14 16:33:27.350 INFO 54635 --- [ main] c..jmstest.JmstestApplicationTests : Started JmstestApplicationTests in 3.996 seconds (JVM running for 6.586)
    2018-10-14 16:33:27.600 INFO 54635 --- [ActiveMQ Task-1] o.a.a.t.failover.FailoverTransport : Successfully connected to ssl://
    1, a message from JH
    2018-10-14 16:33:28.961 INFO 54635 --- [ Thread-5] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@6c1a5b54: startup date [Sun Oct 14 16:33:24 KST 2018]; root of context hierarchy
    2018-10-14 16:33:28.962 INFO 54635 --- [ Thread-5] o.s.c.support.DefaultLifecycleProcessor : Stopping beans in phase 2147483647

    Process finished with exit code 0

Comment and share

  • page 1 of 1

shockshot@naver.com

author.bio


author.job