Search
Duplicate

AOP(Aspect Oriented Programming)

1. 개요

AOP란?

관점 지향 프로그래밍이라고도 불린다.
어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나눠보고 그 관점을 기준으로 각각 모듈화 하겠다는 의미.
핵심적인 관점: 개발자가 적용하고자 하는 핵심 비즈니스 로직.
부가적인 관점: 핵심 로직을 수행하기 위해 필요한 DB연결(JDBC), 로깅, 파일 입출력 등...

다양한 AOP 적용 방법

컴파일
A.java —-(AOP) —→ A.class(AspectJ)
바이트코드 조작
A.java → A.class —-(AOP)—→ 메모리(AspectJ)
프록시패턴과는 다르게 바이트코드 조작방식은 타깃 오브젝트를 뜯어고쳐서 부가기능을 직접 넣어주는 직접적인 방법을 택한다.
AspectJ 는 프록시와는 다르게 좀 더 직접적인 방법으로 부가기능을 제공하는데, 컴파일된 Target의 클래스 파일 자체를 수정하거나 클래스가 JVM에 로딩되는 시점을 가로채 바이트코드를 조작하는 방법을 사용한다. 그렇기 때문에 .java파일과 .class 파일을 비교해보면 내용이 달라진걸 확인할 수 있다. 해당 방법(바이트코드 조작)을 사용하는 이유는 두가지가 있다. 1. 스프링과 같은 DI컨테이너의 도움을 받지 않아도 AOP를 적용할 수 있기 때문이다. 그렇기에 스프링과같은 컨테이너가 사용되지 않는 환경에서도 손쉽게 AOP의 적용이 가능해진다.
2. 프록시 방식보다 강력하고 유연한 AOP가 가능하다. 프록시를 AOP의 핵심 메커니즘으로 사용할 경우 부가기능(공통 모듈)을 부여할 대상은 클라이언트가 호출할 때 사용하는 메소드로 제한된다. 하지만, 바이트코드 조작 방식을 사용하면, 오브젝트의 생성, 필드 값 조회및 조작, 스태틱 초기화 등 다양한 작업에 부가기능을 부여할 수 있다. 이처럼 프록시를 사용한 AOP에서는 불가능한 부분에서까지 부가기능 부여가 가능하기 때문에 강력하고 유연하다.
프록시 패턴(스프링 AOP가 사용하는 방법)
⇒ 공통 모듈을 프록시로 만들어서 DI 로 연결된 빈 사이에 적용해 Target의 메소드 호출 과정에 참여애 부가기능(공통 모듈)을 제공해준다.
그렇기의 JDKSpring Container 외에 특별한 기술 및환경을 요구하지 않는다.
Advice 가 구현하는 MethodInterceptor 인터페이스는 다이내믹 프록시의 InvocationHandler와 마찬가지로 프록시부터 메소드 요청정보를 전달받아 타깃 오브젝트의 메소드를 호출하는데, 이렇게 메소드를 호출하는 전/후로 부가기능(공통 모듈)을 제공할 수 있다.
이런식으로 독립적으로 개발한 부가기능 모듈을 다양한 타깃 오브젝트의 메소드에 다이내믹하게 적용해주기 위해 가장 중요한 역할을 맡고 있는게 프록시고, 스프링 AOP는 프록시 방식의 AOP라 할 수 있다.

주요 키워드

Aspect
⇒ 여러곳에서 쓰이는 공통 부분 코드를 모듈화한 것
Target
⇒ Aspect가 적용되는 곳(Ex: Class, Method ...)
Advice
⇒ Aspect 에서 실질적인 기능에 대한 구현체
Joint point
⇒ Advice 가 Target에 적용되는 시점
⇒ 메서드 진입할 때, 생성자 호출할 때, 필드에서 값을 꺼낼 때 등
Point cut
⇒ Joint Point 의 상세 스펙을 정의한 것
Proxy
⇒ 클라이언트와 타겟 사이에 투명하게 존재하며 부가기능을 제공하는 오브젝트. DI를 통해 타겟 대신 클라이언트에게 주입되며 클라이언트의 메소드 호출을 대신 받아서 타겟에 위임하며 이 과정에서 부가기능을 부여한다.

결국 AOP는 Aspect를 분리하여 핵심기능을 설계 및 구현할 때 객체지향적인 가치를 지킬 수 있도록 도와주는 것이다.

실전 예제 - Spring AOP 구현

AOP를 사용하지 않는 기본 적인 시간 측정 Bean(ExampleService)구현

1. 의존성 추가

Code

2. 인터페이스 구현

Code

3. 인터페이스 구현체 구현

Code

4.테스트 코드 작성

Code

5. 실행 결과

Content

AOP를 이용해 시간측정 Bean(ExampleService) 개선 - Execution expression

1. 공통 모듈분석

⇒ 위 코드(ExampleServiceImpl)을 보면 StopWatch 를 이용해 성능측정을 하는부분이 start, process , end 메서드에 모두 포함된다. 그렇기 때문에 해당 코드는 공통모듈로 묶어서 정의할 수 있다.

2. 공통 모듈추출 및 구현

Code

3. 구현체 공통 모듈 삭제

Code

4. 테스트 코드 작성

Code
참고: Advice 정의(@Aspect)시 Point cut시점을 정의할 수 있는데 위 예제는 @Around로 정의되었지만 @Before@After와같은 어노테이션도 존재한다.

AOP를 이용해 시간측정 Bean(ExampleService) 개선 - Annotation

⇒ 구현체의 특정 메서드들만 부가기능을 부여하려면 기존 Execution expression 방식을 쓰면 표현식을 다 작성해줘야 한다. 그렇기에 Annotation을 이용하여 조금 더 편하게 적용해보자.

1. 어노테이션(Annotation) 구현

Code

2. Aspect 클래스 수정(Execution Expression → Annotation)

Code

3. 구현체(ExampleServiceImpl)에 어노테이션 추가

Code

4. 실행 결과

Content

AOP를 이용해 시간측정 Bean(ExampleService) 개선 - Bean

Bean을 통한 기능 부여도 가능합니다.
Code

AOP 적용 전/후 의존관계

AOP 적용 전 의존관계
helloControllermemberService의 기능들을 사용하며 의존관계가 수립됩니다.
컨트롤러에서는 memberService의 실제 객체(Real Subject)에 바로 접근하여 로직을 수행시킵니다.
memberService 에서는 공통적인 기능들을 추가하려면 각각의 메서드마다 기능들을 추가/수정/삭제 해야합니다.
AOP 적용 후 의존관계
helloControllermemberService 간에 프록시 객체인 memberServiceProxy가 추가되었습니다.
helloController 는 이제 실제객체(Real Subject)에 바로 접근하지않고 프록시객체를 거쳐 프록시 객체에서 실제 객체에 접근하여 로직을 수행합니다.
AOP 적용 전 전체 그림
Client가 Request를 하였을 때 스프링 컨테이너 내부의 의존관계를 보여줍니다.
helloControllermemberService 에 메서드를 호출해 비즈니스 로직을 수행하고 memberService에서는 memberRepository 를 호출하여 레파지토리에서는 DB에 접근해 데이터를 조회/수정/삭제 합니다.
모두 실제객체에 바로바로 접근합니다.
모든 객체들에게 프록시객체가 생성되었고 의존관계도 프록시 객체를 통하여 이뤄집니다.
각각 객체의 기능들을 수행하려 메서드를 호출하면 요청을 프록시 객체가 전달받아서 전처리/후처리 등 추가적인 작업을 수행하면서 실제 객체(Real Subject)에 로직을 수행합니다.