๐ŸŽฏ 15์ฃผ์ฐจ ํ•™์Šต ์ปค๋ฆฌํ˜๋Ÿผ โ€” Spring MVC ์™„์ „ ์ •๋ณต

F-lab 1~14์ฃผ์ฐจ ์ดํ›„ Claude๊ฐ€ ์ž„์˜๋กœ ๊ตฌ์„ฑํ•œ ํ•™์Šต ๊ฒฝ๋กœ.
F-lab์—์„œ ๋‹ค๋ฃจ์ง€ ์•Š์€ ๊ฐ€์žฅ ํฐ ๊ณต๋ฐฑ ์˜์—ญ์ธ Spring MVC์˜ ๋‚ด๋ถ€ ๋ฉ”์ปค๋‹ˆ์ฆ˜ ์„ ์ •๋ณตํ•˜๊ณ , 8-9์ฃผ์ฐจ AOP์™€์˜ ๊ด€๊ณ„๋ฅผ ๋ช…ํ™•ํžˆ ์ •๋ฆฌํ•œ๋‹ค.

  • DispatcherServlet ๋™์ž‘ ์›๋ฆฌ (9๋‹จ๊ณ„ ์š”์ฒญ ์ฒ˜๋ฆฌ ํ๋ฆ„)
  • ์š”์ฒญ/์‘๋‹ต ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ ๋ฉ”์ปค๋‹ˆ์ฆ˜
  • Filter vs Interceptor vs AOP (๋ฉด์ ‘ ๋‹จ๊ณจ)
  • ์˜ˆ์™ธ ์ฒ˜๋ฆฌ์™€ Validation ํ‘œ์ค€ ํŒจํ„ด
  • REST API ์„ค๊ณ„์™€ ๊ตฌํ˜„

4๋…„ ์ฐจ Spring Boot ๊ฐœ๋ฐœ์ž๊ฐ€ "๋งค์ผ ์“ฐ์ง€๋งŒ ๋‚ด๋ถ€๋Š” ๋ชจ๋ฅด๋Š”" ์˜์—ญ์„ ์ฑ„์šฐ๋Š” ์ฃผ์ฐจ๋‹ค.


๐Ÿค” ์™œ 15์ฃผ์ฐจ์— Spring MVC์ธ๊ฐ€

1~14์ฃผ์ฐจ์˜ ๊ฐ€์žฅ ํฐ ๊ณต๋ฐฑ:

์˜์—ญ์ฃผ์ฐจ์ƒํƒœ
Java ์–ธ์–ด1~3์ฃผ์ฐจโœ…
๋™์‹œ์„ฑ4์ฃผ์ฐจโœ…
Spring IoC/DI5์ฃผ์ฐจโœ…
์›น ์ธํ”„๋ผ ์ž…๋ฌธ (Servlet/JSP)6์ฃผ์ฐจโœ…
JPA + ํŠธ๋žœ์žญ์…˜7์ฃผ์ฐจโœ…
Spring AOP8-9์ฃผ์ฐจโœ…
ํŠธ๋žœ์žญ์…˜ ์ •๋ฆฌ + ๊ฒฉ๋ฆฌ ์ˆ˜์ค€10์ฃผ์ฐจโœ…
JPA ๊นŠ์ด11-12์ฃผ์ฐจโœ…
DB ํŽ€๋”๋ฉ˜ํ„ธ + ์šด์˜13-14์ฃผ์ฐจโœ…
Spring MVC ๋‚ด๋ถ€ ๋ฉ”์ปค๋‹ˆ์ฆ˜๋ฏธ๋“ฑ์žฅโŒ

์™œ ์ด ์ฃผ์ œ๊ฐ€ ๊ฒฐ์ •์ ์ธ๊ฐ€:
1. ๋ฉด์ ‘ ๋‹จ๊ณจ โ€” DispatcherServlet 9๋‹จ๊ณ„, Filter/Interceptor/AOP ๋น„๊ต๋Š” ๊ฑฐ์˜ 100%
2. 8-9์ฃผ์ฐจ AOP์˜ ์™„์„ฑ โ€” Filter/Interceptor์™€ ๋น„๊ตํ•ด์•ผ AOP์˜ ์ง„์งœ ์œ„์น˜๊ฐ€ ๋ณด์ž„
3. 6์ฃผ์ฐจ Servlet์˜ ์ž์—ฐ์Šค๋Ÿฌ์šด ํ™•์žฅ โ€” Front Controller ํŒจํ„ด์œผ๋กœ ํ†ตํ•ฉ
4. ์‹ค๋ฌด ๋””๋ฒ„๊น… ๋Šฅ๋ ฅ โ€” ์š”์ฒญ์ด ์–ด๋””์„œ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌ๋˜๋Š”์ง€ ์•Œ์•„์•ผ ๋ฌธ์ œ ์ถ”์  ๊ฐ€๋Šฅ


๐Ÿ“Š ํ•™์Šต ๊ฒฝ๋กœ ํ•œ๋ˆˆ์— ๋ณด๊ธฐ

[Phase 1] ์›น ์š”์ฒญ์˜ ์—ฌ์ • โ€” Servlet์—์„œ Spring MVC๊นŒ์ง€
   โ†“
[Phase 2] DispatcherServlet 9๋‹จ๊ณ„ ์ฒ˜๋ฆฌ ํ๋ฆ„ โ—„ ์ •์  1
   โ†“
[Phase 3] ์š”์ฒญ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ (@RequestBody ๋“ฑ์˜ ๋‚ด๋ถ€)
   โ†“
[Phase 4] ์‘๋‹ต ์ฒ˜๋ฆฌ์™€ ViewResolver
   โ†“
[Phase 5] Filter vs Interceptor vs AOP โ—„ ์ •์  2 (โ˜… ๋ฉด์ ‘ ๋‹จ๊ณจ)
   โ†“
[Phase 6] ์˜ˆ์™ธ ์ฒ˜๋ฆฌ์™€ Validation
   โ†“
[Phase 7] REST API ์„ค๊ณ„์™€ ์‹ค์ „

์ด 7 Phase ร— 24 Unit โ€” ์ •์  2๊ฐœ๋ฅผ ๊ฐ€์ง„ ๋‹จ์ผ ์ฃผ์ฐจ.

๐Ÿ”— 1~15์ฃผ์ฐจ ํ๋ฆ„ ์ •๋ฆฌ

์ฃผ์ฐจ์ฃผ์ œ์˜๋ฏธ
1~3์ฃผ์ฐจJava ์–ธ์–ด์™€ ํ‘œํ˜„๋ ฅ๊ธฐ์ดˆ
4์ฃผ์ฐจ๋™์‹œ์„ฑยท๋ฉ€ํ‹ฐ์Šค๋ ˆ๋”ฉ๋™์‹œ์„ฑ
5์ฃผ์ฐจSpring IoC/DISpring ์ž…๋ฌธ
6์ฃผ์ฐจ์›น ์ธํ”„๋ผ + DB ์ ‘๊ทผ์‹ค์ „ ํ™˜๊ฒฝ ์ž…๋ฌธ
7์ฃผ์ฐจJPA + ํŠธ๋žœ์žญ์…˜ ์ž…๋ฌธ๋ฐ์ดํ„ฐ ์ถ”์ƒํ™”
8-9์ฃผ์ฐจAOP ๋ฉ”์ปค๋‹ˆ์ฆ˜ + ํŠธ๋žœ์žญ์…˜ ์ „ํŒŒAOP ์ •๋ณต
10์ฃผ์ฐจํŠธ๋žœ์žญ์…˜ ์ •๋ฆฌ + ๊ฒฉ๋ฆฌ ์ˆ˜์ค€ํŠธ๋žœ์žญ์…˜ ๋งˆ๋ฌด๋ฆฌ
11-12์ฃผ์ฐจJPA ์˜์†์„ฑ + ์—ฐ๊ด€๊ด€๊ณ„ + N+1JPA ์ •๋ณต
13-14์ฃผ์ฐจDB ํŽ€๋”๋ฉ˜ํ„ธ + ์šด์˜DB ์ •๋ณต
15์ฃผ์ฐจ (์ง€๊ธˆ)Spring MVC ๋‚ด๋ถ€ + REST API์›น ๊ณ„์ธต ์ •๋ณต

โ†’ ์ด์ œ ๋ฐฑ์—”๋“œ ํ’€์Šคํƒ์˜ ๋ชจ๋“  ์˜์—ญ์„ ๋ณธ ์…ˆ.


๐Ÿ—“๏ธ ๊ถŒ์žฅ ํ•™์Šต ์ผ์ • (์••์ถ• 7์ผ)

DayPhaseํ•™์Šต ๋ชฉํ‘œ
1์ผ์ฐจPhase 1Servlet โ†’ Spring MVC ์ง„ํ™”
2-3์ผ์ฐจPhase 2DispatcherServlet 9๋‹จ๊ณ„ (โ˜…)
4์ผ์ฐจPhase 3์š”์ฒญ ๋ฐ”์ธ๋”ฉ ๋ฉ”์ปค๋‹ˆ์ฆ˜
5์ผ์ฐจPhase 4์‘๋‹ต๊ณผ ViewResolver
6์ผ์ฐจPhase 5Filter vs Interceptor vs AOP (โ˜…)
7์ผ์ฐจPhase 6 + 7์˜ˆ์™ธ ์ฒ˜๋ฆฌ + REST API + ์ข…ํ•ฉ ์ž๊ธฐ ์ ๊ฒ€

์—ฌ์œ  ์ผ์ • (10์ผ): Phase 2, 5์— ๊ฐ +1์ผ. ๋‘ ์ •์ ์€ ์ง์ ‘ ๋””๋ฒ„๊ฑฐ๋กœ step-through ๊ถŒ์žฅ.


๐Ÿ“š Phase 1 โ€” ์›น ์š”์ฒญ์˜ ์—ฌ์ • (Servlet์—์„œ Spring MVC๊นŒ์ง€)

๋ชฉํ‘œ: 6์ฃผ์ฐจ์˜ Servlet/JSP ํ•™์Šต์„ ๋‹ค์‹œ ์งš๊ณ , Spring MVC๊ฐ€ ์–ด๋–ค ์ง„ํ™”์˜ ๊ฒฐ๊ณผ์ธ์ง€ ์ดํ•ดํ•œ๋‹ค.

Unit 1.1 โ€” Servlet๊ณผ Servlet Container ๋ณต์Šต

์„ ์ˆ˜ ์ง€์‹: 6์ฃผ์ฐจ Phase 7

ํ•ต์‹ฌ ๋ณต์Šต

Servlet:

  • ์ž๋ฐ” ํ‘œ์ค€ ์›น ์ปดํฌ๋„ŒํŠธ ์ธํ„ฐํŽ˜์ด์Šค
  • HTTP ์š”์ฒญ์„ ๋ฐ›์•„ ์‘๋‹ต์„ ๋งŒ๋“œ๋Š” ์ž๋ฐ” ํด๋ž˜์Šค
  • HttpServletRequest, HttpServletResponse ์‚ฌ์šฉ

Servlet Container (Tomcat ๋“ฑ):

  • Servlet์˜ ์ƒ๋ช…์ฃผ๊ธฐ ๊ด€๋ฆฌ
  • ์š”์ฒญ โ†’ Servlet์œผ๋กœ ๋ผ์šฐํŒ…
  • ์‘๋‹ต โ†’ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ „์†ก

์ „ํ˜•์ ์ธ Servlet ์ฝ”๋“œ (์˜›๋‚  ๋ฐฉ์‹):

@WebServlet("/users/*")
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse res) {
        String path = req.getPathInfo();  // "/123"
        // ๋ถ„๊ธฐ ์ฒ˜๋ฆฌ...
        // JSON ์ง์ ‘ ์ž‘์„ฑ...
        res.getWriter().write(json);
    }
}

Servlet์˜ ํ•œ๊ณ„:
1. URL๋งˆ๋‹ค Servlet์„ ๋งŒ๋“ค๋ฉด Servlet ํญ์ฆ
2. ๊ณตํ†ต ์ฒ˜๋ฆฌ (์ธ์ฆ, ๋กœ๊น…) ๋ฐ˜๋ณต ์ฝ”๋“œ
3. URL ๋ผ์šฐํŒ… ๋ถ„๊ธฐ ์ˆ˜๋™ ์ž‘์„ฑ
4. JSON ๋ณ€ํ™˜ ์ˆ˜๋™ ์ž‘์„ฑ

โ†’ Front Controller ํŒจํ„ด ํ•„์š”

์ž๊ธฐ ์ ๊ฒ€

  • Servlet 1000๊ฐœ๋ฅผ ๋งŒ๋“ค๋ฉด ์–ด๋–ค ๋ฌธ์ œ? (ํžŒํŠธ: ๋งคํ•‘ ๊ด€๋ฆฌ, ๊ณตํ†ต ์ฒ˜๋ฆฌ)
  • 6์ฃผ์ฐจ์˜ ์–ด๋–ค ๋ฌธ์ œ์™€ ์—ฐ๊ฒฐ๋˜๋Š”๊ฐ€? (ํžŒํŠธ: ๋ฐ˜๋ณต ์ฝ”๋“œ์˜ ๋ถ„๋ฆฌ)

