- Published on
Spring AOP
- Authors
- Name
- Chan Sol OH
목차
개요
예를 들어 메서드 시간측정은 메서드 시작 시각과 끝 시각을 구하고 그 차를 계산하는 로직이다. 따라서 시간측정을 위해선 관심 로직 앞 뒤에 시간 계산을 위한 로직을 추가해야한다. 이러면 한 메서드 안에 관심 로직과 시간 측정 로직이 섞여있는 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가지 방식으로 통합한다고 한다.
- 컴파일 :
java
파일을class
파일로 컴파일하는 과정에서 AOP 코드를 끼워넣는다. - 클래스 로드 : 클래스 로더가 JVM 메모리 상에
class
파일을 올릴 때 AOP를 적용 - 프록시 패턴 : Spring AOP에서 사용하는 방법. 대상 로직을 부가 기능으로 감싸서 적용시키는 이유. Spring AOP가 프록시 패턴을 사용하기 때문에 AspectJ의 Advice가 제한되는 것 같다. 원래 AspectJ의 advice는 단순히 실행 전, 후에만 사용되는 것이 아닌 메서드 중간에서도 부가 기능이 실행될 수 있었다.
프록시 패턴
프록시는 다른 객체에 대한 대체 또는 자리표시자를 제공할 수 있는 구조 디자인 패턴이다. 따라서 원래 접근하려는 객체에 요청이 전달되기 전 또는 후에 무언가를 수행할 수 있도록한다.
Service
와 Proxy
모두 ServiceInterface
를 구현하고 있다. 그리고 Client
는 SOLID의 의존관계 역전 원칙에 따라 ServiceInterface
를 의존하고 있다.
원래 프록시가 없었다면 ServiceInterface
의 구현체로 Service
가 들어갔겠지만, 프록시가 존재함으로 써 Service
는 Proxy
에 포함되고 Proxy
가 ServiceInterface
의 구현체고 들어가게된다. 그림과 같이 Proxy
가 먼저 요청의 처리(초기화 지연, 로깅, 캐싱, ...)을 한 후 Service
객체에 요청을 전달한다.
Spring AOP에는 부가 기능을 수행하는 Proxy
에 대상 기능인 Service
를 넣고 Proxy
객체를 컨트롤러나 다른 서비스에서 사용하게된다. 그럴 경우 대상 기능과 부가 기능을 분리하면서 Proxy
의 로깅, 시간측정을 수행하면서 Service
의 대상 기능도 수행할 수 있게된다.
하지만 조심해야할 점이 메소드에 AOP를 적용하면 Proxy
클래스에서 Service
의 메소드에 접근하는 것이기 때문에 Service
의 private 메소드에는 Proxy
가 접근할 수 없다. 그러므로 Spring AOP를 사용하기 위해선 메소드를 public으로 설정해야한다.
AOP 사용사례
- @Transactional 많이 사용하는 @Transactional도 Spring AOP이다. 원래는 서비스 로직 시작할 때 트랜젝션을 열고, 로직이 끝날 때 트랜젝션을 커밋해야한다. 하지만 Spring AOP의 프록시 패턴을 이용해서 @Transactional로 서비스 로직을 감싸게된다.
- Interceptor
- Filter