1. AOP란?
AOP (Aspect-Oriented Programming)는 프로그래밍 패러다임의 하나로, 관심사를 분리하여 코드의 모듈화를 개선하기 위한 기법이다. 이를 통해 코드의 가독성과 유지 보수성을 높일 수 있다.
2. AOP 용어 정리
1. Aspect 생성
- 로깅 기능(Advice)을 포함하는 Aspect를 정의 (클래스 생성)
2. Advice 정의
- 메서드 실행 전후에 로그를 남기는 코드를 작성 (수행할 메서드 생성)
3. Pointcut 설정
- 로깅 기능을 적용할 메서드를 결정. 예를 들어, 모든 public 메서드에 로깅을 적용하려면 해당하는 패턴을 Pointcut에 지정한다. (깃발에 별칭주기)
4. Advice 적용:
- 설정한 Pointcut에 따라 메서드 실행 전후에 로그를 남기는 Advice가 실행됩니다. (PointCut(별칭) 적용)
3. 핸들러 만들기
package shop.mtcoding.blog._core.errors; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect //AOP 등록 @Component //IoC 등록 public class MyValidationHandler { // Advice ( 부가로직 메서드) //Advice가 수행될 위치 == PointCut @Before("@annotation(org.springframework.web.bind.annotation.GetMapping)") //get매핑일때만 실행 public void hello(JoinPoint jp){ //, JoinPoint.aspect.lang System.out.println("MyValidationHandler : hello_________________________"); } }
@Before 어노테이션을 사용하면 GetMapping이 실행되기 전 메서드가 호출된다.
@After 어노테이션을 사용하면 GetMapping이 실행된 후 메서드가 호출된ㄷ
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect //AOP 등록 @Component //IoC 등록 public class MyValidationHandler { // Advice ( 부가로직 메서드) //Advice가 수행될 위치 == PointCut @Before("@annotation(org.springframework.web.bind.annotation.GetMapping)") //get매핑일때만 실행 public void hello(JoinPoint jp){ //, JoinPoint.aspect.lang Object[] args = jp.getArgs(); //파라미터(매개변수) System.out.println("크기 : " +args.length); System.out.println("MyValidationHandler : hello_________________________"); } }
@PathVariable Integer id 로 표기되어 있어도 파라미터를 분석해서 하나의 데이터로 처리한다.
4. AOP 를 활용해 유효성 검사하기
유효성 검사는 BODY 데이터가 있는 PostMapping 과 PutMapping 에 사용한다.
postman으로 로그인
@PostMapping("/api/boards") public ResponseEntity<?> save(@Valid @RequestBody BoardRequest.SaveDTO reqDTO, Errors errors) { if(errors.hasErrors()){ for (FieldError error : errors.getFieldErrors()){ System.out.println(error.getField()); System.out.println(error.getDefaultMessage()); } }
컨트롤러의 파라미터 앞에 @Valid , 뒤에는 Errors errors 를 붙인다. @Valid 는 파라미터의 필드에 유효성 검사를 도와준다 (NotNull, Min, Max 등). errors 에는 유효성 검사에서 실패한 필드와 실패 이유를 저장한다.
UserRequest.SaveDTO
package shop.mtcoding.blog._core.errors; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect //AOP 등록 @Component //IoC 등록 public class MyValidationHandler { // Advice ( 부가로직 메서드) //Advice가 수행될 위치 == PointCut @Before("@annotation(org.springframework.web.bind.annotation.PostMapping)") //post매핑일때만 실행 public void hello(JoinPoint jp){ //, JoinPoint.aspect.lang Object[] args = jp.getArgs(); //파라미터(매개변수) System.out.println("크기 : " +args.length); for(Object arg : args){ System.out.println("매개변수 : " + arg); } System.out.println("MyValidationHandler : hello_________________________"); } }
@PostMapping("/api/boards") public ResponseEntity<?> save(@Valid @RequestBody BoardRequest.SaveDTO reqDTO, Errors errors) { if(errors.hasErrors()){ for (FieldError error : errors.getFieldErrors()){ throw new Exception400(error.getDefaultMessage()+" : "+error.getField()); } } User sessionUser = (User) session.getAttribute("sessionUser"); BoardResponse.DTO respDTO = boardService.글쓰기(reqDTO, sessionUser); return ResponseEntity.ok(new ApiUtil(respDTO)); }
오류가 발생하면 throw 를 날려서 메세지가 하나씩 발생하도록 한다.
5. 코드 모듈화
package shop.mtcoding.blog._core.errors; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; import org.springframework.validation.Errors; import org.springframework.validation.FieldError; import shop.mtcoding.blog._core.errors.exception.Exception400; @Aspect //AOP 등록 @Component //IoC 등록 public class MyValidationHandler { // Advice ( 부가로직 메서드) //Advice가 수행될 위치 == PointCut @Before("@annotation(org.springframework.web.bind.annotation.PostMapping) || @annotation(org.springframework.web.bind.annotation.PutMapping)") //get매핑일때만 실행 public void hello(JoinPoint jp) { // JoinPoint.aspect.lang Object[] args = jp.getArgs(); //파라미터(매개변수) for (Object arg : args) { if (arg instanceof Errors) { Errors errors = (Errors) arg; if (errors.hasErrors()) { for (FieldError error : errors.getFieldErrors()) { throw new Exception400(error.getDefaultMessage() + " : " + error.getField()); } } } } System.out.println("MyValidationHandler : hello_________________________"); } }
받은 데이터를 핸들러에 포함시킨다. AOP는 바디데이터가 있는 Post 와 Put 요청에서만 사용했다. PostMapping와 PutMapping 이 들어오면 핸들러가 실행된다.
6. 표현식
com.example.service 패키지의 MyService에서 save 를 호출할 때 수행된다.
@Pointcut("execution(* com.example.service.MyService.save(..))") public void saveMethod() {}
com.example.controller 패키지 내의 UserController 클래스의 모든 메서드가 호출될때 수행된다.
@Pointcut("execution(* com.example.controller.UserController.*(..))") public void userControllerMethods() {}
리턴타입 지정
표현식 | 설명 |
* | 모든 리턴타입 허용 |
void | 리턴타입이 void인 메서드 선택 |
!void | 리턴타입이 void가 아닌 메서드 선택 |
패키지 지정
표현식 | 설명 |
co.kr.mono.aop.entity | co.kr.mono.aop.entity 패키지만 선택 |
co.kr.mono.aop.entity.. | co.kr.mono.aop.entity 패키지로 시작하는 모든 패키지 선택 |
co.kr.mono.aop.entity..impl | co.kr.mono.aop.entity.. 패키지로 시작하고 마지막 패키지 이름이 impl 로 끝나는 패키지 선택 |
메서드 지정
표현식 | 설명 |
*(..) | 모든 메서드 선택 |
print*(..) | 메서드명이 print로 시작하는 모든 메서드 선택 |
매개변수 지정
표현식 | 설명 |
(..) | 모든 매개변수 |
(*) | 반드시 1개의 매개변수를 가지는 메서드만 선택 |
(com.devlih.domain.user.model.User) | 매개변수로 User를 가지는 메서드만 선택. 반드시 풀패키지명을 적어야 한다. |
(!com.devlih.domain.user.model.User) | 매개변수로 User를 가지지 않는 메서드만 선택 |
(Integer,..) | 1개 이상의 매개변수를 가지되, 첫번째 매개변수의 타입이 Integer인 메서드만 선택 |
(Integer,*) | 반드시 2개의 매개변수를 가지되, 첫번째 매개변수의 타입이 Integer인 메서드만 선택 |
execution(* onj.spring.aop.*.*(..)) : onj.spring.aop 패키지의 모든 메소드가 포인트 컷 execution(* onj.spring.aop..*.*(..)) : onj.spring.aop 패키지와 하위 패키지의 모든 메소드가 포인트 컷 execution(public void insert*(..)) : public에 리턴값, 패키지명 없고 메서드 이름은 insert로 시작, 인자값은 0개 이상인 메서드가 포인트 컷 execution(public * *(..)) : public 메소드가 포인트 컷 execution(* onj.spring.aop.*.*()) : 리턴형 관계없고 onj.spring.aop 패키지의 모든 클래스, 인자값이 없는 모든 메서드가 포인트 컷 execution(* onj.spring.aop..*.*(..)) : 리턴형 관계없고 onj.spring.aop 패키지 및 하위 패키지에 있는 모든 클래스, 인자값이 0개 이상인 메서드가 포인트 컷 execution(* delete*(*)) : 메서드 이름이 delete으로 시작하는 인자값이 1개인 메서드가 포인트 컷 execution(* delete*(*,*)) : 메서드 이름이 delete로 시작하는 인자값이 2개인 메서드가 포인트 컷
특정 어노테이션의 파라미터로 있는 메소드를 pointcut으로 잡으려면, execution expression을 이용하여 어노테이션이 적용된 메소드를 대상으로 pointcut을 작성하면 됩니다. 다만, 이 경우에는 @annotation 대신에 @args 키워드를 사용해야 합니다.
6. 인증 코드 정리
public class RegexTest { @Test public void 한글만된다_test() throws Exception { String value = "한글"; boolean result = Pattern.matches("^[가-힣]+$", value); System.out.println("테스트 : " + result); } @Test public void 한글은안된다_test() throws Exception { String value = "abc"; boolean result = Pattern.matches("^[^ㄱ-ㅎ가-힣]*$", value); System.out.println("테스트 : " + result); } @Test public void 영어만된다_test() throws Exception { String value = "ssar"; boolean result = Pattern.matches("^[a-zA-Z]+$", value); System.out.println("테스트 : " + result); } @Test public void 영어는안된다_test() throws Exception { String value = "가22"; boolean result = Pattern.matches("^[^a-zA-Z]*$", value); System.out.println("테스트 : " + result); } @Test public void 영어와숫자만된다_test() throws Exception { String value = "ab12"; boolean result = Pattern.matches("^[a-zA-Z0-9]+$", value); System.out.println("테스트 : " + result); } @Test public void 영어만되고_길이는최소2최대4이다_test() throws Exception { String value = "ssar"; boolean result = Pattern.matches("^[a-zA-Z]{2,4}$", value); System.out.println("테스트 : " + result); } // username, email, fullname @Test public void user_username_test() throws Exception { String username = "ssar"; boolean result = Pattern.matches("^[a-zA-Z0-9]{2,20}$", username); System.out.println("테스트 : " + result); } @Test public void user_fullname_test() throws Exception { String fullname = "메타코딩"; boolean result = Pattern.matches("^[a-zA-Z가-힣]{1,20}$", fullname); System.out.println("테스트 : " + result); } @Test public void user_email_test() throws Exception { String fullname = "ssaraa@nate.com"; // ac.kr co.kr or.kr boolean result = Pattern.matches("^[a-zA-Z0-9]{2,10}@[a-zA-Z0-9]{2,6}\\.[a-zA-Z]{2,3}$", fullname); System.out.println("테스트 : " + result); } @Test public void account_gubun_test1() throws Exception { String gubun = "DEPOSIT"; // ac.kr co.kr or.kr boolean result = Pattern.matches("^(DEPOSIT)$", gubun); System.out.println("테스트 : " + result); } @Test public void account_gubun_test2() throws Exception { String gubun = "TRANSFER"; // ac.kr co.kr or.kr boolean result = Pattern.matches("^(DEPOSIT|TRANSFER)$", gubun); System.out.println("테스트 : " + result); } @Test public void account_tel_test1() throws Exception { String tel = "010-3333-7777"; // ac.kr co.kr or.kr boolean result = Pattern.matches("^[0-9]{3}-[0-9]{4}-[0-9]{4}", tel); System.out.println("테스트 : " + result); } @Test public void account_tel_test2() throws Exception { String tel = "01033337777"; // ac.kr co.kr or.kr boolean result = Pattern.matches("^[0-9]{11}", tel); System.out.println("테스트 : " + result); } }
Share article