Notepad

주제 DTO, Domain, VO 에 대한 글

  1. 요청(파라미터)는 DTO의 필요성을 명확히 알겠음. 그러면 반환해줄때의 객체의 형태는 DTO여야 하는가 아니면 VO여야 하는가…

  2. DTO, VO, Domain 객체 구분해서 사용하기를 원함.

DTO가 비지니스 종속적여야 하는 이유.
-> 공통된 기능을 가졌다고 해서 다른 비즈니스를 공통된 DTO 사용했을 경우에 추후 다른 속성을 사용하는 비즈니스가 들어왔을경우
이 DTO를 분리시켜야함. 단순히 DTO를 분리시킨다는 관점에서는 큰 문제는 아니지만. 그 기능이 인터페이스로 규격화 되어있을 경우
DTO가 특정항공사는 다른 이슈가 생기기 때문에 이는 인터페이스화 시킬수 없는 문제가 발생.

DTO와 Domain이 구분되어져야 하는 이유.
-> 해당 객체가 생성되는 근본적인 이유가 다르기 때문이다. DTO가 가져야할 특성이 있는데 그 중 주된것들은.fS
첫번째 validation 체크이다. DTO는 데이터를 주고받을 때 사용하는 목적으로 만들어진 객체이기 때문에. validation 체크가 기본적으로 통상 들어간다.
Domain 객체와 이러한 DTO를 같이 사용하게 되면 validation 체크를 DTO 자체적으로 구현하지 못하고 별도 서비스단 등에서 제어해야하는 문제가 발생한다.

두번째. 마샬링, 파싱 등의 작업을 할때 조건을 걸어야 하기때문. 조건은 결국 비지니스 종속적일 수밖에 없고 이를 domain 객체에 넣어둘수는 없다.
domain은 모두 공통적으로 사용해야하는 객체이기 때문에 어떤 한 특정 비지스에 종속적인 객체를 만드는 것은 좋지 않은 패턴이다.

세번쨰. DTO 를 디자인하는 것과 도메인 객체를 디자인하는 근본적인 목적이 다르다. DTO 는 딱 주고 받아야할 데이터만 최대한 간단하게 주고받고록
만들어야하지만 도메인 개체는 객체 모델링 관점에서 실세상을 투영되도록 각 도메인 객체들끼리 관계를 맺어주며 구현해야하기 때문에
설계되는 모양이 근본적으로 다를수 밖에 없다.

Map<> 은 언제 사용되어야 할까.
-> Map은 type safe 하지 않기 때문에 일반적으로는 DTO를 생성하고 해당 객체를 Map을 대신 사용하는 것을 추천한다. 그러나 만약 Map을 사용하는 곳의 Scope가
한 함수의 내부적으로만 사용하고 해당 함수를 사용하는 다른 클라이언트들에게 이 Map 객체를 전달할 일이 없다면 해당 타입을 다른 클라이언트가 지속 사용하는
현상이 일어나지 않음으로 구조가 심플하고, 스코프가 한 함수 내부라면 사용하는것이 좋을듯 하다. Java POJO 객체를 계속 만들어 나가는것도 결국에는 양이 엄청나기 때문
이정도로 사이즈가 작고 클라이언트에게 큰 영향이 없다면 그냥 Collection Framework를 사용하는 것도 좋을 것 같다.

  1. innerClass 를 활용한 DTO, Domain 구분 무엇이 DTO이고 무엇이 Domain 일까
    • 다른패키지에서 같은 이름으로 만들수는있어도 이를 한 클래스에서 같이 사용할 수는 없다.

Domain.DTO -> 이미 장점을 알고있는 구조.
각 비지니스에 맞추어서 POJO 객체를 쓸 수 있다.
Spring Validation 가능 위에서도 이미 언급되고 있음.
Flat Design

Boundary.DTO, Package.DTO
-> innerClass 로 구현
-> 영역의 구분에 대한 장점 패키지로 하는것 보다 더 제한적
-> 이름이 중복될수 없음.
-> 특정 비지니스에 종속되어 있는 DTO 임을 명시 가능.

DTO.Domain -> 불가능한 구조 Domain 이라함은 공용으로 사용하는 POJO 객체 인데. 이것이 특정 비지니스에 맞추어져 있는 DTO 안에 들어갈 수 없음.

