AOP
Aspect Oriented Programming(관점 지향 프로그래밍)
로직을 기준으로 핵심 관점(비즈니스 로직)과 부가 관점을 나누고, 관점을 기준으로 모듈화하는 것
데이터베이스 연결, 로깅, 파일 입출력, 시간 측정 등의 여러 번 반복해서 사용하는 흩어진 Aspect를 모듈화해 핵심 로직에서 분리하여 재사용이 가능하도록 하는 방법이다
같은 일을 반복하는 코드를 독립적으로 작성하고 적용해야 하는 부분을 개발자가 구체적으로 작성해 적용할 수 있다.
주요 용어
Aspect
- 흩어진 관심사를 모듈화 한 것
- Advice + Pointcut
Target
- Aspect를 적용하는 곳(클래스, 메소드 등)
Advice
- 실질적으로 수행하는 기능
- 제공할 부가기능을 담고 있다
Join Point
- Advice가 적용될 위치
- 합류점에 대한 스펙을 정리
- 특정 작업에 끼어드는 시점(ex. 메소드 진입, 생성자 호출 등)
PointCut
- 적용되는 위치
- 실제 Advice가 적용되는 JoinPoint를 나타냄
Weaving
- PointCut에 의해 결정된 Target의 JoinPoint에 Advice를 삽입하는 과정
- AOP가 핵심기능(타겟)의 코드에 영향을 주지 않으면서 필요한 부가기능(어드바이스)을 추가할 수 있도록 해주는 핵심 처리과정
적용 방법
컴파일
자바 파일을 컴파일 해 클래스 파일로 만들 때 Advicde 코드가 추가된 조작된 바이트 코드를 생성하는 방법
로드 타임이나 런타임 때 추가적인 작업을 하지 않아도 되기 때문에 성능적인 부하가 없다는 장점이 존재하지만 별도의 컴파일 과정이 필요하다는 단점이 존재
로드 타임
컴파일은 순수하게 작업하고 컴파일 후 컴파일된 클래스를 로딩하는 시점에 Advcie 소스를 끼워넣은 상태로 로딩을 한다
다양한 기능을 제공하지만 로딩 시점에 부하가 발생한다는 단점이 존재한다
런타임
스프링이 사용하는 방법이다
스프링은 런타임시에 Bean을 생성하는데 이 때 프록시 Bean도 함께 생성한다
메소드를 호출하기 직전 먼저 프록시 Bean이 Advice를 실행하고 기존의 Bean을 호출하는 방법이다
별도의 컴파일 과정을 거치지 않아도 된다는 장점이 있지만 최초 런타임 시점에 비용이 발생한다
Spring AOP
특징
- 프록시 패턴 기반의 AOP구현체
- 스프링 Bean에만 AOP를 적용 가능
- 모든 AOP의 기능을 제공하지 않고 스프링 IoC와 연동해 엔터프라이즈 애플리케이션의 각종 문제(중복 코드, 객체 관계간 복잡도)에 대한 해결책을 지원하는 것이 목적
💡 프록시 패턴
client는 interface 타입으로 프록시 객체를 사용하고, 프록시 객체는 타겟 객체를 참조한다
client로부터 요청이 들어오면 프록시 객체는 기존 객체를 감싸 요청을 처리한다
❓ 그렇다면 왜 프록시 패턴을 사용하는 것이 아닌 AOP가 만들어졌을까?
- 매번 프록시 클래스를 작성하는 것에 대한 번거로움이 존재
- 여러 클래스와 메소드에 적용하려면 객체들간의 관계가 복잡해진다는 단점이 발생한다
- 스프링 IoC 컨테이너가 제공하는 시설과 Dynamic 프록시를 사용해 문제를 해결할 수 있다
- Dynamic 프록시? 동적으로 프록시 객체를 생성하는 방법
- 자바가 제공하는 방법은 인터페이스를 기반한 프록시 생성만 제공한다
- CGlib을 사용하면 클래스 기반의 프록시도 지원해준다
- Dynamic 프록시? 동적으로 프록시 객체를 생성하는 방법
- 기존 빈을 대체하는 동적 프록시 빈을 만들어 등록시켜주기 때문에 클라이언트의 코드의 변경이 없다
사용 방법
매우 간단한 방법만 작성...
의존성 추가
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
Aspect 정의
@Aspect
@Component
public class PerfAspect {
@Around("execution(* com.girawhale.aop..*(..))") // 패키지 하위에 있는 모든 메소드에 aop를 적용한다
private Object logPerf(ProceedingJoinPoint joinPoint) throws Throwable {
long begin = System.currentTimeMillis();
Object retVal = joinPoint.proceed();
System.out.println(System.currentTimeMillis() - begin + "ms");
return retVal;
}
}
@Around는 전반적인 범위이기 때문에 특정 시점에 호출하고 싶다면 @Before, @AfterReturning 등의 다른 애노테이션도 사용이 가능하다
포인트 컷은 애노테이션의 value로 지정할 수 있다. 예제로 작성한 execution 외에도 @annotation을 사용하면 해당 애노테이션이 붙은 메소드에서만 실행이 가능하게 지정할 수 있고, bean을 지정해 해당 빈에 적용되도록 지정할 수도 있다
📚 Reference
'Programming > Spring' 카테고리의 다른 글
[Spring] Bean 주입하기 : DI (0) | 2021.04.24 |
---|---|
[Spring] Bean Scope와 주의할 점 (0) | 2021.04.22 |
[Spring] Bean을 등록하는 방법 (0) | 2021.04.22 |