Unit 1.2 โ€” Front Controller ํŒจํ„ด

์„ ์ˆ˜ ์ง€์‹: Unit 1.1, 8์ฃผ์ฐจ Phase 6 (๋””์ž์ธ ํŒจํ„ด)

ํ•ต์‹ฌ ๊ฐœ๋…

Front Controller ํŒจํ„ด:

"๋ชจ๋“  ์š”์ฒญ์„ ํ•œ ๊ณณ์—์„œ ๋ฐ›์•„์„œ ์ ์ ˆํ•œ ์ฒ˜๋ฆฌ๊ธฐ๋กœ ์œ„์ž„"

๊ตฌ์กฐ:

Before:                          After (Front Controller):
[Client] โ†’ [Servlet A]           [Client] โ†’ [Front Controller]
[Client] โ†’ [Servlet B]                            โ†“ (๋ถ„๊ธฐ)
[Client] โ†’ [Servlet C]                      [Handler A/B/C]

ํšจ๊ณผ:

  • ๊ณตํ†ต ์ฒ˜๋ฆฌ ํ•œ ๊ณณ์— (์ธ์ฆ, ๋กœ๊น…, ๋ณ€ํ™˜)
  • URL ๋ผ์šฐํŒ… ์ค‘์•™ํ™”
  • ํ™•์žฅ์„ฑ

๋น„์œ :

"์ „ํ™” ๊ตํ™˜์› โ€” ๋ชจ๋“  ์ „ํ™”๊ฐ€ ํ•œ ๋ช…์—๊ฒŒ ์™€์„œ, ๊ทธ ์‚ฌ๋žŒ์ด ์ ์ ˆํ•œ ๋ถ€์„œ๋กœ ์—ฐ๊ฒฐ"

8์ฃผ์ฐจ Phase 1๊ณผ ์—ฐ๊ฒฐ:

  • ๋ณ€ํ•˜๋Š” ๊ฒƒ (๊ฐ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง) vs ๋ณ€ํ•˜์ง€ ์•Š๋Š” ๊ฒƒ (๊ณตํ†ต ์ฒ˜๋ฆฌ)
  • โ†’ ๋ถ„๋ฆฌ

โ†’ Spring MVC์˜ DispatcherServlet ์ด ์ •ํ™•ํžˆ ์ด ์—ญํ• 

์ž๊ธฐ ์ ๊ฒ€

  • 5์ฃผ์ฐจ Spring IoC์™€ ์–ด๋–ป๊ฒŒ ์—ฐ๊ฒฐ๋˜๋Š”๊ฐ€? (ํžŒํŠธ: ๋ชจ๋‘ "๊ณตํ†ต ์ถ”์ƒํ™”")
  • Front Controller๊ฐ€ ๋‹จ์ผ ์žฅ์• ์ (SPOF)์ด ๋˜์ง€ ์•Š์„๊นŒ? (ํžŒํŠธ: ๋ฌด์ƒํƒœ + ๊ฐ€๋ฒผ์›€ + ๋‹ค์ค‘ ์ธ์Šคํ„ด์Šค)

Unit 1.3 โ€” Spring MVC ์•„ํ‚คํ…์ฒ˜์™€ DispatcherServlet์˜ ์œ„์น˜

์„ ์ˆ˜ ์ง€์‹: Unit 1.2

ํ•ต์‹ฌ ๊ทธ๋ฆผ

[Client]
    โ†“ HTTP Request
[Servlet Container (Tomcat)]
    โ†“
[Filter Chain]            โ† Servlet ํ‘œ์ค€
    โ†“
[DispatcherServlet]       โ† Spring์˜ Front Controller โญ
    โ†“
[Interceptor Chain]       โ† Spring ์ œ๊ณต
    โ†“
[Controller (@Controller)]
    โ†“
[Service / Repository]    โ† AOP ์ ์šฉ
    โ†“
[Database]

DispatcherServlet์˜ ์ •์ฒด:

  • HttpServlet์„ ์ƒ์†ํ•œ ํ‰๋ฒ”ํ•œ Servlet
  • ๊ทธ๋Ÿฌ๋‚˜ Spring์˜ Front Controller
  • Spring Boot๊ฐ€ ์ž๋™ ๋“ฑ๋ก (/ ๊ฒฝ๋กœ ๋งคํ•‘)

Spring Boot์˜ ์ž๋™ ์„ค์ •:

// ์ž๋™์œผ๋กœ ๋™์ž‘ (๊ฐœ๋ฐœ์ž๊ฐ€ ์ง์ ‘ ๋“ฑ๋ก X)
@Bean
public DispatcherServlet dispatcherServlet() {
    return new DispatcherServlet();
}

ํ•ต์‹ฌ ํ†ต์ฐฐ:

"DispatcherServlet๋„ ๊ฒฐ๊ตญ์€ ํ•˜๋‚˜์˜ Servlet ์ผ ๋ฟ์ด๋‹ค.
๊ทธ๋Ÿฌ๋‚˜ ๊ทธ ์•ˆ์—์„œ Spring์˜ ๋ชจ๋“  ๋งˆ๋ฒ•์ด ์ผ์–ด๋‚œ๋‹ค."

ILIC ๊ด€์ :

  • 431๊ฐœ API ๋ชจ๋‘ โ†’ DispatcherServlet 1๊ฐœ๊ฐ€ ์ฒ˜๋ฆฌ
  • ์ปจํŠธ๋กค๋Ÿฌ๋กœ ๋ผ์šฐํŒ… โ†’ ๊ฐ Service ํ˜ธ์ถœ
  • ์‘๋‹ต์„ JSON์œผ๋กœ ๋ณ€ํ™˜ โ†’ Vue 3 ํด๋ผ์ด์–ธํŠธ๋กœ

์ž๊ธฐ ์ ๊ฒ€

  • DispatcherServlet ์•ˆ์— Vue๊ฐ€ ๋ณด๋‚ด๋Š” ๋ชจ๋“  ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋Š”๊ฐ€? (ํžŒํŠธ: YES, ์ •์  ๋ฆฌ์†Œ์Šค ์ œ์™ธ)
  • 8-9์ฃผ์ฐจ์˜ ์ž๋™ ํ”„๋ก์‹œ ์ƒ์„ฑ๊ธฐ์™€ ์–ด๋–ค ๊ด€๊ณ„? (ํžŒํŠธ: ๋‘˜ ๋‹ค ๋นˆ ํ›„์ฒ˜๋ฆฌ๊ธฐ ๊ฒฐ๊ณผ)

๐Ÿ“š Phase 2 โ€” DispatcherServlet 9๋‹จ๊ณ„ ์ฒ˜๋ฆฌ ํ๋ฆ„ (โ˜… ์ •์  1)

๋ชฉํ‘œ: ๋ฉด์ ‘ ๋‹จ๊ณจ โ€” Spring MVC๊ฐ€ ์š”์ฒญ์„ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋Š”์ง€ 9๋‹จ๊ณ„๋กœ ์™„๋ฒฝํžˆ ์ดํ•ดํ•œ๋‹ค.

Unit 2.1 โ€” 9๋‹จ๊ณ„ ์š”์ฒญ ์ฒ˜๋ฆฌ ํ๋ฆ„ โญโญโญ

์„ ์ˆ˜ ์ง€์‹: Phase 1

ํ•ต์‹ฌ 9๋‹จ๊ณ„ (์™ธ์›Œ์•ผ ํ•จ):

1. [DispatcherServlet] HTTP ์š”์ฒญ ์ˆ˜์‹ 
        โ†“
2. [HandlerMapping] ์–ด๋–ค ์ปจํŠธ๋กค๋Ÿฌ ๋ฉ”์„œ๋“œ์ธ์ง€ ์ฐพ๊ธฐ
        โ†“
3. [HandlerAdapter] ๊ทธ ์ปจํŠธ๋กค๋Ÿฌ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœ
        โ†“
4. [Interceptor preHandle] ์ปจํŠธ๋กค๋Ÿฌ ํ˜ธ์ถœ ์ „ ์ฒ˜๋ฆฌ
        โ†“
5. [Controller] ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์‹คํ–‰ โ†’ ModelAndView ๋ฐ˜ํ™˜
        โ†“
6. [Interceptor postHandle] ์ปจํŠธ๋กค๋Ÿฌ ํ˜ธ์ถœ ํ›„ ์ฒ˜๋ฆฌ
        โ†“
7. [ViewResolver] View ์ด๋ฆ„ โ†’ ์‹ค์ œ View ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜
        โ†“
8. [View.render()] HTML ๋˜๋Š” JSON ์‘๋‹ต ์ƒ์„ฑ
        โ†“
9. [Interceptor afterCompletion] ์‘๋‹ต ์™„๋ฃŒ ํ›„ ์ฒ˜๋ฆฌ
        โ†“
[Client]

REST API ํ๋ฆ„ (View ๋‹จ๊ณ„ ๋‹จ์ˆœํ™”):

1~6. (์œ„์™€ ๋™์ผ)
        โ†“
7. [HttpMessageConverter] ๊ฐ์ฒด โ†’ JSON ๋ณ€ํ™˜
        โ†“
8. JSON ์‘๋‹ต
        โ†“
9. afterCompletion

ํ•ต์‹ฌ ํ†ต์ฐฐ:

  • ๋ชจ๋“  ๋‹จ๊ณ„๊ฐ€ ํ™•์žฅ ๊ฐ€๋Šฅ
  • ๊ฐ ๋‹จ๊ณ„๋งˆ๋‹ค ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ์žˆ๊ณ  ๋นˆ์œผ๋กœ ๋“ฑ๋ก ๊ฐ€๋Šฅ
  • โ†’ Spring์˜ ์ง„์ •ํ•œ ๊ฐ•๋ ฅํ•จ

์ž๊ธฐ ์ ๊ฒ€

  • Step 1๊ณผ Step 9์˜ ์ฐจ์ด๋ฅผ ํ•œ ๋ฌธ์žฅ์”ฉ์œผ๋กœ?
  • Filter๋Š” ์ด 9๋‹จ๊ณ„ ์ค‘ ์–ด๋””์— ์žˆ๋Š”๊ฐ€? (ํžŒํŠธ: 1๋‹จ๊ณ„ ์ด์ „ + 9๋‹จ๊ณ„ ์ดํ›„ โ€” Servlet ํ‘œ์ค€)

Unit 2.2 โ€” HandlerMapping (์–ด๋–ค ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ?)

์„ ์ˆ˜ ์ง€์‹: Unit 2.1

ํ•ต์‹ฌ ์—ญํ• :

"URL โ†’ Controller ๋ฉ”์„œ๋“œ ๋งคํ•‘ ์ •๋ณด๋ฅผ ๊ด€๋ฆฌ"

๋Œ€ํ‘œ์  HandlerMapping:

๊ตฌํ˜„์ฒด๋งคํ•‘ ๋ฐฉ์‹
RequestMappingHandlerMapping@RequestMapping, @GetMapping ๋“ฑ โญ
BeanNameUrlHandlerMapping๋นˆ ์ด๋ฆ„์ด URL์ธ ๊ฒฝ์šฐ
SimpleUrlHandlerMapping๋ช…์‹œ์  URL-๋นˆ ๋งคํ•‘

ํ˜„๋Œ€ Spring Boot์—์„œ๋Š” RequestMappingHandlerMapping ์ด ์‚ฌ์‹ค์ƒ ํ‘œ์ค€.

๋™์ž‘ ์˜ˆ์‹œ:

@RestController
@RequestMapping("/api/bookings")
public class BookingController {
    @GetMapping("/{id}")
    public Booking getBooking(@PathVariable Long id) { ... }
}

์š”์ฒญ: GET /api/bookings/123

โ†’ RequestMappingHandlerMapping์ด BookingController.getBooking ๋ฉ”์„œ๋“œ๋ฅผ ๋ฐ˜ํ™˜

๋‚ด๋ถ€ ๊ตฌ์กฐ:

  • ๋นˆ ๋“ฑ๋ก ์‹œ @RequestMapping ์–ด๋…ธํ…Œ์ด์…˜ ์Šค์บ”
  • URL ํŒจํ„ด + HTTP ๋ฉ”์„œ๋“œ โ†’ ๋ฉ”์„œ๋“œ ๋งคํ•‘ ํ…Œ์ด๋ธ” ๊ตฌ์ถ•
  • ์š”์ฒญ ์‹œ ๋งค์นญ

๋””๋ฒ„๊น… ํŒ:

  • ๋งคํ•‘ ์ •๋ณด ํ™•์ธ: Actuator์˜ /actuator/mappings ์—”๋“œํฌ์ธํŠธ
  • ๋ถ€ํŒ… ๋กœ๊ทธ์— ๋“ฑ๋ก๋œ ๋งคํ•‘ ์ถœ๋ ฅ ๊ฐ€๋Šฅ

์ž๊ธฐ ์ ๊ฒ€

  • ๊ฐ™์€ URL์— ๋‘ ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ ๋งคํ•‘๋˜๋ฉด? (ํžŒํŠธ: AmbiguousMappingException)
  • ILIC์˜ 431 API๊ฐ€ ๋ชจ๋‘ ๊ฐ™์€ HandlerMapping์—์„œ ๊ด€๋ฆฌ๋˜๋Š”๊ฐ€? (YES)