Domain.DTO.DTO -> 가능은 함 그러나 권장하지 않는 구조, Domain.DTO 에 들어가는 조건 중 하나는 Domain과 관련된 DTO가 들어가야함.

Domain.DTO 구조로 설계했는데 Domain에 마땅히 들어갈 데이터가 없다면 Boundary.DTO 형태로 변경을 고려해야함.

Domain이 될수있는 조건은 프로젝트 모든 곳에서 사용할 수 있는 구조인가를 고려해야함. 그리고 그런 데이터인지. 그렇기때문에 일반적으로 데이터베이스 엔티티 구조를 따른다

DAO는 결국 DB와 DOMAIN을 연결하는 DTO의 일종이다. 보통 DTO는 뷰와 DOMAIN을 연결하는 요소로 언급함. 내가볼때는 둘다 DTO로 봐도되지 않나.. 용어의 정의를 내가 내리지 않았으니
이 둘을 구분은 해야할듯 DTO는 View - Domain, DAO는 Domain - DB

jpa를 활용하지 않고 최대한 의존성이 적은 쿼리를 짜는 방법 -> 결국 동적쿼리를 써야함으로 DB 캐싱은 사용할 수 없다 성능은 떨어짐 이슈. 그리고 쿼리가 최대한 의존적이지 않ㄱ ㅔ짜기 때문에
대부분 분리된 방식으로 짬 -> 트랜잭션 관리 가 더커짐.

테이블이 3개를 기준으로 쿼리를 가져와야 한다면. 해당 테이블 기준으로 Domain 객체 3개를 우선 만들고 이들을 합쳐 DAO를 만든다 그래서 파라미터로 들어가야할 부분에는 이 값들을 넣어준다.
쿼리는 가능한 한도 내에서 동적쿼리로 작성

내가아까 푸쉬를 했나??

  1. set하는 코드보다 builder 하는 코드가 좋은이유 가독성 측면에서

10000줄짜리 한 스코프의 코드가 있다고 하자. 이거 자체가 굉장히 억지스러운 일이지만.
만약 1번째 줄에 선언된 변수가 10000째에서 사용된다고 하면. 그 사이의 작업이 어떤것이 있었는지 다 확인해야한다.
그런데 만약 10000번째에 반환시키는 변수를 빌더로 사용한다면. 거기안에 들어가있는 변수들만 쫓아가면 된다
변수를 좀더 사용하기 때문에 공간복잡도는 조금더 올라가겠지만. 가독성은 향상된다.
그러나 변수명을 모두 지정해줘야하는 문제가 있다.

주제 . 인터페이스 설계의 이점

주제. null 체크 (with optional)

한 함수 스코프로 잡고 그 안에서의 null 처리를 하라.
무의미한 null 처리라면 로깅의 목적으로로 쓸수 있다.
객체에서 객체를 계속 참조하는 구조가 nullpointerexception의 취약점이다.
set 할때보다 get 할때를 유의하라.

주제. 리플렉션 기본 문법.

- 필드 가져오기
- 메소드 가져오기
- private 접근하기
- getter/setter 접근하기

주제. 리플렉션 과 스프링 생성자로 만들어진 객체는 바로 빈주입이 되지 않는다

  1. 생성자주입 사용시 NoArgs 생성자 사용을 어떻게 하는가 with reflection
    생성자 주입을 사용하면 해당 객체를 생성할때 해당 의존성을 반드시 전달해야하는 문제.
    리플렉션, new 로 객체생성시 빈등록이 안되는 문제에대해서

해결방법

  1. 스프링 빈 컨테이너를 가지고있는 ApplicationContext 객체를 Static 메모리에 올리고 접근할 수 있도록 함.
  2. 리플렉션시 생성자에 ApplicationContext 객체를 파라미터로 주입받고 이 객체를 통해 내부적으로 빈 주입을 진행.

주제. FLUX

Redux

사용방법

Redux 비동기 통신

비동기 Action에 대해서

Redux 로깅

Redux Thunk

typesafe-actions

Mobx

사용방법

주제. HttpClient 를 활용한 HTTP 통신 방법

URI 생성

쿠키 생성

Header 주입

GET, POST

