Spring AOP

kmb·2022년 7월 21일
0

스프링

목록 보기
3/9
post-thumbnail

AOP (Aspect Oriented Programming)

관점 지향 프로그래밍

스프링 DI가 의존성(new)에 대한 주입이라면
스프링 AOP는 로직(code) 주입이라고 할 수 있다.

위 그림을 보면 로깅, 보안, 트랙잭션 등등 여러가지 모듈에서 반복적으로 나타나는 부분이 존재하는데, 이것을 횡단 관심사(cross-cutting concern) 라고 한다.

 

AOP의 목적

횡단 관심사의 모듈화를 통해 중복을 최소화하고 핵심 관심사항에만 집중하도록 하는것.

스프링 AOP의 핵심 3가지는 다음과 같다
1) 스프링 AOP는 인터페이스 기반.
2) 스프링 AOP는 프록시(proxy) 기반.
3) 스프링 AOP는 런타임(runtime) 기반.

 

AOP 적용 방식

1) 컴파일 시점 : .java 파일을 컴파일러를 통해서 .class를 만드는 시점에 부가 기능 로직을 추가하는 방식.
2) 클래스 로딩 시점 : .class 파일을 JVM 내부의 클래스 로더에 보관하기 전에 부가 기능 로직을 추가하는 방식.
3) 런타임 시점 : 스프링이 사용하는 방식.
컴파일이 끝나고 .class 파일을 클래스 로더에 보관한 이후에 자바가 실행된 다음 동작하는 런타임 방식.
스프링 Bean에만 AOP 적용이 가능.

 

AOP 어노테이션

@Before : 메서드가 실행되기 전.
@After : 메서드가 실행되는 도중에 예외 발생여부에 상관없이 이후에 공통기능을 실행.
@AfterReturing : 메서드가 예외없이 실행된 이후에 공통기능을 실행.
@AfterThrowing : 메서드가 실행되는 도중에 예외가 발생할 경우에만 공통기능을 실행.
@Pointcut : 횡단 관심사를 적용할 타깃 클래스의 타깃 메서드를 지정.
@Advice : Pointcut에 적용할 메서드를 정의. (언제 when 라는 개념을 포함)
@Aspect : 여러개의 Advice와 여러개의 Pointcut의 결합체.
@JoinPoint : Aspect 적용이 가능한 모든 지점.

 

Spring AOP에서 프록시 방식을 사용하는 이유

Spring AOP는 기본적으로 프록시 방식으로 동작한다. (프록시 패턴은 다음 글을 참조)
만약 프록시 객체를 사용하지 않는다면 Aspect 클래스에 정의된 부가기능을 사용하기 위해 직접 Aspect 클래스를 호출해야 된다.
이를 다른 여러개의 Target 클래스에 반복적으로 호출한다면 유지보수성이 떨어진다.

따라서 Spring에서는 프록시 클래스를 통해서 부가기능에 관한 로직을 처리한다면 Target 클래스는 순수 비지니스 로직에만 집중할 수 있다.

 

JDK Proxy 와 CGLib Proxy

JDK Proxy는 Target 클래스의 상위 인터페이스를 상속 받아서 프록시를 생성한다.
따라서 상위 인터페이스를 구현한 클래스가 아니라면 의존 할 수 없다.
또한 프록시 객체를 생성할 때 내부적으로 Reflection을 사용하기 때문에 비용적인 문제가 크다.
Spring에서는 기본적으로 JDK Proxy를 사용한다.

CGLib ProxyTarget 클래스를 상속 받아서 프록시를 생성한다.
따라서 인터페이스를 구현하지 않아도 되며, 런타임 에러가 발생할 확률이 JDK Proxy보다 적다.
바이트코드 조작을 통해 프록시 객체를 생성한다.
Spring Boot에서 기본적으로 CGLib Proxy를 사용한다.

 

Spring AOP의 대표적인 예시 @Transactional

@Transactional은 Proxy 형태로 동작한다.

1. Target에 호출이 오면 AOP Proxy가 이를 가로챈다.
2. Transaction Advisor가 commit 또는 rollback 처리를 한다.
3. Transaction 처리외에 다른 부가기능은 Custom Advisor에서 처리를 한다.
4. 각각의 Advisor에서 처리를 마치면 Target Method를 수행한다.
5. interceptor chain을 따라서 호출자에게 결과를 반환한다.

public class TransactionProxy{
    private final TransactonManager manager = TransactionManager.getInstance();
		...

    public void transactionLogic() {
        try {
            // 트랜잭션 전처리(트랜잭션 시작, autoCommit(false) 등)
			manager.begin();

			// 다음 처리 로직(타겟 비스니스 로직, 다른 부가 기능 처리 등)
			target.logic();
            
			// 트랜잭션 후처리(트랜잭션 커밋 등)
            manager.commit();
        } catch ( Exception e ) {
			// 트랜잭션 오류 발생 시 롤백
            manager.rollback();
        }
    }

 

참조

profile
꾸준하게

0개의 댓글