์šฐ๊ทœ์ด์ธ์šฐ์œค
Eager To Learn ๐ŸŒŒ
์šฐ๊ทœ์ด์ธ์šฐ์œค
์ „์ฒด ๋ฐฉ๋ฌธ์ž
์˜ค๋Š˜
์–ด์ œ

๋ธ”๋กœ๊ทธ ๋ฉ”๋‰ด

  • ๐Ÿก ํ™ˆ
  • ๐Ÿš€ ๊นƒํ—ˆ๋ธŒ
  • โ›… ํƒœ๊ทธ ํด๋ผ์šฐ๋“œ
  • ๋ถ„๋ฅ˜ ์ „์ฒด๋ณด๊ธฐ (217)
    • ๐Ÿ‘จ๐Ÿป‍๐Ÿ’ป PS (170)
      • JAVA (82)
      • MYSQL (1)
      • Docker (2)
      • PYTHON (24)
      • LeetCode 150 (39)
      • Algorithm ๊ธฐ๋ฒ• (1)
      • ๋ฐ”ํ‚น๋… (21)
    • ๋ธ”๋กœ๊ทธ ์ด์‚ฌ (0)
    • Error (1)
    • CS (15)
      • DataBase (2)
      • OS (7)
      • Network (1)
      • Spring (1)
      • ์ž๋ฃŒ๊ตฌ์กฐ (3)
      • Java (1)
    • Learned (7)
      • Spring (7)
    • ๊ฐœ๋ฐœ์„œ์  (15)
      • ๊ฐ€์ƒ ๋ฉด์ ‘ ์‚ฌ๋ก€๋กœ ๋ฐฐ์šฐ๋Š” ๋Œ€๊ทœ๋ชจ ์‹œ์Šคํ…œ ์„ค๊ณ„ ๊ธฐ์ดˆ (1)
      • ์˜ค๋ธŒ์ ํŠธ - ์กฐ์˜ํ˜ธ (7)
      • ์นœ์ ˆํ•œ SQL ํŠœ๋‹ (7)
    • ํšŒ๊ณ  (2)
hELLO ยท Designed By ์ •์ƒ์šฐ.
์šฐ๊ทœ์ด์ธ์šฐ์œค

Eager To Learn ๐ŸŒŒ

Learned/Spring

Dto Validation ์˜ˆ์™ธ ์ฒ˜๋ฆฌ์— AOP๋ฅผ ์ ์šฉํ•ด๋ณด์ž!

2023. 8. 8. 20:51

๊ธฐ์กด ๋ฐฉ์‹

implementation 'org.springframework.boot:spring-boot-starter-validation'

์œ„ validation ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด

 

Controller ์—์„œ @RequestBody ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ๋งคํ•‘ํ•˜๋Š” Request Dto ์˜ ํ•„๋“œ ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•  ์ˆ˜ ์žˆ๋‹ค.

 

public class UserCreateRequest {

    @Email(message = "์˜ฌ๋ฐ”๋ฅธ ์ด๋ฉ”์ผ ํ˜•์‹์ด ์•„๋‹™๋‹ˆ๋‹ค. '@' ๋ฅผ ํฌํ•จ์‹œ์ผœ์ฃผ์„ธ์š”.")
    private String email;

    @Length(min = 8,message = "๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ์ตœ์†Œ 8์ž ์ด์ƒ์ž…๋‹ˆ๋‹ค.")
    private String password;

}

์œ„์™€ ๊ฐ™์ด ๋งคํ•‘๋˜๋Š” ํ•„๋“œ์— validation์—์„œ ์ œ๊ณตํ•˜๋Š” ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

 

 

 

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/users")
public class UserApiController {
    private final UserService userService;

    @PostMapping
    public ResponseEntity<Response<UserCreateResponse>> create(@RequestBody @Validated UserCreateRequest requestDto, BindingResult br) {

        if (br.hasErrors()) {
            throw new BindingException(br.getFieldError().getDefaultMessage());
        }

        UserCreateResponse response = userService.createUser(requestDto);

        return ResponseEntity.ok(Response.success(response));
    }
}

 

