반응형
AOP(Aspect Oriented Programming)
AOP는 '관점 지향 프로그래밍'이라 불리며, 객체 지향 프로그래밍(OOP)의 부족한 부분을 보완하기 위해 등장한 프로그래밍 패러다임이다.
AOP는 OOP와 달리 횡단적 관심사(cross-cutting concern)를 분리하여 모듈화하고, 이를 필요한 부분에 적용하는 방식으로 동작합니다.
- 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나눈 후 나눈 관점으로 분리한 기준들을 모듈화(공통된 로직)
- 핵심적인 관점: 개발자가 적용 하고자 하는 비지니스 로직
- 부가적인 관점: 핵심 로직을 수행하기 위한 로깅, 보안, 트랜잭션 처리 등
- 관심사는 'Aspect'라는 단위로 추상화하며, 이 Aspect를 프로그램 코드에 적용하는 방식으로 동작한다.
AOP는 주로 로깅, 보안, 트랜잭션 처리 등의 관심사를 분리하여 모듈화하고, 런타임 시에 필요한 부분에 적용하는데 많이 사용된다.
1. AOP 용어
- Target : 부가기능을 부여할 대상으로 Aspect가 적용되는 곳이다(Class, Method 등)
- Advice : Target에게 제공할 부가 기능을 담은 모듈로 실제 기능을 정의한 구현체이다.
- JoinPoint : Advice가 적용될 위치/지점이다. 스프링 프록시 AOP에서 조인포인트는 메서드 실행 단계 에서만 동작한다.
- PotinCut : Advice를 적용할 JoinPoint들의 위치를 선별한다. 스프링 AOP의 JoinPoint는 메소드실행 시점만 가능하다.
- Aspect : 흩어진 관심사들을 모듈화 한 것 으로 한 개 또는 기 이상의 [Pointcut + Advice]으로 마들어진다.
2. 스프링 AOP
- 스프링은 AOP는 프로시 기반의 AOP 구현체다.
- DI를 통해 타깃 대신 클라이언트에게 주입되며, 클라이언트의 메서드 호출 대신 받아 타깃에 위임하며 그 과정에서 부가기능을 부여한다.
3. 스프링 AOP 구현
Spring AOP dependency 추가
maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
gradle
implementation 'org.springframework.boot:spring-boot-starter-aop'
Spring AOP는 AnnotationAwareAspectJAutoProxyCreator 를 통해 Advisor 프록시 생성하는 역할을 하며 @Aspect를 보고 Advisor로 변환하여 저장하는 작업을 수행.
@Aspect
@Component
public class LogAspect {
...
}
위 코드처럼 @Aspect 어노테이션을 지정하면 Spring AOP 프록시 생성기에서 Adviosr로 변환하여 @Aspect Advisor 빌더 내부에 저장된다.
또한 프록시 생성기에서 Bean을 조회할 때 @Aspect가 빈을 추가하지 않기 때문에 @Bean 또는 @Component를 추가하여야 스프링 컨테이너가 빈을 등록된다.
@Aspect를 지정하고나면 Aspect 실행 시점을 지정할 수 있는 어노테이션들이 있다.
- @Pointcut 포인트컷 설정
- 포인트컷 지시자 종류
- execution : 메서드 실행 조인 포인트를 매칭
- 표현식 : `execution([접근제한자 패턴] 리턴 값 타입패턴 [패키지 타입패턴.]메소드이름패턴 (파라미터 타입패턴 | "..", ...) [throws 예외 패턴])
- within : 특정 타입 내의 조인 포인트를 매칭한다.
- args : 인자가 주어진 타입의 인스턴스인 조인 포인트
- this : 스프링 빈 객체(스프링 AOP 프록시)를 대상으로 하는 조인 포인트
- target : Target 객체(스프링 AOP 프록시가 가리키는 실제 대상)를 대상으로 하는 조인 포인트
- @target : 실행 객체의 클래스에 주어진 타입의 어노테이션이 있는 조인 포인트
- @within : 주어진 어노테이션이 있는 타입 내 조인 포인트
- @annotation : 메서드가 주어진 어노테이션을 가지고 있는 조인 포인트를 매칭
- @args : 전달된 실제 인수의 런타임 타입이 주어진 타입의 어노테이션을 갖는 조인 포인트
- bean : 스프링 전용 포인트컷 지시자로 빈의 이름으로 포인트컷을 지정한다.
- execution : 메서드 실행 조인 포인트를 매칭
- 포인트컷 지시자 종류
- @Around
- (@Before, @AfterReturning, @AferThrowing, @After) 어노테이션을 모두 포함
- 메서드 호출 전후 작업 명시 가능
- 조인 포인트 실행 여부 선택 가능
- 반환값 자체를 조작 가능
- 예외 자체를 조작 가능
- 조인 포인트를 여러번 실행 가능(재시도)
- @Before
- 조인 포인트(Target Method) 수행 이전에 실행
- @AfterReturning
- 조인 포인트(Target Method) 수행 완료 후 실행
- @AfterThrowing
- Target Method에서 Exception이 발생할 경우 실행(에외 조작 불가능)
- @After
- 모든 조인 포인트(Target Method)의 정상, 예외 동작과 무관하게 무조건 실행
Ex) 로그 기록 AOP
package com.study.aop.log;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;
@Aspect
@Component
public class LogAspect {
private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
@Pointcut("execution(* com.study.aop.controller.*.*(..))")
public void controller() {
}
private volatile long startTime = 0;
@Before("controller()")
public void startLog(JoinPoint joinPoint) {
try {
Signature signature = joinPoint.getSignature();
RequestMapping requestMapping = getAnnotation(joinPoint, RequestMapping.class);
this.startTime = System.currentTimeMillis();
log.debug("requestMapping={}", requestMapping);
String path = "";
if (requestMapping != null) {
String method = requestMapping.method()[0].name();
if (method.equals("GET")) {
path = AopUtils.getAnnotation(joinPoint, GetMapping.class).value()[0];
} else if (method.equals("POST")) {
path = AopUtils.getAnnotation(joinPoint, PostMapping.class).value()[0];
} else if (method.equals("PUT")) {
path = AopUtils.getAnnotation(joinPoint, PutMapping.class).value()[0];
} else {
path = AopUtils.getAnnotation(joinPoint, DeleteMapping.class).value()[0];
}
log.info("============================================================");
log.info("## == Start ==");
log.info("## == Package : {} ", signature.getDeclaringTypeName());
log.info("## == Method : {}", signature.getName());
log.info("## == URI : {}, Method : {}", path, method);
log.info("============================================================");
}
} catch (Exception e) {
log.error("startLog exception=", e);
}
}
@AfterReturning("controller()")
public void endLog(JoinPoint joinPoint) {
try {
Signature signature = joinPoint.getSignature();
long endTime = System.currentTimeMillis();
log.info("============================================================");
log.info("## == END ==");
log.info("## == Package : " + signature.getDeclaringTypeName());
log.info("## == Method : {}", signature.getName());
log.info("## -> elapsed time: " + (endTime - this.startTime) + "ms");
log.info("============================================================");
log.info("");
} catch (Exception e) {
log.error("endLog Exception :" , e);
}
}
@After("controller()")
public void afterLog(JoinPoint joinPoint) {
log.info("Start afterLog...");
}
protected <A extends Annotation> A getAnnotation(JoinPoint joinPoint, @Nullable Class<A> cls) {
//call controller Method
MethodSignature method = ((MethodSignature) joinPoint.getSignature());
return AnnotationUtils.findAnnotation(method.getMethod(), cls);
}
}
토비스프링 AOP
반응형
'Spring' 카테고리의 다른 글
[Spring] Reactive Stream 개요 (0) | 2023.05.02 |
---|---|
@EventListener를 사용한 발행-구독 패턴 (0) | 2023.05.02 |
[Spring Framework] IOC/DI (0) | 2023.03.29 |
[스프링] 자동으로 빈 와이어링하기(Bean Auto Wiring) (0) | 2018.11.19 |
스프링 목적과 DI 정의 (0) | 2018.11.16 |