AOP(Aspect Oriented Programming)
관점지향 프로그래밍은 복잡한 프로그래밍 세상에서 관심사를 분리하여 모듈로 사용하는데 목적이 있다.
AOP 는 핵심기능과 횡단 관심사 (부가기능) 를 분리해서 관리하는 것
AOP 의 키워드
1. 어드바이스 ( Advice )
2. 포인트컷 ( PointCut )
3. 타겟 ( Target )
4. 조인포인트 ( JoinPoint )
5. 애스팩트 ( Aspect )
a. 어드바이스
- 어드바이스는 실제로 실행되는 횡단관심사(부가기능) 코드를 의미
b. 포인트컷
- 포인트컷은 AOP 에서 어드바이스를 적용할 구체적인 범위를 선택하는 규칙.
- 해당 범위는 특정 패키지 내의 모든 메서드들일 수도 있고, 특정 패키지내의 클래스의 모든 메서드들일 수도 있음.
c. 타겟
- 타겟은 AOP 에서 어드바이스가 적용되는 객체를 의미.
- 예를 들면 포인트컷으로 적용된 특정 클래스(CourseService) 가 타겟이 되면, 그 클래스 내의 모든 메서드들이 어드바이스의 적용을 받음.
execution(int createCourse(int, int)) // 반환타입 int, createCourse 이라는 매서드, 매개변수는 int 형 2개
execution(* createCourse(int, int)) // 반환타입 상관없음, createCourse 이라는 매서드, 매개변수는 int 형 2개
execution(* createCourse(..)) // 반환타입 상관없음, createCourse 이라는 매서드, 매개변수 상관없음
execution(* *(..)) // 반환타입 상관없음, 메서드이름 상관없음, 매개변수 상관없음
d. 조인포인트
- 조인포인트는 어드바이스(횡단관심사) 가 적용될 수 있는 실행 지점을 의미.
- 우리가 메서드 기준으로 어드바이스를 동작시킴으로 메서드들이 조인포인트가 됨.
e. 애스팩트
- 애스팩트는 어드바이스(횡단관심사)와 포인트컷(횡단 관심사를 어디에 적용할지) 를 하나로 묶은 모듈.
AOP 적용
준비
@Slf4j
@Aspect
public class AspectPractice {
aop 폴더 생성, AOP 파일을 만들어준다.
@Configuration
public class WebConfig {
@Bean
public AspectPractice getAspectPracticeAop() {
return new AspectPractice();
}
WebConfig 가 있으면 추가하고 없으면 만들어주어서 등록해주자.
패키지 범위 기반 포인트컷 만들기
/**
* 포인트컷 : 서비스(Service) 패키지 기반
*/
@Pointcut("execution(* com.standard.sparta.service..*(..))")
// 모든 반환타입, 경로 안에 있는 모든 메서드, 매개변수 상관없음
private void serviceLayer() {}
어드바이스
Before
/**
* 어드바이스: @Before
* 메서드 실행 전에 수행되는 로직을 처리할 때 사용되는 AOP
*/
@Before("serviceLayer()")
public void beforeMethod() {
log.info("::: BEFORE 실행 :::");
}
AfterReturning
/**
* 어드바이스: @AfterReturning
* 메서드가 정상적으로 반환된 후에 실행
* 예외가 발생하지 않고 정상적으로 결과값이 반환됐을 때 동작
*/
@AfterReturning(pointcut = "serviceLayer()", returning = "result")
public void afterReturningMethod(Object result) {
// result 관련 로직을 짤 수 있음
log.info("::: AFTER RETURNING :::");
}
AfterThrowing
/**
* 어드바이스: @AfterThrowing
* 메서드 실행 중 예외가 발생했을 때에만 실행
*/
@AfterThrowing(pointcut = "serviceLayer()", throwing = "ex")
public void afterThrowingMethod(Throwable ex) {
// ex <- 예외가 발생했을 때 필요한 조작
log.info("::: AFTER THROWING :::");
}
After
/**
* 어드바이스: @After
* 메서드가 정상적으로 실행되건, 예외가ㅣ 발생하건, 항상 실행
*/
@After("serviceLayer()")
public void afterMethod() {
log.info("::: AFTER :::");
}
Around
/**
* 어드바이스: @Around
* 가장 강력한 어드바이스, 전체 흐름을 제어할 수 있는 어드바이스
*/
@Around("serviceLayer()")
public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("::: BEFORE :::");
try {
Object result = joinPoint.proceed();
log.info("::: AFTER RETURNING :::");
return joinPoint;
} catch (Exception e) {
log.info("::: AFTER THROWING :::");
throw e;
} finally {
log.info("::: AFTER :::");
}
}
패키지 범위 기반 어드바이스 적용
/**
* 포인트컷 : 서비스(Service) 패키지 기반
*/
@Pointcut("execution(* com.standard.sparta.service..*(..))")
// 모든 반환타입, 경로 안에 있는 모든 메서드, 매개변수 상관없음
private void serviceLayer() {}
@Around("serviceLayer()")
public Object advicePackageMethod(ProceedingJoinPoint joinPoint) throws Throwable {
// 측정 시작
long startTime = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
return result;
} finally {
// 측정 완료
long endTime = System.currentTimeMillis();
long executionTime = endTime - startTime;
log.info("::: ExecutionTime : {} ms", executionTime);
}
}
어노테이션 범위 기반 어드바이스 적용
어노테이션 생성
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TrackTime {
}
포인트컷 생성
/**
* 포인트컷 : 어노테이션 범위 기반
*/
@Pointcut("@annotation(com.standard.sparta.annotation.TrackTime)")
public void trackTimeAnnotation() {}
어드바이스
/**
* 어드바이스 : 어노테이션 범위 기반
*/
@Around("trackTimeAnnotation()")
public Object adviceAnnotation(ProceedingJoinPoint joinPoint) throws Throwable {
// 측정 시작
long startTime = System.currentTimeMillis();
try {
Object proceed = joinPoint.proceed();
return proceed;
} finally {
// 측정 완료
long endTime = System.currentTimeMillis();
long executionTime = endTime - startTime;
log.info("::: ExecutionTime : {} ms", executionTime);
}
}
// 패키지 단위로 전체 다 설정하려면 execution 범위 설정 방법이 좋고
// 하나하나 원하는 메서드를 선택하려면 어노테이션 방식이 좋다.
AOP 동작 원리
스프링에서 객체를 빈으로 등록할 때 빈 후처리기 를 거치게 됨. 이때 빈 후처리기 는 AOP 설정이 적용된 객체를 감지하고, 해당 객체를 프록시객체로 감싼 후 스프링 컨테이너에 등록함.
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/bean")
public class BeanController {
private final ApplicationContext applicationContext;
@GetMapping
public void checkBeanApi() {
Object courseService = applicationContext.getBean("courseService");
Class<?> aClass = courseService.getClass();
log.info(aClass.getName());
}
}
BeanController 를 만들어서 한 번 테스트를 해보았음
com.standard.sparta.service.CourseService // APO 미적용: 실제 객체 사용
com.standard.sparta.service.CourseService$$SpringCGLIB$$0 // AOP 적용: Proxy 객체 사용
이처럼 AOP 를 적용하면 CourseService 에 접근하기 전에 Proxy 객체에 먼저 접근하는 걸로 보아 Proxy 객체가 Service 를 감싸고 있는 것을 확인할 수 있었다.
'컴퓨터 프로그래밍 > Spring' 카테고리의 다른 글
[Spring] Service Test Code 작성 (0) | 2024.09.14 |
---|---|
[Spring] Controller Test Code 작성 (0) | 2024.09.14 |
[Spring] 통합 테스트 (1) | 2024.09.10 |
[Spring] Mockito (0) | 2024.09.09 |
[Spring] 단위 테스트 프레임워크 JUnit5, Given-When-Then 패턴 (0) | 2024.09.09 |