1
2
3
4
5
6
7
8
9
10
11
12
13

public static String get(String url) throws URISyntaxException, IOException {
URI uri = new URI("http://batchadmin.interparktour.com/admin/airControlTimerGet.do?searchAirline=AL");
uri = new URIBuilder(uri).addParameter("searchAirline", "AL").build();

HttpClient httpClient = HttpClientBuilder.create().build();

HttpGet httpGet = new HttpGet(uri);
httpGet.setHeader("Content-Type", "application/json");
HttpResponse response = httpClient.execute(httpGet);
HttpEntity entity = response.getEntity();
return EntityUtils.toString(entity);
}
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

public static String get(String url) throws URISyntaxException, IOException {
URI uri = new URI(url);
uri = new URIBuilder(uri).addParameter("searchAirline", "AL").build();

/** 쿠키 처리 */
BasicCookieStore cookieStore = new BasicCookieStore();
BasicClientCookie cookie = new BasicClientCookie("ADMIN_ID", "N19147");
cookie.setDomain("batchadmin.interparktour.com");
cookie.setPath("/");
cookieStore.addCookie(cookie);

BasicClientCookie cookie2 = new BasicClientCookie("AWP_KEY", Utility.getEncSHA256("interpark_N19147_airadmin12#$"));
cookie2.setDomain("batchadmin.interparktour.com");
cookie2.setPath("/");
cookieStore.addCookie(cookie2);

HttpClient httpClient = HttpClientBuilder.create().setDefaultCookieStore(cookieStore).build();

/** Get 혹은 Post */
HttpGet httpGet = new HttpGet(uri);
httpGet.setHeader("Content-Type", "application/json");
HttpResponse response = httpClient.execute(httpGet);
HttpEntity entity = response.getEntity();

return EntityUtils.toString(entity);
}

주제. async, await, promise, then

주제 React에서 Component 와 Data

Component 설계할때 데이터에 영향이 있는것은 주입받도록 만들고 나머지는 해당 컨테이너에서 처리하면된다.
추상화 기법은 Component 작업할때 별로 좋지 않은것 같다.
styled 와 관련된 Component 를 두고 예를 추상화 시켜서 쓰려고 했는데. 결국 css 문법을 계속 한번씩 확인하게 되고.

width, height 등과 관련된 Layout Component 하나 두고 이걸로 저런 값들을 조절하고

button, label 등의 글자나 특색이있는 UI 요소들은 다른 component로 둬서 둘을 결합해서 사용하는게 좋은듯.

이런식으로 기본적으로 내가 만든 모든 컴포넌트는 레이아웃에 특별함이 필요하다하면 저걸로 감싸는거지.

레이아웃적인 요소와 특정 컴포넌트만 가지는 요소를 구분시키고 이 레이아웃적인 요소를 감싸게끔 만드는게 진짜 조은듯

Data는 State를 기준으로 Store, Action, Reducer를 설계한다. State를 표현하기 위한 Store, State를 조작하기 위한 Action 들 인것이다.

주제. 제네릭 메서드 빌더

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

package com.awb.domair.adminAwp.vo;

public class Response<T> {
private int status;
private T body;

public int getStatus() {
return status;
}

public void setStatus(int status) {
this.status = status;
}

public T getBody() {
return body;
}

public void setBody(T body) {
this.body = body;
}

@Override
public String toString() {
return "Response{" +
"status=" + status +
", body=" + body +
'}';
}

public static class Builder<T> {
private final Response<T> response;

public Builder() {
this.response = new Response<>();
}

public Builder<T> status(int val) {
this.response.status = val;
return this;
}

public Builder<T> body(T val) {
this.response.body = val;
return this;
}

public Response<T> build() {
return response;
}
}
}

O.O.P 와 F.P 문제 해결 접근 방법 비교

DBResultSet <-> Map, T

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

import com.fasterxml.jackson.databind.ObjectMapper;
import java.sql.SQLException;
import java.util.*;

