AOP
AOP는 애플리케이션의 구조적 향상을 위해 기존에 자바에서 사용하던 OOP방식을 좀더 보안하여 나온 개념이다. OOP에서는 class를 중점으로 모듈화*를 하였지만 AOP는 관점이라는 추상적인 개념을 중점으로 모듈화를 진행한다.
쉽게 설명하자면 OOP의 class들에서 어떠한 특정한 목적을 위해 공통으로 구현하는 프로세스(공통 관심)들을 또다시 분리하여 코드의 반복을 줄이고 유지보수에 더 용이하게함으로써 더욱 객체지향적 프로그래밍이 되도록 하는 기술이다.
(개인적으로 노드JS의 미들웨어정도로 생각하니 이해하기 좀더 수월했다.)
(*모듈화: 어떤 시스템의 구성요소가 분리되고 재결합 할 수 있게 만드는 행위)
이러한 AOP의 주 컨샙으로는 8가지가 존재한다.
- Aspect
- Join point
- Advice
- Pointcut
- Introduction
- Target
- AOP proxy
- Weaving
(Spring AOP에서는 Advisor라는 개념이 추가된다.)
AOP를 구현하는 방법에는 여러가지가 있지만 크게 AspectJ와 Spring AOP를 이용하여 AOP를 구현하며 이번 포스트에서는 Spring AOP에 대해 알아본다.
Aspect
여러 클래스(핵심관심사)들에게 공통적으로 들어가는 기능(공통관심사)들을 말한다. 예를들자면 트랜잭션, 로깅, 권한관리 등이 있다.
스프링에서는 이러한 공통기능들을 하나의 클래스에 @Aspect 어노테이션을 이용하여 정의하며 해당 클래스안에서 여러 어드바이스와 포인트컷을 정의해 줄 수 있다. 즉, 스프링에서는 Advise와 Pointcut을 합친것들을 Aspect로 정의 할 수 있다.
- 하나의 Advise와 하나의 Pointcut(해당 경로에는 여러 핵심기능들이 포함될수는 있음)만을 가진 Aspect를 Advisor라고 한다.
@Order 애너테이션을 Aspect "클래스"에 적용시켜 Aspect들 간의 실행순서를 정할수 있다. 오로지 "클래스"에만 사용가능하다.
Join point
Aspect가 실행될 시간적 조건이다. 즉, Advice(Aspect의 로직, 공통기능)를 Pointcut(공통기능이 실행될 메서드)에서 어느 시점에 실행시킬 것인가를 정하는 지점이다.
Spring AOP는 Spring IoC에서 제공하는 간편한 AOP기능이기에 Spring 컨테이너가 관리하는 Bean에만 적용이 가능하므로 조인포인트는 항상 핵심관심사(Bean으로 등록된)의 메서드 실행 지점으로 제한된다.
Advice
공통관심사의 기능이 수행되는 코드이다. Aspect 클래스의 메서드 형태로 존재하며 5가지 타입의 애너테이션으로 실행시점(조인포인트)를 정한다.
- @Around - 따로 설명
- @Before : 핵심관심사의 코드가 실행되기 이전에 Advice 실행
- @AfterReturning : 핵심관심사의 코드의 결과값이 반환된 이후에 Advice 실행
- @AfterThrowing : 핵심관심사의 코드가 오류를 발생했을 시점에 Advice 실행
- @After : 핵심관심사의 모든 코드가 종료된 이후 Advice 실행
@Around
가장 많이 쓰이는 애너테이션이며 다른 4가지 애너테이션의 기능을 모두 표현할 수 있다. 또한 다른 애너테이션들과 달리 ProceedingJoinPoint 인터페이스를 매개변수로 받을수 있다.
ProceedingJoinPoint 인터페이스를 이용하여 핵심기능코드의 실행시점과 Advise의 Join point를 개발자가 원하는대로 정할수 있으며 이를 이용하여 다른 4가지 애너테이션과 같은 기능들을 모두 표현할 수 있는 것이다.
Pointcut
정의한 Advise와 Join point가 적용될 위치이다. @Pointcut 애너테이션을 사용할 수 있으며 특정한 메서드들 혹은 어떠한 메서드들을 포함하는 경로를 AspectJ 표현식을 사용하여 지정한다.
쉽게 설명하면 Advise를 실행시킬 핵심관심사의 메서드(Spring의 AOP는 Bean에 등록된 메서드에만 가능)의 경로이다.
Introduction
Advice가 특정한 인터페이스를 구현하여 추가적인 메서드나 필드를 선언하는 것을 Introduction이라한다. 예를들어 어떠한 Aspect를 IsModified라는 인터페이스를 구현하게 하여 캐싱을 단순화 할 수도 있다.
AspectJ에서는 Introduction을 inter-type declaration이라 한다.
Target object
하나 이상의 Aspect에 영향을 받는 객체를 의미한다. 즉 정의된 Aspect의 Pointcut에 해당되는 모든 핵심관심사의 모듈들이 Target object에 속한다.
또한 Advised object라고도 불리며 Spring에서는 모든 Target object들이 proxied object(프록시를 사용하는 객체)가 된다.
AOP proxy
Spring을 포함한 다른 AOP framework가 Aspect를 구현하기 위해 proxy 기술의 객체를 생성하며 Spring에서는 JDK 동적 프록시 혹은 CGLIB 프록시를 사용한다.
Proxy
Proxy라는 단어의 뜻은 '대리' 이다. 우리는 AOP를 이용하여 핵심기능코드와 관심기능코드를 특정한 시점에 맞는 순서로 사용해 주어야하는데 이때 필요한 것이 Proxy이다.
즉, Proxy라는 객체가 생성되어 원하는 시점에 맞게 핵심기능코드와 관심기능코드가 연결(Weaving)되며 해당 코드들이 직접 실행이되는 것이 아닌, Proxy 객체가 대리로 실행해 주는 것이며 Spring AOP에서는 JDK dinamic 혹은 CGLIB가 사용된다.
Weaving
Aspect(공통관심기능이 되는 코드)를 Target(핵심관심기능이 되는 코드)와 연결시키는 행위이다.
(Weaving이 동명사라는 것을 생각해 보면 어떠한 행위에 대해 정의한 것임을 추측할 수 있다.)
Weaving의 종류
- Run-time : Spring AOP에서 사용하는 방식
- Load-time : AspectJ에서 사용
- Compile-time : AspectJ에서 사용
(Advanced concepts of Proxy and Weaving 관련 블로그 : https://jiwondev.tistory.com/152 )
정리
여기까지 기본 개념들을 종합해 Spring AOP의 흐름을 정리하자면
- Advice는 관심기능들이 실행될 실제 코드들과 실행될 시점(Join point)로 만들어진다.
- Aspect는 1번의 Advice와 Pointcut(@Bean으로 정의된 핵심관심사항의 클래스들의 경로)로 이루어저 있다.
- Pointcut으로 등록된 특정 클래스가 실행이되면 Proxy 객체가 생성
- Proxy 객체는 Advice와 Target을 Weaving 한뒤 실행
예제코드 및 출력
@Slf4j
@Aspect
@Order(1)
public class Aspect6 {
@Around("com.aop.aop.order.Aspect6.Pointcuts.orderAndService()")
public Object doTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
try {
//@Before
log.info("[around] 트랜잭션 시작 -> {}", joinPoint.getSignature());
Object result = joinPoint.proceed();
// @AfterReturning
log.info("[around] 트랜잭션 커밋 -> {}", joinPoint.getSignature());
return result;
} catch (Exception e) {
// @AfterThrowing
log.info("[around] 트랜잭션 롤백 -> {}", joinPoint.getSignature());
throw e;
} finally {
// @After
log.info("[around] 리소스 릴리즈 -> {}", joinPoint.getSignature());
}
}
@Before("com.aop.aop.order.Aspect6.Pointcuts.orderAndService()")
public void doBefore(JoinPoint joinPoint) {
log.info("[before] {}", joinPoint.getSignature());
}
@AfterReturning(value = "com.aop.aop.order.Aspect6.Pointcuts.orderAndService()", returning = "result")
public void doReturn(JoinPoint joinPoint, Object result) {
log.info("[return] {} return={}", joinPoint.getSignature(), result);
}
@AfterThrowing(value = "com.aop.aop.order.Aspect6.Pointcuts.orderAndService()", throwing = "ex")
public void doThrowing(JoinPoint joinPoint, Exception ex) {
log.info("[throwing] {} message={}", joinPoint.getSignature(), ex.getMessage());
}
@After(value = "com.aop.aop.order.Aspect6.Pointcuts.orderAndService()")
public void doAfter(JoinPoint joinPoint) {
log.info("[after] {}", joinPoint.getSignature());
}
}
Join point를 위한 코드의 일부분이며 전체코드는 깃헙 클릭
(전체 예제 코드 : https://github.com/Mason3144/Examples_of_Spring_AOP )
참고자료
Spring official site : https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop
'Java > Spring & Spring Boot' 카테고리의 다른 글
Entity와 Mapper (0) | 2023.04.13 |
---|---|
DTO (Data Transfer Object) 과 Validation (0) | 2023.04.12 |
Servlet과 JSP 그리고 MVC(Model, View, Controller) (0) | 2023.04.11 |
스프링 컨테이너 (0) | 2023.04.05 |
POJO (0) | 2023.04.03 |