본문 바로가기

CS 스터디/Spring AOP

Spring AOP

AOP(Aspect-Oriented Programming)

- 관점 지향형 프로그래밍

- 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 각각 모듈화하겠다는 것

- 모둘화하여 재사용할 수 있도록 지원

- 모듈화: 어떤 공통된 로직이나 기능을 하나의 단위로 묶는 것

ex)

핵심적인 관점: 우리가 적용하고자 하는 핵심 비즈니스 로직(회원가입, 로그인 ,,,)

부가적인 관점: 핵심 로직을 실행하기 위해 행해지는 데이터베이스 로직, 로깅, 파일 입출력 등

 

흩어진 관심사(Crosscutting Concerns)

- AOP에서 각 관점을 기준으로 로직을 모듈화 한다는 것은 코드들을 부분적으로 나누어 모듈화한다는 의미

- 이때, 소스 코드상에서 다른 부분에 계속 반복해서 쓰는 코드

- 흩어진 관심사를 Aspect로 모듈화하고 핵심적인 비즈니스 로직에서 분리하여 재사용하는 것이 AOP의 취지

 

OOP

- 비즈니스 로직의 모듈화

- 모둘화의 핵심 단위는 비즈니스 로직

- 공통된 기능을 재사용하는 방법 -> 상속, 위임

 

AOP

- 인프라 혹은 부가기능의 모듈화

ex) 모니터링 및 로깅, 동기화, 오류 검사 및 처리, 서능 최적화(캐싱) 등

- 각각의 모듈들의 주 목적 외에 필요한 부가적인 기능

-  "공통된 기능(부가 기능)을 재사용하는 기법"

 

AOP 장점

- 애플리케이션 전체에 흩어진 공통 기능이 하나의 장소에서 관리되어 유지보수가 좋음

- 핵심 로직과 부가 기능의 명확한 분리로, 핵심 로직은 자신의 목적 외에 사항들에 신경쓰지 않아도 됨

 

 

AOP 적용 시점 3가지

1. 컴파일 시점

- . java 파일을 컴파일러를 통해 .class를 만드는 시점에 부가 기능 로직을 추가하는 방식

- 모든 지점에서 사용 가능

- AspectJ가 제공하는 특별한 컴파일러를 사용해야 하기 때문에 특별한 컴파일러가 필요한 점과 복잡하다는 단점 존재

 

2. 클래스 로딩 시점

- .class 파일을 JVM 내부의 클래스 로더에 보관하기 전에 조작하여 부가 기능 로직 추가하는 방식

- 모든 지점에 적용 가능

- 특별한 옵션과 클래스 로더 조작기를 지정해야 하므로 운영하기 어려움

 

3. 런타임 시점

- 스프링을 사용하는 방식

- 컴파일이 끝나고 클래스 로더에 이미 다 올라가 자바가 실행된 다음에 동작하는 런타임 방식

- 실제 대상 코드는 그대로 유지되고 프록시를 통해 부가 기능이 적용

- 프록시는 메서드 오버라이딩 개념으로 동작하기 때문에 메서드에만 적용 가능 -> 스프링 빈에만 AOP를 적용 가능

- 특별한 컴파일러나, 복잡한 옵션, 클래스 로더 조작기를 사용하지 않아도 스프링만 있으면 AOP를 적용할 수 있기 때문에, Spring AOP는 런타임 방식을 사용

 

 

프록시 패턴

- Spring AOP는 프록시 패턴이라는 디자인 패턴을 사용해 AOP 효과를 냄

- 프록시 패턴을 사용하면, 어떤 기능을 추가하려 할 때, 기존 코드를 변경하지 않고 기능을 추가할 수 있음

- 어떤 클래스가 Spring AOP의 대상이라면, 기존 클래스의 빈이 만들어질 때 Spring AOP가 프록시(기능이 추가된 클래스)를 자동으로 만들고 원본 클래스 대신 프록시를 빈으로 등록