public class ResultSetUtil {

/** DBResultSet -> List<Map<String, Object>> */
public static List<Map<String, Object>> toMap(DBResultSet rset) throws SQLException {
List<Map<String, Object>> rows = new ArrayList<>();
rset.initRow();

while (rset.next()) {
Map<String, Object> column = new HashMap<>();
for(int i$ = 1; i$ <= rset.columnCount; i$++) {
column.put(rset.getColumnName(i$), rset.getObject(i$));
}
rows.add(column);
}

return rows;
}

/** List<Map<String, String>> -> List<T> */
public static <T> List<T> toDomain(List<Map<String, Object>> maps, Class<T> clazz) {
List<T> objects = new ArrayList<>();
for(Map<String, Object> map : maps) {
objects.add(new ObjectMapper().convertValue(map, clazz));
}

return objects;
}

/** DBResultSet -> List<T> */
public static <T> List<T> toDomain(DBResultSet rset, Class<T> clazz) throws SQLException {
return toDomain(toMap(rset), clazz);
}

/** List<Map<String, String>> -> DBResultSet */
public static DBResultSet toResultSet(List<Map<String, Object>> rows) throws SQLException {
Set<String> columnNames = rows.get(0).keySet();
EditableResultSet ers = new EditableResultSet(columnNames.toArray(new String[0]));

for(Map<String, Object> row : rows) {
ers.absolute(ers.addRow());
for(String columnName : columnNames) {
ers.setValue(columnName, row.get(columnName));
}
}

return ers;
}

/** List<T> -> DBResultSet */
public static <T> DBResultSet parseResultSet(List<T> rows) throws SQLException {
return toResultSet(toMap(rows));
}

/** List<T> -> List<Map<String, String>> */
@SuppressWarnings("unchecked")
public static <T> List<Map<String, Object>> toMap(List<T> rows) {
List<Map<String, Object>> maps = new ArrayList<>();

for(T row : rows) {
maps.add(new ObjectMapper().convertValue(row, Map.class));
}

return maps;
}
}


주제. 리플렉션

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

public static <T extends AirLine> AirLine create(String airline) throws Exception {
Class<T> clazz = (Class<T>) Class.forName("com.air.interpark.ariLine.AirLine" + airline);

Class[] classArgs = {};
Constructor<T> constructor = clazz.getDeclaredConstructor(classArgs);

return constructor.newInstance();
}

public static AirLine createRaw(String airline) throws Exception {
Class clazz = Class.forName("com.air.interpark.ariLine.AirLine" + airline);

Class[] classArgs = {};
Constructor constructor = clazz.getDeclaredConstructor(classArgs);

return (AirLine) constructor.newInstance();
}

주제 Spring AOP

Spring boot 안쓰고 Spring 으로 할경우 Dependencies 추가해줘야함.

1
2
compile group: 'org.aspectj', name: 'aspectjrt', version: '1.9.2'
compile group: 'org.aspectj', name: 'aspectjweaver', version: '1.9.2'

아래와 같이 Aspect 소스를 짰음

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

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@Aspect
public class AirlineLog {
@Around("within(com.air.interpark.ariLine.common.*)")
public Object loggerAop(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("It's loggerAop");
return joinPoint.proceed();
}
}

어드바이스 종류는 대따 많음

Around 어드바이스

타겟의 메서드가 호출되기 이전(before) 시점과 이후 (after) 시점에 모두 처리해야 할 필요가 잇는

부가기능을 정의한다.

-> Joinpoint 앞과 뒤에서 실행되는 Advice

Before 어드바이스

타겟의 메서드가 실행되기 이전(before) 시점에 처리해야 할 필요가 있는 부가기능을 정의한다.

-> Jointpoint 앞에서 실행되는 Advice

After Returning 어드바이스

타겟의 메서드가 정상적으로 실행된 이후(after) 시점에 처리해야 할 필요가 있는 부가기능을 정의한다.

-> Jointpoint 메서드 호출이 정상적으로 종료된 뒤에 실행되는 Advice

After Throwing 어드바이스

타겟의 메서드가 예외를 발생된 이후(after) 시점에 처리해야 할 필요가 있는 부가기능을 정의한다.

-> 예외가 던져 질때 실행되는 Advice

조인포인트를 어노테이션으로도 줄수 있음

@EnableAspectJAutoProxy
->

ProxyTargetClass
->

joinPoint.getArgs() 이걸로 타켓 메소드에 들어가는 parameter 정보를 가져올 수 있다.

pointjoint 할때 쓰는 expression 들도 알아야 할듯

