๐Ÿ“Œ Spring Validation + @InitBinder ํ™œ์šฉ๋ฒ• / PropertyEditorSupport๋กœ ์ž…๋ ฅ ๋ฐ์ดํ„ฐ ํฌ๋งทํŒ… ์ž๋™ํ™”

My Pale Blue Dotยท2025๋…„ 4์›” 24์ผ
0

SPRING

๋ชฉ๋ก ๋ณด๊ธฐ
8/36
post-thumbnail

๐Ÿ“… 2025-04-24

๐Ÿ“ ํ•™์Šต ๋‚ด์šฉ

0๏ธโƒฃ PropertyEditorSupport์™€ ํฌ๋งทํŒ…(Formatting) ๊ฐœ๋… ์ •๋ฆฌ

โœ… PropertyEditorSupport๋ž€?

  • Spring ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ ๊ณผ์ •์—์„œ ๋ฌธ์ž์—ด ๋ฐ์ดํ„ฐ๋ฅผ ์›ํ•˜๋Š” ํƒ€์ž…์œผ๋กœ ๋ณ€ํ™˜ํ•ด์ฃผ๋Š” ํด๋ž˜์Šค.
  • ์ฃผ๋กœ ์‚ฌ์šฉ์ž ์ž…๋ ฅ๊ฐ’(String)์„ LocalDate, ์ •์ œ๋œ String ๋“ฑ์œผ๋กœ ๋ณ€ํ™˜ํ•  ๋•Œ ์‚ฌ์šฉ.
  • @InitBinder์™€ ํ•จ๊ป˜ ํŠน์ • ํ•„๋“œ์—๋งŒ ์ ์šฉ ๊ฐ€๋Šฅ.

๐Ÿšจ ์–ธ์ œ ์‚ฌ์šฉํ• ๊นŒ?

  • ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•˜๋Š” ๋ฐ์ดํ„ฐ ํฌ๋งท์ด ์ผ์ •ํ•˜์ง€ ์•Š์„ ๋•Œ (๋‚ ์งœ, ์ „ํ™”๋ฒˆํ˜ธ, ๊ธˆ์•ก ๋“ฑ)
  • ๋ฐ˜๋ณต์ ์ธ ๋ณ€ํ™˜ ๋กœ์ง์„ ์—†์• ๊ณ , ๋ฐ”์ธ๋”ฉ ์‹œ์ ์—์„œ ์ž๋™ ๋ณ€ํ™˜ ์ฒ˜๋ฆฌํ•˜๊ณ  ์‹ถ์„ ๋•Œ

โš ๏ธ PropertyEditorSupport ์‚ฌ์šฉ ์‹œ ์ฃผ์˜ํ•  ์ 

  • PropertyEditorSupport์˜ getValue() ์™€ setValue() ๋ฉ”์„œ๋“œ๋Š” thread-safe ํ•˜์ง€ ์•Š๋‹ค.
  • ์ฆ‰, ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ํ™˜๊ฒฝ์—์„œ ๊ณต์œ ํ•˜๋ฉด ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Œ.
  • ๋”ฐ๋ผ์„œ ์ ˆ๋Œ€ Spring Bean์œผ๋กœ ๋“ฑ๋กํ•ด์„œ ์‹ฑ๊ธ€ํ†ค์œผ๋กœ ์‚ฌ์šฉํ•˜๋ฉด ์•ˆ ๋จ!

โœ”๏ธ ์•ˆ์ „ํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๋ ค๋ฉด?

  • ๋งค ์š”์ฒญ๋งˆ๋‹ค ์ƒ์„ฑ๋˜๋Š” WebDataBinder์— ๋“ฑ๋กํ•ด์•ผ ํ•จ.
  • ์ด๋ฅผ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋ฐ”๋กœ @InitBinder.

โœ”๏ธ ๊ฒฐ๋ก : PropertyEditor๋Š” ์š”์ฒญ ์Šค์ฝ”ํ”„ ๋‚ด์—์„œ๋งŒ ์‚ฌ์šฉํ•ด์•ผ ์•ˆ์ „!


