서버/Spring

[Spring] Spring AOP (Aspect-Oriented Programming) - 횡단 관심사 분리

sm.jeon 2024. 11. 7. 05:54
반응형

AOP란?

애플리케이션의 횡단 관심사를 모듈화하는 방법이다.

AOP를 통해 로깅, 트랜잭션 관리, 보안 등 공통 기능을 애플리케이션의 다른 부분에 영향을 주지 않고 분리하여 관리한다.

Spring AOP는 프록시 패턴으로 구현되었다.

메소드 실행 전후, 예외 발생 시점에 추가적인 행동을 정의한다.

 

적용 방식

컴파일 시점 적용

AspectJ 컴파일러가 .java파일 컴파일 시에 부가기능을 넣어 .class 파일로 컴파일한다.

이를 위빙이라 한다.

 

클래스 로딩 시점 적용

JVM내 클래스로더에 .class 파일을 올릴때 바이트코드를 조작하는 방식이다.

 

런타임 시점 적용

애플리케이션 main 메서드 실행 이후에 자바가 제공하는 범위에서 부가기능을 적용한다.

코드를 조작하기 어려워, 프록시를 통해 부가기능 적용한다.

 

Spring AOP는 런타임 시점에 적용한다.

 

AOP 용어 및 개념

Aspect

공통 관심 사항을 모듈화한 것으로, 예를 들어 로깅이나 보안 등의 기능을 Aspect로 정의한다.

이곳에서 Pointcut을 지정하며, Advice를 정의한다.

 

Join Point

메소드 호출이나 필드 접근과 같은 프로그램 실행의 특정 지점을 의미한다.

= Aspect가 적용될 수 있는 위치이다.

 

Advice

Join Point에서 실행되는 코드이다. 부가기능 코드 자체를 말한다.

Aspect를 언제 핵심 코드에 적용할지(메서드 실행 전, 후 등) 정의한다.

 

Pointcut

Join Point중 실제로 Advice가 적용될 위치를 선별하는 표현식이다.

 

Target

Advice가 적용되는 대상을 지칭한다.

 

Advice에서 정의하는 Aspect 적용 타이밍

@Before 조인포인트 실행 이전에 실행
@AfterReturning 조인포인트 완료후 실행
@AfterThrowing 메서드가 예외를 던지는 경우 실행
@After 조인포인트의 동작과 관계없이 실행
@Around 메서드 호출 전후에 수행 (조인포인트 실행 여부 선택, 반환 값 변환, 예외 변환, try~catch~finally 구문 처리 가능 등)

 

구현

implementation("org.springframework.boot:spring-boot-starter-aop")
@Component
@Aspect
@Order(1)
class Aspect {

	private val logger = LoggerFactory.getLogger(javaClass)

    //service 패키지 ..Service 클래스 하위 모든 메서드에 적용
	@Pointcut("execution(* com.example.spring.service..*Service.*(..))")
    fun servicePointcut() {}

    //사용자 지정 어노테이션에 적용
    @Pointcut("@annotation(com.example.spring.annotation.Logger)")
    fun loggerPointcut() {}

    @Before("servicePointcut() && loggerPointcut()")
    fun serviceStart(point: JoinPoint) {
        val className = point.target.javaClass.simpleName
        val methodName = point.signature.name
        val arguments = point.args
        logger.trace("method info : $className.$methodName($arguments)")
    }
    
    @Around("servicePointcut()")
    fun serviceAround(point: ProceedingJoinPoint) {
        val startTime = System.currentTimeMillis()
        point.proceed()
        val endTime = System.currentTimeMillis()
        val runningTime = endTime - startTime
    }

    @After("servicePointcut()")
    fun serviceAfter(point: JoinPoint) {
    }

    @AfterReturning("servicePointcut()", returning = "returnObject")
    fun serviceAfterReturning(returnObject: Any) {
    }

    @AfterThrowing("servicePointCut()", throwing = "e")
    fun serviceAfterThrowing(e: Exception) {
    }
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Logger {}
반응형