Unit 2.3 โ€” HandlerAdapter (์–ด๋–ป๊ฒŒ ํ˜ธ์ถœํ• ๊นŒ)

์„ ์ˆ˜ ์ง€์‹: Unit 2.2

ํ•ต์‹ฌ ๊ฐœ๋…

๋ฌธ์ œ: Controller๋Š” ๋‹ค์–‘ํ•œ ํ˜•ํƒœ๋กœ ์ž‘์„ฑ ๊ฐ€๋Šฅ

  • ์–ด๋…ธํ…Œ์ด์…˜ ๊ธฐ๋ฐ˜ (@Controller)
  • HttpRequestHandler ์ธํ„ฐํŽ˜์ด์Šค ๊ธฐ๋ฐ˜
  • ์˜›๋‚  Servlet ์Šคํƒ€์ผ

โ†’ ํ˜ธ์ถœ ๋ฐฉ๋ฒ•์ด ๋‹ค ๋‹ค๋ฆ„

HandlerAdapter์˜ ํ•ด๊ฒฐ:

"๋‹ค์–‘ํ•œ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ํ†ต์ผ๋œ ๋ฐฉ์‹์œผ๋กœ ํ˜ธ์ถœ"

๋Œ€ํ‘œ์  HandlerAdapter:

๊ตฌํ˜„์ฒด๋Œ€์ƒ
RequestMappingHandlerAdapter@RequestMapping ๋ฉ”์„œ๋“œ โญ
HttpRequestHandlerAdapterHttpRequestHandler
SimpleControllerHandlerAdapter์˜› Controller ์ธํ„ฐํŽ˜์ด์Šค

๋””์ž์ธ ํŒจํ„ด:

  • Adapter Pattern (5์ฃผ์ฐจ ๋””์ž์ธ ํŒจํ„ด ์ถ”๊ฐ€)
  • ๋‹ค์–‘ํ•œ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ•œ ์ธํ„ฐํŽ˜์ด์Šค๋กœ ํ†ต์ผ

ํ•ต์‹ฌ ๋™์ž‘ โ€” ์–ด๋…ธํ…Œ์ด์…˜ ๊ธฐ๋ฐ˜์˜ ๋งˆ๋ฒ•:

@GetMapping("/users/{id}")
public User getUser(
    @PathVariable Long id,
    @RequestParam String type,
    HttpServletRequest req,
    @AuthenticationPrincipal User currentUser
) { ... }

โ†’ HandlerAdapter๊ฐ€:
1. @PathVariable โ†’ URL์—์„œ ์ถ”์ถœ
2. @RequestParam โ†’ ์ฟผ๋ฆฌ ์ŠคํŠธ๋ง์—์„œ ์ถ”์ถœ
3. HttpServletRequest โ†’ ์š”์ฒญ ๊ฐ์ฒด ๊ทธ๋Œ€๋กœ ์ „๋‹ฌ
4. @AuthenticationPrincipal โ†’ Security Context์—์„œ ์ถ”์ถœ

ArgumentResolver:

  • ๊ฐ ํŒŒ๋ผ๋ฏธํ„ฐ ํƒ€์ž…๋ณ„ ์ถ”์ถœ ๋กœ์ง
  • RequestParamMethodArgumentResolver, PathVariableMethodArgumentResolver ๋“ฑ
  • ํ™•์žฅ ๊ฐ€๋Šฅ โ†’ ์ปค์Šคํ…€ ์–ด๋…ธํ…Œ์ด์…˜ ๋งŒ๋“ค๊ธฐ

์ž๊ธฐ ์ ๊ฒ€

  • 8-9์ฃผ์ฐจ ProxyFactory์™€ ์–ด๋–ค ํŒจํ„ด์ด ๊ฐ™์€๊ฐ€? (ํžŒํŠธ: Adapter โ€” JDK/CGLIB ํ†ตํ•ฉ)
  • ILIC์—์„œ ์ปค์Šคํ…€ ArgumentResolver๋ฅผ ๋งŒ๋“ ๋‹ค๋ฉด? (ํžŒํŠธ: ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž ์ •๋ณด ์ž๋™ ์ฃผ์ž…)

Unit 2.4 โ€” HandlerInterceptor (์ „ํ›„ ์ฒ˜๋ฆฌ)

์„ ์ˆ˜ ์ง€์‹: Unit 2.3

ํ•ต์‹ฌ ๊ฐœ๋…

HandlerInterceptor:

"Controller ํ˜ธ์ถœ ์ „ยทํ›„ยท์™„๋ฃŒ ํ›„ ๊ฐ€๋กœ์ฑ„๊ธฐ"

3๊ฐ€์ง€ ๋ฉ”์„œ๋“œ โญ :

public interface HandlerInterceptor {
    boolean preHandle(req, res, handler);     // Controller ํ˜ธ์ถœ ์ „
    void postHandle(req, res, handler, mav);  // Controller ํ˜ธ์ถœ ํ›„, View ๋ Œ๋”๋ง ์ „
    void afterCompletion(req, res, handler, ex);  // ์‘๋‹ต ์™„๋ฃŒ ํ›„ (์˜ˆ์™ธ ํฌํ•จ)
}

์˜ˆ์‹œ โ€” ์ธ์ฆ ์ฒดํฌ:

public class AuthInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) {
        String token = req.getHeader("Authorization");
        if (!isValid(token)) {
            res.setStatus(401);
            return false;  // ์ปจํŠธ๋กค๋Ÿฌ ํ˜ธ์ถœ ์•ˆ ํ•จ!
        }
        return true;
    }
}

๋“ฑ๋ก:

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AuthInterceptor())
                .addPathPatterns("/api/**")
                .excludePathPatterns("/api/auth/**");
    }
}

preHandle false ๋ฐ˜ํ™˜ ์‹œ:

  • Controller ํ˜ธ์ถœ ์•ˆ ๋จ
  • postHandle, afterCompletion๋„ ํ˜ธ์ถœ ์•ˆ ๋จ
  • ๋‹ค์Œ Interceptor๋„ ํ˜ธ์ถœ ์•ˆ ๋จ

ํ™œ์šฉ ์˜ˆ์‹œ:

  • ์ธ์ฆ/์ธ๊ฐ€ ๊ฒ€์ฆ
  • ์š”์ฒญ ๋กœ๊น…
  • ์„ฑ๋Šฅ ์ธก์ • (Phase 5์—์„œ AOP์™€ ๋น„๊ต)
  • ๋น„์ฆˆ๋‹ˆ์Šค ํ†ต๊ณ„ ์ˆ˜์ง‘

์ž๊ธฐ ์ ๊ฒ€

  • Interceptor์™€ 8-9์ฃผ์ฐจ AOP๋Š” ์–ด๋–ป๊ฒŒ ๋‹ค๋ฅธ๊ฐ€? (Phase 5์—์„œ ์ž์„ธํžˆ)
  • ์ปจํŠธ๋กค๋Ÿฌ ๋ฉ”์„œ๋“œ ๋‹จ์œ„๋กœ ์ ์šฉํ•˜๋ ค๋ฉด? (ํžŒํŠธ: ํŒจํ„ด ๋งค์นญ ๋˜๋Š” ์–ด๋…ธํ…Œ์ด์…˜ ๊ธฐ๋ฐ˜)

๐Ÿ“š Phase 3 โ€” ์š”์ฒญ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ

๋ชฉํ‘œ: @RequestBody, @RequestParam, @PathVariable ๋“ฑ์ด ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€ ์ดํ•ดํ•œ๋‹ค.

Unit 3.1 โ€” 4๊ฐ€์ง€ ํŒŒ๋ผ๋ฏธํ„ฐ ์–ด๋…ธํ…Œ์ด์…˜ ๋น„๊ต

์„ ์ˆ˜ ์ง€์‹: Phase 2

ํ•ต์‹ฌ ๋น„๊ต โญ :

์–ด๋…ธํ…Œ์ด์…˜์–ด๋””์„œ ์ถ”์ถœํƒ€์ž…
@PathVariableURL ๊ฒฝ๋กœ/users/{id}
@RequestParam์ฟผ๋ฆฌ ์ŠคํŠธ๋ง / ํผ?name=Alice
@RequestBodyHTTP bodyJSON, XML
@ModelAttribute์ฟผ๋ฆฌ + ํผ โ†’ ๊ฐ์ฒด ๋ฐ”์ธ๋”ฉ(์ž๋™)

์˜ˆ์‹œ ์ข…ํ•ฉ:

// 1. PathVariable
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) { ... }
// ์š”์ฒญ: GET /users/42

// 2. RequestParam
@GetMapping("/users")
public List<User> searchUsers(
    @RequestParam(required = false) String name,
    @RequestParam(defaultValue = "10") int size
) { ... }
// ์š”์ฒญ: GET /users?name=Alice&size=20

// 3. RequestBody
@PostMapping("/users")
public User createUser(@RequestBody UserDto dto) { ... }
// ์š”์ฒญ: POST /users
// Body: {"name": "Alice", "email": "..."}

// 4. ModelAttribute
@GetMapping("/search")
public List<User> search(@ModelAttribute SearchCriteria criteria) { ... }
// ์š”์ฒญ: GET /search?name=Alice&age=25
// โ†’ SearchCriteria ๊ฐ์ฒด์— ์ž๋™ ๋ฐ”์ธ๋”ฉ

์–ธ์ œ ๋ฌด์—‡์„ โญ :

์ƒํ™ฉ์ถ”์ฒœ
๋ฆฌ์†Œ์Šค ์‹๋ณ„์ž (id)@PathVariable
๊ฒ€์ƒ‰ ์กฐ๊ฑด, ํ•„ํ„ฐ@RequestParam
๋ณต์žกํ•œ ๊ฐ์ฒด (POST/PUT)@RequestBody
GET์˜ ๋‹ค์ค‘ ๊ฒ€์ƒ‰ ์กฐ๊ฑด โ†’ ๊ฐ์ฒด@ModelAttribute

์ž๊ธฐ ์ ๊ฒ€

  • ILIC์˜ ์šด์ž„ ๊ฒ€์ƒ‰ 6๊ฐœ ์กฐ๊ฑด์€ ์–ด๋–ค ์–ด๋…ธํ…Œ์ด์…˜์ด ์ ํ•ฉ? (ํžŒํŠธ: ModelAttribute)
  • POST ์š”์ฒญ์— @RequestParam์„ ์“ธ ์ˆ˜ ์žˆ๋Š”๊ฐ€? (ํžŒํŠธ: form-encoded๋ฉด ๊ฐ€๋Šฅ)

Unit 3.2 โ€” HttpMessageConverter (๊ฐ์ฒด โ†” JSON ๋ณ€ํ™˜)

์„ ์ˆ˜ ์ง€์‹: Unit 3.1

ํ•ต์‹ฌ ์—ญํ• :

"HTTP body(JSON/XML) โ†” ์ž๋ฐ” ๊ฐ์ฒด ๋ณ€ํ™˜"

@RequestBody / @ResponseBody ์˜ ์ง„์งœ ์ •์ฒด:

  • ๋‹จ์ˆœ ์–ด๋…ธํ…Œ์ด์…˜์ด ์•„๋‹Œ โ†’ HttpMessageConverter ํŠธ๋ฆฌ๊ฑฐ

๋Œ€ํ‘œ Converter:

Converter์ฒ˜๋ฆฌ
MappingJackson2HttpMessageConverterJSON โ†” ๊ฐ์ฒด โญ
StringHttpMessageConvertertext/plain
Jaxb2RootElementHttpMessageConverterXML
ByteArrayHttpMessageConverterbyte[]

๋™์ž‘ ํ๋ฆ„ (@RequestBody):

[HTTP Body]
{ "name": "Alice", "age": 25 }
        โ†“
[MappingJackson2HttpMessageConverter.read()]
        โ†“
[Jackson ObjectMapper]
        โ†“
[์ž๋ฐ” ๊ฐ์ฒด]
new UserDto("Alice", 25)
        โ†“
[Controller ๋ฉ”์„œ๋“œ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ฃผ์ž…]

@ResponseBody (์ •ํ™•ํžˆ ๋ฐ˜๋Œ€):

[Controller ๋ฐ˜ํ™˜๊ฐ’]
        โ†“
[Jackson ObjectMapper.writeValueAsString()]
        โ†“
[JSON ๋ฌธ์ž์—ด]
        โ†“
[HTTP Response Body]

์ปค์Šคํ„ฐ๋งˆ์ด์ง•:

@Configuration
public class JacksonConfig {
    @Bean
    public ObjectMapper objectMapper() {
        return new ObjectMapper()
            .registerModule(new JavaTimeModule())  // LocalDateTime
            .setDateFormat(new SimpleDateFormat("yyyy-MM-dd"))
            .setSerializationInclusion(Include.NON_NULL);
    }
}

ILIC ํ™œ์šฉ:

  • ์šด์ž„ ๊ฒฌ์  ๊ฐ์ฒด โ†’ JSON ์ž๋™ ๋ณ€ํ™˜
  • Vue 3์—์„œ ๋ฐ›๋Š” JSON โ†’ ์ž๋ฐ” ๊ฐ์ฒด ์ž๋™ ๋ณ€ํ™˜