- 그리고 원본 클래스가 사용되는 지점에서 프록시를 대신 사용

ex) @Transactional

MemberRepository 인터페이스에 @Transactional Annotation 사용한다고 가정

- @Transactional Annotation이 붙어있으면 MemberRepository 타입의 프록시가 새로 만들어지고 Spring AOP에 의해 자동으로 생성되는 MemberRepository의 프록시에는 @Transactional Annotation이 지시하는 코드가 삽입됨

 

@Transactional Annotation에 의해 추가되는 기능

- JDBC에서 트랜잭션 처리를 하려면 SQL 실행문 앞뒤에 setAutoCommit()와 commit()/rollback() 코드가 항상 붙는데, @Transactional Annotation은 프로시에 자동으로 그 코드를 넣어서 반복, 중복되는 코드를 생략 가능

-> 이로 인해 개발자는 비즈니스 로직에만 집중 가능

 

 

AOP 주요 용어

1. Join point

- 추상적인 개념으로 advice가 적용될 수 있는 모든 위치

ex) 메서드 실행 시점, 생성자 호출 시점, 필드 값 접근 시점 등,,,

- Spring AOP는 프록시 방식으로 사용하므로 조인 포인트는 항상 메서드 실행 시점

 

2. Pointcut

- 조인 포인트 중에서 advice가 적용될 위치를 선별하는 기능

- Spring AOP는 프록시 기반이기 때문에 조인 포인트가 메서드 실행 시점 뿐이고, 포인트컷도 메서드 실행 시점만 가능

- Joint point의 상세한 스펙을 정의한 것

- 'A란 메서드의 진입 시점에 호출할 것'과 같이 더욱 구체적으로 Advice가 실행될 지점을 정할 수 있음

 

3. Target

- advice의 대상이 되는 객체, 즉 Aspect를 적용하는 곳

- Pointcut으로 결정

 

4. advice

- 실질적인 부가 기능 로직을 정의하는 곳

- 특정 조인 포인트에서 Aspect에 의해 취해지는 조치

 

5. Aspect

- advice + pointcut을 모듈화한 것

- @Aspect와 같은 의미

 

6. Advisor

- Spring AOP에서만 사용되는 용어로 advice + pointcut 한쌍

 

7. Weaving

- pointcut으로 결정한 타겟의 join point에 advice를 적용하는 것

 

8. AOP proxy

- AOP 기능을 구현하기 위해 만든 프록시 객체

- 스프링에서 AOP proxy는 JDK 동적 proxy 또는 CGLIB proxy

- Spring AOP의 기본값은 CGLIB proxy

 

Advice

- Advice는 실질적으로 프록시에서 수행하게 되는 로직을 정의하게 되는 곳

- 5가지 Annotation 제공

- Annotation은 메서드에 붙이게 되는데, 해당 메서드는 advice의 로직을 정의하게 되고, 애노테이션의 종류에 따라 포인트 컷에 지정된 대상 메서드에서 Advice가 실행되는 시점을 정할 수 있음

 

1. @Around

- 타겟 메서드를 감싸서 특정 Advice를 실행한다는 의미

- @Before, @AfterReturning, @AfterThrowing, @After 를 모두 포함하는 Annotation

- 메서드 호출 전후 작업 명시 가능

- 조인 포인트 실행 여부 선택 가능

- 반환값 자체를 조작 가능

- 예외 자체를 조작 가능

- 조인 포인트를 여러번 실행 가능(재시도)

 

사용 예시

ex) @Around(execution(* com.ssafy..*.EventService.*(..)))

- com.ssafy 아래의 패키지 경로의 EventService 객체의 모든 메서드에 이 Aspect 적용

 

ex) @Around("@annotation(TestLogging)")

- 특정 어노테이션이 붙은 포인트에 해당 Apsect를 실행

