sol 개발 블로그 로고
Published on

Spring AOP

Authors
  • avatar
    Name
    Chan Sol OH
    Twitter

목차

개요

예를 들어 메서드 시간측정은 메서드 시작 시각과 시각을 구하고 그 를 계산하는 로직이다. 따라서 시간측정을 위해선 관심 로직 앞 뒤에 시간 계산을 위한 로직을 추가해야한다. 이러면 한 메서드 안에 관심 로직시간 측정 로직이 섞여있는 SOID에서 단일책임원칙을 지키지 못한 사례라고 할 수 있다.

이처럼 시간 측정, 권한 확인, 드랜잭션 같이 관심 로직이 아닌 것들을 인프라 로직이라 한다.

인프라 로직

인프라 로직은 애플리케이션 전 영역에서 나타날 수 있고, 동일한 작업을 반복적으로 수행하기 때문에 중복 코드를 만들어낼 수 있다. 인프라 로직도 어떤 목적을 가지고 실행되는 관심사가 존재하고 전 애플리케이션에 걸쳐서 수행되기 때문에 횡단 관심사라고 한다.

**AOP(Aspect-Oriented Programming)**는 횡단 관심사에 따라 프로그래밍하는 패러다임이다. 그렇기 때문에 실제로 구현하는 AspectJ라는 구현제가 AOP가 있다.

Spring AOP 용어

횡단위로 관심사를 분리한다면, 분리한 로직을 언제 어디에 누구에게 적용할지 결정할 필요가 있다.

  • Target : 어떤 대상에 부가 기능을 부여할 것인가
  • Advice : 어떤 부가 기능을 사용할 것인가
  • Join point : 어디에 적용할 것인가? (메서드. 필드, 객체, ...)
  • Point cut : 실제 advice가 적용될 지점, Spring AOP에서는 advice가 적용될 메서드를 선정

Advice는 5가지 부가기능을 제공한다.

  • Before : Join point가 호출되기 전
  • AfterReturning : 메서드 실행 후 결과 값을 리턴한 후
  • AfterThrowing : 메서드에서 예외가 발생한 후 (catch 같은 느낌)
  • After : 메서드에 예외가 발생하던 안하던 메서드 실행 후 무조건 실행 (finally 같은 느낌)
  • Aound : 메서드 실행 앞, 뒤를 감싸는 경우
    @Around("repositoryClassMethods()")
    public Object measureMethodExecutionTime(ProceedingJoinPoint pjp) throws Throwable {
        // 메서드 실행 직후
        long start = System.nanoTime();
        Object retval = pjp.proceed(); // 메서드 완료 대기
        // 메서드 실행 완료 후
        long end = System.nanoTime();
        String methodName = pjp.getSignature().getName();
        logger.info("Execution of " + methodName + " took " +
          TimeUnit.NANOSECONDS.toMillis(end - start) + " ms");
        return retval;
    }
    

AspectJ에서 AOP를 구현하는 방법

AOP는 행단위로 관심사를 분리했으니 실행하기 위해선 다시 통합시켜야한다. AspectJ는 3가지 방식으로 통합한다고 한다.

  1. 컴파일 : java 파일을 class 파일로 컴파일하는 과정에서 AOP 코드를 끼워넣는다.
  2. 클래스 로드 : 클래스 로더가 JVM 메모리 상에 class 파일을 올릴 때 AOP를 적용
  3. 프록시 패턴 : Spring AOP에서 사용하는 방법. 대상 로직을 부가 기능으로 감싸서 적용시키는 이유. Spring AOP가 프록시 패턴을 사용하기 때문에 AspectJ의 Advice가 제한되는 것 같다. 원래 AspectJ의 advice는 단순히 실행 전, 후에만 사용되는 것이 아닌 메서드 중간에서도 부가 기능이 실행될 수 있었다.

프록시 패턴

프록시는 다른 객체에 대한 대체 또는 자리표시자를 제공할 수 있는 구조 디자인 패턴이다. 따라서 원래 접근하려는 객체에 요청이 전달되기 또는 에 무언가를 수행할 수 있도록한다.

프록시 패턴 구조

ServiceProxy 모두 ServiceInterface를 구현하고 있다. 그리고 Client는 SOLID의 의존관계 역전 원칙에 따라 ServiceInterface를 의존하고 있다.

원래 프록시가 없었다면 ServiceInterface의 구현체로 Service가 들어갔겠지만, 프록시가 존재함으로 써 ServiceProxy포함되고 ProxyServiceInterface의 구현체고 들어가게된다. 그림과 같이 Proxy가 먼저 요청의 처리(초기화 지연, 로깅, 캐싱, ...)을 한 후 Service 객체에 요청을 전달한다.

Spring AOP에는 부가 기능을 수행하는 Proxy에 대상 기능인 Service를 넣고 Proxy 객체를 컨트롤러나 다른 서비스에서 사용하게된다. 그럴 경우 대상 기능과 부가 기능을 분리하면서 Proxy의 로깅, 시간측정을 수행하면서 Service의 대상 기능도 수행할 수 있게된다.

하지만 조심해야할 점이 메소드에 AOP를 적용하면 Proxy 클래스에서 Service의 메소드에 접근하는 것이기 때문에 Serviceprivate 메소드에는 Proxy가 접근할 수 없다. 그러므로 Spring AOP를 사용하기 위해선 메소드를 public으로 설정해야한다.

AOP 사용사례

  • @Transactional 많이 사용하는 @Transactional도 Spring AOP이다. 원래는 서비스 로직 시작할 때 트랜젝션을 열고, 로직이 끝날 때 트랜젝션을 커밋해야한다. 하지만 Spring AOP의 프록시 패턴을 이용해서 @Transactional로 서비스 로직을 감싸게된다.
  • Interceptor
  • Filter