인생을 코딩하다.

[Spring] AOP(Aspect-Oriented Programming) 본문

Spring

[Spring] AOP(Aspect-Oriented Programming)

Hyung1 2021. 2. 10. 04:41
728x90
반응형

AOP(Aspect-Oriented Programming)란?

"형일, 회원가입 하는 시간 측정해서 로그로 남겨줄 수 있어요?"

 

난 회원가입 하는 서비스로 가서 시간을 측정해본다.

이렇게 객체로 분리하여 시간 측정을 하게된다.

그리고 log가 남는 것을 확인 할 수 있다.

 

그런데 이렇게 메서드에 스탑워트 기능을 추가하면 뭔가 이상하다. 스탑워치라는 시간을 재는 기능을 만들긴 했는데

서비스에 이 로직이 들어가는게 맞을까?

 

사실 여기서 필요한 핵심 로직은 위에 빨간색 박스에 있는 로직 아닐까?

 

SRP에서 클래스를 변경하는 이유는 하나라고 했는데 여기에 서비스 로직 말고 위의 빨간 박스에 있는 로직들 처럼 부가기능이 들어간 것 같다는 생각을 할 수 있다.

 

사실 서비스에서 필요한 내용은 비즈니스 로직이라고 불리는 핵심 기능만 수행하면 된다. 그런데 시간 측정을 한다던지, 권한을 체크한다던지, 트랜잭션을 건다던지 등은 일종의 인프라 로직이라고 불린다.

 

인프라 로직

  • 애플리케이션의 전 역역에서 나타날 수 있음
  • 중복코드를 만들어낼 가능성때문에 유지보수가 힘들어짐
  • 비즈니스 로직과 함께 있으면 비즈니스 로직을 이해하기 어려워짐

이렇게 인프라 로직은

 

성능측정, 권한검사, 트랜잭션 등 하나의 관심사를 가지게 된다. 비즈니스 로직을 수행하는데 있어 부가기능이 되는 인프라 로직의 중복이 횡단으로 나타나는 경우에 횡단 관심사(cores-cutting concern) 라고 불린다.

 

그래서 AOP란?

Aspect-Oriented Programming : 관점 지향 프로그래밍

 

앞에서 봤던 횡단 관심에 따라 프로그래밍한다고 생각하면 편하다.

 

Spring의 핵심 개념중 하나인 DI가 애플리케이션 모듈들 간의 결합도를 낮춰준다면, AOP는 애플리케이션 전체에 걸쳐 사용되는 기능을 재사용하도록 지원하는 것이다.  

 

AOP라는 말에서 우리가 흔히 알고있는 OOP와 대치될 것 같다는 느낌을 받을 수 도 있는데, 

전혀 그렇치 않다. 오히려 더 객체지향 적으로 코드를 구성할 수 있도록 보완해준다.

위의 사진은 스프림 도큐에 있는 글인데, 어떤 프로그램 구조에 관해서 다른 생각의 방향을 제시함으로써 AOP가 OOP를 보완하고 있다고 이야기하고 있다.

 

AOP의 부가기능이라는 것은 횡단에 관심을 가지기 때문에 필수적으로 아래와 같은 질문을 만나게 된다.

 

Target

  • 어떤 대상에 부가 기능을 부여할 것인가

Advice

  • 어떤 부가 기능? Before, AfterReturning, AfterThrowing, After, Around

JoinPoint

  • 어디에 적용할 것 인가? 메서드, 필드, 객체, 생성자 등
  • AspectJ라는 AOP를 구현한 자바의 구현체에서는 메서드, 필드, 객체, 생성자 등에 접근할때에 관해 다양하게 구현해놨다. 즉, 여러가지 상황에서 부가기능들을 실행 시킬 수 있는데, 스프링의 AOP는 메서드가 실행될 때에만 한정짓고 있다.

PointCut

  • 실제 advice가 적용될 지점, Spring AOP에서는 advice가 적용될 메서드를 선정

Spring AOP 사용(supported by @AspectJ)

코드로 보자.

implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-aop'

AuthController -> AuthService 2개를 만들었다.

