0227 ~ 0305


# 0227 ~ 0305

# 0227 - Filter๋ฅผ ํ†ตํ•œ Logging

  • LoggingFilter
@Component
public class LoggingFilter extends OncePerRequestFilter {

    protected static final Logger log = LoggerFactory.getLogger(LoggingFilter.class);

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        MDC.put("traceId", UUID.randomUUID().toString());
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        if (isAsyncDispatch(request)) {
            filterChain.doFilter(request, response);
        } else {
            doFilterWrapped(new RequestWrapper(request), new ResponseWrapper(response), filterChain);
        }
        stopWatch.stop();
        log.info("Response Time : {}(ms)", stopWatch.getTotalTimeMillis());
        MDC.clear();
    }

    protected void doFilterWrapped(RequestWrapper request, ContentCachingResponseWrapper response, FilterChain filterChain) throws ServletException, IOException {
        try {
            logRequest(request);
            filterChain.doFilter(request, response);
        } finally {
            logResponse(response);
            response.copyBodyToResponse();
        }
    }

    private static void logRequest(RequestWrapper request) throws IOException {
        String queryString = request.getQueryString();
        log.info("Request : {} uri=[{}] content-type=[{}]",
                request.getMethod(),
                queryString == null ? request.getRequestURI() : request.getRequestURI() + queryString,
                request.getContentType()
        );

        logPayload("Request", request.getContentType(), request.getInputStream());
    }

    private static void logResponse(ContentCachingResponseWrapper response) throws IOException {
        logPayload("Response", response.getContentType(), response.getContentInputStream());
    }

    private static void logPayload(String prefix, String contentType, InputStream inputStream) throws IOException {
        boolean visible = isVisible(MediaType.valueOf(contentType == null ? "application/json" : contentType));
        if (visible) {
            byte[] content = StreamUtils.copyToByteArray(inputStream);
            if (content.length > 0) {
                String contentString = new String(content);
                log.info("{} Payload: {}", prefix, contentString);
            }
        } else {
            log.info("{} Payload: Binary Content", prefix);
        }
    }

    private static boolean isVisible(MediaType mediaType) {
        final List<MediaType> VISIBLE_TYPES = Arrays.asList(
                MediaType.valueOf("text/*"),
                MediaType.APPLICATION_FORM_URLENCODED,
                MediaType.APPLICATION_JSON,
                MediaType.APPLICATION_XML,
                MediaType.valueOf("application/*+json"),
                MediaType.valueOf("application/*+xml"),
                MediaType.MULTIPART_FORM_DATA
        );

        return VISIBLE_TYPES.stream()
                .anyMatch(visibleType -> visibleType.includes(mediaType));
    }
}
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
  • RequestWrapper
public class RequestWrapper extends HttpServletRequestWrapper {

    private final byte[] cachedInputStream;

    public RequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        InputStream requestInputStream = request.getInputStream();
        this.cachedInputStream = StreamUtils.copyToByteArray(requestInputStream);
    }

    @Override
    public ServletInputStream getInputStream() {
        return new ServletInputStream() {
            private final InputStream cachedBodyInputStream = new ByteArrayInputStream(cachedInputStream);

            @Override
            public boolean isFinished() {
                try {
                    return cachedBodyInputStream.available() == 0;
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return false;
            }

            @Override
            public boolean isReady() {
                return true;
            }

            @Override
            public void setReadListener(ReadListener readListener) {
                throw new UnsupportedOperationException();
            }

            @Override
            public int read() throws IOException {
                return cachedBodyInputStream.read();
            }
        };
    }
}
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
  • ResponseWrapper
public class ResponseWrapper extends ContentCachingResponseWrapper {
    public ResponseWrapper(HttpServletResponse response) {
        super(response);
    }
}
1
2
3
4
5

# [ Filter์—์„œ ์š”์ฒญ๋œ HTTP ์ •๋ณด์— ์ ‘๊ทผํ•˜๊ธฐ ]

  • ContentCachingRequestWrapper
    ContentCachingRequestWrapper๋ฅผ ์ƒ์†๋ฐ›์•„ Logging Filter๋ฅผ ๋งŒ๋“ ๋‹ค๋ฉด HTTP Request๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ ์ฝ์–ด๋“ค์ผ ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ Request PayLoad๋ฅผ ์ฝ์ง€ ๋ชปํ•˜๊ณ , content-type์ด 'x-www-form-urlencoded'๋งŒ ์ง€์›ํ•œ๋‹ค.

  • HttpServletRequestWrapper
    HttpServletRequestWrapper๋ฅผ ์ƒ์†๋ฐ›์•„์„œ HTTP Request์˜ ์š”์ฒญ๊ณผ Payload๋ฅผ ์ถœ๋ ฅ