Controller์˜ ๋ฉ”์„œ๋“œ์—์„œ @Validated ๋ฅผ ์œ ํšจ์„ฑ์„ ๊ฒ€์ฆํ•  ๊ฐ์ฒด ์•ž์— ๋ถ™์ด๊ณ 

 

๋ฐ”๋กœ ๋’ค์— BindingResult ๊ฐ์ฒด๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ถ”๊ฐ€ํ•ด์ฃผ๋ฉด ์˜ˆ์™ธ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๐Ÿšจ ๊ผญ, ๋ฐ”๋กœ ๋’ค ํŒŒ๋ผ๋ฏธํ„ฐ์— BindingResult ๊ฐ์ฒด๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

 

์ด๋ ‡๊ฒŒ ์ ์šฉํ•˜๊ณ  ๋‚˜๋‹ˆ, ๋” ๊ฐœ์„ ํ•˜๊ณ  ์‹ถ์€ ๋ถ€๋ถ„์ด ๋ณด์˜€๋‹ค.

 

if (br.hasErrors()) {
        throw new BindingException(br.getFieldError().getDefaultMessage());
}

๐Ÿ’ก validation์„ ํ†ตํ•ด ์˜ˆ์™ธ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๋‹ค๋ณด๋ฉด, ์ด ๋ถ€๋ถ„์ด ๊ณตํ†ต์ ์œผ๋กœ ์‚ฌ์šฉ๋  ๊ฒƒ์ด ์˜ˆ์ƒ๋˜์—ˆ๋‹ค.

 

AOP ๋ฅผ ํ™œ์šฉํ•ด ์ปค์Šคํ…€ ์–ด๋…ธํ…Œ์ด์…˜์„ ๋งŒ๋“ค์–ด ๋ฐ”์ธ๋”ฉ ์ฒดํฌ๋ฅผ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ๋ช…์‹œํ•˜๊ณ 

 

๋ฉ”์„œ๋“œ ์•ˆ์—๋Š” ํ•ต์‹ฌ ๋กœ์ง์œผ๋กœ๋งŒ ๊ตฌ์„ฑํ•ด ๊ฐ€๋…์„ฑ์„ ๋†’์ด๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ฐœ์„ ํ•ด์•ผ๊ฒ ๋‹ค ์ƒ๊ฐํ–ˆ๋‹ค.

 


AOP ๋ฅผ ํ™œ์šฉํ•ด ๊ฐœ์„ ํ•ด๋ณด์ž

 

// ์ž๋ฐ” 17๋ฒ„์ „, ์Šคํ”„๋ง ๋ถ€ํŠธ 3.1.2 ๊ธฐ์ค€
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-aop', version: '3.1.2'
testImplementation group: 'org.springframework.boot', name: 'spring-boot-starter-aop', version: '3.1.2'

 

AOP๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ ์œ„ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•ด์ค€๋‹ค.

 

๊ทธ๋Ÿฌ๋ฉด ์Šคํ”„๋ง ๋ถ€ํŠธ ์ž๋™ ์„ค์ •์œผ๋กœ AnnotationAwareAspectJAutoProxyCreator ๋ผ๋Š” ๋นˆ ํ›„์ฒ˜๋ฆฌ๊ธฐ๊ฐ€ ์Šคํ”„๋ง ๋นˆ์— ๋“ฑ๋ก๋œ๋‹ค.

 

๊ทธ๋Ÿฌ๋ฉด ์ด ๋นˆ ํ›„์ฒ˜๋ฆฌ๊ธฐ๊ฐ€ ์Šคํ”„๋ง ๋นˆ์œผ๋กœ ๋“ฑ๋ก๋œ Advisor๋ฅผ ์ž๋™์œผ๋กœ ์ฐพ์•„์„œ ํ•„์š”ํ•œ ๊ณณ์— ํ”„๋ก์‹œ๋ฅผ ์ ์šฉํ•˜๊ณ 

 