์ž๊ธฐ ์ ๊ฒ€

  • 11-12์ฃผ์ฐจ์˜ JPA Lazy ํ”„๋ก์‹œ๋ฅผ JSON ๋ณ€ํ™˜ ์‹œ ์–ด๋–ค ๋ฌธ์ œ? (ํžŒํŠธ: LazyInitializationException, ๋ฌดํ•œ ๋ฃจํ”„)
  • ํ•ด๊ฒฐ์ฑ… 3๊ฐ€์ง€๋Š”? (ํžŒํŠธ: DTO ๋ณ€ํ™˜, @JsonIgnore, FetchType.EAGER ๋˜๋Š” fetch join)

Unit 3.3 โ€” Content Negotiation (Accept ํ—ค๋”)

์„ ์ˆ˜ ์ง€์‹: Unit 3.2

ํ•ต์‹ฌ ๊ฐœ๋…

Content Negotiation:

"ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์š”์ฒญํ•œ ์‘๋‹ต ํ˜•์‹ ์— ๋งž์ถฐ ์‘๋‹ต"

HTTP Accept ํ—ค๋”:

GET /api/users/1 HTTP/1.1
Accept: application/json

โ†’ ์„œ๋ฒ„: JSON์œผ๋กœ ์‘๋‹ต

GET /api/users/1 HTTP/1.1
Accept: application/xml

โ†’ ์„œ๋ฒ„: XML๋กœ ์‘๋‹ต (ํ•ด๋‹น Converter ๋“ฑ๋ก ์‹œ)

Spring์˜ ๊ฒฐ์ • ํ๋ฆ„:
1. ์ปจํŠธ๋กค๋Ÿฌ ๋ฉ”์„œ๋“œ ๋ฐ˜ํ™˜ ํƒ€์ž… ํ™•์ธ
2. ์‘๋‹ต ๊ฐ€๋Šฅํ•œ Converter ๋ชฉ๋ก ํ™•์ธ
3. Accept ํ—ค๋”์™€ ๋งค์นญ ๋˜๋Š” Converter ์„ ํƒ
4. ๋ณ€ํ™˜

produces ๋ช…์‹œ:

@GetMapping(value = "/users/{id}", produces = "application/json")
public User getUser(@PathVariable Long id) { ... }

โ†’ JSON๋งŒ ์‘๋‹ต (๊ทธ ์™ธ Accept๋Š” 406 Not Acceptable)

consumes ๋ช…์‹œ:

@PostMapping(value = "/users", consumes = "application/json")
public User createUser(@RequestBody UserDto dto) { ... }

โ†’ Content-Type์ด JSON์ผ ๋•Œ๋งŒ ์ฒ˜๋ฆฌ

ILIC ์‹œ๋‚˜๋ฆฌ์˜ค:

  • API๋Š” ๋ชจ๋‘ JSON โ†’ produces = "application/json" ๋ช…์‹œ ๊ฐ€๋Šฅ
  • ๊ทธ๋Ÿฌ๋‚˜ ๋ณดํ†ต Spring Boot ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์ถฉ๋ถ„

์ž๊ธฐ ์ ๊ฒ€

  • Vue 3 ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ณด๋‚ด๋Š” ์š”์ฒญ์˜ Content-Type์€ ๋ณดํ†ต? (ํžŒํŠธ: application/json)
  • ๊ฐ™์€ URL์ด JSON/XML ๋‘˜ ๋‹ค ์‘๋‹ต ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๋ ค๋ฉด? (ํžŒํŠธ: produces ๋‹ค์ค‘ ์ง€์ • ๋˜๋Š” ๋‘˜ ๋‹ค ๋“ฑ๋ก)

๐Ÿ“š Phase 4 โ€” ์‘๋‹ต ์ฒ˜๋ฆฌ์™€ ViewResolver

๋ชฉํ‘œ: REST API ์‹œ๋Œ€์˜ ViewResolver ์˜๋ฏธ์™€ ResponseEntity ํ™œ์šฉ์„ ์ •๋ฆฌํ•œ๋‹ค.

Unit 4.1 โ€” ViewResolver์˜ ์—ญํ• 

์„ ์ˆ˜ ์ง€์‹: Phase 3

ํ•ต์‹ฌ ๊ฐœ๋…

ViewResolver:

"Controller๊ฐ€ ๋ฐ˜ํ™˜ํ•œ View ์ด๋ฆ„ โ†’ ์‹ค์ œ View ๊ฐ์ฒด ๋ณ€ํ™˜"

์ „ํ†ต์  ํ๋ฆ„ (View ๊ธฐ๋ฐ˜):

@Controller
public class HomeController {
    @GetMapping("/")
    public String home(Model model) {
        model.addAttribute("user", currentUser);
        return "home";  // View ์ด๋ฆ„ ๋ฐ˜ํ™˜
    }
}

โ†’ ViewResolver: "home" โ†’ home.html ๋˜๋Š” home.jsp ์ฐพ์•„์„œ ๋ Œ๋”๋ง

๋Œ€ํ‘œ ViewResolver:

๊ตฌํ˜„์ฒด์ฒ˜๋ฆฌ ๋Œ€์ƒ
ThymeleafViewResolverThymeleaf ํ…œํ”Œ๋ฆฟ
InternalResourceViewResolverJSP
BeanNameViewResolver๋นˆ ์ด๋ฆ„์ด View

ํ˜„๋Œ€ Spring Boot โ€” REST API ์‹œ๋Œ€:

  • Vue/React ๋“ฑ SPA ์‚ฌ์šฉ
  • ์„œ๋ฒ„๋Š” JSON๋งŒ ๋ฐ˜ํ™˜
  • โ†’ ViewResolver ๊ฑฐ์˜ ์•ˆ ์”€

@RestController ์˜ ๋™์ž‘:

@RestController
public class UserController {
    @GetMapping("/users/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.find(id);  // ๊ฐ์ฒด ๋ฐ˜ํ™˜
    }
}

โ†’ ViewResolver๋ฅผ ๊ฑฐ์น˜์ง€ ์•Š๊ณ  HttpMessageConverter๋กœ ์ง์ ‘ JSON ๋ณ€ํ™˜

ILIC ์‹œ๋‚˜๋ฆฌ์˜ค:

  • ๋ชจ๋“  ์ปจํŠธ๋กค๋Ÿฌ โ†’ @RestController
  • ViewResolver ๋ฏธ์‚ฌ์šฉ
  • Vue 3๊ฐ€ Frontend ๋ Œ๋”๋ง ๋‹ด๋‹น

์ž๊ธฐ ์ ๊ฒ€

  • ViewResolver๊ฐ€ ์ •๋ง ์‚ฌ๋ผ์ง„ ๊ฑด๊ฐ€? (ํžŒํŠธ: ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง SSR ์‹œ ์—ฌ์ „ํžˆ ์‚ฌ์šฉ)
  • Thymeleaf์™€ React๋Š” ์–ด๋–ค ์ฐจ์ด? (ํžŒํŠธ: ์„œ๋ฒ„ vs ํด๋ผ์ด์–ธํŠธ ๋ Œ๋”๋ง)

Unit 4.2 โ€” @RestController vs @Controller

์„ ์ˆ˜ ์ง€์‹: Unit 4.1

ํ•ต์‹ฌ ์ฐจ์ด โญ :

@Controller@RestController
์ •์˜View ์ด๋ฆ„ ๋ฐ˜ํ™˜๋ฐ์ดํ„ฐ ์ง์ ‘ ๋ฐ˜ํ™˜
์‘๋‹ต๋ณดํ†ต HTML๋ณดํ†ต JSON
@ResponseBody๋ฉ”์„œ๋“œ๋งˆ๋‹ค ๋ช…์‹œ ํ•„์š”์ž๋™ ์ ์šฉ
์šฉ๋„SSR, ์ „ํ†ต ์›นREST API

์‹ค์ œ ์ •์˜:

@Controller
@ResponseBody  // โ† ํ•ฉ์นœ ๊ฒŒ RestController
public @interface RestController { ... }

์˜ˆ์‹œ ๋น„๊ต:

@Controller  // ์ „ํ†ต (SSR)
public class TraditionalController {
    @GetMapping("/users")
    public String list(Model model) {
        model.addAttribute("users", users);
        return "user-list";  // View ์ด๋ฆ„
    }
    
    @GetMapping("/api/users")
    @ResponseBody  // ๋ช…์‹œ ํ•„์š”
    public List<User> apiList() {
        return userService.findAll();
    }
}

@RestController  // ํ˜„๋Œ€ (REST API) โญ
public class ApiController {
    @GetMapping("/users")
    public List<User> list() {
        return userService.findAll();  // ์ž๋™ JSON ๋ณ€ํ™˜
    }
}

ILIC: ๋ชจ๋“  ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ @RestController

์ž๊ธฐ ์ ๊ฒ€

  • ํ•œ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ SSR๊ณผ REST๋ฅผ ์„ž์œผ๋ ค๋ฉด? (ํžŒํŠธ: @Controller + ์„ ํƒ์  @ResponseBody)
  • ๋ณดํ†ต ๊ทธ๋ ‡๊ฒŒ ์„ž๋Š”๊ฐ€? (ํžŒํŠธ: ๋ถ„๋ฆฌ ๊ถŒ์žฅ)

Unit 4.3 โ€” ResponseEntity์™€ HTTP ์ƒํƒœ ์ฝ”๋“œ

์„ ์ˆ˜ ์ง€์‹: Unit 4.2

ํ•ต์‹ฌ ๊ฐœ๋…

ResponseEntity:

"HTTP ์ƒํƒœ ์ฝ”๋“œ, ํ—ค๋”, ๋ฐ”๋”” ๋ฅผ ์ง์ ‘ ์ œ์–ด"

๊ธฐ๋ณธ ๋ฐ˜ํ™˜ vs ResponseEntity:

// ๊ธฐ๋ณธ: ํ•ญ์ƒ 200 OK
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
    return userService.find(id);
}

// ResponseEntity: ์ƒํƒœ ์ฝ”๋“œ ๋ช…์‹œ ๊ฐ€๋Šฅ
@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
    User user = userService.find(id);
    if (user == null) {
        return ResponseEntity.notFound().build();  // 404
    }
    return ResponseEntity.ok(user);  // 200
}

์ž์ฃผ ์“ฐ๋Š” ํŒจํ„ด:

// 201 Created (POST)
return ResponseEntity.status(HttpStatus.CREATED)
    .header("Location", "/users/" + user.getId())
    .body(user);

// 400 Bad Request
return ResponseEntity.badRequest().body(errorMessage);

// 204 No Content (DELETE ํ›„)
return ResponseEntity.noContent().build();

// 400๋Œ€ with body
return ResponseEntity.status(409)  // Conflict
    .body(Map.of("error", "์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค"));

REST API ํ‘œ์ค€ ์ƒํƒœ ์ฝ”๋“œ โญ :

์ฝ”๋“œ์˜๋ฏธ์‚ฌ์šฉ ์‹œ์ 
200 OK์„ฑ๊ณต์ผ๋ฐ˜ ์กฐํšŒ/์ˆ˜์ •
201 Created์ƒ์„ฑ๋จPOST ํ›„
204 No Content์„ฑ๊ณต (์‘๋‹ต X)DELETE ํ›„
400 Bad Request์ž˜๋ชป๋œ ์š”์ฒญValidation ์‹คํŒจ
401 Unauthorized์ธ์ฆ ํ•„์š”๋กœ๊ทธ์ธ ์•ˆ ๋จ
403 Forbidden๊ถŒํ•œ ์—†์Œ์ธ์ฆ์€ OK, ๊ถŒํ•œ X
404 Not Found๋ฆฌ์†Œ์Šค ์—†์Œ์ž˜๋ชป๋œ ID
409 Conflict์ถฉ๋Œ์ค‘๋ณต ๋“ฑ๋ก
500 Internal Server Error์„œ๋ฒ„ ์˜ค๋ฅ˜์˜ˆ์™ธ ๋ฐœ์ƒ

ILIC ์ ์šฉ:

@PostMapping("/bookings")
public ResponseEntity<Booking> create(@RequestBody BookingDto dto) {
    Booking saved = bookingService.create(dto);
    return ResponseEntity
        .status(HttpStatus.CREATED)
        .header("Location", "/bookings/" + saved.getId())
        .body(saved);
}

์ž๊ธฐ ์ ๊ฒ€

  • ๋ชจ๋“  ์‘๋‹ต์„ 200์œผ๋กœ๋งŒ ๋ณด๋‚ด๋ฉด ์–ด๋–ค ๋ฌธ์ œ? (ํžŒํŠธ: REST ์›์น™ ์œ„๋ฐ˜, ํด๋ผ์ด์–ธํŠธ ์ฒ˜๋ฆฌ ์–ด๋ ค์›€)
  • 401๊ณผ 403์˜ ์ฐจ์ด๋ฅผ ํ•œ ๋ฌธ์žฅ์œผ๋กœ? (ํžŒํŠธ: ์ธ์ฆ vs ๊ถŒํ•œ)

๐Ÿ“š Phase 5 โ€” Filter vs Interceptor vs AOP (โ˜… ์ •์  2 โ€” ๋ฉด์ ‘ ๋‹จ๊ณจ)