리플렉션하는 소스를 스테이틱이 아닌 스프링 빈을 활용한 싱글톤 형태로 구현하면.
리플렉션시 빈을 주입받지 못하는 오류를 해결할 수 있다.

주제. AOP

  1. 기본적인 로깅

  2. 중복되는 로깅 소스 발견 -> 새로운 로깅 객체 생성 하여 콜

  3. 비지니스 로직에 새로운 로깅 객체를 계속 콜하는 소스가 발견 -> 메인 비지니스 소스에는 로깅 같은 소스를 안넣었으면 좋겟음. -> proxy 패턴 사용

  4. 인터페이스를 활용한 proxy 패턴 확장

  5. Spring AOP 활용.

  6. Annotation 활용한 AOP

  7. Spring AOP + custom annotation + SpEL

주제. Annotation

1
2
3
4
5
6
7
8
9
10
11
12
13

package reflection;

import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* 어노테이션 정의
*/
@Retention(RUNTIME)
public @interface Check {
String value();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

package reflection;

/**
* 조작 대상 클래스
*/
@Check("클래스에 부여")
public class AnnotationSample {

@Check("메소드에 부여")
public void print(@Check("인수에 부여") String message) {
System.out.println(message);
}
}

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

package reflection;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

public class AnnotationMainSample {

public static void main(String[] args) throws NoSuchMethodException {

Class<AnnotationSample> clazz = AnnotationSample.class;

/////////////////////////////////////////////////////////////////////////////
// 클래스에 부여한 @Check를 얻기
/////////////////////////////////////////////////////////////////////////////
{
Check check = clazz.getAnnotation(Check.class);
System.out.println(check);
System.out.println(check.value());
}

/////////////////////////////////////////////////////////////////////////////
// 메소드에 부여한 @Check를 얻기
/////////////////////////////////////////////////////////////////////////////
Method method = clazz.getMethod("print", String.class);

// @Check가 부여되어 있는 경우
if (method.isAnnotationPresent(Check.class)) {
Check check = method.getAnnotation(Check.class);
System.out.println(check);
System.out.println(check.value());
}

/////////////////////////////////////////////////////////////////////////////
// 인수에 부여한 @Check를 얻기
/////////////////////////////////////////////////////////////////////////////
for (Annotation[] params : method.getParameterAnnotations()) {
for (Annotation annotation : params) {
Check check = (Check) annotation;
System.out.println(check);
System.out.println(check.value());
}
}
}
}

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

import java.lang.annotation.*;

@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME) // 컴파일 이후에도 JVM에 의해서 참조가 가능합니다.
//@Retention(RetentionPolicy.CLASS) // 컴파일러가 클래스를 참조할 때까지 유효합니다.
//@Retention(RetentionPolicy.SOURCE) // 어노테이션 정보는 컴파일 이후 없어집니다.
@Target({
ElementType.PACKAGE, // 패키지 선언시
ElementType.TYPE, // 타입 선언시
ElementType.CONSTRUCTOR, // 생성자 선언시
ElementType.FIELD, // 멤버 변수 선언시
ElementType.METHOD, // 메소드 선언시
ElementType.ANNOTATION_TYPE, // 어노테이션 타입 선언시
ElementType.LOCAL_VARIABLE, // 지역 변수 선언시
ElementType.PARAMETER, // 매개 변수 선언시
ElementType.TYPE_PARAMETER, // 매개 변수 타입 선언시
ElementType.TYPE_USE // 타입 사용시
})
public @interface MyAnnotation {
/* enum 타입을 선언할 수 있습니다. */
public enum Quality {BAD, GOOD, VERYGOOD}
/* String은 기본 자료형은 아니지만 사용 가능합니다. */
String value();
/* 배열 형태로도 사용할 수 있습니다. */
int[] values();
/* enum 형태를 사용하는 방법입니다. */
Quality quality() default Quality.GOOD;
}

위와같이 다양한 형태의 값을 넣을 수 있음

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

