AOP(Aspect Oriented Programming): 관점지향 프로그래밍
여러가지 서비스들에 횡단 관심이 있을 때 사용이 가능한 프로그래밍 기법
주요 Annotation
Annotation | 의미 |
@Aspect | 자바에서 널리 사용하는 AOP 프레임워크에 포함되며, AOP를 정의하는 Class에 할당 |
@Pointcut | 기능을 어디에 적용시킬지, 메소드? Annotation? 등 AOP를 적용 시킬 지점을 설정 |
@Before | 메소드 실행하기 이전 |
@After | 메소드가 성공적으로 실행 후, 예외가 발생 되더라도 실행 |
@AfterReturning | 메소드 호출 성공 실행 시(Not Throws) |
@AfterThrowing | 메소드 호출 실패 예외 발생(Throws) |
@Around | @Befor/ @After 모두 제어 |
사용 예제
출력을 하는 부분이 공통적으로 들어갈 때, AOP를 사용해서 일괄적으로 처리할 수 있다.
@RestController
@RequestMapping("/api")
public class RestApiController {
@GetMapping("/get/{id}")
public String get(@PathVariable Long id, @RequestParam String name) {
return id + " " + name;
}
@PostMapping("/post")
public User post(@RequestBody User user) {
return user;
}
}
위와 같은 컨트롤러가 있을 때
실행 메서드, 입력 타입, 입력 값, 리턴 값에 대하여 출력하고 싶을 때
@Aspect
@Component
public class ParameterAop {
// Pointcut 문법 알고갈것
@Pointcut("execution(* com.example.springdiioc.controller..*.*(..))")
private void cut() {
}
@Before("cut()")
public void before(JoinPoint joinPoint){
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
// 실행 메서드
System.out.println("method.getName() = " + method.getName());
Object[] args = joinPoint.getArgs();
for(Object obj : args){
// 입력 타입
System.out.println("type : " + obj.getClass().getSimpleName());
// 입력 값
System.out.println("value : " + obj);
}
}
@AfterReturning(value = "cut()", returning = "returnObj" /* 리턴 변수명과 일치*/)
public void afterReturn(JoinPoint joinPoint, Object returnObj) {
System.out.println("return obj");
// 리턴 값
System.out.println("returnObj = " + returnObj);
}
}
위와 같이 코드를 작성해서 컨트롤러에서 실행하는 모든 EndPoint에 대하여 AOP를 적용시킬 수 있다.
Custom Annotation 적용
이러한 방식 말고도 Custom Annotation을 제작해서 해당 Annotation이 붙은 곳에만 AOP를 적용시킬 수도 있다.
우선 Annotation을 만들고
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Timer {
}
이 Annotation에 대한 내용을 작성해준다.
@Aspect
@Component
public class TimerAop {
@Pointcut("execution(* com.example.springdiioc.controller..*.*(..))")
private void cut(){}
// Timer Annotation에 대한 Pointcut
@Pointcut("@annotation(com.example.springdiioc.controller.annotation.Timer)")
private void enableTimer(){}
@Around("cut() && enableTimer()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
StopWatch stopWatch = new StopWatch();
// 시간 측정 시작
stopWatch.start();
// 실질적 메서드 실행
Object reuslt = joinPoint.proceed();
// 시간 측정 종료
stopWatch.stop();
// 시간
System.out.println("total time = " + stopWatch.getTotalTimeSeconds());
}
}
이렇게 하면 Controller 밑에 @Timer를 붙인 메서드에 대한 실행 시간을 구할 수 있다.
AOP로 값 변환
이러한 것들 말고도 들어온 값을 디코딩 하여 다른 로직으로 전달하고, 반환 할 때에는 다시 디코딩 하여 리턴을 해줄 수도 있다.
우선 Decode Annotation을 만든다.
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Decode {
}
그리고 이를 사용하는 DecodeAop를 만든다.
@Aspect
@Component
public class DecodeAop {
@Pointcut("execution(* com.example.springdiioc.controller..*.*(..))")
private void cut(){}
@Pointcut("@annotation(com.example.springdiioc.controller.annotation.Decode)")
private void enableDecode(){}
@Before("cut() && enableDecode()")
public void before(JoinPoint joinPoint){
Arrays.stream(joinPoint.getArgs()).forEach(arg -> {
if(arg instanceof User user){ // 들어온 값이 User타입 일 때
String base64Email = user.getEmail(); // 이메일 가져오고
// 가져온 이메일을 base64로 디코딩
String email = new String(Base64.getDecoder().decode(base64Email), StandardCharsets.UTF_8);
// 디코딩 한 값으로 set
user.setEmail(email);
}
});
}
@AfterReturning(value = "cut() && enableDecode()", returning = "returnObj")
public void afterReturn(JoinPoint joinPoint, Object returnObj){
if(returnObj instanceof User user){ // 리턴이 User타입이면
String email = user.getEmail(); // 이메일 가져와서
// base64로 다시 인코딩
String base64Email = Base64.getEncoder().encodeToString(email.getBytes());
// 인코딩 한 값으로 set
user.setEmail(base64Email);
}
}
}
위와 같이 간단하게 반복되는 로직들를 AOP를 사용하여 분리해낼 수 있다.
'Back-End > Spring Boot' 카테고리의 다른 글
Spring Boot Filter란 (2) | 2024.06.16 |
---|---|
Spring Boot @RestControllerAdvice를 이용한 예외 처리 방법 (0) | 2024.06.09 |
IoC와 DI (0) | 2024.06.08 |
Spring Boot Custom Validation만들기 (0) | 2024.06.07 |
Spring Boot Validation 사용 (0) | 2024.06.07 |