Notice
Recent Posts
Recent Comments
Link
«   2024/12   »
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
Tags
more
Archives
Today
Total
관리 메뉴

임도현의 성장

Spring-Boot AOP개념 @Aspect Advisor 본문

Spring Boot

Spring-Boot AOP개념 @Aspect Advisor

림도현 2024. 10. 3. 12:58
AOP란?
Aspect-Oriented Programming의 줄임말이다.
AOP는 애플리케이션의 핵심 비즈니스 로직과 관련 없는 부가적인 기능들을 모듈화하여 코드의 증복성을 줄이고 유지보수성을 향상시키는 데에 주로 활용한다. AOP에서는 다양한 관점(Aspect)을 정의하고 이러한 관점들을 핵심 로직에 적용하는 방식으로 동작한다.

👿 Advisor 구성

  • 포인트컷(PointCut) : 어디에 부가 기능을 적용할지,  클래스와 매서드 이름으로 필터링 하는데 쉽게 말하면 어디 기능에 적용하지 정하는 곳 (필터 역할)
  • 어드바이스(Advice) : 프록시가 호출하는 부가 기능이다. 단순하게 프록시 로직이라 생각하면 된다. (부가 기능 로직 담당)
  • 어드바이저(Advisor) : 하나의 포인트컷과 하나의 어드바이스를 가지고있는거다. 쉽게 말해 포인트컷 + 어드바이스 

👨‍💻@Aspect 개념

@Aspect 애노테이션으로 매우 편리하게 포인트컷(PointCut) 과 어드바이스(Advice) 로 구성되어 있는 어드바이저 생성 기능을 지원한다. 포인트컷과 어드바이스를 합치면 어드바이저(Advisor)가 아닌가 라고 생각 할 수 있다. @Aspect 어노테이션을 사용하면 어드바이저(Advisor)를 직접 생성하지 않아도 된다.

👀예제 코드

클라이언트가 문자 abc를 보낼때 소문자를 대문자로 보이게 출력하는 요구사항이 있다고 가장하자

build.gradle  추가

implementation 'org.springframework.boot:spring-boot-starter-aop'

Controller : 클라이언트로부터 요청을 받습니다.

요청 : /process?input=abcd
@RestController
@RequiredArgsConstructor
public class UpperController {

    private final UpperService upperService;

    @GetMapping("/process")
    public String processString(@RequestParam String input) {
        return upperService.processString(input);
    }
}

Service : 비즈니스 로직을 처리합니다.

@Service
public class UpperService {

    public String processString(String input) {
        return input; // 이 부분에서 AOP로 대문자 변환을 처리하게 됩니다.
    }
}

AOP : 메서드 호출 전후에 특정 로직을 적용합니다. 여기서는 입력된 문자열을 대문자로 변환하는 역할을 합니다.

@Aspect
@Component
@Slf4j
public class UpperAspect {

    @Around("execution(* hello.aopTest.V1.UpperService.processString(..))")
    public Object toUpperCaseAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] args = joinPoint.getArgs();

        log.info("args={}", args);
        log.info("joinPoint={}", joinPoint);
        log.info("joinPoint.getSignature={}", joinPoint.getSignature());

        if (args.length > 0 && args[0] instanceof String) {
            // 입력값을 대문자로 변환
            String input = (String) args[0];
            args[0] = input.toUpperCase();
        }

        // 변경된 인자로 원래 메서드 실행
        return joinPoint.proceed(args);
    }
}

🤖자세한 UpperAspect클래스 설명

  • @Around("execution(* hello.aopTest.V1.UpperService.processString(..))")
    • 이부분이 포인트컷(PointCut) 과 같은 역할을 함 @Around 매서드 호출 전후에 수행을 한다.
    • 괄혼 안에는 패턴이다 어디에 부가기능을 적용하지 정하는 파일 구조다.
  • public Object toUpperCaseAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
    • 어드바이스(Advice) 역할과 똑같은 역할을 한다.  프록시가 호출하는 부가 기능이다.
  • 위 두개의 어노테이션과 매서드가 합치면 어드바이저(Advisor)가 된다.

🙊어디바이스 종류

  • @Around : 메서드 호출 전후에 수행 
  • @Before : 조인 포인트 실행 이전에 실행
  • @AfterReturning : 조인 포인트가 정상 완료후 실행
  • @AfterThrowing : 메서드가 예외를 던지는 경우 실행
  • @After : 조인 포인트가 정상 또는 예외에 관계없이 실행(finally)

🧐joinPoint 인터페이스 메서드 종류

메소드명 설명
joinPoint.getArgs()  파라미터 전달된 인자를 배열로 반환
joinPoint.getThis() AOP 프록시 객체를 반환
joinPoint.getTarget() AOP가 적용된 대상 객체를 반환
joinPoint.getSignature()  호출되는 메서드 정보 반환
Signature가 제공하는 메서드 예) joinPoint.getSignature() . toShortString();
getName() 클라이언트가 호출한 메서드의 이름을 반환
toLongString() 클라이언트가 호출한 메서드의 리턴타입, 이름, 매개변수를 패키지 경로까지 포함해서 반환
toShortString() 클라이언트가 호출한 메서드 시그니처를 축약한 문자열로 반환

🐼포인트컷 분리

위에 @Around 어노테이션안에 바로 패턴을 작성해도 되지만 만약 패턴이 2개 이상이면 코드가 길어지어면 유지보수가 어렵다. 그걸 방지하기 위해 우리는 포인터 컷을 분리하였다.  && (AND), || (OR), ! (NOT) 3가지 조합이 가능하다.

밑에 코드 처럼 하면 Service Repository 에서 processString이라는 메서드에 기능이 적용된다.

@Aspect
@Component
@Slf4j
public class UpperAspect {

    @Pointcut("execution(* hello.aopTest.V1.UpperService.processString(..))")
    private void changeUpper(){}
    
    @Pointcut("execution(* hello.aopTest.V2.LowerRepository.processString(..))")
    private void changeLower(){}

    @Around("changeUpper() && changeLower()")
    public Object toUpperCaseAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] args = joinPoint.getArgs();

        if (args.length > 0 && args[0] instanceof String) {
            String input = (String) args[0];
            args[0] = input.toUpperCase();
        }

        return joinPoint.proceed(args);
    }
}

🥵순서 정리 

Controller => Aspect => Service

Controller가 클라이언트로 부터 요청을 받아 Service로보냅니다. 근데 Aspect가 포인트컷에 해당되는 패키지 매서드명이 있으면 Service 메서드 호출 전후에 특정 로직을 적용합니다. 아래 이미지를 보면 쉽게 이해가 된다.

이런식으로 AOP를 사용하면 핵심 로직만 남기고 부가 기능이나 증복성있는 코들을 따로 분리하여 유지보수가 편리하고 좋은 설계를 할 수 있다.


최대한 쉽게 설명하였으나 내 코드에 직접 적용하기에는 
아직 AOP공부를 더 해야할거 같다.

참고자료

스프링 핵심원리 - 고급편 강의