AOP에 대해 알아보자

김재연·2025년 8월 24일
post-thumbnail

회사에서 자주 사용되는 AOP를 회사 코드를 통해 공부를 진행해보자.

관점지향 프로그래밍인 AOP를 활용할 수 있는 어노테이션이다.

Proxy 패턴을 활용한다.

@Around("execution(* com.package1.package2.api.oauth2.endpoint.LoginEndpoint.login(..))")  
public Object saveAccessLog(ProceedingJoinPoint joinPoint) throws Throwable {  
    Object[] args = joinPoint.getArgs();  
  
    try {  
        Object proceed = joinPoint.proceed();  
        // Exception이 없으면 로그인이 성공  
        saveSuccessLog(args[0]);  
        return proceed;  
    } catch (Exception e) { // Exception이 발생하면 로그인이 실패  
        saveFailLog(e, args);  
        throw e;  
    } finally {  
        accessLogSaveCommand.execute();  
    }  
}

예시 코드

여기서 이제 ProceedingJoinPoint를 활용하여, args를 조정도 할 수 있다.

또한, @Before, @PointCut 등도 있고, 그 내에 있는 것들은 execution 등등이 있다. within 이런 것들도, 이런 것들을 사용할 때에는 정규식을 사용할 수도 있다. 조금 더 효율을 증진시키기 위해

@Before("(@within(com.package1.package2.api.aspect.AopController) || @within(com.package1.package2.api.aspect.RestAopController))"  
        + " && excludeLoginPackage()")  
public void before() {  
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();  
    String userId = authentication.getName();  
  
    if (isAnonymous(userId) || UserContextHolder.isExist(userId)) {  
        return;  
    }  
  
    setContext(userId, authentication);  
}

예시 코드

보면 within을 || 로 연결하기도 하는 것을 볼 수 있고, Class를 명시하는 것도, 또한, 아래의 메서드를 포함해서 진행하는 것도 있다.

쨌든 Aspect한 메서드 이전에 실행되는 것이기에, 아무런 인자가 없는 것을 볼 수 있다.

@Pointcut("!execution(* com.package1.package2.api.oauth2.endpoint.LoginEndpoint.*(..))")  
private void excludeLoginPackage() {  
    // Pointcut for excluding package  
}

흠.. 근데, PointCut이 이해가 안된다. 이를 이해하기 이전에 다음과 같은 그림을 접하게 되었고, JoinPoint에 대한 이해도가 부족하다는 것을 알게되었다.

  • 횡단 관심사

    다음과 같이 보안, 예외처리와 같은 것들은 핵심 관심사에 여러곳으로 걸쳐져 있는 부분을 의미한다. 즉, 중복되는 부분을 의미하는 것 같은데, 여기서 더 찾아보지 않고, 그냥 중복을 제거하기 위해 AOP (Aspect Oriented Programing) 을 사용한다. 라고 생각하자.

그래서 Aspect, Join Point, Point Cut 이 무엇일까?

Aspect, Join Point, Advice, Pointcut, Target을 각각 하나씩 봐보자.

1. Aspect (애스펙트)

  • 개념: 프로그램에서 공통적으로 사용되는 기능이나 관심사를 모듈화한 것입니다.
  • 쉽게 말해: 여러 군데에서 반복적으로 나타나는 로직이나 기능을 하나로 묶어놓은 것입니다.
  • 예시: 로그 기록, 보안 검사, 트랜잭션 관리 등은 프로그램의 여러 부분에서 필요하지만, 이 기능들을 하나의 Aspect로 모아 관리하면 코드가 깔끔해집니다.

2. Join Point (조인 포인트)

  • 개념: 프로그램 실행 중, Aspect가 적용될 수 있는 특정 지점입니다.
  • 쉽게 말해: 프로그램 내에서 특정 기능(예: 메서드 호출, 예외 발생 등)이 실행되는 바로 그 순간입니다.
  • 예시: 메서드의 시작이나 끝, 예외가 발생하는 순간이 Join Point가 될 수 있습니다.

즉, JoinPoint는 정말 실행 시점을 이야기하는 것 같다. (어떤 메서드가 실행되면 진행된다. 이런 게 아니라 정말 Proxy가 동작하는 그 순간)

3. Advice (어드바이스)

  • 개념: Aspect가 적용되는 Join Point에서 실행되는 구체적인 행동입니다.
  • 쉽게 말해: Join Point에서 실제로 실행되는 코드입니다. 어떤 작업을 하겠다는 '지시' 같은 것이라고 보면 됩니다.
  • 예시: 메서드가 실행될 때 로그를 남기는 코드를 실행하는 것이 Advice입니다.

즉, 그냥 메서드에 짜놓은 로직들을 Advice라고 칭할 수도 있을 것 같음

4. Pointcut (포인트컷)

  • 개념: Join Point를 선별하는 규칙이나 표현식입니다.
  • 쉽게 말해: Aspect를 적용할 Join Point를 선택하는 조건입니다.
  • 예시: 특정 패키지 내의 모든 메서드, 이름이 "save"로 시작하는 메서드 등, 이와 같은 조건에 따라 Join Point를 선택할 수 있습니다.

PointCut은 이제, Trigger를 의미하는 것 같음

5. Target (타겟)

  • 개념: Aspect가 적용되는 실제 객체입니다.
  • 쉽게 말해: Aspect의 기능이 적용될 대상 객체입니다.
  • 예시: Aspect가 로그를 기록하는 기능을 제공한다고 할 때, 실제로 로그를 남기고 싶은 객체가 Target입니다.

GPT 센세가 이야기했다.

PointCut은 그 시점을 결정하는 것, 그냥 JoinPoint의 조건을 결정하는 것이라고 생각하면 된다고 한다.

그렇기 때문에, 이를 타 Aspect Annotation에서 excludeLoginPackage를 사용해 조건으로 사용할 수 있는 것이다.

또한, @Around는 Before, After를 합쳐놓은 것이라고 보면 되는데, 그냥 아무 시점에서 진행할 수 있는 것이고, 여기서 값을 반환하게 되면, Proxy가 적용된 메서드의 반환값에서 Customizing하게 나가기 때문에, 이 부분에서 성능적인 우려와(아무래도 한번 더 거치게 되고, Proxy를 찾는데에도 조금 resource가 들지 않을까 싶다.) 보이지 않는 로직이 존재해 떡칠을 하면 유지보수가 어려워질 수 있다는 단점이 있다.

참고자료

https://velog.io/@shining_dr/Annotation%EA%B8%B0%EB%B0%98-%EC%8A%A4%ED%94%84%EB%A7%81-AOP
https://ittrue.tistory.com/233
https://wpunch2000.tistory.com/22
https://hellomooneekim.netlify.app/%ED%9A%A1%EB%8B%A8%EA%B4%80%EC%8B%AC%EC%82%AC/
https://f-lab.kr/insight/understanding-spring-aop

profile
끊임없이 '성장'하는 개발자 김재연입니다.

0개의 댓글