@Around(value = "@annotation(domActionLog)")
public Object domActionLogging(ProceedingJoinPoint joinPoint, DomActionLog domActionLog) throws Throwable {
log.info(domActionLog.toString());

log.info("REQ : " + Arrays.toString(joinPoint.getArgs()));

Class<AirlineRS> clazz = AirlineRS.class;

Method method = clazz.getMethod("pnrStatus", PnrStatus.Req.class);
if(method.isAnnotationPresent(DomActionLog.class)) {
DomActionLog annotation = method.getAnnotation(DomActionLog.class);
System.out.println(annotation);
System.out.println(annotation.prId());
System.out.println(annotation.pnr1());
}

Object obj = null;
try {
obj = joinPoint.proceed();
log.info("RES : " + obj.toString());
} catch (Exception e) {
log.info("RES : " + e.toString());
}

return obj;
}

DomActionLog 부분을 Around annotation 이름과 맞추면 파라미터로 받을 수 있다.

내가 만들어둔 소스

dependencies

1
2
compile group: 'org.aspectj', name: 'aspectjrt', version: '1.9.2'
compile group: 'org.aspectj', name: 'aspectjweaver', version: '1.9.2'

config (It should be turned on @EnableAspectJAutoProxy with proxyTargetClass = true option)

1
2
3
4
5
6
7
8
9
10

@Configuration
@EnableWebMvc // <annotation-driven />
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ComponentScan(basePackages = { "com.air.*" })
@Slf4j
public class WebConfig extends WebMvcConfigurerAdapter{
...
}

annotation

1
2
3
4
5
6
7
8
9
10
11
12
13

import java.lang.annotation.*;

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DomActionLog {
String prId();
String pnr1();
String param() default "";
}


Aspect Source

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

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;



@Slf4j
@Aspect
@Component
public class CommonAspect {

@Around(value = "@annotation(DomActionLog)")
public Object domActionLogging(ProceedingJoinPoint joinPoint) throws Throwable {

MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
DomActionLog annotation = method.getAnnotation(DomActionLog.class);

Object pnr1 = getDynamicValue(signature.getParameterNames(), joinPoint.getArgs(), annotation.pnr1());
Object prId = getDynamicValue(signature.getParameterNames(), joinPoint.getArgs(), annotation.prId());

Object result = joinPoint.proceed();

return result;
}

public static Object getDynamicValue(String[] parameterNames, Object[] args, String key) {
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();

for(int i$=0; i$< parameterNames.length; i$++) {
context.setVariable(parameterNames[i$], args[i$]);
}

return parser.parseExpression(key).getValue(context, Object.class);
}
}


target source

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

@Override
@DomActionLog(prId = "#req.prId", pnr1 = "#req.pnr1")
public PnrStatus.Res pnrStatus(PnrStatus.Req req) throws Exception {
/* 필수 파라미터 확인 */
if(req == null || StringUtils.isEmpty(req.getPnr1())) {
throw new Exception("파라미터 'pnr1' 정보가 null 이거나 공백입니다");
}

/* 리트리브 */
RetrieveDetail retrieveDetail = (RetrieveDetail) jaxb.unMarshall(RetrieveDetail.class,
airLineGateway.call(
AirLineGateway.ServiceUrl.RETRIEVE.name(),
airLineRQBuilder.retrieveRQBuilder(new PassengerRecordVO(req.getPnr1(), "-1", req.getAirline()), ""),
req.getAirline()
)
);

/* 취소 리트리브 */
List<RetrieveResult> retrieveResults = new ArrayList<>();
for(String ticketNo : this.ticketNos(retrieveDetail)) {
retrieveResults.add((RetrieveResult) jaxb.unMarshall(RetrieveResult.class,
airLineGateway.call(
AirLineGateway.ServiceUrl.TICKET_RETRIEVE.name(),
airLineRQBuilder.ticketDetailRetrieve(req.getPnr1(), "", req.getAirline(), Collections.singletonList(ticketNo)),
req.getAirline()
)
));
}

return this.pnrStatus(retrieveResults);
}

Test code

1
2
3
4
5
6

@Test
public void run() throws Exception {
PnrStatus.Res res = airlineRS.pnrStatus(new PnrStatus.Req.Builder().airline("RS").prId("50001234").pnr1("JR85F").build());
}

예외처리

3가지 형태의 케이스가 존재한다.

  1. 정상적인 경우
    -> 소스를 짤때 일반적으로 이 부분에 몰두를 하고 작업을 한다.

  2. 처리가 가능한 예외가 발생한 경우
    -> 보완로직을 수행하도록 분기한다.

  3. 처리가 불가능한 예외가 발생한 경우
    -> 상세 에러 메시지를 포함하여 Exception을 던진다.