# 0302 - ํ…œํ”Œ๋ฆฟ ๋ฉ”์†Œ๋“œ ํŒจํ„ด (Template Method Pattern)

์•Œ๊ณ ๋ฆฌ์ฆ˜์˜ ์ผ๋ถ€ ๋‹จ๊ณ„๋ฅผ ์„œ๋ธŒํด๋ž˜์Šค์—์„œ ์ •์˜ํ•œ๋‹ค.

# ์˜๋„

# [ ์˜๋„ ]

  • GoF๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ดํŒจํ„ด์˜ ์˜๋„๋ฅผ ์„ค๋ช…ํ•œ๋‹ค.

๊ฐ์ฒด์˜ ์—ฐ์‚ฐ์—๋Š” ์•Œ๊ณ ๋ฆฌ์ฆ˜์˜ ๋ผˆ๋Œ€๋งŒ์„ ์ •์˜ํ•˜๊ณ  ๊ฐ ๋‹จ๊ณ„์—์„œ ์ˆ˜ํ–‰ํ•  ๊ตฌ์ฒด์  ์ฒ˜๋ฆฌ๋Š” ์„œ๋ธŒํด๋ž˜์Šค ์ชฝ์œผ๋กœ ๋ฏธ๋ฃน๋‹ˆ๋‹ค. ์•Œ๊ณ ๋ฆฌ์ฆ˜์˜ ๊ตฌ์กฐ ์ž์ฒด๋Š” ๊ทธ๋Œ€๋กœ ๋†”๋‘” ์ฑ„ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ฐ ๋‹จ๊ณ„ ์ฒ˜๋ฆฌ๋ฅผ ์„œ๋ธŒํด๋ž˜์Šค์—์„œ ์žฌ์ •์˜ํ•  ์ˆ˜ ์žˆ๊ฒŒํ•ฉ๋‹ˆ๋‹ค.

  • ์‹ค์šฉ์ฃผ์˜ ๋””์ž์ธ ํŒจํ„ด์—์„œ๋Š” ์ด ํŒจํ„ด์ด ์ƒ์† ๊ธฐ๋ฐ˜์˜ ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ ์‚ฌ์šฉ๋œ๋‹ค๊ณ  ์„ค๋ช…ํ•œ๋‹ค.

Template Method๋Š” ๋ณดํ†ต ์ƒ์† ๊ธฐ๋ฐ˜์˜ ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ ์‚ฌ์šฉ๋œ๋‹ค. ํ”„๋ ˆ์ž„์›Œํฌ๋Š” ์ž‘์—…์˜ 90% ์ •๋„๋ฅผ ๊ธฐ๋ฐ˜ ํด๋ž˜์Šค๋ฅผ ํ†ตํ•ด ์ œ๊ณตํ•˜๋ฉฐ, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์šฉ๋„์— ๋งž๊ฒŒ ๋งž์ถคํ™”ํ•  ํ•„์š”๊ฐ€ ์žˆ๋Š” ๋ถ€๋ถ„์€ ์ถ”์ƒ ๋ฉ”์†Œ๋“œ๋กœ ๋‚จ๊ฒจ๋†“๋Š”๋‹ค. ์ด ๋ง์€ ๊ณง ๊ธฐ๋ฐ˜ ํด๋ž˜์Šค๊ฐ€ ์ถ”์ƒ ํ…œํ”Œ๋ฆฟ ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค๋Š” ์˜๋ฏธ์ด๋‹ค. ์‚ฌ์šฉ์ž๋Š” ํด๋ž˜์Šค๋ฅผ ์ƒ์†ํ•˜๊ณ  ์ถ”์ƒ ๋ฉ”์†Œ๋“œ๋ฅผ ํ•„์š”์— ๋งž๊ฒŒ ๊ตฌํ˜„ํ•จ์œผ๋กœ์จ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋œ๋‹ค.

  • ํŒจํ„ด์„ ํ™œ์šฉํ•œ ๋ฆฌํŒฉํ„ฐ๋ง์—์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค๋ช…ํ•œ๋‹ค.

