SpringBoot + MariaDB 11 AOP자동에러처리(Spring AOP)
AOP(Aspect Orientied Programing)관점 지향 프로그래밍
관점지향 프로그래밍 + 객체지향 프로그래밍
로그인 기능과 회원가입 기능이 있다고하자.
로그인 기능
- 유효성 검사, 보안처리
메인 기능(
username과 password를 잘 받아야하고.
DB에 존재하고 일치하는지 확인
)
후처리 로그 시간남기기
회원가입 기능
- 유효성 검사, 보안처리
메인 기능(
username password등 여러 데이터를 받고
DB에 insert되게된다.
)
후처리 로그
그럼 두 개의 기능을 보았을 때 공통점이 어디에 있나요?
메인을 제외하고 전과 후가 똑같습니다.
그림으로 표현하면 이러하다
이때까지 에러처리를 함수마다 각각 걸어주었지만 모두 전과 후가 같으니 에러처리해주는 코드를 어디다 저장을하고 불러오기만 하면 코드가 훨씬 깔금해질것이다.
public String signup(@Valid SignupDto signupDto, BindingResult bindingResult) { // key=vaule (x-www-form-urlencoded)
if(bindingResult.hasErrors()) {
Map<String, String>errorMap = new HashMap<>();
for(FieldError error : bindingResult.getFieldErrors()) {
errorMap.put(error.getField(), error.getDefaultMessage());
}
throw new CustomValidationException("유효성검사 실패함",errorMap);
}else {
User user = signupDto.toEntity();
authService.회원가입(user);
return "auth/signin";
}
}
위 코드는 회원가입을 할때 에러처리를 해주는 함수인데 if문을 사용해서 에러처리를하고 else로 에러가없으면 회원가입해주고 페이지를 이동시켜준다 이코드를 깔금하게 만들어보자 우선 mavenRepository에서 라이브러리를 받아오자
Spring AOP받기
위와같이 검색후 제일 최신버전을 받아 pom.xml에 dependencies에 추가해주기
AOP생성
handler.aop.ValidationAdivice.java
AOP같이 애매모한 것은 @Component라고 명시하자
@Aspect도 추가
package com.cos.photogramstart.handler.aop;
@Component //RestContorller, Service 모든 것들이 Component를 상속해서 만들어져 있음.
@Aspect
public class ValidationAdivice {
// execution()안에 처음은 접근제한자가 들어오게된다.(public,private,등)
// *로하면 모든 접근제한자 허용
@Around("execution(* com.cos.photogramstart.web.api.*Controller.*(..))") // 시작과 끝까지 간섭
// web.*Controller = webn의 모든Controller로 끝나는것
// *Controller.*(..) = 컨트롤러의 모든 메서드 (..)은 모든 파라미터의 값이 어떤것이든 하겠다는뜻.
public Object apiAdivice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
System.out.println("web api 컨트롤러");
Object[] args = proceedingJoinPoint.getArgs();
for(Object arg : args) {
if(arg instanceof BindingResult) {
System.out.println("유효성 검사를 하는 함수입니다.");
BindingResult bindingResult = (BindingResult) arg;
if(bindingResult.hasErrors()) {
Map<String, String>errorMap = new HashMap<>();
for(FieldError error : bindingResult.getFieldErrors()) {
errorMap.put(error.getField(), error.getDefaultMessage());
}
throw new CustomValidationApiException("유효성검사 실패함",errorMap);
}
}
}
return proceedingJoinPoint.proceed();
}
@Around("execution(* com.cos.photogramstart.web.*Controller.*(..))") // 시작과 끝까지 간섭
public Object advice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
System.out.println("web 컨트롤러");
Object[] args = proceedingJoinPoint.getArgs();
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());
}
throw new CustomValidationException("유효성검사 실패함",errorMap);
}
}
}
return proceedingJoinPoint.proceed(); // 이때 해당함수가 실행된다.
}
}
우리가 만약 댓글을 작성한다고 가정해보자 댓글을 작성하게되면
@PostMapping("/api/comment")
public ResponseEntity<?> commentSave(@Valid @RequestBody CommentDto commentDto,BindingResult bindingResult,@AuthenticationPrincipal PrincipalDetails principalDetails){
Comment comment = commentService.댓글쓰기(commentDto.getContent(),commentDto.getImageId(),principalDetails.getUser().getId());
return new ResponseEntity<>(new CMRespDto<>(1,"댓글쓰기성공",comment),HttpStatus.CREATED);
}
위의 함수가 실행되게 된다 이때 함수가 실행되기전에 함수의 데이터만 들고 방금만든 advice로 전해지게된다
현재 함수는 web.api.CommentApiController이다 그렇다면 위에 advice에서 무슨 함수가 실행이되겠는가?
당연히 위에함수이다 왜? @Around경로를 보면 api는 위에 포함되기 때문이다 이렇게 api와 페이지요청을 분간 가능한것이고 api에 따른 공통 에러처리 페이지요청에따른 공통 에러처리가 가능하게 되는것이다.
Object[] args = proceedingJoinPoint.getArgs();
for(Object arg : args) {
if(arg instanceof BindingResult) {
System.out.println("유효성 검사를 하는 함수입니다.");
BindingResult bindingResult = (BindingResult) arg;
if(bindingResult.hasErrors()) {
Map<String, String>errorMap = new HashMap<>();
for(FieldError error : bindingResult.getFieldErrors()) {
errorMap.put(error.getField(), error.getDefaultMessage());
}
throw new CustomValidationApiException("유효성검사 실패함",errorMap);
}
}
return proceedingJoinPoint.proceed();
}
그럼 api 에러처리의 그해당 메서드의 데이터를 가져오는 함수를 보자.
1. 메서드의 모든 데이터는 procedingJoinPoint로 담겨와 .getArgs() 배열형태로 담아오게 된다
2. 모든 데이터를 수용가능한 데이터 타입은 Object로 지정한다
3. Object 반복문돌려 내가 원하는 instance를 instanceof로 찾은다음 해당 데이터를 찾는다
4. BindingResult 형태로 다운 캐스팅하여 for문 실행
5. 에러가 있다면 throw 익셉션이 발동하여 에러처리가되고
만약 에러가 없다면 return proceedingJoinPoint.proceed(); 가 실행되어 원래 실행할려던 함수가 실행되게된다.
어노테이션 정리
@Around // 시작과 끝까지 간섭
@After // 끝난뒤 간섭
@Before // 시작전 간섭
위 3개의 어노테이션이있고 지금 사용한 @Around와 @Before는 함수가 실행전에 사용되지만
@After은 함수가 끝나고 실행될것을 지정할 수 있다.
그렇다면 적용되면 함수는 어떻게 변할까?
@PostMapping("/auth/signup")
public String signup(@Valid SignupDto signupDto, BindingResult bindingResult) { // key=vaule (x-www-form-urlencoded)
User user = signupDto.toEntity();
authService.회원가입(user);
return "auth/signin";
}
맨위에 보여졌던 signup코드와 비교해보면 훨씬 깔금해진 모습이다 그리고 이러한 코드가 몇백개가있다고하면 재사용성으로도 굉장히 유용한 것이다 꼭 기억해서 AOP를 적용시키자.
이렇게 해서 AOP자동 에러처리가 끝난다.