null 체크할때 마지막애는 필수로 안해도된다

주제. JPA Tree Structure 매핑하기

객체의 구조가 트리구조일때 이걸 엔티티로 어떻게 매핑??

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

package com.kidongyun.bridge.api.vo;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;

@Slf4j
@Getter
@Setter
public class TreeNode<T> {
private T data;
private TreeNode<T> parent;
private List<TreeNode<T>> children;

public TreeNode() {
children = new ArrayList<>();
}

public List<T> traversal() {
return dfs(this, new ArrayList<>());
}

public void addChild(T data) {
children.add(new TreeNode.Builder<T>().data(data).parent(this).build());
}

public void removeChild(Predicate<T> condition) {
List<TreeNode<T>> result = new ArrayList<>();
for(TreeNode<T> child : children) {
if(condition.test(child.data)) {
continue;
}
result.add(child);
}
this.setChildren(result);
}

private List<T> dfs(TreeNode<T> treeNode, List<T> dataList) {
dataList.add(treeNode.data);

for(TreeNode<T> child : treeNode.children) {
child.dfs(child, dataList);
}

return dataList;
}

public TreeNode<T> of(Set<T> set) {
return new TreeNode.Builder<T>().build();
}

@Override
public String toString() {
return data.toString();
}

public static class Builder<T> {
private final TreeNode<T> treeNode;

public Builder() {
this.treeNode = new TreeNode<>();
}

public Builder<T> data(T val) {
this.treeNode.data = val;
return this;
}

public Builder<T> parent(TreeNode<T> val) {
this.treeNode.parent = val;
return this;
}

public Builder<T> children(List<TreeNode<T>> val) {
this.treeNode.children = val;
return this;
}

public TreeNode<T> build() {
return this.treeNode;
}
}
}


lombok

@Builder.Default

@Builder

@SuperBuilder

@ToString(callSuper)

주제

Jackson 의존성 주입시 스프링 부트에서 버전관리를 해주끼 때문에 굳이 버저닝 하지 말자.
했다가 스프링이랑 버전안맞아서 골치 아프다.

예외처리

컨트롤러쪽과 서비스쪽의 예외처리 방식이 조금은 달라야할 것 같다. 둘다 모두 paramter, response 강경하게 예외 처리를 했더니. 서비스쪽에서 이 예외처리한 코드 때문에 컨트롤러가 유연하게 해당 서비스를 사용하지 못하는 케이스가 발생한다.

그래서 다시 정리한 내용은 우선 parameter, response 이 들이 예외처리를 해야하는 포인트인 것은 맞다. 다만 그렇다면 이것을 모두 한 함수 범위 내에서 검증을 해야하는가 에 대한 질문은 아니다로 바뀌었다.

컨트롤러는 스프링에서 엔드포인트이기 때문에 모두 검증을 하는것이 맞지만. 서비스쪽에서는 유연성을 보다 가지게 하기 위해서. paramter 검증을 하되 예외가 발생했을시 exception을 던지지말고 null 객체가 반환되게끔 return 하는 구조를 가지도록 하고

response 검증은 컨트롤러쪽에서 할수 있도록 null을 포함한 객체로 내려주자.

1
2
3
4
5
6
7
8
9
10
11
12
13
컨트롤러 
parameter 검증 : 강경하게 모두 해야함
response 검증 : 강경하게 모두 해야함

서비스
parameter 검증 : 검증을 하지만 문제가 있을경우 null을 반환
response 검증 : 검증하지 않고 컨트롤러에게 넘김

null을 반환하는 종류
(1) 단일 객체를 반환하는 함수 : Optional 을 사용하여 Optional.empty() 반환
(2) Collection 객체를 반환하는 함수 : 해당 컬렉션의 빈 객체를 반환 ex) Set.of();
(3) void 인 함수 : Exception을 던진다.