ํ…œํ”Œ๋ฆฟ ๋ฉ”์„œ๋“œ๋Š” '์•Œ๊ณ ๋ฆฌ์ฆ˜์—์„œ ๋ถˆ๋ณ€์ ์ธ ๋ถ€๋ถ„์€ ํ•œ ๋ฒˆ๋งŒ ๊ตฌํ˜„ํ•˜๊ณ  ๊ฐ€๋ณ€์ ์ธ ๋™์ž‘์€ ์„œ๋ธŒํด๋ž˜์Šค์—์„œ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋‚จ๊ฒจ๋‘” ๊ฒƒ'์„ ๋งํ•œ๋‹ค. ์„œ๋ธŒํด๋ž˜์Šค์— ๋ถˆ๋ณ€์ ์ธ ๋ถ€๋ถ„๊ณผ ๊ฐ€๋ณ€์ ์ธ ๋ถ€๋ถ„์ด ๋’ค์„ž์—ฌ ์žˆ๋‹ค๋ฉด, ๋ถˆ๋ณ€์ ์ธ ๋ถ€๋ถ„์ด ์—ฌ๋Ÿฌ ์„œ๋ธŒํด๋ž˜์Šค์—์„œ ์ค‘๋ณต๋  ๊ฒƒ์ด๋‹ค. ์ด๋Ÿฐ ์ฝ”๋“œ๋ฅผ Template Method ํŒจํ„ด์œผ๋กœ ๋ฆฌํŒฉํ„ฐ๋งํ•˜๋ฉด, ๋ถˆ๋ณ€์ ์ธ ๋ถ€๋ถ„์— ๋Œ€ํ•œ ๊ตฌํ˜„์€ ํ•œ ๊ณณ์—๋งŒ, ์ฆ‰ ์ˆ˜ํผํด๋ž˜์Šค ๋ฉ”์„œ๋“œ ๋‚ด์˜ ์ผ๋ฐ˜ํ™”๋œ ์•Œ๊ณ ๋ฆฌ์ฆ˜์—๋งŒ ์กด์žฌํ•˜๊ฒŒ ๋˜๋ฏ€๋กœ ์ฝ”๋“œ ์ค‘๋ณต์ด ์‚ฌ๋ผ์ง„๋‹ค.

ํ…œํ”Œ๋ฆฟ ๋ฉ”์„œ๋“œ์˜ ๋ถˆ๋ณ€์  ๋™์ž‘์€ ๋‹ค์Œ์„ ํฌํ•จํ•œ๋‹ค.

  • ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ๊ตฌ์„ฑํ•˜๋Š” ๋ฉ”์„œ๋“œ ๋ชฉ๋ก๊ณผ ๊ทธ ํ˜ธ์ถœ ์ˆœ์„œ
  • ์„œ๋ธŒํด๋ž˜์Šค๊ฐ€ ๊ผญ ์˜ค๋ฒ„๋ผ์ด๋“œํ•ด์•ผ ํ•  ์ถ”์ƒ ๋ฉ”์„œ๋“œ
  • ์„œ๋ธŒํด๋ž˜์Šค๊ฐ€ ์˜ค๋ฒ„๋ผ์ด๋“œํ•ด๋„ ๋˜๋Š” ํ›… ๋ฉ”์„œ๋“œ hook method, ์ฆ‰ ๊ตฌ์ฒด ๋ฉ”์„œ๋“œ

# [ ์šฉ์–ด ]

  • ํ…œํ”Œ๋ฆฟ ๋ฉ”์†Œ๋“œ
    • ํ•„์ˆ˜ ์ฒ˜๋ฆฌ ์ ˆ์ฐจ๋ฅผ ์ •์˜ํ•œ ๋ฉ”์†Œ๋“œ
    • ์„œ๋ธŒํด๋ž˜์Šค๊ฐ€ ์˜ค๋ฒ„๋ผ์ด๋“œํ•˜๋Š” ์ถ”์ƒ ๋ฉ”์†Œ๋“œ๋“ค์„ ์‚ฌ์šฉํ•˜์—ฌ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์ •์˜ํ•˜๋Š” ๋ฉ”์†Œ๋“œ
  • ํ›… ์—ฐ์‚ฐ(hook operation)
    • ํ•„์š”ํ•˜๋‹ค๋ฉด ์„œ๋ธŒํด๋ž˜์Šค์—์„œ ํ™•์žฅํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋ณธ์ ์ธ ํ–‰๋™์„ ์ œ๊ณตํ•˜๋Š” ์—ฐ์‚ฐ(๋ฉ”์†Œ๋“œ)
    • ๊ธฐ๋ณธ์ ์œผ๋กœ๋Š” ์•„๋ฌด ๋‚ด์šฉ๋„ ์ •์˜ํ•˜์ง€ ์•Š๋Š”๋‹ค