๋ชฉํ‘œ: ๊ฐ€์žฅ ์ž์ฃผ ๋ฌป๋Š” ๋ฉด์ ‘ ์งˆ๋ฌธ โ€” ์„ธ ๊ฐ€์ง€ ํšก๋‹จ ๊ด€์‹ฌ์‚ฌ ์ฒ˜๋ฆฌ ๋„๊ตฌ์˜ ์ฐจ์ด๋ฅผ ๋ช…ํ™•ํžˆ ์žก๋Š”๋‹ค.

Unit 5.1 โ€” Servlet Filter

์„ ์ˆ˜ ์ง€์‹: Phase 1

ํ•ต์‹ฌ ๊ฐœ๋…

Servlet Filter:

"DispatcherServlet ๋„๋‹ฌ ์ „ยทํ›„ ์—์„œ ๋™์ž‘ โ€” Servlet ํ‘œ์ค€"

์œ„์น˜:

[Client] โ†’ [Filter] โ†’ [DispatcherServlet] โ†’ [Interceptor] โ†’ [Controller]

๊ตฌํ˜„:

@Component
public class LoggingFilter implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) 
            throws IOException, ServletException {
        // ์š”์ฒญ ์ „
        long start = System.currentTimeMillis();
        
        chain.doFilter(req, res);  // ๋‹ค์Œ Filter / DispatcherServlet
        
        // ์‘๋‹ต ํ›„
        long duration = System.currentTimeMillis() - start;
        log.info("Request took {}ms", duration);
    }
}

ํŠน์ง•:

  • Servlet ํ‘œ์ค€ โ€” Spring ์—†์ด๋„ ๋™์ž‘
  • request/response ์ž์ฒด ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ (Wrapping)
  • DispatcherServlet ๋„๋‹ฌ ์ „ โ†’ Spring ๋นˆ ์ •๋ณด ๋ชจ๋ฆ„
  • ๊ทธ๋Ÿฌ๋‚˜ @Component + Filter Bean ๋“ฑ๋ก์€ ๊ฐ€๋Šฅ

ํ™œ์šฉ ์‚ฌ๋ก€:

  • ์š”์ฒญ/์‘๋‹ต ๋กœ๊น…
  • ์ธ์ฝ”๋”ฉ ์„ค์ • (CharacterEncodingFilter)
  • Body ์บ์‹ฑ (ContentCachingRequestWrapper)
  • CORS ์ฒ˜๋ฆฌ
  • Security ์ธ์ฆ (Spring Security Filter Chain)

์ž๊ธฐ ์ ๊ฒ€

  • Spring Security๊ฐ€ ์™œ Filter ๊ธฐ๋ฐ˜์ธ๊ฐ€? (ํžŒํŠธ: ๊ฐ€์žฅ ์™ธ๊ณฝ์—์„œ ์ฐจ๋‹จ)
  • Filter์—์„œ Spring ๋นˆ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š”๊ฐ€? (ํžŒํŠธ: @Component ์‚ฌ์šฉ ์‹œ ๊ฐ€๋Šฅ)

Unit 5.2 โ€” Spring Interceptor (๋ณต์Šต)

์„ ์ˆ˜ ์ง€์‹: Unit 2.4, 5.1

ํ•ต์‹ฌ ์ฐจ์ด:

Interceptor:

"DispatcherServlet ์•ˆ์—์„œ Controller ์ „ยทํ›„ ๋™์ž‘ โ€” Spring ์ œ๊ณต"

์œ„์น˜:

[Client] โ†’ [Filter] โ†’ [DispatcherServlet] โ†’ [Interceptor] โ†’ [Controller]

Filter์™€์˜ ๊ฒฐ์ •์  ์ฐจ์ด โญ :

  • DispatcherServlet ์ดํ›„ ๋™์ž‘
  • โ†’ Spring ๋นˆ ์ •๋ณด ํ™œ์šฉ ๊ฐ€๋Šฅ
  • โ†’ HandlerMethod ์ •๋ณด (์–ด๋–ค ์ปจํŠธ๋กค๋Ÿฌ ๋ฉ”์„œ๋“œ์ธ์ง€) ์•Œ ์ˆ˜ ์žˆ์Œ
@Override
public boolean preHandle(req, res, handler) {
    if (handler instanceof HandlerMethod) {
        HandlerMethod method = (HandlerMethod) handler;
        // ์–ด๋…ธํ…Œ์ด์…˜ ํ™•์ธ ๊ฐ€๋Šฅ
        if (method.hasMethodAnnotation(Auditable.class)) {
            // ๊ฐ์‚ฌ ๋กœ๊ทธ
        }
    }
    return true;
}

ํ™œ์šฉ ์‚ฌ๋ก€:

  • ์ธ์ฆ/์ธ๊ฐ€ (Spring Security ์•ˆ ์“ธ ๋•Œ)
  • ์š”์ฒญ ๋กœ๊น… (Filter๋ณด๋‹ค ํ’๋ถ€ํ•œ ์ •๋ณด)
  • API ํ˜ธ์ถœ ํšŸ์ˆ˜ ์ œํ•œ (Rate Limiting)
  • ํƒ€์ด๋ฐ ์ธก์ •

Unit 5.3 โ€” Spring AOP (8-9์ฃผ์ฐจ ๋ณต์Šต)

์„ ์ˆ˜ ์ง€์‹: 8-9์ฃผ์ฐจ, Unit 5.2

ํ•ต์‹ฌ ์œ„์น˜:

[Client] โ†’ [Filter] โ†’ [DispatcherServlet] โ†’ [Interceptor] โ†’ [Controller] โ†’ [Service (AOP)]

AOP์˜ ์œ„์น˜:

  • Interceptor ์•ˆ์ชฝ โ†’ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ๋‹จ์œ„
  • ์ปจํŠธ๋กค๋Ÿฌ๋„, ์„œ๋น„์Šค๋„ ๋ชจ๋‘ ์ ์šฉ ๊ฐ€๋Šฅ

8-9์ฃผ์ฐจ ๋ณต์Šต:

  • @Aspect + @Around
  • ProceedingJoinPoint
  • Pointcut์œผ๋กœ ์–ด๋””์—, Advice๋กœ ๋ฌด์—‡์„

ํ™œ์šฉ ์‚ฌ๋ก€:

  • ํŠธ๋žœ์žญ์…˜ (@Transactional) โ€” 7์ฃผ์ฐจ
  • ๋กœ๊น… โ€” ๋ฉ”์„œ๋“œ ๋‹จ์œ„
  • ์บ์‹ฑ โ€” ๋ฉ”์„œ๋“œ ๋‹จ์œ„
  • ๋ณด์•ˆ ๋ฉ”์„œ๋“œ (@PreAuthorize)

Unit 5.4 โ€” ๋น„๊ต ๋งคํŠธ๋ฆญ์Šค์™€ ์„ ํƒ ๊ธฐ์ค€ (โ˜…โ˜…โ˜… ๋ฉด์ ‘ ๋‹จ๊ณจ)

์„ ์ˆ˜ ์ง€์‹: Unit 5.1~5.3

์™„์ „ ๋น„๊ต โญ :

FilterInterceptorAOP
์œ„์น˜DispatcherServlet ์™ธ๋ถ€DispatcherServlet ๋‚ด๋ถ€๋ฉ”์„œ๋“œ ๋‹จ์œ„
ํ‘œ์ค€Servlet ํ‘œ์ค€SpringSpring
Spring ๋นˆ ์ ‘๊ทผ์ œํ•œ์ ๊ฐ€๋Šฅ๊ฐ€๋Šฅ
Handler ์ •๋ณดXโœ…โœ…
req/res ๋ณ€๊ฒฝโœ… (Wrapper)์–ด๋ ต๋ฉ”์„œ๋“œ ์ธ์ž/๋ฐ˜ํ™˜
๋ฉ”์„œ๋“œ ๋‹จ์œ„ ์ ์šฉXURL ๋‹จ์œ„๋ฉ”์„œ๋“œ ๋‹จ์œ„ โญ
์˜ˆ์™ธ ์ฒ˜๋ฆฌ ์œ„์น˜์™ธ๊ณฝ์ปจํŠธ๋กค๋Ÿฌ ์ „ํ›„๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์‹œ
๋Œ€ํ‘œ ํ™œ์šฉSecurity, CORS, ์ธ์ฝ”๋”ฉ์ธ์ฆ, ๋กœ๊น…ํŠธ๋žœ์žญ์…˜, ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ๊น…

์„ ํƒ ๊ธฐ์ค€ โญ:

์ƒํ™ฉ์„ ํƒ
๋ชจ๋“  ์š”์ฒญ์˜ ์ธ์ฝ”๋”ฉ / CORSFilter
Spring SecurityFilter (Security๊ฐ€ Filter ๊ธฐ๋ฐ˜)
Request/Response ์ž์ฒด ์กฐ์ž‘Filter
URL ํŒจํ„ด๋ณ„ ์ธ์ฆInterceptor
์ปจํŠธ๋กค๋Ÿฌ ๋ฉ”์„œ๋“œ ์ง„์ž… ์ „ ๊ถŒํ•œ ํ™•์ธInterceptor
๋ฉ”์„œ๋“œ์˜ ํŠธ๋žœ์žญ์…˜AOP (@Transactional)
๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๋กœ๊น…AOP
๋ฉ”์„œ๋“œ ์ธ์ž/๋ฐ˜ํ™˜๊ฐ’ ๋ณ€๊ฒฝAOP

ํ˜ธ์ถœ ์ˆœ์„œ (๋””๋ฒ„๊ฑฐ๋กœ ํ™•์ธ ๊ถŒ์žฅ):

[Filter] preHandle
    โ†“
[Interceptor] preHandle
    โ†“
[AOP] before
    โ†“
[Controller method]
    โ†“
[AOP] after
    โ†“
[Interceptor] postHandle
    โ†“
[Interceptor] afterCompletion
    โ†“
[Filter] postHandle (์—ญ์ˆœ)

ILIC ์ ์šฉ:

  • CORS, ์ธ์ฝ”๋”ฉ โ†’ Filter
  • ์‚ฌ์šฉ์ž ํ™œ๋™ ๋กœ๊น… โ†’ Interceptor
  • @Transactional, ๋น„์ฆˆ๋‹ˆ์Šค ๊ฐ์‚ฌ ๋กœ๊ทธ โ†’ AOP
  • โ†’ ILIC์˜ ๋ณ€๊ฒฝ ์ด๋ ฅ ์‹œ์Šคํ…œ(์ด์ „ ์ปจํ…์ŠคํŠธ) ์€ AOP๊ฐ€ ์ž์—ฐ์Šค๋Ÿฌ์šด ์„ ํƒ

๋ฉด์ ‘ ๋ชจ์˜ ๋‹ต๋ณ€:

"Filter๋Š” Servlet ํ‘œ์ค€์œผ๋กœ DispatcherServlet ์™ธ๊ณฝ์—์„œ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. Interceptor๋Š” Spring ์ œ๊ณต์œผ๋กœ ์ปจํŠธ๋กค๋Ÿฌ ์ „ํ›„๋ฅผ ๊ฐ€๋กœ์ฑ„๋ฉฐ Handler ์ •๋ณด๋ฅผ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. AOP๋Š” ๋ฉ”์„œ๋“œ ๋‹จ์œ„๋กœ ๋™์ž‘ํ•˜๋ฉฐ ๊ฐ€์žฅ ์„ธ๋ฐ€ํ•œ ์ œ์–ด๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. Spring Security์ฒ˜๋Ÿผ ๊ฐ•๋ ฅํ•œ ์™ธ๊ณฝ ์ฐจ๋‹จ์ด ํ•„์š”ํ•˜๋ฉด Filter, ์ปจํŠธ๋กค๋Ÿฌ ๋‹จ์œ„ ์ธ์ฆ/๋กœ๊น…์ด๋ฉด Interceptor, ๋ฉ”์„œ๋“œ ๋‹จ์œ„ ํšก๋‹จ ๊ด€์‹ฌ์‚ฌ๋ฉด AOP ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค."

์ž๊ธฐ ์ ๊ฒ€

  • "์™œ Spring Security๋Š” Filter ๊ธฐ๋ฐ˜์ธ๊ฐ€?"
  • ILIC์—์„œ ์ƒˆ๋กœ์šด ํšก๋‹จ ๊ด€์‹ฌ์‚ฌ (์˜ˆ: API ํ˜ธ์ถœ ํ†ต๊ณ„)๊ฐ€ ํ•„์š”ํ•˜๋ฉด ์…‹ ์ค‘ ๋ฌด์—‡? (ํžŒํŠธ: ๋ฉ”์„œ๋“œ ๋‹จ์œ„๋ฉด AOP, URL ๋‹จ์œ„๋ฉด Interceptor)

๐Ÿ“š Phase 6 โ€” ์˜ˆ์™ธ ์ฒ˜๋ฆฌ์™€ Validation

๋ชฉํ‘œ: REST API์˜ ํ‘œ์ค€ ์—๋Ÿฌ ์‘๋‹ต ํŒจํ„ด๊ณผ Bean Validation ํ™œ์šฉ์„ ๋งˆ์Šคํ„ฐํ•œ๋‹ค.