โœ… ์™œ @InitBinder๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ• ๊นŒ?

  • @InitBinder๋Š” ์ปจํŠธ๋กค๋Ÿฌ ๋‹จ์œ„๋กœ ๋™์ž‘ํ•˜๋ฉฐ, ์š”์ฒญ ์‹œ ์ƒ์„ฑ๋˜๋Š” WebDataBinder์— ์ปค์Šคํ…€ ์—๋””ํ„ฐ๋ฅผ ๋“ฑ๋ก.
  • ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋งค ์š”์ฒญ๋งˆ๋‹ค ์ƒˆ๋กœ์šด ์ธ์Šคํ„ด์Šค๊ฐ€ ์ ์šฉ๋˜๋ฏ€๋กœ, thread-safeํ•˜์ง€ ์•Š์€ PropertyEditorSupport๋„ ์•ˆ์ „ํ•˜๊ฒŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅ.
  • ํŠน์ • ํ•„๋“œ์—๋งŒ ์ ์šฉ๋˜๊ธฐ ๋•Œ๋ฌธ์—, ๊ธ€๋กœ๋ฒŒ ์„ค์ •๋ณด๋‹ค ๋” ์„ธ๋ฐ€ํ•œ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์ด ๊ฐ€๋Šฅ!

โœ”๏ธ ์ •๋ฆฌ:
@InitBinder ์‚ฌ์šฉ ์ด์œ 
1๏ธโƒฃ ์š”์ฒญ๋งˆ๋‹ค ์•ˆ์ „ํ•˜๊ฒŒ ์ปค์Šคํ…€ ์—๋””ํ„ฐ ์ ์šฉ
2๏ธโƒฃ ๋ฐ˜๋ณต์ ์ธ ๋ฐ์ดํ„ฐ ํฌ๋งทํŒ… ์ž๋™ํ™”
3๏ธโƒฃ ํ•„๋“œ ๋‹จ์œ„ ์„ธ๋ฐ€ํ•œ ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜


1๏ธโƒฃ Validation ์• ๋…ธํ…Œ์ด์…˜ ์ •๋ฆฌ

javax.validation ์ฃผ์š” ์• ๋…ธํ…Œ์ด์…˜

  • ์ˆซ์ž: @Min, @Max, @Positive, @Negative
  • ๋ฌธ์ž์—ด: @NotBlank, @Size, @Email, @Pattern
  • ๋‚ ์งœ: @Future, @Past, @DateTimeFormat
  • ๊ธฐํƒ€: @AssertTrue, @CreditCardNumber

2๏ธโƒฃ @InitBinder์™€ Custom PropertyEditor ์ ์šฉ

โœ… UserController.java

@Controller
@Slf4j
public class UserController {

    @InitBinder
    public void dataBinder(WebDataBinder webDataBinder) {
        // ์ „ํ™”๋ฒˆํ˜ธ์™€ ์ƒ์ผ ํ•„๋“œ์— ๋Œ€ํ•ด ์ปค์Šคํ…€ ์—๋””ํ„ฐ ๋“ฑ๋ก
        webDataBinder.registerCustomEditor(String.class, "phone", new PhoneNumberEditor());
        webDataBinder.registerCustomEditor(LocalDate.class, "birthday", new BirthdayEditor());
    }

    @GetMapping("/join")
    public void join() {
        log.info("GET /join...");
    }

    @PostMapping("/join")
    public void join_post(@Valid UserDto dto, BindingResult bindingResult, Model model) {
        log.info("POST /join..." + dto);

        if (bindingResult.hasErrors()) {
            for (FieldError error : bindingResult.getFieldErrors()) {
                log.info("Error Field: " + error.getField() + " | Msg: " + error.getDefaultMessage());
                model.addAttribute(error.getField(), error.getDefaultMessage());
            }
        }
    }

    // ์ƒ์ผ ๋ณ€ํ™˜์šฉ ์—๋””ํ„ฐ
    private static class BirthdayEditor extends PropertyEditorSupport {
        @Override
        public void setAsText(String text) {
            LocalDate date = text.isEmpty() ? LocalDate.now()
                : LocalDate.parse(text.replaceAll("~", "-"), DateTimeFormatter.ofPattern("yyyy-MM-dd"));
            setValue(date);
        }
    }