Advisor : ๋ถ€๊ฐ€๊ธฐ๋Šฅ๊ณผ ๋ถ€๊ฐ€๊ธฐ๋Šฅ์„ ์ ์šฉํ•  ๋Œ€์ƒ์„ ์•Œ๊ณ  ์žˆ๋Š” ์˜ค๋ธŒ์ ํŠธ

 

ํ”„๋ก์‹œ ๊ฐ์ฒด์—์„œ ์›ํ•˜๋Š” ๋ถ€๊ฐ€๊ธฐ๋Šฅ์„ ์‹คํ–‰์‹œํ‚ค๊ณ  ์›๋ณธ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๊ฑฐ๋‚˜, ์›๋ณธ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ๋ถ€๊ฐ€๊ธฐ๋Šฅ์„ ์‹คํ–‰์‹œํ‚ค๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface BindingCheck {

}

 

์œ„์™€ ๊ฐ™์€ ์ปค์Šคํ…€ ์–ด๋…ธํ…Œ์ด์…˜์„ ์ •์˜ํ•œ๋‹ค.

 

 

@Aspect
@Component
public class BindingCheckAop {

    @Before(value = "@annotation(com.wanted.global.annotation.BindingCheck)")
    public void before(JoinPoint joinPoint) {
        for (Object arg : joinPoint.getArgs()) {
            if (arg instanceof BindingResult) {
                BindingResult br = (BindingResult) arg;
                if (br.hasErrors()) {
                    throw new BindingException(br.getFieldError().getDefaultMessage());
                }
            }
        }
    }
}

 

์œ„์™€ ๊ฐ™์ด @Aspect ์™€ @Component ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด ๋นˆ ํ›„์ฒ˜๋ฆฌ๊ธฐ๊ฐ€ ๋‚ด๊ฐ€ ์ •์˜ํ•œ Advisor๋ฅผ ์ฐพ์„ ์ˆ˜ ์žˆ๋‹ค.

 

@Before ์™€ value ์˜ต์…˜๊ฐ’์— ์ปค์Šคํ…€ ์–ด๋…ธํ…Œ์ด์…˜์˜ ๊ฒฝ๋กœ ์ •๋ณด๋ฅผ ์ž…๋ ฅํ–ˆ๋‹ค.

 

์ด ์–ด๋…ธํ…Œ์ด์…˜์ด ์ ์šฉ๋œ ๋ฉ”์„œ๋“œ๊ฐ€ ์‹คํ–‰ํ•˜๊ธฐ ์ „, before() ์— ์ •์˜ํ•œ ๋กœ์ง์ด ์‹คํ–‰๋˜๋„๋ก ์„ค์ •ํ•˜์˜€๋‹ค.

 

JoinPoint ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด์„œ ์ปค์Šคํ…€ ์–ด๋…ธํ…Œ์ด์…˜์ด ์ ์šฉ๋œ ๋ฉ”์„œ๋“œ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ณ 

 

BindingResult ๊ฐ์ฒด์— ์ ‘๊ทผํ•ด ์—๋Ÿฌ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๋ฉ”์„œ๋“œ์ด๋‹ค.

 

 

๊ฐœ์„  ํ›„

 

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/users")
public class UserApiController {
    private final UserService userService;

    @PostMapping
    @BindingCheck
    public ResponseEntity<Response<UserCreateResponse>> create(@RequestBody @Validated UserCreateRequest requestDto, BindingResult br) {

        UserCreateResponse response = userService.createUser(requestDto);

        return ResponseEntity.ok(Response.success(response));
    }
}

 

Controller ์ฝ”๋“œ๊ฐ€ ๊น”๋”ํ•ด์กŒ๋‹ค.

 

 

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ

 