# [ ๊ตฌํ˜„ ํŒ ]

  • ํ…œํ”Œ๋ฆฟ ๋ฉ”์†Œ๋“œ๊ฐ€ ํ˜ธ์ถœํ•˜๋Š” ๋ฉ”์†Œ๋“œ๋“ค์„ ํ…œํ”Œ๋ฆฟ ๋ฉ”์†Œ๋“œ๋งŒ ํ˜ธใ…œใ„นํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•œ๋‹ค.
    • protected ์ ‘๊ทผ ์ œํ•œ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค
  • ํ…œํ”Œ๋ฆฟ ๋ฉ”์†Œ๋“œ๋Š” ์˜ค๋ฒ„๋ผ์ด๋“œํ•  ์ˆ˜ ์—†๋„๋ก ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•œ๋‹ค.
    • Java๋ผ๋ฉด ํ…œํ”Œ๋ฆฟ ๋ฉ”์†Œ๋“œ์— final์„ ๋‹ฌ์•„์ฃผ๋ฉด ๋œ๋‹ค.
  • ๊ตฌํ˜„ํ•ด์•ผ ํ•˜๋Š” abstract ๋ฉ”์†Œ๋“œ์˜ ์ˆ˜๊ฐ€ ๋„ˆ๋ฌด ๋งŽ์•„์ง€๋‹ˆ ์•Š๋„๋ก ์ฃผ์˜ํ•œ๋‹ค.
  • ์žฌ์ •์˜ํ•  abstract ๋ฉ”์†Œ๋“œ๋Š” ์‹๋ณ„ํ•˜๊ธฐ ์‰ฝ๋„๋ก ์ ‘๋‘์‚ฌ๋ฅผ ๋ถ™์—ฌ์ฃผ์ž
    • ์˜ˆ๋ฅผ ๋“ค์–ด ๋ฉ”์†Œ๋“œ ์ด๋ฆ„์ด Do๋กœ ์‹œ์ž‘ํ•˜๋„๋ก ํ•œ๋‹ค.

# ํ—ค๋“œ ํผ์ŠคํŠธ ๋””์ž์ธ ํŒจํ„ด์˜ ์˜ˆ์ œ