Unit 6.1 โ€” HandlerExceptionResolver

์„ ์ˆ˜ ์ง€์‹: Phase 2

ํ•ต์‹ฌ ๊ฐœ๋…

HandlerExceptionResolver:

"Controller์—์„œ ๋˜์ง„ ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌ ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ"

๊ธฐ๋ณธ ๋™์ž‘:

  • Controller์—์„œ ์˜ˆ์™ธ ๋ฐœ์ƒ
  • DispatcherServlet์ด HandlerExceptionResolver ๋“ค์—๊ฒŒ ์œ„์ž„
  • ํ•ด๊ฒฐ๋˜๋ฉด โ†’ ์ ์ ˆํ•œ ์‘๋‹ต
  • ์•ˆ ๋˜๋ฉด โ†’ 500 Internal Server Error

๋Œ€ํ‘œ ๊ตฌํ˜„์ฒด:

๊ตฌํ˜„์ฒด์ฒ˜๋ฆฌ
ExceptionHandlerExceptionResolver@ExceptionHandler ๋ฉ”์„œ๋“œ โญ
ResponseStatusExceptionResolver@ResponseStatus ์–ด๋…ธํ…Œ์ด์…˜
DefaultHandlerExceptionResolverSpring ๊ธฐ๋ณธ ์˜ˆ์™ธ (404, 405 ๋“ฑ)

์ž๊ธฐ ์ ๊ฒ€

  • ์ปจํŠธ๋กค๋Ÿฌ ์•ˆ์˜ try-catch์™€ ๋น„๊ตํ•ด ์–ด๋–ค ์žฅ์ ? (ํžŒํŠธ: ์ค‘์•™ํ™”, ์žฌ์‚ฌ์šฉ)

Unit 6.2 โ€” @ExceptionHandler / @ControllerAdvice โญ

์„ ์ˆ˜ ์ง€์‹: Unit 6.1

ํ•ต์‹ฌ ํŒจํ„ด

์ปจํŠธ๋กค๋Ÿฌ ๋‹จ์œ„ ์ฒ˜๋ฆฌ (@ExceptionHandler):

@RestController
public class UserController {
    @GetMapping("/users/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.find(id);  // ์—†์œผ๋ฉด UserNotFoundException
    }
    
    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<ErrorResponse> handle(UserNotFoundException ex) {
        return ResponseEntity.status(404)
            .body(new ErrorResponse(ex.getMessage()));
    }
}

์ „์—ญ ์ฒ˜๋ฆฌ (@ControllerAdvice ๋˜๋Š” @RestControllerAdvice) โญ :

@RestControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(EntityNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFound(EntityNotFoundException ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
            .body(ErrorResponse.of("NOT_FOUND", ex.getMessage()));
    }
    
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidation(MethodArgumentNotValidException ex) {
        // Validation ์—๋Ÿฌ ์ฒ˜๋ฆฌ
    }
    
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleAll(Exception ex) {
        log.error("Unexpected error", ex);
        return ResponseEntity.status(500)
            .body(ErrorResponse.of("INTERNAL_ERROR", "์„œ๋ฒ„ ์˜ค๋ฅ˜"));
    }
}

@ControllerAdvice ์˜ ๊ฐ•์ :

  • ๋ชจ๋“  ์ปจํŠธ๋กค๋Ÿฌ ์˜ ์˜ˆ์™ธ๋ฅผ ํ•œ ๊ณณ์—์„œ
  • ์˜ˆ์™ธ๋ณ„ ํ‘œ์ค€ ์‘๋‹ต ๋ณด์žฅ
  • ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๊ณผ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋ถ„๋ฆฌ

ํ‘œ์ค€ ์—๋Ÿฌ ์‘๋‹ต ์„ค๊ณ„ โญ :

public record ErrorResponse(
    String code,           // "USER_NOT_FOUND"
    String message,        // ์‚ฌ๋žŒ์ด ์ฝ์„ ๋ฉ”์‹œ์ง€
    LocalDateTime timestamp,
    String path,           // ์š”์ฒญ URL
    List<FieldError> errors  // Validation ์—๋Ÿฌ (์žˆ์„ ๋•Œ)
) {}

@Getter @AllArgsConstructor
class FieldError {
    private String field;
    private String message;
}

์˜ˆ์‹œ ์‘๋‹ต:

{
    "code": "USER_NOT_FOUND",
    "message": "User with id 42 not found",
    "timestamp": "2026-05-04T10:30:00",
    "path": "/api/users/42",
    "errors": []
}

ILIC ํ‘œ์ค€ํ™”:

  • ๋ชจ๋“  API๊ฐ€ ๊ฐ™์€ ์—๋Ÿฌ ์‘๋‹ต ๊ตฌ์กฐ
  • Vue 3 ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ผ๊ด€๋˜๊ฒŒ ์ฒ˜๋ฆฌ

์ž๊ธฐ ์ ๊ฒ€

  • @ControllerAdvice ์™€ @RestControllerAdvice ์˜ ์ฐจ์ด๋Š”? (ํžŒํŠธ: ํ›„์ž๋Š” @ResponseBody ์ž๋™)
  • Validation ์—๋Ÿฌ๋Š” ์–ด๋–ค ์˜ˆ์™ธ ํƒ€์ž…? (ํžŒํŠธ: MethodArgumentNotValidException)

Unit 6.3 โ€” Bean Validation (@Valid)

์„ ์ˆ˜ ์ง€์‹: Unit 6.2

ํ•ต์‹ฌ ๊ฐœ๋…

Bean Validation (JSR-380):

"์–ด๋…ธํ…Œ์ด์…˜ ๊ธฐ๋ฐ˜ ๊ฒ€์ฆ โ€” ์ž๋™ํ™”๋œ Validation"

๊ธฐ๋ณธ ์–ด๋…ธํ…Œ์ด์…˜:

์–ด๋…ธํ…Œ์ด์…˜๊ฒ€์ฆ
@NotNullNULL ์•„๋‹˜
@NotEmptyNULL ์•„๋‹˜ + ๋น„์–ด์žˆ์ง€ ์•Š์Œ (String/Collection)
@NotBlankNULL ์•„๋‹˜ + ๊ณต๋ฐฑ ์•„๋‹˜ (String)
@Size(min, max)๊ธธ์ด/ํฌ๊ธฐ ๋ฒ”์œ„
@Min / @Max์ˆซ์ž ๋ฒ”์œ„
@Pattern(regexp)์ •๊ทœํ‘œํ˜„์‹
@Email์ด๋ฉ”์ผ ํ˜•์‹
@Past / @Future๋‚ ์งœ

์˜ˆ์‹œ:

public record UserCreateRequest(
    @NotBlank(message = "์ด๋ฆ„์€ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค")
    @Size(min = 2, max = 50)
    String name,
    
    @NotBlank
    @Email
    String email,
    
    @Min(0) @Max(150)
    int age
) {}

Controller์—์„œ ์‚ฌ์šฉ โญ :

@PostMapping("/users")
public ResponseEntity<User> create(
    @Valid @RequestBody UserCreateRequest request  // โ† @Valid ํ•„์ˆ˜
) {
    return ResponseEntity.ok(userService.create(request));
}

@Valid ๊ฐ€ ์—†์œผ๋ฉด ๊ฒ€์ฆ ์•ˆ ๋จ!

Validation ์‹คํŒจ ์‹œ:

  • MethodArgumentNotValidException ๋ฐœ์ƒ
  • @RestControllerAdvice ์—์„œ ์ฒ˜๋ฆฌ

์ค‘์ฒฉ ๊ฒ€์ฆ (@Valid ์žฌ๊ท€):

public record OrderRequest(
    @NotNull
    @Valid  // ์ค‘์ฒฉ ๊ฒ€์ฆ
    AddressDto address,
    
    @NotEmpty
    @Valid  // ๋ฆฌ์ŠคํŠธ์˜ ๊ฐ ์š”์†Œ ๊ฒ€์ฆ
    List<OrderItemDto> items
) {}

์ปค์Šคํ…€ Validator (์ฐธ๊ณ ):

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneNumberValidator.class)
public @interface PhoneNumber {
    String message() default "์œ ํšจํ•˜์ง€ ์•Š์€ ์ „ํ™”๋ฒˆํ˜ธ";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

ILIC ํ™œ์šฉ:

  • ๋ชจ๋“  DTO์— Validation ์–ด๋…ธํ…Œ์ด์…˜
  • ํ‘œ์ค€ ์—๋Ÿฌ ์‘๋‹ต์œผ๋กœ ์ผ๊ด€์„ฑ

์ž๊ธฐ ์ ๊ฒ€

  • @Valid ์™€ @Validated ์˜ ์ฐจ์ด๋Š”? (ํžŒํŠธ: ํ›„์ž๋Š” ๊ทธ๋ฃน ์ง€์ • ๊ฐ€๋Šฅ)
  • ํผ validation์„ ์ปจํŠธ๋กค๋Ÿฌ ์™ธ์— ์„œ๋น„์Šค ๋ ˆ์ด์–ด์—์„œ๋„ ํ•ด์•ผ ํ•˜๋Š”๊ฐ€? (ํžŒํŠธ: ๋„๋ฉ”์ธ ๋ฌด๊ฒฐ์„ฑ์€ ํ•ญ์ƒ ๊ฒ€์ฆ)

Unit 6.4 โ€” ํ‘œ์ค€ ์—๋Ÿฌ ์‘๋‹ต ์„ค๊ณ„

์„ ์ˆ˜ ์ง€์‹: Unit 6.2~6.3

ํ•ต์‹ฌ ์›์น™

1. ์ผ๊ด€๋œ ๊ตฌ์กฐ:

  • ๋ชจ๋“  ์—๋Ÿฌ๊ฐ€ ๊ฐ™์€ JSON ์Šคํ‚ค๋งˆ
  • ํด๋ผ์ด์–ธํŠธ์˜ ์ผ๊ด€๋œ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ

2. ์—๋Ÿฌ ์ฝ”๋“œ + ๋ฉ”์‹œ์ง€ ๋ถ„๋ฆฌ:

  • ์ฝ”๋“œ: ํ”„๋กœ๊ทธ๋ž˜๋ฐ์  ์‹๋ณ„ (USER_NOT_FOUND)
  • ๋ฉ”์‹œ์ง€: ์‚ฌ๋žŒ์ด ์ฝ์„ ์„ค๋ช…

3. ๋ฏผ๊ฐ ์ •๋ณด ๋…ธ์ถœ X:

  • ์Šคํƒ ํŠธ๋ ˆ์ด์Šค โ†’ ์šด์˜ ํ™˜๊ฒฝ์—์„œ ์ˆจ๊น€
  • DB ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ โ†’ ์ผ๋ฐ˜ ๋ฉ”์‹œ์ง€๋กœ ๋ณ€ํ™˜

์ „์—ญ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ํ…œํ”Œ๋ฆฟ:

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    
    // 1. ๋น„์ฆˆ๋‹ˆ์Šค ์˜ˆ์™ธ (์‚ฌ์šฉ์ž ์ž…๋ ฅ ๋ฌธ์ œ)
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ErrorResponse> handleBusiness(BusinessException ex) {
        log.warn("Business error: {}", ex.getMessage());
        return ResponseEntity.status(ex.getStatus())
            .body(ErrorResponse.of(ex.getCode(), ex.getMessage()));
    }
    
    // 2. Validation ์‹คํŒจ
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidation(MethodArgumentNotValidException ex) {
        List<FieldError> errors = ex.getBindingResult().getFieldErrors().stream()
            .map(e -> new FieldError(e.getField(), e.getDefaultMessage()))
            .toList();
        return ResponseEntity.badRequest()
            .body(ErrorResponse.builder()
                .code("VALIDATION_FAILED")
                .message("์ž…๋ ฅ๊ฐ’์ด ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค")
                .errors(errors)
                .build());
    }
    
    // 3. ๋ฐ์ดํ„ฐ ์—†์Œ
    @ExceptionHandler({EntityNotFoundException.class, NoSuchElementException.class})
    public ResponseEntity<ErrorResponse> handleNotFound(Exception ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
            .body(ErrorResponse.of("NOT_FOUND", ex.getMessage()));
    }
    
    // 4. ๋ชจ๋“  ์˜ˆ์™ธ์˜ ์ตœ์ข… ์ฒ˜๋ฆฌ (Fallback)
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleAll(Exception ex) {
        log.error("Unexpected error", ex);
        return ResponseEntity.status(500)
            .body(ErrorResponse.of("INTERNAL_ERROR", "์„œ๋ฒ„ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค"));
    }
}

ILIC ์ ์šฉ ๊ถŒ์žฅ:

  • ํ‘œ์ค€ ErrorResponse ํด๋ž˜์Šค
  • BusinessException ๊ณ„์ธต ๊ตฌ์กฐ
  • ์šด์˜/๊ฐœ๋ฐœ ํ™˜๊ฒฝ๋ณ„ ์ƒ์„ธ ์ •๋ณด ๋…ธ์ถœ ์ฐจ์ด