๋จผ์ €, Controller ํ…Œ์ŠคํŠธ๋ฅผ WebMvcTest์™€ ์ง์ ‘ ์„ค์ •ํ•œ Spring Security๋ฅผ import ํ•ด์„œ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์—ˆ๋‹ค.

 

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์—์„œ๋„ AOP ์ ์šฉ์„ ์œ„ํ•ด์„œ๋Š”, ์ถ”๊ฐ€ ์–ด๋…ธํ…Œ์ด์…˜์ด ํ•„์š”ํ•˜๋‹ค.

 

@WebMvcTest(value = UserApiController.class)
@EnableAspectJAutoProxy
@Import({SecurityConfig.class, BindingCheckAop.class})
class UserApiControllerTest {
    
   .....
   
}

 

@EnableAspectJAutoProxy ์™€ ์ •์˜ํ•œ Advisor ํด๋ž˜์Šค์ธ BindingCheckAop.class ๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์—ˆ๋‹ค.

 

        private static Stream<Arguments> createUserFailScenarios() {
            return Stream.of(
                    Arguments.of(EMAIL_BINDING_ERROR_MESSAGE, UserCreateRequest.builder().email("email").password("12345678").build()),
                    Arguments.of(PASSWORD_BINDING_ERROR_MESSAGE, UserCreateRequest.builder().email("email@email.com").password("1234567").build())
            );
        }

        @DisplayName("Request Dto ์œ ํšจ์„ฑ ๊ฒ€์ฆ ์‹คํŒจ")
        @ParameterizedTest
        @MethodSource("createUserFailScenarios")
        void createUser_error_bindingError(String errorMessage, UserCreateRequest request) throws Exception {

            mockMvc.perform(post("/api/v1/users")
                            .contentType(APPLICATION_JSON)
                            .content(objectMapper.writeValueAsString(request)))
                    .andDo(print())
                    .andExpect(status().isBadRequest())
                    .andExpect(jsonPath("$.message").exists())
                    .andExpect(jsonPath("$.message").value("ERROR"))
                    .andExpect(jsonPath("$.result").exists())
                    .andExpect(jsonPath("$.result").value(errorMessage));

        }

 

์œ„์™€ ๊ฐ™์ด Controller ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด๋ณด์•˜๋‹ค.

 

์ฒซ๋ฒˆ์งธ ์‹œ๋‚˜๋ฆฌ์˜ค๋Š” @๊ฐ€ ํฌํ•จ๋˜์–ด์žˆ์ง€ ์•Š์€ email์„ ๋‹ด์•„ ์š”์ฒญํ–ˆ๊ณ 

 

๋‘๋ฒˆ์งธ ์‹œ๋‚˜๋ฆฌ์˜ค๋Š” 8์ž๋ฆฌ๋ณด๋‹ค ์งง์€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋‹ด์•„ ์š”์ฒญํ–ˆ๋‹ค.

 

 

์›ํ•˜๋Š” ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์™”๋‹ค.

    'Learned/Spring' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€
    • Dispatcher Servlet์€ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ• ๊นŒ?
    • Swagger API ์ ์šฉ ์‹œ, Controller ์ฝ”๋“œ๊ฐ€ ๋„ˆ๋ฌด ๋”๋Ÿฌ์›Œ์ง„๋‹ค.. ๋ถ„๋ฆฌํ•ด๋ณด์ž
    • ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ปค๋ฒ„๋ฆฌ์ง€ ๋ถ„์„ ๋„๊ตฌ Jacoco๋ฅผ ์ ์šฉํ•ด๋ณด์ž!
    • @ControllerAdvice ๋Š” ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š” ๊ฒƒ์ผ๊นŒ?
    ์šฐ๊ทœ์ด์ธ์šฐ์œค
    ์šฐ๊ทœ์ด์ธ์šฐ์œค
    ๊ฐœ๋ฐœ์ž ๊ฟˆ๋‚˜๋ฌด

    ํ‹ฐ์Šคํ† ๋ฆฌํˆด๋ฐ”