AOP는 핵심 비즈니스 로직과 부가적인 기능(로깅, 트랜잭션, 보안 등)을 분리하여 코드의 가독성을 높이고 유지보수를 용이하게 하는 프로그래밍 기법이며, Spring AOP를 사용하면 메서드 실행 전후, 예외 발생 시 특정 로직을 실행하는 Aspect(관점)을 적용할 수 있습니다.
Spring AOP를 이용하면 @Aspect와 어노테이션을 활용하여 라이브러리(공통 기능)를 인터페이스나 특정 로직에 적용할 수 있습니다.
어노테이션 정의
@Target(ElementType.METHOD) // 메서드에만 적용
@Retention(RetentionPolicy.RUNTIME) // 런타임 시점까지 유지
public @interface LogExecutionTime {
}
AOP설정 및 Aspect 클래스 구현
- @Aspect를 사용하여 특정 어노테이션이 붙은 메서드의 실행 시간을 측정하는 기능을 추가
@Aspect
@Component
public class LoggingAspect {
@Around("@annotation(com.example.annotation.LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object proceed = joinPoint.proceed(); // 실제 메서드 실행
long executionTime = System.currentTimeMillis() - start;
System.out.println(joinPoint.getSignature() + " 실행 시간: " + executionTime + "ms");
return proceed;
}
}
AOP 적용
- 어노테이션 사용 특정 메서드에 @LogExecutionTime 어노테이션을 붙이면 실행 시간이 자동으로 로그에 찍힙니다.
@Service
public class UserService {
@LogExecutionTime
public void getUsers() {
try {
Thread.sleep(500); // 0.5초 대기
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("유저 목록 조회 완료");
}
}
execution(UserService.getUsers()) 실행 시간: 501ms
유저 목록 조회 완료
AOP 기능을 라이브러리로 만들어서 공통 모듈로 활용할 수도 있습니다.
이를 위해 별도의 JAR 파일로 만들어 다른 프로젝트에서도 적용할 수 있습니다.
특정 인터페이스를 구현하는 모든 클래스에서 AOP를 적용하려면 아래와 같이 설정할 수 있습니다.
@Around("execution(* com.example.service.UserServiceImpl.*(..))")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
/**
* AOP 기능 유지
*/
}
또는 특정 인터페이스인 UserService를 구현한 모든 클래스에 적용하려면
@Around("execution(* com.example.service.UserService+.*(..))")
이렇게 +
를 붙이면, UserService를 구현한 모든 클래스에 적용되게 된느 것입니다.
스프링 AOP를 활용하면 특정 메소드에 어노테이션을 붙여 해당 메소드 호출 시 자동으로 로깅 등의 공통 관심사를 적용할 수 있습니다. Gradle 의존성을 추가하고, 커스텀 어노테이션과 Aspect 클래스를 작성하는 방식으로 구현할 수 있습니다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-aop'
}
만약 Spring Boot가 아닌 일반 스프링 프로젝트라면, AspectJ 관련 라이브러리와 스프링 AOP 의존성을 별도로 추가해야 합니다.
로그를 남길 위치에 붙일 커스텀 어노테이션을 생성합니다. 예를 들어, @LogExecution이라는 어노테이션을 다음과 같이 작성할 수 있습니다
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecution {
}
이 어노테이션은 런타임까지 유지되며, 메소드에 붙여서 로깅을 적용할 수 있습니다.
이제 실제 AOP 로직을 담은 Aspect 클래스를 작성합니다. Aspect 클래스에서는 어노테이션이 붙은 메소드를 포인트컷으로 지정하고, 해당 메소드의 실행 전후에 로깅을 수행하도록 구현합니다.
@Aspect
@Component
public class LoggingAspect {
@Around("@annotation(com.example.LogExecution)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
// 메소드 실행 전 시간 기록
long start = System.currentTimeMillis();
// 실제 대상 메소드 실행
Object proceed = joinPoint.proceed();
// 메소드 실행 후 경과 시간 계산
long executionTime = System.currentTimeMillis() - start;
// 로깅 (SLF4J를 사용한 예)
Logger logger = LoggerFactory.getLogger(joinPoint.getTarget().getClass());
logger.info("{} executed in {} ms", joinPoint.getSignature(), executionTime);
return proceed;
}
}
Spring Boot를 사용하는 경우 별도의 설정 없이도 AOP가 활성화됩니다.
일반 스프링 프로젝트라면 설정 클래스에 @EnableAspectJAutoProxy 어노테이션을 추가하여 AOP를 활성화해야 합니다
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
/**
* 기타 설정들
*/
}
로깅을 적용하고자 하는 메소드에 커스텀 어노테이션을 붙입니다. 예를 들어, 서비스 클래스의 메소드에 적용할 수 있습니다.
@Service
public class SomeService {
@LogExecution
public void performTask() {
// 비즈니스 로직 실행
}
}
이제 performTask() 메소드가 호출될 때마다, LoggingAspect에서 정의한 로직에 따라 메소드의 실행 시간 등 로그가 자동으로 기록됩니다.