public class Coffee {
    // ์ปคํ”ผ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•
    void prepareRecipe() {
        boilWater();
        brewCoffeeGrinds();
        pourInCup();
        addSugarAndMilk();
    }
    public void boilWater() {
        System.out.println("๋ฌผ ๋“์ด๋Š” ์ค‘");
    }
    public void brewCoffeeGrinds() {
        System.out.println("ํ•„ํ„ฐ๋ฅผ ํ†ตํ•ด์„œ ์ปคํ”ผ๋ฅผ ์šฐ๋ ค๋‚ด๋Š” ์ค‘");
    }
    public void pourInCup() {
        System.out.println("์ปต์— ๋”ฐ๋ฅด๋Š” ์ค‘");
    }
    public void addSugarAndMilk() {
        System.out.println("์„คํƒ•๊ณผ ์šฐ์œ ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ์ค‘");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Tea {
    // ํ™์ฐจ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•
    void prepareRecipe() {
        boilWater();
        steepTeaBag();
        pourInCup();
        addLemon();
    }
    public void boilWater() {
        System.out.println("๋ฌผ ๋“์ด๋Š” ์ค‘");
    }
    public void steepTeaBag() {
        System.out.println("์ฐจ๋ฅผ ์šฐ๋ ค๋‚ด๋Š” ์ค‘");
    }
    public void pourInCup() {
        System.out.println("์ปต์— ๋”ฐ๋ฅด๋Š” ์ค‘");
    }
    public void addLemon() {
        System.out.println("๋ ˆ๋ชฌ์„ ์ถ”๊ฐ€ํ•˜๋Š” ์ค‘");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

ํ…œํ”Œ๋ฆฟ ๋ฉ”์†Œ๋“œ ํŒจํ„ด์€ ์œ„์˜ ๋‘ ํด๋ž˜์Šค๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ฆฌํŒฉํ† ๋งํ•œ๋‹ค.

  • ๊ณตํ†ต์ ์ธ ๋ถ€๋ถ„์„ ๋ฝ‘์•„ ์ถ”์ƒ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ ๋‹ค.
    • ์•Œ๊ณ ๋ฆฌ์ฆ˜์˜ ์„ธ๋ถ€ ํ•ญ๋ชฉ์—์„œ ์ฐจ์ด๊ฐ€ ์žˆ๋Š” ๊ณณ์€ ์ถ”์ƒ ๋ฉ”์†Œ๋“œ๋กœ ์ •์˜ํ•œ๋‹ค.
public abstract class CaffeineBeverage {
    // ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ๊ฐ–๊ณ  ์žˆ๋Š” ์ด ๋ฉ”์†Œ๋“œ๋ฅผ 'ํ…œํ”Œ๋ฆฟ ๋ฉ”์†Œ๋“œ'๋ผ ๋ถ€๋ฅธ๋‹ค
    final void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        addCondiments();
    }

    abstract void brew();           // ์„œ๋ธŒํด๋ž˜์Šค์—์„œ ๊ตฌํ˜„
    abstract void addCondiments();  // ์„œ๋ธŒํด๋ž˜์Šค์—์„œ ๊ตฌํ˜„

    void boilWater() {
        System.out.println("๋ฌผ ๋“์ด๋Š” ์ค‘");
    }
    void pourInCup() {
        System.out.println("์ปต์— ๋”ฐ๋ฅด๋Š” ์ค‘");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Coffee extends CaffeineBeverage {
    public void brew() {
        System.out.println("ํ•„ํ„ฐ๋กœ ์ปคํ”ผ๋ฅผ ์šฐ๋ ค๋‚ด๋Š” ์ค‘");
    }
    public void addCondiments() {
        System.out.println("์„คํƒ•๊ณผ ์ปคํ”ผ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ์ค‘");
    }
}
1
2
3
4
5
6
7
8

public class Tea extends CaffeineBeverage {
    public void brew() {
        System.out.println("์ฐจ๋ฅผ ์šฐ๋ ค๋‚ด๋Š” ์ค‘");
    }
    public void addCondiments() {
        System.out.println("๋ ˆ๋ชฌ์„ ์ถ”๊ฐ€ํ•˜๋Š” ์ค‘");
    }
}
1
2
3
4
5
6
7
8
9

# [ hook ๋ฉ”์†Œ๋“œ ]

  • ์„œ๋ธŒํด๋ž˜์Šค ๊ตฌํ˜„์‹œ ์œตํ†ต์„ฑ์„ ๋ฐœํœ˜ํ•˜๊ธฐ ์œ„ํ•œ ๋ฉ”์†Œ๋“œ
  • ์ถ”์ƒ ํด๋ž˜์Šค์—์„œ ์„ ์–ธํ•˜์ง€๋งŒ ๊ธฐ๋ณธ์ ์ธ ๋‚ด์šฉ๋งŒ ๊ตฌํ˜„๋˜์–ด ์žˆ๊ฑฐ๋‚˜ ๋‚ด์šฉ์ด ๋น„์–ด ์žˆ๋Š” ๋ฉ”์†Œ๋“œ
public abstract class CaffeineBeverage {
    // ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ๊ฐ–๊ณ  ์žˆ๋Š” ์ด ๋ฉ”์†Œ๋“œ๋ฅผ 'ํ…œํ”Œ๋ฆฟ ๋ฉ”์†Œ๋“œ'๋ผ ๋ถ€๋ฅธ๋‹ค
    final void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        // ๊ณ ๊ฐ์ด ์›ํ•˜๋Š” ๊ฒฝ์šฐ์—๋งŒ ์ฒจ๊ฐ€๋ฌผ์„ ๋„ฃ๋Š”๋‹ค
        if (customerWantsCondiments()) {
            addCondiments();
        }
    }

    abstract void brew();           // ์„œ๋ธŒํด๋ž˜์Šค์—์„œ ๊ตฌํ˜„
    abstract void addCondiments();  // ์„œ๋ธŒํด๋ž˜์Šค์—์„œ ๊ตฌํ˜„

    void boilWater() {
        System.out.println("๋ฌผ ๋“์ด๋Š” ์ค‘");
    }
    void pourInCup() {
        System.out.println("์ปต์— ๋”ฐ๋ฅด๋Š” ์ค‘");
    }

    // ์ด ๋ฉ”์†Œ๋“œ๊ฐ€ hook ๋ฉ”์†Œ๋“œ
    boolean customerWantsCondiments() {
        return true;
    }
}
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

# ๋žŒ๋‹ค๋ฅผ ์‚ฌ์šฉํ•ด ์„œ๋ธŒ ํด๋ž˜์Šค ์ œ๊ฑฐํ•˜๊ธฐ

1๊ฐœ์˜ ์ถ”์ƒ ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํ…œํ”Œ๋ฆฟ ๋ฉ”์†Œ๋“œ๊ฐ€ ์žˆ๋‹ค.

abstract class OnlineBanking {
    // template method
    public void processCustomer(int id) {
        Customer c = Database.getCustomerWithId(id);
        makeCustomerHappy(c);
    }
    abstract void makeCustomerHappy(Customer c);
}
1
2
3
4
5
6
7
8

์ด ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ์„œ๋ธŒ ํด๋ž˜์Šค๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค.

class OnlineBankingKorea extends OnlineBanking {
    @Override
    void makeCustomerHappy(Customer c) {
        System.out.println("์•ˆ๋…•ํ•˜์„ธ์š” " + c.getName());
    }
}
1
2
3
4
5
6

๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

new OnlineBankingKorea().processCustomer(1337);
1

๊ตฌํ˜„ํ•ด์•ผ ํ•  ์ถ”์ƒ ๋ฉ”์†Œ๋“œ๊ฐ€ ํ•˜๋‚˜ ๋ฟ์ด๋ฏ€๋กœ ๋žŒ๋‹ค์˜ ์‚ฌ์šฉ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ๋‹ค.
OnlineBacking์—์„œ abstract ํ‚ค์›Œ๋“œ๋ฅผ ์‚ญ์ œํ•˜๊ณ , processCustomer ๋ฉ”์†Œ๋“œ๊ฐ€ Consumer๋ฅผ ๋ฐ›๋„๋ก ์ˆ˜์ •ํ•œ๋‹ค.

class OnlineBanking {
    public void processCustomer(int id, Consumer<Customer> makeCustomerHappy) {
        Customer c = Database.getCustomerWithId(id);
        makeCustomerHappy.accept(c);
    }
}
1
2
3
4
5
6

์ด์ œ ์ƒ์† ์—†์ด OnlineBanking ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

new OnlineBanking()
    .processCustomer(1337,
        (Customer c) -> System.out.println("์•ˆ๋…•ํ•˜์„ธ์š”" + c.getName())
);
1
2
3
4

# ๊ณ ๋ คํ•  ์ ๋“ค

  • From: ์‹ค์šฉ์ฃผ์˜ ๋””์ž์ธ ํŒจํ„ด

Template Method ํŒจํ„ด์€ ๊ฐ€๋Šฅํ•œ ์ ˆ์ œํ•ด ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค. ํด๋ž˜์Šค ์ž์ฒด๊ฐ€ ์ „์ ์œผ๋กœ ํŒŒ์ƒ ํด๋ž˜์Šค์˜ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•์— ์˜์กดํ•˜๋Š” ์ผ์ข…์˜ 'ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€' ๋˜๋ฉด ์ด ์—ญ์‹œ ๋งค์šฐ ๋ถ€์„œ์ง€๊ธฐ ์‰ฝ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๊ธฐ๋ฐ˜ ํด๋ž˜์Šค๋Š” ๋งค์šฐ ๊นจ์ง€๊ธฐ ์‰ฝ๋‹ค. ๋‚˜๋Š” MFC์—์„œ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ํ•  ๋•Œ, ๋งˆ์ดํฌ๋กœ์†Œํ”„ํŠธ๊ฐ€ ์ƒˆ๋กœ์šด ๋ฒ„์ „์„ ์ผใ„น๋ฆฌ์ฆˆํ•  ๋•Œ๋งˆ๋‹ค ์ „์ฒด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์žฌ์ž‘์„ฑํ•ด์•ผ๋งŒ ํ–ˆ๋˜ ์•…๋ชฝ์„ ๋–จ์ณ๋ฒ„๋ฆด ์ˆ˜๊ฐ€ ์—†๋‹ค. ์ข…์ข… ์ฝ”๋“œ๋Š” ์ž˜ ์ปดํŒŒ์ผ๋˜์ง€๋งŒ, ๋ช‡๋ช‡ ๊ธฐ๋ฐ˜ ํด๋ž˜์Šค์˜ ๋ฉ”์†Œ๋“œ๊ฐ€ ๋ณ€๊ฒฝ๋˜์–ด ํ”„๋กœ๊ทธ๋žจ์ด ์ œ๋Œ€๋กœ ์‹คํ–‰๋˜์ง€ ์•Š์•˜๋˜ ๊ฒƒ์ด๋‹ค.

Template Method ํŒจํ„ด์€ ๋˜ํ•œ '์ด๋””์—„'๊ณผ 'ํŒจํ„ด'์‚ฌ์ด๊ฐ€ ์–ผ๋งˆ๋‚˜ ๊ฐ€๊นŒ์šธ ์ˆ˜ ์žˆ๋Š”์ง€๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ์˜ˆ์ด๊ธฐ๋„ ํ•˜๋‹ค. Template Method ํŒจํ„ด์€ ๋‹คํ˜•์„ฑ์„ ์กฐ๊ธˆ ์‘์šฉํ–ˆ์„ ๋ฟ ํŒจํ„ด์ด๋ž€ ์˜๊ด‘์˜ ํƒ€์ดํ‹€์„ ์“ฐ๊ธฐ์—” ๋ถ€์กฑํ•˜๋‹ค๊ณ  ์ฃผ์žฅํ•  ์ˆ˜๋„ ์žˆ๋Š” ๊ฒƒ์ด๋‹ค.

  • From: ํŒจํ„ด์„ ํ™œ์šฉํ•œ ๋ฆฌํŒฉํ„ฐ๋ง

Template Method ํŒจํ„ด์„ ๊ตฌํ˜„ํ•  ๋•Œ์— ์‹ค๋ฌด์ ์ธ ์ฃผ์˜์‚ฌํ•ญ์ด ํ•˜๋‚˜ ์žˆ๋Š”๋ฐ, ์„œ๋ธŒํด๋ž˜์Šค์—์„œ ์˜ค๋ฒ„๋ผ์ด๋“œํ•ด์•ผํ•˜๋Š” ๋ฉ”์„œ๋“œ๊ฐ€ ๋„ˆ๋ฌด ๋งŽ์œผ๋ฉด ๊ณค๋ž€ํ•˜๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. ์„œ๋ธŒํด๋ž˜์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ๊ฐ€ ์–ด๋ ค์›Œ์ง€๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๊ทธ๋ž˜์„œ [Design Patterns]์—์„œ๋Š” ์„œ๋ธŒํด๋ž˜์Šค์—์„œ ์˜ค๋ฒ„๋ผ์ด๋“œํ•ด์•ผ ํ•˜๋Š” ์ถ”์ƒ ๋ฉ”์„œ๋“œ์˜ ๊ฐœ์ˆ˜๋ฅผ ์ตœ์†Œํ™”ํ•ด์•ผ ํ•œ๋‹ค๊ณ  ์ง€์ ํ•œ๋‹ค. ๊ทธ๋Ÿฌ์ง€ ์•Š์œผ๋ฉด ํ…œํ”Œ๋ฆฟ ๋ฉ”์„œ๋“œ์˜ ๋‚ด์šฉ์„ ์ž์„ธํžˆ ์‚ดํŽด๋ณด์ง€ ์•Š๊ณ ๋Š” ํ”„๋กœ๊ทธ๋ž˜๋จธ๊ฐ€ ์–ด๋–ค ๋ฉ”์„œ๋“œ๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋“œํ•ด์•ผ ํ• ์ง€ ์‰ฝ๊ฒŒ ์•Œ ์ˆ˜ ์—†์„ ๊ฒƒ์ด๋‹ค.

Java ๊ฐ™์€ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด์—์„œ๋Š” ํ…œํ”Œ๋ฆฟ ๋ฉ”์„œ๋“œ๋ฅผ final๋กœ ์„ ์–ธํ•ด ์„œ๋ธŒํด๋ž˜์Šค๊ฐ€ ์‹ค์ˆ˜๋กœ ์˜ค๋ฒ„๋ผ์ด๋“œํ•˜๋Š” ๊ฒƒ์„ ์˜ˆ๋ฐฉํ•  ์ˆ˜๋„ ์žˆ๋‹ค. ๋‹จ, ์ด๋Ÿฐ ๋ฐฉ๋ฒ•์€ ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ์—์„œ ํ…œํ”Œ๋ฆฟ ๋ฉ”์„œ๋“œ์˜ ๋ถˆ๋ณ€์ ์ธ ๋ถ€๋ถ„์„ ์ „ํ˜€ ๋ณ€๊ฒฝํ•  ํ•„์š”๊ฐ€ ์—†๋Š” ๊ฒƒ์ด ํ™•์‹คํ•  ๋•Œ์—๋งŒ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.


# 0304 - @Constraint, ConstraintValidator

์Šคํ”„๋ง์—์„œ๋Š” JSR 303 ๊ธฐ๋ฐ˜ ์–ด๋…ธํ…Œ์ด์…˜ ๊ธฐ๋ฐ˜์œผ๋กœ ์ผ๊ด€์„ฑ ์žˆ๋Š” Validation์„ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ธฐ๋ณธ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” ์–ด๋…ธํ…Œ์ด์…˜ ๋ง๊ณ  @Cnstraint๋กœ ์ปค์Šคํ…€ Validation์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

# [ ์žฅ์  ]

  • ์ผ๊ด€์„ฑ์žˆ๋Š” ์ฒ˜๋ฆฌ ๋ฐฉ๋ฒ• - ๊ฒ€์ฆ๋ฐฉ๋ฒ•๊ณผ ๊ฒ€์ฆ์‹œ์ ์— ๋Œ€ํ•ด ํ†ต์ผ์„ฑ์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค. ํ•ด๋‹น ์ปค์Šคํ…€ Validation์„ ๊ตฌํ˜„ํ•˜๋ฉด @NotNull @Empty์™€ ๊ฐ™์€ ๋‹จ๊ณ„์ธ interceptor์—์„œ ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.
  • ๊ฐ„๊ฒฐํ•จ - ๋กœ์ง ํ๋ฆ„์— ๋Œ€ํ•œ ์ปจํ…์ŠคํŠธ๊ฐ€ ์‘์ถ•๋ผ ์žˆ์–ด ์ ์žฌ์ ์†Œ์— ์‚ฌ์šฉ๋œ๋‹ค๋ฉด ๋ถˆํ•„์š”ํ•œ ๋ฐ˜๋ณต์ฝ”๋“œ๊ฐ€ ์ค„์–ด๋“ ๋‹ค.
  • ์ผ๊ด€์„ฑ ์žˆ๋Š” ErrorResponse - ConstraintViolationException ์—๋Ÿฌ๋ฅผ ํ†ตํ•ด ์‘๋‹ต์„ ์ผ๊ด€์„ฑ์žˆ๊ฒŒ ๋ฆฌํ„ดํ•  ์ˆ˜ ์žˆ๋‹ค.

# [ ๊ตฌํ˜„ ]

  • ์–ด๋…ธํ…Œ์ด์…˜ ์ƒ์„ฑ(ํ•„๋“œ ์˜ˆ์ œ)
@Constraint(validatedBy = GenderValidator.class)
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidGender {
    String message() default "์˜ฌ๋ฐ”๋ฅธ ์„ฑ๋ณ„์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}
1
2
3
4
5
6
7
8
  • ConstraintValidator๋ฅผ ๊ตฌํ˜„ํ•œ Validator ์ƒ์„ฑ
public class GenderValidtor implements ConstraintValidator<ValidGender, String>{

    @Override
    public boolean isValid(String gender, ConstraintValidatorContext context) {
        try {
            Gender.valueOf(gender.toUpperCase()); 
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
  • dto
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class CreateUserReqDto {
        @Email(message = "์˜ฌ๋ฐ”๋ฅธ ์ด๋ฉ”์ผ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”")
        private String email;

        private String password;

        @ValidGender(message = "์˜ฌ๋ฐ”๋ฅธ ์„ฑ๋ณ„์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”") <-- CustomValidator
        private Gender gender;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
  • ์ถ”๊ฐ€์˜ˆ์ œ
@Constraint(validatedBy = NotEqualValidator.class)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface NotEqual {
    String message() default "์ƒํ–‰ ์—ญ๊ณผ ํ•˜ํ–‰ ์—ญ์€ ๊ฐ™์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    String upStationId();

    String downStationId();

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @interface List {
        NotEqual[] value();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class NotEqualValidator implements ConstraintValidator<NotEqual, Object> {
    private String upStationId;
    private String downStationId;

    @Override
    public void initialize(NotEqual constraintAnnotation) {
        this.upStationId = constraintAnnotation.upStationId();
        this.downStationId = constraintAnnotation.downStationId();
    }

    @Override
    public boolean isValid(Object object, ConstraintValidatorContext context) {
        Object upStationValue = new BeanWrapperImpl(object).getPropertyValue(upStationId);
        Object downStationValue = new BeanWrapperImpl(object).getPropertyValue(downStationId);
        return !upStationValue.equals(downStationValue);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@NotEqual(upStationId = "upStationId", downStationId = "downStationId")
public class SectionRequest {
    @NotNull
    private Long upStationId;

    @NotNull
    private Long downStationId;
    
    // ...
}
1
2
3
4
5
6
7
8
9
10

# [ ๊ฒฐ๋ก  ]

์ปค์Šคํ…€ ์–ด๋…ธํ…Œ์ด์…˜์„ ์ž˜ ์ด์šฉํ•˜๋ฉด ๋ถˆํ•„์š”ํ•œ ๋ฐ˜๋ณต์ฝ”๋“œ๊ฐ€ ์ค„์–ด๋“ค๊ณ , ๋น„์ง€๋‹ˆ์Šค ๋กœ์ง์— ๋” ์ง‘์ค‘ ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์žฅ์ ์ด ์žˆ๋‹ค.
๋‹ค๋งŒ, ์ปค์Šคํ…€ ์–ด๋…ธํ…Œ์ด์…˜์€ ์˜๋„์™€ ๋ชฉ์ ์„ ๋ช…ํ™•ํžˆ ํ•˜์—ฌ ๊ตฌ์„ฑ์›๊ฐ„ ๊ณต๊ฐ๋Œ€๋ฅผ ์ด๋ฃฌ ํ›„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

Last update: March 9, 2023 23:26
Contributors: jaesungahn91