React.Hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
useEffect(() => {
if(selectCell instanceof Objective && selectCell.type === "OBJECTIVE") {
postOrPut = "PUT";
setObjective(selectCell);

if(selectCell.endDateTime === undefined) {
return;
}

const deadline: string = selectCell.endDateTime.toISOString();
setYear(deadline.substring(0, 4));
setMonth(deadline.substring(5, 7));
setDate(deadline.substring(8, 10));

console.log(postOrPut);
}
}, [selectCell]);

useEffect에 두번째 인자로 들어로는 배열에 값을 넣으면, 해당 값들이 변화가 생길때 useEffect() 첫번째 인자로 넣은 함수가 호출된다.

WebMvcTest 슬라이스 테스트할떄 Spring Security 적용 안하기

1
2
3
4
5
6
7
8
9
10
11

@Slf4j
@RunWith(SpringRunner.class)
@WebMvcTest(controllers = ObjectiveController.class, excludeAutoConfiguration = SecurityAutoConfiguration.class)
public class ObjectiveControllerTest {
@MockBean
private SecurityConfig securityConfigMock;
@MockBean
private TokenProvider tokenProviderMock;
}

JPA @Enumerated 애노테이션

STRING으로 지정하면 “MALE”, “FEMALE” 문자열 자체가 저장된다.

Gender에 선언되어 있는 순서가 값이 되기 때문에 2가 DB에 저장된다.

Enum 기반의 객체를 QueryDSL로 쿼리를 날리면 타입이 안맞는 오류가 계속 뜨는데 이거 해결해야함..

maven

1
2
3
4
5

<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

이거는 depencency에 넣으면 안댄다. 버전 관리가 안되서 unknown 뜬다.

intellij 는 초기 설정시에 openjdk를 다운받아서 자동으로 설정해서 사용한다.
이거를 환경변수로 등록해두어야 mvn 을 cli로 돌릴때 오류가 안생긴다.

openjdk 경로를 확인하려면 maven install을 돌려보면 console 창에 java가 실행되는 경로가 나오는데 해당 경로 가지고 환경변수 등록한다.

maven cli 에서 테스트를 스킵하고 jar 생성하려면 아래 명령어 추가 입력

1
mvn package -DskipTests

명령어 스킵하는 설정을 프로젝트의 디폴트로 가져가려면 아래 빌드 의존성을 추가하고 옵션을 추가

1
2
3
4
5
6
7
<plugin> 
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>

Optional 에서 orElse() 함수의 페이크

이름이 조건이 해당하지 않으면 실행되지 않을 것 같이 생겼지만. 이 함수도 근본적으로 자바로 이루어진 함수이다.
실행되고 그 내부적으로 비즈니스가 실행되지 않을 뿐이다. 실제로는 함수 자체는 실행된다.

Array 먼저시작하는 json 객체 TypeReference 객체 안쓰고 쉽게 readvalue() 하는 방법

1
Arrays.asList(objectMapper.readValue(response, Plan[].class));

Maven 프로젝트 임포트시 Class 객체를 인지를 못하고 Java 파일로 머물러 있을 때

maven 프로젝트의 소스는 기본적으로 src > main > java 폴더 안 부터 패키지로 인정이 된다.
해당 디렉토리를 생성해주고 그다음에 java 폴더를 src 디렉토리로 마킹해주자.

Jwt DatatypeConverter 관련 오류가 발생시 아래 의존성이 있는지 확인

Spring Security 프레임워크와 JsonWebToken 을 이용해 웹인증을 구현하는 도중, 로그인을 시도해서 서버로부터 토큰을 얻으려고 할때, 웹서버 로그에 다음과 같은 에러가 발생했습니다

java.lang.NoClassDefFoundError: Could not initialize class javax.xml.bind.DatatypeConverterImpl

오류 로그를 자세하게 읽어보고, 관련하여 코딩한 컨트롤러와 클래스들을 살펴보아도 문제가 없는 것 같아서 구글링했습니다. (stackoverflow.com/questions/55606519/getting-exception-java-lang-noclassdeffounderror-could-not-initialize-class-jav)

위 링크에서 답변한 솔루션을 참고하여 저같은 경우는 다음과 같은 종속성을 pom.xml 에 추가하여 해결하였습니다.

1
2
3
4
5
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.1</version>
</dependency>

위 종속성을 추가하니, 서버에서 정상적으로 토큰을 반환했습니다.

궁금한 내용은 댓글 남겨주세요!

Share