엄범

 

AOP란?

Aspect Oriented Programming. 즉 관점 지향 프로그래밍이다. 이게 무슨 뜻인지 체감하려면 실사례를 생각해보는게 가장 좋다.

전문 통신 하는 상황을 생각해 보자. 전문 객체는 전문 필드 형식에 맞게 바이너리로 serialization 되어야 한다.

```java

class telegramA {

    String field1;

    byte[] serialize() {...}

}

class telegramB {

    String field1;

    byte[] serialize() {...}

}

```

telegramA의 field1은 length가 4여야 하고, telegramB의 field1은 length가 8이어야 한다. 각각의 전문 클래스 마다 전문 필드 형식이 다 다르기 때문에 이를 serialization하는건 보통 각 클래스의 메서드로 구성하게 된다.

 

serialize라는 작업이 매 전문 클래스마다 중복되는데, 이 작업에 AOP를 적용하면?

serialize 로직을 Aspect로 분리해 낼 수 있다. 그러면 어떻게 되는가?

```java

class telegramA {

    @length(4)

    String field;

}

```

요렇게 애너테이션 같은 문법을 이용해서 이 field는 길이가 4야. 라는 것만 지정해주고, serialize하는 로직은 AOP로 빼서 한 번만 작성하면 된다. 단, @length를 읽어와서 이 길이 대로 serialize해주어야 하겠지.

 

OOP에서 기능들이 상속 계층을 타고 세로로 내려오는 거라면, AOP는 비슷한 클래스들에서 공통된 기능을 가로로 빼서 분리해낸다는 느낌이다.

 

스프링에서 @annotation resolve는 AOP proxy를 이용한다.

스프링에는 달아 놓기만 하면 알아서 동작하는 많은 애너테이션들이 있다.

런타임에 이런 애너테이션이 동작하려면, 원래는 애너테이션을 적용하기 위한 별도의 proxy 함수가 필요하다.

```

- 별도의 proxy 함수를 호출

-- 리플렉션으로 애너테이션 체크

-- 애너테이션 동작 - 실제 함수 호출 ( 애너테이션에 따라 시점은 다를 수 있음. )

```

그러나 스프링에서는 이런 별도의 proxy 함수를 호출하지 않아도 annotation이 동작한다.

이게 가능한 이유는 AOP proxy가 동작하기 때문이다.

 

스프링 컨테이너는 어떤 Bean을 주입할 때 proxy로 감싼 aspect-aware bean을 주입해준다.

따라서 어떤 개체가 DI 받게되는 bean은 aspect-aware bean이고, 이를 통해 메서드를 호출하면 실은 proxy 함수가 호출된다. (이게 스프링에서 AOP를 구현하는 방식이다.)

따라서 별도의 proxy 함수를 직접 불러주지 않아도, @애너테이션을 달아 놓는 것 만으로도 동작하게 된다.

이런 식으로 공통 기능을 별도로 분리해내서 핵심 기능 작성에만 집중할 수 있도록 하는 방식을 AOP라고 하며, 스프링의 AOP는 proxy 기반으로 동작한다.

 

internal use method에 @애너테이션을 붙여도 동작하지 않는 현상

아래는 @HystrixCommand를 예로 들어 설명했지만, @Transactional 같은 모든 애너테이션에도 적용 되는 얘기다.

```java

public class PartnerApiClient {

    public UserInfo getUserInfo(String userCi) {

        this._getUserInfo(userCi);

    }

 

    @HystrixCommand(fallbackMethod = "fallback")

    private UserMileageInfoResponseWrapper _getUserInfo(String userCi)  {...}

 

    private UserMileageInfoResponseWrapper fallback(String userCi) {... }

}

```

애너테이션에 따르면 분명 _getUserInfo에서 실패하면 fallback 메서드로 빠져야 하는데 그렇지 않다. 왜일까?

_getUserInfo가 private이라? 아니다. 

이는 Spring의 AOP 지원이 프록시 기반이라는 점에서 기인한다. 

The general idea is that spring AOP is proxy-based, i.e. it assumes that when the bean is used as a dependency and its method(s) should be advised by particular aspect(s) the container injects aspect-aware bean proxy instead of the bean itself. 

 

https://github.com/Netflix/Hystrix/issues/1020#issuecomment-199416640

http://blog.harmonysoft.tech/2009/07/spring-aop-top-problem-1-aspects-are.html

 

위에서 애너테이션이 동작하려면 aspect-aware bean이어야 하며, 스프링 컨테이너는 어떤 Bean을 DI할 때 메서드들을 proxy 함수로 wrapping한 aspect-aware bean을 주입해준다고 했다.

 

반면 ``java this._getUserInfo()`` 처럼 Bean 내부에서 자기 자신의 메서드를 호출하게 되면, 컨테이너로부터 DI받은 Bean을 통해 메서드를 호출하는게 아니라 proxy로 감싸져 있지 않은 자기 자신 메서드를 호출하는 것이다. 즉, aspect-aware bean이 아니라 pure-bean의 메서드를 호출하게 되는 것. 그래서 AOP가 동작하지 않아 @HystrixCommand가 동작하지 않는다.

 

해결 방법은 4가지. 

1. self-invocation 하지 않도록 리팩토링 하는 방법.

 

2. replace self-calls with aspect-aware proxy calls, i.e. use the statement like 

```java

((PartnerApiClient)AopContext.currentProxy())._getUserInfo(userCi);

 

@EnableAspectJAutoProxy(exposeProxy = true) // Application 클래스에 이 것도 추가해주어야 함.

```

이 방법은 AopContext라는 API를 사용하면서 프레임워크와 결합이 생긴다.

 

3. use aspectj weaving ( 별도의 API가 비즈니스 로직에 들어가지 않으니 프레임워크 결합 없이 처리할 수 있어 추천하는 방법 ) 

https://minwan1.github.io/2017/10/29/2017-10-29-Spring-Transaction,AspectJ-Compile/

 

4. 아예 @애너테이션을 안쓰고 Programmatic한 방법으로 직접 API를 불러 사용하는 방법.

예를 들면 @Transactional 대신 PlatformTransactionManager를 직접 DI 받아서 사용한다던가.

단, 2번 처럼 프레임워크 API를 사용하면서 생기는 프레임워크 의존성에 대해서는 생각해보아야 함.

 

참고) Spring에서 annotation은 interface로 부터는 상속되지 않고, abstract class로부터는 상속된다.

https://stackoverflow.com/questions/18585374/spring-aop-inherited-annotation-from-an-interface

물론 class에서 상속 받는 것도 @Inherited 메타 애너테이션이 붙어 있는 애너테이션이어야 가능하다.

 

이런 애너테이션 상속은, 프레임워크에서 해당 메서드(필드)에 달려있는 애너테이션을 어디까지 검색할 것인지에 달려있으므로 프레임워크 마다 다를 수 있다.