    // ์ „ํ™”๋ฒˆํ˜ธ ํฌ๋งทํŒ… ์—๋””ํ„ฐ
    private static class PhoneNumberEditor extends PropertyEditorSupport {
        @Override
        public void setAsText(String phone) {
            if (phone.isEmpty()) {
                setValue("");
            } else {
                String formatNumber = phone.replace("-", "");
                setValue(formatNumber);
            }
            log.info("PhoneNumberEditor's setAsText invoke..." + phone);
        }
    }
}

3๏ธโƒฃ JSP ํšŒ์›๊ฐ€์ž… ํผ

<form action="${pageContext.request.contextPath}/join" method="post">
    <h1>ํšŒ์›๊ฐ€์ž…</h1>
    ...
    <div>
        <label>phone:</label><span>${phone}</span><br>
        <input name="phone" placeholder="ex)010-0000-0000" />
    </div>
    <div>
        <label>birthday:</label><span>${birthday}</span><br>
        <input type="text" name="birthday" placeholder="ex)2025~01~01"/>
    </div>
    <div>
        <input type="submit" value="ํšŒ์›๊ฐ€์ž…" />
    </div>
</form>

๐Ÿ”น ๋กœ๊ทธ ์ถœ๋ ฅ ์˜ˆ์‹œ (๋ฐ”์ธ๋”ฉ ๊ณผ์ • ํ™•์ธ)

INFO : UserController - UserController's dataBinder ํ˜ธ์ถœ
INFO : UserController - POST /join...UserDto(phone=01012345678, birthday=2025-04-24)
INFO : PhoneNumberEditor - PhoneNumberEditor's setAsText invoke...010-1234-5678

๐Ÿ”— ์ฐธ๊ณ  ์ž๋ฃŒ


๋А๋‚€ ์ 

  • @InitBinder๋ฅผ ํ†ตํ•ด ์ž…๋ ฅ ๋ฐ์ดํ„ฐ์˜ ํฌ๋งท ๋ฌธ์ œ๋ฅผ ์‚ฌ์ „์— ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ด ๊ฐ€์žฅ ํฐ ์ˆ˜ํ™•.
  • ๋‹จ์ˆœํžˆ ๊ธฐ๋Šฅ ๊ตฌํ˜„์ด ์•„๋‹ˆ๋ผ, ์•ˆ์ •์„ฑ(thread-safe) ๊นŒ์ง€ ๊ณ ๋ คํ•ด์•ผ ํ•จ์„ ๋‹ค์‹œ ํ•œ๋ฒˆ ๋А๊ผˆ๋‹ค.
  • ์‹ค๋ฌด์—์„œ๋„ ์ ๊ทน ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ข‹์€ ํŒจํ„ด์ด๋ผ๊ณ  ์ƒ๊ฐ!

์š”์•ฝ

  • PropertyEditorSupport๋Š” ์ž…๋ ฅ๊ฐ’ ๋ณ€ํ™˜์— ์œ ์šฉํ•˜์ง€๋งŒ, thread-safe ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ํ•ญ์ƒ @InitBinder๋กœ ์š”์ฒญ๋งˆ๋‹ค ๋“ฑ๋กํ•ด์•ผ ํ•จ.
  • Validation ์• ๋…ธํ…Œ์ด์…˜์œผ๋กœ ๊ธฐ๋ณธ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ณ , ์ปค์Šคํ…€ ์—๋””ํ„ฐ๋กœ ํฌ๋งทํŒ…์„ ์ž๋™ํ™”.
  • ์‚ฌ์šฉ์ž ์ž…๋ ฅ ํฌ๋งท์ด ๋‹ค์–‘ํ•œ ํ™˜๊ฒฝ์—์„œ ์ฝ”๋“œ ํšจ์œจ์„ฑ๊ณผ ์•ˆ์ •์„ฑ์„ ๋™์‹œ์— ํ™•๋ณดํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•!

profile
Here, My Pale Blue.๐ŸŒ

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