이런 식으로 시간을 측정하는 것은 메서드가  ("실행되기 전과 후의 필요하기 때문에 @Around라는 advice로 하게 된다.

그리고 그 안에 "execution( * org.~~~~)"  point cut을 지정해준다. 지금 보이는 point cut은 어떤 패키지 구조에서 어떤 클래스에 어떤 메서드에 지정하겠다 라는 point cut이 되겠다.

 

이것을 좀더 간단하게 표현하면 

@Around("@annotation(LogExecutionTime)")

이렇게 @annotation 기반으로도 point cut을 지정할 수 있다.

 

LogExeuctionTime은 내가 따로 만들어 둔 커스텀 어노테이션이고, 이 어노테이션을 단 메서드에서는 해당하는 AOP가 적용될 것이라는 point cut을 지정해주게 된다.

  • @Target로 어노테이션을 메서드에 사용하기 위해서 설정해준다.
  • @Retention으로 어노테이션이 런타임까지 유지되도록 설정해준다.

AOP의 구현 방법 (SPRING AOP가 아닌 자바의 전체적인 구현 방법) 

컴파일

  • J.java -> J.calss로 컴파일 하는 시점에 해당하는 Aspect들을 끼어 넣어 준다. AOP를 적용시키는 것이다.

클래스 로드시

  • J.class까지 컴파일 되는 것 까진 괜찮은데, 이것을 클래스 로드가 메모리 상에 올릴때에 AOP를 적용시키는 것이다.

프록시 패턴

  • Spring AOP에서는 사용하는게 프록시 패턴이다. J라는 타켓 클래스를 프록시로 감싸서 부가기능을 제공하는 프록시로 감싸서 실행하는 방식
public AuthController(AuthService authService) {
    System.out.println(authService.geClass().getName());
    this.authService = authService;
}

AuthController이 AuthService를 DI 받게 되는데 AuthService의 클래스의 이름을 찍어봤다.

 

처음에 AOP가 적용되지 않은 AuthService를 찍어보면 

org.hyungil.aoppractice.sevice.AuthService

AuthService가 그대로 나온다.

 

그 다음에 AOP를 적용한 후 AuthService를 찍어보면

org.hyungil.aoppractice.sevice.AuthService$$EnhancerBySpringCGLIB$$81be6d68

AuthService뒤에 Enhan~~~ 이 뜨게 된다.

 

이것을 디버거로 찍어서 들어가보면,

DI받는 AuthService는 target 안에 {AuthService@4811}가 들어있다.프록시로 감싸주게 된 것이다.

 

즉, 이게 런타임 시에는 

이런 식으로 된다.

 

원래 AuthController이 AuthService를 의존하고 있었다면, 런타임시에는 AuthService&&블라블라를 생성해서 프록시를 만들어서 이것으로 갈아 끼워주게 된다.

 

여기서  AuthService&&블라블라가 AuthService 타입을 갖게 될텐데 AuthService 안에 있는 private 메서드에 적용하면 어떻게 될까?

public void join(JoinRequest joinRequest) {
    inner();
    memberRepository.save(joinRequest.toMember());
}

@PerformanceCheck
private void inner() {
    System.out.println("Hyung1jung blog~~");
}

AuthService에 join이라는 메서드에는 AOP를 적용하지 않고, 그 안에 private로 inner()라는 메서드를 만들어서

여기서 @LogExecutionTime라는 AOP를 적용을 했다. 그리고 join()에서 inner()라는 메서드를 호출을 하면 당연히 

LogExecutionTime이 불리지 않게 된다.

왜냐하면 프록시로 감싼 객체가 실제로 target 오브젝트 일때 join메서드를 실행 할때는 inner 메서드가 프록시로 감싼 객체가 아니기 때문에 자기 자신을 호출할 때에는 AOP로 감싸지지 않은 메서드가 호출이 된다.

 

이떄 위를 바탕으로 보면,

service에서 private 메서드에다가 트랜잭션 어노테이션을 붙였을때 작동이 안하면, 작동이 하지 않는 이유는 이것 때문이다. 

 

service 로직을 하나의 트랜잭션으로 만들 때, 로직의 시작점에 트랜잭션을 열어주고 로직이 끝나는 시점에서 트랜잭션을 커밋하는 코드가 들어가야 하는데 @트랜잭셔널 어노테이션으로 인해서 우리는 비즈니스 로직에만 집중할 수 있고

트랜잭션이라는 인프라 로직은 AOP로 분류하게 된다.

 

그리고 interceptor이나 filter도 모두 AOP의 일종 이라고 할 수 있다. 

 

Spring AOP VS AspectJ

  Spring AOP AspectJ
목표 간단한 AOP 기능 제공 완벽한 AOP 기능 제공
joint point 메서드 레벨만 지원 생성자, 필드, 메서드 등 다양하게 지원
weaving 런타임 시에만 가능 런타임은 제공하지 않음. compile-time, post-compile, load-time 제공
대상 Spring Container가 관리하는 Bean에만 가능 모든 Java Object에 가능

자바에서 구현한 AspectJ는 완벽한 AOP 기능을 제공하려 한다. 이 말은 join point가 메서드 호출시점이나, 메서드가 실행되는 시점이나, 객체가 생성되는 시점이나, 필드에 접근하는 시점이나, 이렇게 다양한 join point를 지원한다는 이야기다.

 

반면에 Spring AOP는 개발자들이 만날 수 있는 문제를 위해서 간단하게 프록시 패턴을 통해서 제공한다. 

그렇기 때문에 메서드 레벨만 지원해주고, ( * weaving : AOP를 끼워넣어 주는 시기 ) weaving은 런타임 시에만 가능하다. 이게 가능한 이유는 Spring Container가 관리를 해주기 때문에, 어떤 객체의 생성을 관리해 주기 때문이다. 그렇기 때문에 target 오브젝트도 스프링이 관리하는 빈에만 가능하게 된다.

 

반면에 AspectJ는 런타임을 제공하지 않는다. 스프링에서 제공하는 IOC나 DI기능이 없기 때문이다. 그래서 모든 오브젝트에 제공이 가능하다.

 

실습 더보기

Spring AOP를 이용한 메서드 시간 측정

 

프록시 패턴에 관하여

프록시 패턴(Proxy Pattern)

 

네트워크 프록시에 관해 자세히

Proxy. Forward Proxy, Reverse Proxy, Load Balancer

 

참고 문헌 :

www.eclipse.org/aspectj/doc/released/progguide/index.html

spring.io/guides

www.youtube.com/watch?v=Hm0w_9ngDpM&t=292s

www.yes24.com/Product/Goods/17350624

 

728x90
반응형
Comments