[Spring] AOP - Aspect Oriented Programming

728x90
반응형
SMALL

AOP (Aspect Oriented Programming)

관점 지향 프로그래밍

 

흩어진 Aspect를 모듈화할 수 있는 프로그래밍 기법이다.

 

동일한 색상의 선을 중복되는 메서드 & 필드 & 코드라고 가정할 때,

여러 클래스에 반복되서 사용 되기 때문에 흩어진 관심사(Crosscutting Concerns)이라 부른다.

 

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

 

Interceptor, Filter도 일종의 AOP이다.

 

또한, 중복되는 주황색 부분의 수정이 생기면 A, B, C 클래스 모두 수정을 해야 한다.

이는 SOLID 원칙 중 SRP(Single Responsibility Programming)에 위반된다.

 


 

 

 

Spring AOP

스프링은 프록시 패턴을 사용해서 AOP를 구현한다.

 

 


 

프록시 패턴

Client는 Subject 인터페이스 타입으로 Proxy 객체를 사용하게 되고,

Proxy는 Real Subject를 감싸서 Client의 요청을 처리하는 구조이다.

 

여기서 프록시 패턴의 목적은 기존 코드 변경 없이 접근 제어 또는 부가 기능을 추가하기 위해서이다.

 

@Component
@RequiredArgsConstructor
public class AppRunner implements ApplicationRunner {


    private final EventService eventService;
    
    @Override
    public void run(ApplicationArguments args) throws Exception {
    	eventService.createEvent();
        eventService.publishEvent();
    }
    
}



public interface EventService {

	void createEvent();
    void publishEvent();
    
}



@Service
public class EventServiceImpl implements EventService {
	
    @Override
    public void createEvent() {
    	...
    }
    
    @Override
    public void publishEvent() {
    	...
    }
    
}
  • AppRunner == Client
  • EventService == Subject
  • EventServiceImpl == Real Subject

이때, AOP의 대표적인 예제인 실행 시간 출력하는 로직을 createEvent, publishEvent에 추가해보자.

 

가장 간단한 방법은 실질적인 구현체인 EventServiceImpl의 각 메서드에 실행 시간을 출력하는 로직을 중복 추가 하는 것이다.

하지만, 이 방법은 기존 코드를 건드려서 하는 방식이다. 즉, 프록시 패턴의 목적인 "기존 코드 변경 없이"를 위반한다.

 

@Primary
@Service
@RequiredArgsConstructor
public class ProxyEventServiceImpl implements EventService {

	private final EventServiceImpl eventServiceImpl;
    
    @Override
    public void createEvent() {
		+ 실행 시간 측정 로직
		eventServiceImpl.createEvent();
    }
    
    @Override
    public void publishEvent() {
    	+ 실행 시간 측정 로직
		eventServiceImpl.publishEvent();
    }
   
}

우선, @Primary 어노테이션을 사용하여 같은 타입의 빈이 여러개일 때 우선권을 부여하게 한다.

 

그리고 EventService 인터페이스를 implements 하고, 각 메서드들을 재정의한다.

재정의 할 때, 실행 시간 출력 로직을 추가하고 핵심 로직이 구현되어 있는 EventServiceImpl의 메서드들을 호출한다.

 

이렇게 하면 Proxy(ProxyEventServiceImpl)가 Real Subject(EventServiceImpl)을 가지고 있고, 

Real Subject에 일을 위임해서 대신 처리하고, 부가적인 기능(실행 시간 출력)도 가지고 있게 된다.

 

하지만, 여전히 실행 시간 측정 로직이 중복되어 있고 프록시 클래스를 생성해야 하는 단점이 있다.

 


 

@AOP

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
@Component
@Aspect
public class PerfAspect {
	
    @Around("execution(* package.package..*.EventService.*(..))")
    public Object logPerf() {
    	+ 실행 시간 측정 
    }


	또는
    
    @Around("@annotaion(LogExecutionTime)")
    public Object logPerf(ProceedingJoinPoint pjp) {
		+ 실행 시간 측정
    }
    
}

 

기본적인 프록시 패턴을 사용했을 때의 단점은 Spring AOP를 이용하여 해결할 수 있다.

 

우선, AOP를 사용하기 위해 gradle dependency를 추가한다.

이후 @Aspect 어노테이션을 사용하여 Aspect 클래스임을 명시하고, @Component를 사용하여 빈에 등록한다.

@Around를 사용해서 적용 해야 하는 위치, 즉 Point Cut을 명시한다.

 

참고로 @Around 외 Aspect 실행 지점을 지정할 수 있는

@Before, @After, @AfterReturing, @AfterThrowing 도 있다.

 

Advice = 실행 시간 측정,

Target = PerfAspect Class

JointPoint = Point Cut에 명시된 곳

Point Cut = 패키지.패키지 하위 클래스 중 EventService에 있는 모든 메서드

또는

Point Cut = Custom Annotation을 생성하여 해당 Annotation을 사용하는 곳

 

이렇게, 별도 Aspect 클래스를 생성하고 원하는 위치에서 동작 시킬 수 있다.

 


 

AOP 주요 개념

  • Aspect : 흩어진 관심사를 모듈화 한 것. 주로 핵심이 아닌 부가 로직을 모듈화 한다.
  • Target : Aspect를 적용 하는 곳
  • Advice : 구체적인 부가 로직
  • Joint Point : Advice가 적용될 위치, 호출 시점 등
  • Point Cut : Joint Point의 상세한 스팩, 구체적인 Advice 실행 지점 및 시점

 

 

자주 사용하는 @Transactional 도 AOP를 기반으로 동작한다.

 

@Transactional 어노테이션이 붙은 메서드가 Joint Point이고,

트랜잭션 전처리, 후처리 로직이 Advice 이다.

 


 

Proxy 외 AOP 구현 방법

.java -> .class로 컴파일 하는 시점에 해당하는 Aspect들을 끼어 넣어 AOP를 적용한다.

 


 

참고

- https://www.inflearn.com/course/spring-framework_core

728x90
반응형
LIST