

AOP는 관점 지향 프로그래밍으로 관점을 기준으로 다양한 기능을 분리하여 보는 프로그래밍이다. 이 관점은 핵심 기능(비즈니스 로직)과 부가 기능을 나누는 관점을 뜻한다.
모든 메소드의 호출 시간을 측정하고 싶다면?
관점 지향 프로그래밍은 객체지향 프로그래밍을 보완하기 위해 등장한 개념으로 횡단 관심사(공통 관심 사항) 코드를 모듈화해 코드의 재사용성과 유지보수성을 높이는 것을 목적으로 한다.

Aspect
Target
Advice
Join Point
Point Cut
Proxy


AOP는 Bean에만 적용될 수 있고, 따라서 객체의 역할로서만 존재하기 때문에 Aspect와 Target을 연결해주는 역할이 없다. 그 역할을 Proxy 클래스가 대신 해준다.
가짜 스프링 빈이라 생각해도 좋다.
분리된 핵심 기능과 부가 기능은 서로 양방향으로 알아야 할까?
아니다! 부가 기능을 담당한 쪽에서만 핵심 기능 정보를 알고, 사용해야 한다.

Proxy란타겟 객체의 메소드를 호출하기 전이나 후에 가로채 부가 작업을 수행할 수 있도록 하는 대리 객체이다.
프록시를 사용하면 부가 기능 제공 뿐만 아니라 클라이언트는 프록시 사용 여부를 모르고 타겟 객체를 사용하는 것처럼 느끼기 때문에 투명한 방식으로 공통 기능을 제공할 수 있다는 장점이 있다.
앞서 설명한 개념들을 적용한 TimeTraceAspect를 구현해보자 !
@Aspect
@Component
public class TimeTraceAspect {
@Around("execution(* com.project.LIA..*(..))")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("START: "+joinPoint.toString());
try {
return joinPoint.proceed();
} finally {
long finish = System.currentTimeMillis();
long time = finish - start;
System.out.println("END: " + joinPoint.toString() + " " + time + "ms");
}
}
}
실행결과
START: execution(List com.project.LIA.service.BookServiceImpl.myList(String,Integer,Model))
START: execution(Page com.project.LIA.repository.BookRepository.findByUserUsername(String,Pageable))
END: execution(Page com.project.LIA.repository.BookRepository.findByUserUsername(String,Pageable)) 9ms
END: execution(List com.project.LIA.service.BookServiceImpl.myList(String,Integer,Model)) 9ms
END: execution(void com.project.LIA.controller.BookController.list(String,String,String,Integer,Model)) 17ms
AOP가 사용되는 대표적인 예시로 로깅이 있다.
간단한 로깅 처리를 Aspect의 다양한 어노테이션을 써보면서 AOP로 구현해보자 !
package com.project.LIA.aop;
@Aspect
@Component
public class LoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
@Before("execution(* com.project.LIA.service..*(..))")
public void logBefore() {
logger.info("메소드 시행 전 ");
}
@AfterReturning("execution(* com.project.LIA.service..*(..))")
public void logAfterReturning(){
logger.info("메소드 정상 실행 후 ");
}
@AfterThrowing("execution(* com.project.LIA.service..*(..))")
public void logAfterThrowing(){
logger.info("");
}
@After("execution(* com.project.LIA.service..*(..))")
public void logAfter(){
logger.info("메소드 종료 후 성공 여부와 상관 없이 항상 실행 ");
}
@Around("execution(* com.project.LIA.service..*(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
logger.info("메소드 실행 전");
Object result;
try {
result = joinPoint.proceed(); // 실제 메소드 실행
}catch (Exception e){
logger.error("메소드 실행 중 예외 발생"+e.getMessage());
throw e;
}
logger.info("메소드 실행 후 ");
return result;
}
}
메소드를 실행하면 다음과 같은 로그가 찍힌다.
2024-08-22T13:42:00.449+09:00 INFO 76834 --- [nio-8080-exec-6] com.project.LIA.aop.LoggingAspect : 메소드 정상 실행 후
2024-08-22T13:42:00.449+09:00 INFO 76834 --- [nio-8080-exec-6] com.project.LIA.aop.LoggingAspect : 메소드 종료 후 성공 여부와 상관 없이 항상 실행
2024-08-22T13:42:00.449+09:00 INFO 76834 --- [nio-8080-exec-6] com.project.LIA.aop.LoggingAspect : 메소드 실행 후