728x90

 

AOP 란?

 

AOP Aspect Oriented Programming의 약자로 관점 지향 프로그래밍이라고 불린다. 관점 지향은 쉽게 말해 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 각각 모듈화하겠다는 것이다. 여기서 모듈화란 어떤 공통된 로직이나 기능을 하나의 단위로 묶는 것을 말한다.

 

예로들어 핵심적인 관점은 결국 우리가 적용하고자 하는 핵심 비즈니스 로직이 된다. 또한 부가적인 관점은 핵심 로직을 실행하기 위해서 행해지는 데이터베이스 연결, 로깅, 파일 입출력 등을 예로 들 수 있다.

 

AOP에서 각 관점을 기준으로 로직을 모듈화한다는 것은 코드들을 부분적으로 나누어서 모듈화하겠다는 의미다. 이때, 소스 코드상에서 다른 부분에 계속 반복해서 쓰는 코드들을 발견할 수 있는 데 이것을 흩어진 관심사 (Crosscutting Concerns)라 부른다. 



출처: https://engkimbs.tistory.com/746 [새로비]

 

Validation dependency 를 활용하여 Dto 검증 하기

https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation

 

spring boot starter validation 을 gradle 또는 maven 에 추가 합니다.

 

그리고나서 검증하고 싶은 Dto 에 다음과 같이 코드로 작성합니다.

 

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SignupRequestDto {

    @Email(message = "email 형식 이 올바르지 않습니다.")
    private String email;

    @NotEmpty(message = "nickname 은 필수 값 입니다.")
    @Size(min = 2, max = 8, message = "nickname 의 길이는 2 ~ 8 입니다.")
    private String nickname;

    @NotEmpty(message = "password 는 필수 값 입니다.")
    private String password;

    public User toUserEntity() {
        User user = new User();
        user.setEmail(email);
        user.setNickname(nickname);
        user.setPassword(password);
        return user;
    }
}

 

그런 다음 컨트롤러 에서 Valid 또는 Validated 어노테이션을 객체 앞에 추가하여 검증 합니다.

그리고 주의 하실점은 Bindresult 를 항상 검증 하고자 하는 객체 바로 뒤에 위치 시켜 줘야합니다.

그 이유는 Bindresult 바로 앞에 있는 객체를 맵핑 하는데 Binding result 가 없더라도 Valid 검사는 진행 되지만 

추후에 작성할 Aop 에서 Aop 로직을 타지 않고 바로 Valid 에러 를 클라이언트에게 리턴 함으로 response 를 핸들링 할수 없습니다.

 

@PostMapping("/signup")
public ResponseEntity<?> registerUser(@Validated @RequestBody SignupRequestDto signupDto,
    BindingResult bindingResult) {
    User user = signupDto.toUserEntity();

    if (userService.findByEmail(user.getEmail()) != null) {
        ResponseDto<SignupRequestDto> responseDto = new ResponseDto<>("이미 가입된 회원 이메일 입니다.",
            signupDto,
            HttpStatus.BAD_REQUEST);
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(responseDto);
    }

    User createdUser = userService.create(user);
    ResponseDto<SignupResponseDto> responseDto = new ResponseDto<>("회원가입 성공",
        new SignupResponseDto(createdUser.getId()), HttpStatus.OK);
    return ResponseEntity.ok(responseDto);
}

 

Aop 등록하기

@Slf4j
@Aspect
@Component
public class BindingAdvice {

    @Pointcut("execution(* io.eyelighting.backend.controller..*.*(..))")
    public void cut() {
    }

    @Around("cut()")
    public Object validationHandler(ProceedingJoinPoint joinPoint) throws Throwable {
        String type = joinPoint.getSignature().getDeclaringTypeName();
        String method = joinPoint.getSignature().getName();
        log.info("validation handler type = {}", type);
        log.info("validation handler method = {}", method);

        Object[] args = joinPoint.getArgs(); // join point parameter

        for (Object arg : args) {
            if (arg instanceof BindingResult) {
                BindingResult bindingResult = (BindingResult) arg;

                if (bindingResult.hasErrors()) {
                    Map<String, String> errorMap = new HashMap<>();

                    for (FieldError error : bindingResult.getFieldErrors()) {
                        errorMap.put(error.getField(), error.getDefaultMessage());
                    }
                    // TODO: 나중에 함수로 뺄 것
                    ResponseDto<?> responseDto = new ResponseDto<>(method + " 요청에 실패 하였습니다.",
                        errorMap,
                        HttpStatus.BAD_REQUEST);
                    return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(responseDto);
                }
            }
        }

        return joinPoint.proceed();
    }
}

 

Around 는 컨트롤러 앞뒤로 둘다 검사하지만 Before 로만 작성하셔도 무방합니다.

joinPoint 의 파라미터 들을 args 로 가져와 그중 bindingResult 클래스와 일치 하는 것을 타입 캐스팅하여 에러가 있는지 검사한뒤 

에러가 있으면 Map 객체에 메세지를 담아 리턴합니다.

 

 

728x90