@Target(ElementType.METHOD) // 애노테이션을 메소드에 사용할 것이라고 설정
@Retention(RetentionPolicy.RUNTIME) // 애노테이션을 RUNTIME까지 유지되도록 설정
public @interface TestLogging {
}
@Component // 이 클래스를 스프링 빈에 등록
@Aspect // 이 클래스가 Aspect를 나타내는 클래스라는 것을 명시
public class LogAspect {
    
    Logger logger = LoggerFactory.getLogger(LogAspect.class);
    
    @Around("@annotation(TestLogging)")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        
        // @TestLogging 애노테이션이 붙어있는 타겟 메소드를 실행
        Object proceed = joinPoint.proceed();
        
        stopWatch.stop();
        logger.info(stopWatch.prettyPrint());
        
        return proceed; // 결과 리턴
    }
}

- Around Annotation을 붙인 메소드에서는 ProceedingJoinPoint 파라미터를 받을 수 있음

- Annotation의 value를 "@annotation(TestLogging)"으로 지정함으로서

- joinPoint는 @TestLogging을 붙인 타겟 메소드를 의미

 

ex) @Around("bean(testEventService)")

- 스프링 빈(TestEventService)의 모든 메서드에 적용할 수 있음

 

2. @Before

- 어드바이스 타겟 메소드가 호출되기 전에 어드바이스 기능을 수행

- 입력값 자체는 조작 불가능

- 입력값의 내부에 setter 같은 수정자가 있다면, 내부값은 수정 가능

 

3. @AfterReturning

- 타겟 메소드가 성공적으로 결과값을 반환 후에 어드바이스 기능 수행

- 반환값 자체는 조작 불가능

- 반환값 내부에 setter같은 수정자가 있다면 내부값은 수정 가능

 

4. @AfterThrowing

- 타겟 메소드가 수행 중 예외를 던지게 되면 어드바이스 기능을 수행

- 예외 조작 불가능

 

5. @After

- 타겟 메소드의 결과에 관계없이(즉 성공, 예외 관게 없이) 타겟 메소드가 완료되면 어드바이스 기능 수행

 

Annotation 동작 순서

 

 

Pointcut

- Pointcut은 Advice가 적용될 위치를 선별하는 기능

- Spring AOP는 프록시 기반이기 때문에 메서드만 적용 가능하므로 어느 메서드에 적용할 것인지 명시하는 것이라고 생각해도 됨

- 포인트컷 지시자의 종류는 여러가지가 있지만, 실질적으로는 excution와 @annotation만 거의 사용

 

Pointcut의 종류

1. execution

- 많은 종류의 포인트컷이 있지만, 실질적으로는 가장 많이 사용하게 되는 종류

execution(접근제어자? 반환타입 선언타입?메서드이름(파리미터) 예외?)

- ?가 붙은 것들은 생략 가능 -> 접근 제어자, 선언타입, 예외

- * 패턴을 통해 모든 타입 허용 표현

- ...을 통해 모든 타입 허용과 파라미터의 수가 상관없다는 것을 표현

- 기본적으로 상위 타입을 명시하면 하위 타입에도 적용되지만, 하위 타입에만 메서드를 명시하는 경우 매칭이 불가능

- 즉 타입은 상위타입으로 명시하고 메서드는 하위타입에만 있다면 적용 불가능

- 파라미터 타입의 경우 정확해야만 매칭 -> 부모 타입 허용 X

 

2. @annotation

- @annotation은 메서드가 주어진 애노테이션을 갖고 있는 경우 적용

- @Target의 경우는 애노테이션이 붙어있는 클래스였는데 @annotation의 경우는 메서드

- @annotation의 경우는 종종 사용하는 경우가 있어 알아두면 좋음

 

3. bean

- 스프링 빈의 이름으로 AOP 적용 여부 지정

- 스프링에서만 사용할 수 있는 특별한 지시자

// 빈 이름이 orderService이거나 Repository로 끝나는 빈에 적용
@Around("bean(orderService) || bean(*Repository)")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
    // 생략
}

4. 그 외

- within

- args

- this

- target