(Obsidian 기록일 : 24년 01월 11일, 공식문서를 번역하다 보니 오역이 첨가될 수 있음 / 초안)
AOP
- AOP는 Aspect Oriented Programming의 약자로 관점 지향 프로그래밍이라고 불린다.
- 교차 관심사(Cross-cutting concerns)를 분리하여 모듈성을 높이는 것을 목표로 하는 프로그래밍 패러다임이다.
- 코드 자체를 수정하지 않고 기존 코드에 추가로 동작하게 하는 것을 수행한다.
- 본문에서는 Spring AOP에 관하여 설명
- Spring AOP 공식문서 : https://docs.spring.io/spring-framework/reference/core/aop.html
- OOP는 모든 것을 객체로 만드는 데 중점을 두고 있는 반면, AOP는 다른 객체의 동작을 보완하기 위해 자신의 동작을 주입하고 래핑하는 특별한 유형의 객체인 관점을 도입한다.
- 즉, OOP가 Class를 기반으로 모듈화 한다면 AOP는 Aspect를 기반으로 모듈화 한다 ( The key unit of modularity in OOP is the class, whereas in AOP the unit of modularity is the aspect)
- 아래 그림은 로깅, 보안, 트랜잭션 관리와 같은문제가 여러 계층에 걸쳐 어떻게 나타나고 있는지 보여준다.
- cross-cutting concerns 의 예는 다음과 같다.
- Logging
- Security
- Transaction management
- Auditing,
- Caching
- Internationalization
- Error detection and correction
- Memory management
- Performance monitoring
- Synchronization
AOP의 용어 정리
- Aspect : transaction 관리처럼 여러 type, object들이 crosscutting concern에 대한 것들. Spring AOP에서는 특정 interface를 구현하거나 @Aspect 어노테이션을 붙여서 사용 가능하다. 흩어진 관심사를 모듈화 한 것.
- Join point : 특정 Method를 실행하거나 Exception을 처리할 때 처럼 프로그램이 실행되고 있는 어느 한 시점. Spring AOP 에서는 언제나 Method 실행 시점을 의미한다.
- Advice : 특정 join point나 aspect에 수행할 행동을 의미. around, before, after 등의 종류가 존재. Spring AOP를 포함해서 많은 AOP 프레임워크들은 advice를 Interceptor 로 모델링하고 join point around의 interceptor 체인을 관리한다.
- Pointcut : join points의 한 종류. advice는 pointcut에 일치하는 join point에 실행됨 (예를 들면 어떤 특정 이름의 메소드 실행). Spring AOP는 AspectJ 의 pointcut 표현 방식을 기본으로 한다.
- Introduction : Declaring additional methods or fields on behalf of a type. Spring AOP lets you introduce new interfaces (and a corresponding implementation) to any advised object. For example, you could use an introduction to make a bean implement an IsModified interface, to simplify caching. (An introduction is known as an inter-type declaration in the AspectJ community.) (이해를 잘 못해서 원문을 들고옴)
- Target object : advice 가 적용되는 개체. advised object라고도 함. Spring AOP에서는 언제나 runtime proxies를 사용하여 구현하므로 proxied object라고도 할 수 있음. 이는 대상 메서드가 재정의되고 해당 구성(configuration)에 따라 advice가 포함되는 하위 클래스가 런타임에 생성된다는 의미.
- AOP proxy : aspect contracts를 구현하기 위해 AOP 프레임워크에 의해서 만들어진 object. Spring Framework에서는 JDK dynamic proxy 또는 CGLIB proxy를 사용.
- Weaving : aspects와 어플리케이션의 object 또는 type들을 연결하여 advised object들을 만들어내는 과정. compile time, load time, runtime 모두에 실행될 수 있음. 다른 순수 자바 AOP 프레임워크들과 마찬가지로 Spring AOP도 runtime에 weaving을 수행.
Advice 종류
- Before advice ( @Before ) : join point가 실행되기 이전 시점에 실행되는 advice.
- After returning advice ( @AfterReturning ) : join point가 완료되고 리턴한 다음에 실행되는 advice. 조인포인트 메소드가 정상적으로 실행되는 경우에만 어드바이스 메소드가 실행
- After throwing advice ( @AfterThrowing ) : join point가 Exception을 던지며 종료된 다음에 실행되는 advice
- After (finally) advice( @After ): join point가 정상적으로 종료된 여부에 관계 없이 항상 실행되는 advice. 정상적으로 또는 예외를 발생시켜 조인 포인트 메서드의 실행이 완료된 후에 실행
- Around advice ( @Around(메서드 실행 전후 ): method 호출처럼 join point를 둘러싸는 advice. 가장 강력한 종류의 advice로 method의 호출의 이전과 이후에 특정한 행동을 수행하도록 하는 것도 가능. join point를 실행할 것인가 아니면 자체적인 값을 리턴하거나 Exception을 던져서 생략할 것인가 결정할 수도 있음
AOP의 종류
Spring AOP
- 스프링에서 AOP를 사용할 때 기본적으로 사용하는 방식.
- 모듈들은 순수한 자바로 작성
- 별도의 복잡한 설정이 필요없다는 것이 장점.
- 현재로는 method 실행 join point에만 advice를 걸 수가 있음
- 즉, 스프링컨테이너에 의해 관리되는 Beans에만 작동가능
Full AspectJ
- AspectJ compiler 또는 weaver를 사용해서 AOP 기능을 사용하는 방식
- 다소 구현이 복잡하지만 많은 기능을 제공
프로젝트에 적용해 보기
1. Dependency 추가
dependencies {
compile('org.springframework.boot:spring-boot-starter-aop')
}
// 또는.. 이 방법으로
dependencies {
compile("org.springframework:spring-aop:${springAopVersion}")
compile("org.aspectj:aspectjweaver:${aspectjweaverVersion}")
}
2. 어노테이션 붙이기
- @EnabledAspectJAutoProxy 라는 어노테이션을 앱의 @Configuration 클래스에 붙여야된다.
- 메인에다가 설정 해도 무방
@EnableAspectJAutoProxy
@SpringBootApplication
public class MiristockApplication {
public static void main(String[] args) {
SpringApplication.run(MiristockApplication.class, args);
}
}
3. @Aspect 클래스 만들기
package com.udteam.miristock.config;
import com.google.common.base.Joiner;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
@Aspect // 1
@Component //2
public class RequestLoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(RequestLoggingAspect.class);
private String paramMapToString(Map<String, String[]> paramMap) {
return paramMap.entrySet().stream()
.map(entry -> String.format("%s -> (%s)",
entry.getKey(), Joiner.on(",").join(entry.getValue())))
.collect(Collectors.joining(", "));
}
@Pointcut("within(com.udteam.miristock.interfaces..*)") // 3
public void onRequest() {}
@Around("com.udteam.miristock.config.RequestLoggingAspect.onRequest()") // 4
public Object doLogging(ProceedingJoinPoint pjp) throws Throwable {
HttpServletRequest request =
((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
Map<String, String[]> paramMap = request.getParameterMap();
String params = "";
if (!paramMap.isEmpty()) {
params = " [" + paramMapToString(paramMap) + "]";
}
long start = System.currentTimeMillis();
try {
return pjp.proceed(pjp.getArgs()); // 5
} finally {
long end = System.currentTimeMillis();
logger.info("Request: {} {}{} | Host: {} ({}ms)", request.getMethod(), request.getRequestURI(),
params, request.getRemoteHost(), end - start);
}
}
}
4. 적용확인
'BackEnd > Spring' 카테고리의 다른 글
스프링 프레임워크 특징과 구조 (0) | 2023.10.02 |
---|---|
스프링 프레임워크와 스프링 부트 차이 간단 정리 (0) | 2023.09.12 |