์ž๊ธฐ ์ ๊ฒ€

  • ์šด์˜ ํ™˜๊ฒฝ์—์„œ ์Šคํƒ ํŠธ๋ ˆ์ด์Šค๋ฅผ ์‘๋‹ต์œผ๋กœ ๋…ธ์ถœํ•˜๋ฉด? (ํžŒํŠธ: ๋ณด์•ˆ ์‚ฌ๊ณ )
  • ์—๋Ÿฌ ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค ๋•Œ ๊ถŒ์žฅ ํŒจํ„ด์€? (ํžŒํŠธ: ๋„๋ฉ”์ธ_์ƒํƒœ โ€” USER_NOT_FOUND)

๐Ÿ“š Phase 7 โ€” REST API ์„ค๊ณ„์™€ ์‹ค์ „

๋ชฉํ‘œ: 4๋…„ ์ฐจ ํ’€์Šคํƒ ๊ฐœ๋ฐœ์ž๊ฐ€ ์•Œ์•„์•ผ ํ•  REST API ์„ค๊ณ„ ์›์น™๊ณผ ํŽ˜์ด์ง•/๋ฌธ์„œํ™”๋ฅผ ์ •๋ฆฌํ•œ๋‹ค.

Unit 7.1 โ€” RESTful ์›์น™

์„ ์ˆ˜ ์ง€์‹: 6์ฃผ์ฐจ Phase 7

ํ•ต์‹ฌ 6๊ฐ€์ง€ ์›์น™:

  1. Client-Server ๋ถ„๋ฆฌ: UI์™€ ๋น„์ฆˆ๋‹ˆ์Šค ๋ถ„๋ฆฌ
  2. Stateless: ์„œ๋ฒ„๋Š” ์ƒํƒœ ์ €์žฅ X
  3. Cacheable: ์‘๋‹ต์„ ์บ์‹œ ๊ฐ€๋Šฅ
  4. Uniform Interface: ์ผ๊ด€๋œ ์ธํ„ฐํŽ˜์ด์Šค โญ
  5. Layered System: ๊ณ„์ธต ๊ตฌ์กฐ (LB, CDN ๋“ฑ)
  6. Code-on-Demand: ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ ๋™์  ๋‹ค์šด๋กœ๋“œ (Optional)

REST API URL ์„ค๊ณ„ ์›์น™ โญ :

์ข‹์€ ์˜ˆ๋‚˜์œ ์˜ˆ
GET /usersGET /getUsers (๋™์‚ฌ X)
GET /users/123GET /user?id=123
POST /usersPOST /createUser
DELETE /users/123POST /deleteUser/123
GET /users/123/ordersGET /userOrders?userId=123

ํ•ต์‹ฌ:

  • ๋ช…์‚ฌ ์‚ฌ์šฉ (๋™์‚ฌ๋Š” HTTP ๋ฉ”์„œ๋“œ)
  • ๋ณต์ˆ˜ํ˜• ๊ถŒ์žฅ (/users)
  • ๊ณ„์ธต ๊ตฌ์กฐ (/users/{id}/orders)
  • ์†Œ๋ฌธ์ž + ํ•˜์ดํ”ˆ (/order-items)

์ž๊ธฐ ์ ๊ฒ€

  • ILIC์˜ ์–ด๋–ค API URL์ด RESTful ์›์น™์— ์–ด๊ธ‹๋‚  ์ˆ˜ ์žˆ์„๊นŒ?
  • "Stateless"๋Š” ์„ธ์…˜์„ ์“ฐ๋ฉด ์•ˆ ๋œ๋‹ค๋Š” ๋œป์ธ๊ฐ€? (ํžŒํŠธ: ์„œ๋ฒ„์—์„œ ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ ์ถ”์ธก X)

Unit 7.2 โ€” HTTP ๋ฉ”์„œ๋“œ์™€ ์ƒํƒœ ์ฝ”๋“œ

์„ ์ˆ˜ ์ง€์‹: Unit 7.1, 4.3

ํ•ต์‹ฌ ๋งคํ•‘ โญ :

์ž‘์—…HTTP ๋ฉ”์„œ๋“œURL์„ฑ๊ณต ์ฝ”๋“œ
๋ชฉ๋ก ์กฐํšŒGET/users200 OK
๋‹จ๊ฑด ์กฐํšŒGET/users/{id}200 OK
์ƒ์„ฑPOST/users201 Created
์ „์ฒด ์ˆ˜์ •PUT/users/{id}200 ๋˜๋Š” 204 No Content
๋ถ€๋ถ„ ์ˆ˜์ •PATCH/users/{id}200 ๋˜๋Š” 204
์‚ญ์ œDELETE/users/{id}204 No Content

Spring ์–ด๋…ธํ…Œ์ด์…˜:

@GetMapping("/users")          // GET
@PostMapping("/users")         // POST
@PutMapping("/users/{id}")     // PUT
@PatchMapping("/users/{id}")   // PATCH
@DeleteMapping("/users/{id}")  // DELETE

PUT vs PATCH โญ :

  • PUT: ์ „์ฒด ๊ต์ฒด (๋ชจ๋“  ํ•„๋“œ ๋ณด๋‚ด์•ผ ํ•จ)
  • PATCH: ๋ถ€๋ถ„ ์ˆ˜์ • (๋ณ€๊ฒฝ ํ•„๋“œ๋งŒ)

์˜ˆ์‹œ โ€” PATCH์˜ ํšจ์œจ์„ฑ:

// PUT โ€” ์‚ฌ์šฉ์ž ์ •๋ณด ์ „์ฒด
PUT /users/1
{
    "name": "Alice",
    "email": "alice@example.com",
    "age": 25,
    "address": "Seoul"
    // ... ๋ชจ๋“  ํ•„๋“œ
}

// PATCH โ€” ๋ณ€๊ฒฝ๋œ ๋ถ€๋ถ„๋งŒ
PATCH /users/1
{
    "email": "alice.new@example.com"
}

Idempotent (๋ฉฑ๋“ฑ์„ฑ) โญ :

  • ๊ฐ™์€ ์š”์ฒญ์„ ์—ฌ๋Ÿฌ ๋ฒˆ ๋ณด๋‚ด๋„ ๊ฒฐ๊ณผ๊ฐ€ ๊ฐ™์Œ
  • GET, PUT, DELETE โ†’ ๋ฉฑ๋“ฑ โœ…
  • POST โ†’ ๋น„๋ฉฑ๋“ฑ โŒ (ํ˜ธ์ถœ๋งˆ๋‹ค ์ƒˆ ๋ฆฌ์†Œ์Šค ์ƒ์„ฑ)

์™œ ์ค‘์š”ํ•œ๊ฐ€:

  • ๋„คํŠธ์›Œํฌ ์žฌ์‹œ๋„ ์‹œ ์•ˆ์ „
  • API ํด๋ผ์ด์–ธํŠธ์˜ ์‹ ๋ขฐ์„ฑ

์ž๊ธฐ ์ ๊ฒ€

  • PATCH์— ๋ฉฑ๋“ฑ์„ฑ์ด ๋ณด์žฅ๋˜๋Š”๊ฐ€? (ํžŒํŠธ: ๊ตฌํ˜„์— ๋”ฐ๋ผ โ€” ์ ˆ๋Œ€๊ฐ’ vs ์ฆ๊ฐ)
  • POST๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ ํ˜ธ์ถœํ•˜๋ฉด ์–ด๋–ค ์‚ฌ๊ณ ? (ํžŒํŠธ: ์ค‘๋ณต ์ƒ์„ฑ โ€” Idempotency Key ํŒจํ„ด)

Unit 7.3 โ€” ํŽ˜์ด์ง• (Pageable, Page, Slice)

์„ ์ˆ˜ ์ง€์‹: 11-12์ฃผ์ฐจ Phase 9 (ํŽ˜์ด์ง• ํ•จ์ •)

ํ•ต์‹ฌ ๊ฐœ๋…

Spring Data JPA์˜ ํŽ˜์ด์ง• ์ถ”์ƒํ™”:

// Repository
public interface BookingRepository extends JpaRepository<Booking, Long> {
    Page<Booking> findByCustomerId(Long customerId, Pageable pageable);
}

// Controller
@GetMapping("/bookings")
public Page<Booking> list(
    @RequestParam Long customerId,
    Pageable pageable  // ์ž๋™ ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ”์ธ๋”ฉ
) {
    return bookingRepository.findByCustomerId(customerId, pageable);
}

์š”์ฒญ ์˜ˆ์‹œ:

GET /bookings?customerId=1&page=0&size=20&sort=createdAt,desc

Pageable ์ž๋™ ํŒŒ์‹ฑ:

  • page: ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ (0๋ถ€ํ„ฐ)
  • size: ํŽ˜์ด์ง€ ํฌ๊ธฐ
  • sort: ์ •๋ ฌ (field,asc/desc)

Page vs Slice โญ :

PageSlice
์ „์ฒด ๊ฐœ์ˆ˜โœ… (count ์ฟผ๋ฆฌ ์ถ”๊ฐ€)โŒ
๋‹ค์Œ ํŽ˜์ด์ง€ ์กด์žฌ ์—ฌ๋ถ€์žˆ์Œ (์ „์ฒด๋กœ ๊ณ„์‚ฐ)์žˆ์Œ (size+1 ์กฐํšŒ)
์‚ฌ์šฉ ์‚ฌ๋ก€ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ UI๋ฌดํ•œ ์Šคํฌ๋กค
์„ฑ๋Šฅ์•ฝ๊ฐ„ ๋А๋ฆผ (count ์ถ”๊ฐ€)๋น ๋ฆ„

Slice ํ™œ์šฉ:

public interface BookingRepository extends JpaRepository<Booking, Long> {
    Slice<Booking> findByCustomerId(Long customerId, Pageable pageable);
}

โ†’ ๋ชจ๋ฐ”์ผ ๋ฌดํ•œ ์Šคํฌ๋กค์— ์ ํ•ฉ


Cursor-based Pagination (์ฐธ๊ณ ):

  • offset ๊ธฐ๋ฐ˜์˜ ๋‹จ์  (๋Œ€์šฉ๋Ÿ‰ ์‹œ ๋А๋ฆผ) ํ•ด๊ฒฐ
  • ๋งˆ์ง€๋ง‰ ID๋ฅผ ์ „๋‹ฌ
GET /bookings?afterId=100&size=20

ILIC ์‹œ๋‚˜๋ฆฌ์˜ค:

  • ์šด์ž„ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ โ†’ Page (์ „์ฒด ๊ฐœ์ˆ˜ ํ‘œ์‹œ)
  • ์•Œ๋ฆผ ๋ชฉ๋ก โ†’ Slice ๋˜๋Š” Cursor (๋ฌดํ•œ ์Šคํฌ๋กค)

12์ฃผ์ฐจ N+1 ํ•จ์ • ์ฃผ์˜ โญ :

  • ํŽ˜์ด์ง• + fetch join + OneToMany โ†’ ๋ฉ”๋ชจ๋ฆฌ ํŽ˜์ด์ง• ์œ„ํ—˜
  • โ†’ @BatchSize ์‚ฌ์šฉ

์ž๊ธฐ ์ ๊ฒ€

  • 1000๋งŒ ๊ฑด ํ…Œ์ด๋ธ”์—์„œ page=999, size=20 ์š”์ฒญ ์‹œ ์–ด๋–ค ๋ฌธ์ œ? (ํžŒํŠธ: OFFSET ๋น„์šฉ)
  • ILIC์˜ ์šด์ž„ ๊ฒ€์ƒ‰์—์„œ Page๋ฅผ ์“ฐ๋ฉด ๋งค๋ฒˆ COUNT ์‹คํ–‰ โ€” ์–ด๋–ป๊ฒŒ ์ตœ์ ํ™”? (ํžŒํŠธ: ์ฒซ ํŽ˜์ด์ง€๋งŒ COUNT, ์บ์‹ฑ)

Unit 7.4 โ€” API ๋ฌธ์„œํ™” (Swagger/SpringDoc)

์„ ์ˆ˜ ์ง€์‹: ์ „์ฒด

ํ•ต์‹ฌ ๋„๊ตฌ

SpringDoc OpenAPI (Swagger ํ›„์†):

  • Spring Boot 3์™€ ํ˜ธํ™˜
  • ์ž๋™์œผ๋กœ OpenAPI 3 ๋ฌธ์„œ ์ƒ์„ฑ
  • ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ UI ์ œ๊ณต

์˜์กด์„ฑ:

implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'

์ž๋™ ์ƒ์„ฑ:

  • http://localhost:8080/swagger-ui.html โ†’ UI
  • http://localhost:8080/v3/api-docs โ†’ JSON

์–ด๋…ธํ…Œ์ด์…˜ ์ถ”๊ฐ€ (์„ ํƒ):

@RestController
@Tag(name = "Booking", description = "์˜ˆ์•ฝ ๊ด€๋ฆฌ API")
public class BookingController {
    
    @Operation(summary = "์˜ˆ์•ฝ ์ƒ์„ฑ", description = "์ƒˆ ์šด์ž„ ์˜ˆ์•ฝ์„ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค")
    @ApiResponses({
        @ApiResponse(responseCode = "201", description = "์„ฑ๊ณต"),
        @ApiResponse(responseCode = "400", description = "์ž˜๋ชป๋œ ์š”์ฒญ")
    })
    @PostMapping("/bookings")
    public ResponseEntity<Booking> create(@RequestBody BookingRequest request) { ... }
}

