일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- EC2
- string
- 알고리즘
- immutable
- Action
- aws
- redis
- AOP
- JUnit
- git
- 사이드 프로젝트
- build_test
- rds
- java
- Github
- Spring
- QueryDSL
- compiler
- springboot
- template
- mutable
- kotlin
- db
- CodeDeploy
- Airflow
- 토비의 스프링
- JPA
- workflow
- Today
- Total
개발 일기
Spring AOP 를 활용해보자! - BindingResult 본문
https://github.com/pursue503/study-tdd
GitHub - pursue503/study-tdd: TDD
TDD . Contribute to pursue503/study-tdd development by creating an account on GitHub.
github.com
최근 백엔드 지인과 TDD 로 회원가입 및 게시판을 만드는 간단한 프로젝트를 진행하였다.
해당 프로젝트를 진행하는 도중에
컨트롤러에서 중복된 코드가 많이 발생하였는데
코드는 아래처럼 되어 있었다.
@Slf4j
@RequiredArgsConstructor
@RestController
public class PostController {
private final PostService postService;
/**
* 게시물 저장 요청을 받아 저장 처리후 반환값으로 저장된 게시물의 postId를 반환합니다.
*
* @param dto 게시물 제목, 게시물 내용, 이미지 주소 (선택사항)
* @return 성공적으로 저장된 게시물의 고유 아이디
*/
@PostMapping("/posts")
public ResponseDTO<Long> savePost(@Valid @RequestBody PostSaveRequestDTO dto, BindingResult result) {
if (result.hasErrors()) {
throw new InvalidPostParameterException(result, GeneralParameterErrorCode.INVALID_PARAMETER)
}
return new ResponseDTO<>(postService.savePost(dto), PostMessage.SAVE_POST_SUCCESS, HttpStatus.OK);
}
@GetMapping("/posts/{postId}")
public ResponseDTO<PostOneDTO> findOnePost(@PathVariable Long postId) {
return new ResponseDTO<>(postService.findOnePost(postId), PostMessage.FIND_POST_ONE_SUCCESS, HttpStatus.OK);
}
@GetMapping("/posts")
public ResponseDTO<PageResponseDTO> findPostPage(@RequestParam int page) {
return new ResponseDTO<>(postService.findPostsPage(page), PostMessage.FIND_POST_PAGE_SUCCESS, HttpStatus.OK);
}
@PatchMapping("/posts")
public ResponseDTO<UpdatedPostDTO> updateOnePost(@Valid @RequestBody PostPatchRequestDTO dto, BindingResult result) {
if (result.hasErrors()) {
throw new InvalidPostParameterException(result, GeneralParameterErrorCode.INVALID_PARAMETER)
}
return new ResponseDTO<>(postService.updateOnePost(dto), PostMessage.UPDATE_POST_SUCCESS, HttpStatus.OK);
}
@DeleteMapping("/posts/{postId}")
public ResponseDTO<LocalDateTime> deleteOnePost(@PathVariable Long postId) {
return new ResponseDTO<>(postService.deleteOnePost(postId), PostMessage.DELETE_POST_SUCCESS, HttpStatus.OK);
}
}
여기서 지금 중복되고 있는 코드는 BindResult 부분이다
BindResult Valid 에서는 따로 설명을 하지 않겠다.
이 코드를 공통으로 묶어서 처리할 방법이 없을까 라는 생각을 가지게 되었는데
지난달에 토비의 스프링을 학습하면서
AOP 부분을 본 기억이 났다. 이 코드를 Spring AOP 로 묶어서 처리해볼수 없을까?
한번 바로 적용을 해봤다.
https://dev-jo.tistory.com/58?category=947368
토비의 스프링 6장 AOP
노션을 참고하면 더욱 좋습니다. 6장 AOP AOP 는 IoC , DI, 서비스 추상화와 더불어 스프링 3대 기반기술의 하나이다. AOP는 정말 중요한 개념이다 해당 장을 이용하여 자세히 알아보도록 하자 6.1 트랜
dev-jo.tistory.com
적용한 코드는 아래처럼 변경되었다.
BindingReuslt 의 if hasErrors가 사라진 컨트롤러
@Slf4j
@RequiredArgsConstructor
@RestController
public class PostController {
private final PostService postService;
/**
* 게시물 저장 요청을 받아 저장 처리후 반환값으로 저장된 게시물의 postId를 반환합니다.
*
* @param dto 게시물 제목, 게시물 내용, 이미지 주소 (선택사항)
* @return 성공적으로 저장된 게시물의 고유 아이디
*/
@PostMapping("/posts")
public ResponseDTO<Long> savePost(@Valid @RequestBody PostSaveRequestDTO dto, BindingResult result) {
return new ResponseDTO<>(postService.savePost(dto), PostMessage.SAVE_POST_SUCCESS, HttpStatus.OK);
}
@GetMapping("/posts/{postId}")
public ResponseDTO<PostOneDTO> findOnePost(@PathVariable Long postId) {
return new ResponseDTO<>(postService.findOnePost(postId), PostMessage.FIND_POST_ONE_SUCCESS, HttpStatus.OK);
}
@GetMapping("/posts")
public ResponseDTO<PageResponseDTO> findPostPage(@RequestParam int page) {
return new ResponseDTO<>(postService.findPostsPage(page), PostMessage.FIND_POST_PAGE_SUCCESS, HttpStatus.OK);
}
@PatchMapping("/posts")
public ResponseDTO<UpdatedPostDTO> updateOnePost(@Valid @RequestBody PostPatchRequestDTO dto, BindingResult result) {
return new ResponseDTO<>(postService.updateOnePost(dto), PostMessage.UPDATE_POST_SUCCESS, HttpStatus.OK);
}
@DeleteMapping("/posts/{postId}")
public ResponseDTO<LocalDateTime> deleteOnePost(@PathVariable Long postId) {
return new ResponseDTO<>(postService.deleteOnePost(postId), PostMessage.DELETE_POST_SUCCESS, HttpStatus.OK);
}
}
그리고 모든 컨트롤러에 Before 에서 BindResult 로직을 수행할 AOP가 추가되었다.
@Slf4j
@Component
@Aspect
public class BindingResultAop {
/**
*
* api 패키지 내부 컨트롤러 실행전에 실행
*
* @param joinPoint
* @return
* @throws Throwable
*/
@Before("execution(* study.tdd.simpleboard.api..*Controller.*(..))")
public void bindingBefore(JoinPoint joinPoint) throws Throwable {
for (Object arg : joinPoint.getArgs()) {
if (arg instanceof BindingResult) {
BindingResult result = (BindingResult) arg;
if (result.hasErrors()) {
throw new InvalidPostParameterException(result, GeneralParameterErrorCode.INVALID_PARAMETER);
}
}
} // end for
} // end
}
이 코드를 설명하자면
모든 컨트롤러가 실행되기전에 실행되고
JoinPoint.getArgs() 컨트롤러의 모든 파라미터를 가져와서
반복문을 통해 현재 Object 가 BindResult 로 instanceof 변환이 가능하다면
캐스팅을 한 후 공통 로직을 수행하도록 설정했다.
추후 리팩토링 검증을 위해 모든 테스트 코드를 실행해 보았고.
모든 테스트 코드가 통과되었다.
1. Spring AOP 는 컨트롤러 말고 서비스단에서도 가능하다 ( 유저 처리 공통 로직 등 )
2. Spring AOP 는 정말 대단하다..
2022. 01. 24 추가
이전 코드에는 BindResult도 파라미터에서 제거하였는데
제거 하면 AOP에서 BIndReuslt 를 못잡습니다.
그래서 if has errors의 코드만 제거하고 파라미터는 그대로 둬야 합니다.
https://github.com/pursue503/study-tdd/pull/22
'Spring' 카테고리의 다른 글
Spring AOP 를 활용해보자! - BindingResult 2편 (0) | 2022.01.25 |
---|---|
토비의 스프링 9.3장 아키텍처 (0) | 2021.12.13 |
토비의 스프링 6장 AOP (0) | 2021.12.13 |
토비의 스프링 5장 - 서비스 추상화 (0) | 2021.12.13 |
토비의 스프링 4장 - 예외 (0) | 2021.12.13 |