์ข‹์€ API ๋ฌธ์„œ์˜ ์š”์†Œ:
1. ๋ช…ํ™•ํ•œ ์„ค๋ช… (Operation, Parameter)
2. ์š”์ฒญ/์‘๋‹ต ์˜ˆ์‹œ
3. ์—๋Ÿฌ ์‘๋‹ต ์˜ˆ์‹œ
4. ์ธ์ฆ ๋ฐฉ๋ฒ•
5. ํŽ˜์ด์ง• ๊ทœ์น™


API ๋ฒ„์ €๋‹ ์ „๋žต:

๋ฐฉ์‹์˜ˆ
URL Path/api/v1/users, /api/v2/users
ํ—ค๋”X-API-Version: 2
์ฟผ๋ฆฌ ์ŠคํŠธ๋ง/api/users?version=2

โ†’ URL Path ๋ฐฉ์‹์ด ๊ฐ€์žฅ ๋ช…ํ™• โญ

ILIC ๊ถŒ์žฅ:

  • SpringDoc ๋„์ž… (์ž๋™ ๋ฌธ์„œํ™”)
  • v1 โ†’ v2 ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์‹œ URL ๋ฒ„์ €๋‹
  • Vue 3 ํŒ€๊ณผ ๊ณต์œ ํ•  ๋ช…ํ™•ํ•œ ๋ฌธ์„œ (Frontend ํ˜‘์—…)

์ž๊ธฐ ์ ๊ฒ€

  • API ๋ฌธ์„œ๋ฅผ ์ž๋™ ์ƒ์„ฑ๊ณผ ์ˆ˜๋™ ์ž‘์„ฑ ์ค‘ ๋ฌด์—‡์ด ๋” ์ข‹์€๊ฐ€? (ํžŒํŠธ: ์ž๋™ + ๋ณด๊ฐ•)
  • ILIC์˜ 431 API ๋ฌธ์„œํ™”๊ฐ€ ์•ˆ ๋˜์–ด ์žˆ๋‹ค๋ฉด ์–ด๋–ค ๋น„์šฉ? (ํžŒํŠธ: Frontend ํŒ€ ์ปค๋ฎค๋‹ˆ์ผ€์ด์…˜, ์‹ ๊ทœ ๊ฐœ๋ฐœ์ž ์˜จ๋ณด๋”ฉ)

๐ŸŽ“ ์ข…ํ•ฉ ์ž๊ธฐ ์ ๊ฒ€ (15์ฃผ์ฐจ ์กธ์—… ์‹œํ—˜)

DispatcherServlet ํ๋ฆ„

  1. DispatcherServlet 9๋‹จ๊ณ„๋ฅผ ์ˆœ์„œ๋Œ€๋กœ ๋‚˜์—ดํ•˜๋ผ
  2. HandlerMapping๊ณผ HandlerAdapter์˜ ์—ญํ•  ์ฐจ์ด๋Š”?
  3. RequestMappingHandlerAdapter๊ฐ€ ์–ด๋…ธํ…Œ์ด์…˜์„ ์–ด๋–ป๊ฒŒ ํ•ด์„ํ•˜๋Š”๊ฐ€?
  4. HandlerInterceptor์˜ 3๊ฐ€์ง€ ๋ฉ”์„œ๋“œ์™€ ํ˜ธ์ถœ ์‹œ์ ์€?

์š”์ฒญ/์‘๋‹ต ์ฒ˜๋ฆฌ

  1. @RequestParam, @PathVariable, @RequestBody, @ModelAttribute์˜ ์ฐจ์ด๋Š”?
  2. @RequestBody๊ฐ€ ๋™์ž‘ํ•˜๋Š” ๋ฉ”์ปค๋‹ˆ์ฆ˜ (HttpMessageConverter ํ๋ฆ„)์€?
  3. @Controller์™€ @RestController์˜ ์ฐจ์ด๋ฅผ ์ฝ”๋“œ๋กœ ์„ค๋ช…ํ•˜๋ผ
  4. ResponseEntity๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ  3๊ฐ€์ง€๋Š”?

Filter vs Interceptor vs AOP (โ˜…)

  1. ์„ธ ๊ฐ€์ง€์˜ ์œ„์น˜์™€ ์ฐจ์ด๋ฅผ ๋‹ค์ด์–ด๊ทธ๋žจ์œผ๋กœ ๊ทธ๋ ค๋ผ
  2. Spring Security๊ฐ€ Filter ๊ธฐ๋ฐ˜์ธ ์ด์œ ๋Š”?
  3. URL ํŒจํ„ด ์ธ์ฆ์€ Filter/Interceptor ์ค‘ ์–ด๋””์—? ๋ฉ”์„œ๋“œ ๊ถŒํ•œ์€ Filter/Interceptor/AOP ์ค‘ ์–ด๋””์—?
  4. ์„ธ ๋„๊ตฌ์˜ ํ˜ธ์ถœ ์ˆœ์„œ๋Š”?

์˜ˆ์™ธ ์ฒ˜๋ฆฌ์™€ Validation

  1. @ExceptionHandler์™€ @ControllerAdvice์˜ ์ฐจ์ด๋Š”?
  2. @Valid๊ฐ€ ์—†์œผ๋ฉด ์–ด๋–ป๊ฒŒ ๋˜๋Š”๊ฐ€?
  3. ํ‘œ์ค€ ์—๋Ÿฌ ์‘๋‹ต์— ํฌํ•จ์‹œ์ผœ์•ผ ํ•  5๊ฐ€์ง€ ํ•„๋“œ๋Š”?
  4. MethodArgumentNotValidException์€ ์–ธ์ œ ๋ฐœ์ƒํ•˜๋Š”๊ฐ€?

REST API ์„ค๊ณ„

  1. RESTful URL ์„ค๊ณ„์˜ ํ•ต์‹ฌ ์›์น™ 3๊ฐ€์ง€๋Š”?
  2. PUT๊ณผ PATCH์˜ ์ฐจ์ด๋Š”?
  3. ๋ฉฑ๋“ฑ์„ฑ(Idempotent)์ด๋ž€? GET/POST/PUT/DELETE ์ค‘ ๋ฉฑ๋“ฑ์ธ ๊ฒƒ์€?
  4. Page์™€ Slice์˜ ์ฐจ์ด์™€ ๊ฐ๊ฐ์˜ ์‚ฌ์šฉ ์‚ฌ๋ก€๋Š”?

๋ฉด์ ‘ ๋ชจ์˜ ๋‹ต๋ณ€

  1. "DispatcherServlet์˜ ๋™์ž‘ ์›๋ฆฌ๋ฅผ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”" (3๋ถ„)
  2. "Filter, Interceptor, AOP์˜ ์ฐจ์ด๋Š”?" (2๋ถ„)
  3. "REST API ์„ค๊ณ„ ์‹œ ๊ฐ€์žฅ ์ค‘์š”ํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋Š” ๊ฒƒ์€?" (2๋ถ„)

๐Ÿ“Œ ํ•™์Šต ์šด์˜ ํŒ

9-์„น์…˜ ๋งˆ์Šคํ„ฐ ํ”„๋กฌํ”„ํŠธ๋กœ ๊นŠ์ด ํŒŒ์•ผ ํ•  Unit

โ˜…โ˜…โ˜… ๋ฉด์ ‘ ๋‹จ๊ณจ (๋ฐ˜๋“œ์‹œ):

  • Unit 2.1 โ€” DispatcherServlet 9๋‹จ๊ณ„
  • Unit 5.4 โ€” Filter vs Interceptor vs AOP ๋น„๊ต
  • Unit 6.2 โ€” @ControllerAdvice
  • Unit 7.2 โ€” HTTP ๋ฉ”์„œ๋“œ์™€ ์ƒํƒœ ์ฝ”๋“œ + ๋ฉฑ๋“ฑ์„ฑ

โ˜…โ˜… ๋งค์šฐ ๊ถŒ์žฅ:

  • Unit 2.3 โ€” HandlerAdapter์™€ ArgumentResolver
  • Unit 3.2 โ€” HttpMessageConverter
  • Unit 4.3 โ€” ResponseEntity์™€ ์ƒํƒœ ์ฝ”๋“œ
  • Unit 6.4 โ€” ํ‘œ์ค€ ์—๋Ÿฌ ์‘๋‹ต ์„ค๊ณ„

๋‘ ์ •์  โ€” Phase 2์™€ Phase 5

Phase 2 (DispatcherServlet 9๋‹จ๊ณ„):

  • ๋ชจ๋“  Spring Boot ๊ฐœ๋ฐœ์ž๊ฐ€ ๋งค์ผ ์‚ฌ์šฉํ•˜๋Š” ํ๋ฆ„
  • ๊ทธ๋Ÿฌ๋‚˜ 9๋‹จ๊ณ„๋ฅผ ์ •ํ™•ํžˆ ์™ธ์šฐ๋Š” ์‚ฌ๋žŒ์€ ์ ์Œ
  • ๋ฉด์ ‘ ์ฐจ๋ณ„ํ™” ํฌ์ธํŠธ

Phase 5 (Filter/Interceptor/AOP ๋น„๊ต):

  • 8-9์ฃผ์ฐจ AOP ํ•™์Šต์˜ ์ง„์งœ ์˜๋ฏธ๋ฅผ ๋ณด๋Š” ์ง€์ 
  • ๋ฉด์ ‘์—์„œ ๊ฑฐ์˜ 100% ์ถœ์ œ
  • ์‹ค๋ฌด ๋””๋ฒ„๊น… ๋Šฅ๋ ฅ์˜ ์ฒ™๋„

1~15์ฃผ์ฐจ ํ•™์Šต ์—ฌ์ •์˜ ๋งˆ๋ฌด๋ฆฌ

์ด์ œ ์ž๋ฐ” ๋ฐฑ์—”๋“œ ํ’€์Šคํƒ ๊ฐœ๋ฐœ์ž๊ฐ€ ์•Œ์•„์•ผ ํ•  ๊ฑฐ์˜ ๋ชจ๋“  ๊ฒƒ ์„ ๋ณธ ์…ˆ:

์˜์—ญ์ฃผ์ฐจ
Java ์–ธ์–ด1-3์ฃผ์ฐจ
๋™์‹œ์„ฑ4์ฃผ์ฐจ
Spring Core (IoC, AOP)5, 8-9์ฃผ์ฐจ
ํŠธ๋žœ์žญ์…˜7, 10์ฃผ์ฐจ
JPA7, 11-12์ฃผ์ฐจ
๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค13-14์ฃผ์ฐจ
Spring MVC + REST API15์ฃผ์ฐจ

๋‹ค์Œ ์ถ”์ฒœ ์ฃผ์ œ (์ž๊ธฐ ํ•™์Šต์šฉ):

  • Spring Security (์ธ์ฆ/์ธ๊ฐ€์˜ ๊นŠ์€ ์ดํ•ด)
  • ํ…Œ์ŠคํŠธ ์‹ฌํ™” (MockMvc, Testcontainers, ArchUnit)
  • ๋ถ„์‚ฐ ์‹œ์Šคํ…œ / ์‹œ์Šคํ…œ ๋””์ž์ธ (Kafka, Redis, MSA)
  • Observability (Prometheus, Grafana, OpenTelemetry)
  • CI/CD ํŒŒ์ดํ”„๋ผ์ธ (GitHub Actions, ArgoCD)

ํ•™์Šต ์‹œ ์ฃผ์˜ โ€” ๋””๋ฒ„๊ฑฐ ํ™œ์šฉ

์ด๋ฒˆ ์ฃผ์ฐจ๋Š” ๋””๋ฒ„๊ฑฐ step-through ๊ฐ€ ๊ฐ€์žฅ ๋น ๋ฅธ ํ•™์Šต๋ฒ•์ž…๋‹ˆ๋‹ค:

  1. Phase 2 โ€” DispatcherServlet:

    • DispatcherServlet.doDispatch ์— ๋ธŒ๋ ˆ์ดํฌํฌ์ธํŠธ
    • ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ  ๋‹จ๊ณ„๋ณ„๋กœ ๋”ฐ๋ผ๊ฐ€๊ธฐ
    • HandlerMapping โ†’ HandlerAdapter ํ๋ฆ„ ์ง์ ‘ ํ™•์ธ
  2. Phase 3 โ€” HttpMessageConverter:

    • MappingJackson2HttpMessageConverter.read() ์— ๋ธŒ๋ ˆ์ดํฌ
    • JSON ํŒŒ์‹ฑ ๊ณผ์ • ๊ด€์ฐฐ
  3. Phase 5 โ€” ํ˜ธ์ถœ ์ˆœ์„œ:

    • Filter, Interceptor, AOP์— ๋ชจ๋‘ ๋ธŒ๋ ˆ์ดํฌ
    • ํ˜ธ์ถœ ์ˆœ์„œ๊ฐ€ ์ •๋ง ์œ„์—์„œ ์„ค๋ช…ํ•œ ๋Œ€๋กœ์ธ์ง€ ํ™•์ธ

profile
Software Developer

0๊ฐœ์˜ ๋Œ“๊ธ€