스프링

스프링5 프로그래밍 입문 - AOP 프로그래밍

담쏙 2023. 10. 28. 14:28
728x90
  • AOP (Aspect Oriented Programming)
  • aspectjweaver 의존을 추가해야 함 (스프링이 AOP 구현 시 사용하는 모듈)
    • spring-context 모듈을 추가하면 spring-aop 모듈도 함께 의존 대상에 포함
    • asepctjweaver 모듈은 AOP를 설정하는데 필요한 애노테이션 제공

 

프록시

  • 부가적인 기능 구현을 위해 기존 코드를 수정하지 않고, 코드 중복도 피하기 위해서 프록시 객체 사용
  • 핵심 기능의 실행은 다른 객체에 위임하고 부가적인 기능을 제공하는 객체를 프록시라고 부름
    • 프록시의 특징이 핵심 기능은 구현하지 않는다는 것
    • 핵심 기능을 구현하지 않는 대신 여러 객체에 공통으로 적용할 수 있는 기능 구현
  • 실제 핵심 기능을 실행하는 객체는 대상 객체라고 부름

 

AOP

  • 여러 객체에 공통으로 적용할 수 있는 기능을 분리해서 재사용성을 높여주는 프로그래밍 기법
  • 공통 기능 구현과 핵심 기능 구현을 분리하는 것이 AOP의 핵심
  • 핵심 기능을 구현한 코드의 수정 없이 공통 기능을 적용할 수 있음
  • 핵심 기능에 공통 기능을 삽입하는 방법
    • 컴파일 시점에 코드 공통 기능을 삽입하는 방법 (AOP 개발 도구가 소스 코드를 컴파일 하기 전에 소스에 삽입)
    • 클래스 로딩 시점에 바이트 코드에 공통 기능을 삽입하는 방법
    • 런타임에 프록시 객체를 생성해서 공통 기능을 삽입하는 방법 (스프링에서 제공하는 방법)
      • 중간에 프록시 객체를 생성하고 실제 객체의 기능을 실행하기 전/후에 공통기능을 호출
      • 이때 스프링 AOP는 프록시 객체를 자동으로 생성해줌
용어 의미
Advice 언제 공통 관심 기능을 핵심 로직에 적용할 지 정의. 메서드를 호출하기 전(언제)에 트랜잭션 시작(공통 기능) 기능을 적용한다는 것을 정의
Joinpoint Advice를 적용 가능한 지점. 메서드 호출, 필드 값 변경 등이 해당. 스프링은 프록시를 이용해서 AOP를 구현해서 메서드 호출에 대한 Joinpoint만 지원
Pointcut Joinpoint의 부분 집합으로서 실제 Advice가 적용되는 Joinpoint를 나타냄. 정규 표현식이나 AspectJ 문법 이용
Weaving Advice를 핵심 로직 코드에 적용하는 것
Aspect 여러 객체에 공통으로 적용되는 기능. 트랜잭션이나 보안 등

 

Advice의 종류

  • 메서드 호출 시점에 Aspect를 적용하기 때문에 구현 가능한 Advice의 종류는 아래와 같다.
종류 설명
Before Advice 대상 객체의 메서드 호출 전에 공통 기능을 실행
After Returning Advice 대상 객체의 메서드가 익셉션 없이 실행된 이후에 공통 기능을 실행
After Throwing Advice 대상 객체의 메서드를 실행하는 도중 익셉션이 발생한 경우에 공통 기능 실행
After Advice 익셉션 발생 여부에 상관없이 대상 객체의 메서드 실행 후 공통 기능을 실행 (가장 널리 사용 됨. 다양한 시점에 원하는 기능을 삽입 가능)
Around Advice 대상 객체의 메서드 실행 전, 후 또는 익셉션 발생 시점에 공통 기능을 실행하는데 사용 (캐시 기능, 성능 모니터링 기능 구현 시 사용)

 

스프링 AOP 구현

  • Aspect로 사용할 클래스에 @Aspect 애노테이션 붙이기
  • @Pointcut 애노테이션으로 공통 기능을 적용할 Pointcut 정의
  • 공통 기능을 구현한 메서드에 @Around 애노테이션 적용
@Aspect
public class ExamAspect{
	@Pointcut("execution(public * sample..*(..))") //Advice를 적용할 메서드 지정
    private void publicTarget() {
    }
    
    @Around("publicTarget()") //publicTarget() 메서드에 정의한 Pointcut에 공통 기능 적용
    public Object commonFunc(ProceedingJointPoint joinPoint) throws Throwable {
    	// ... 공통 로직
        Object result = joinPoint.proceed(); // 프록시 대상 객체의 메서드 호출
        // ... 공통 로직 
    }

 

execution 표현식

execution(수식어패턴? 리턴타입패턴 클래스이름패턴?메서드이름패턴(파라미터패턴))
  • 수식어패턴 : 생략 가능. 스프링 AOP는 public 메서드에만 적용할 수 있기 때문에 public만 의미 있음
  • 리턴타입패턴 : 리턴타입 명시
  • 클래스이름패턴, 메서드이름패턴 : 클래스 이름 및 메서드 이름 명시 (패키지명으로도 표현 가능)
  • 파라미터패턴 : 매칭될 파라미터에 대해서 명시
  • 각 패턴은 *을 이용해서 모든 값을 표현할 수 있으며, .. 을 이용하여 0개 이상이라는 의미 표현 가능

 

ProceedingJoinPoint 메서드

  • 공통 기능 메서드는 대부분 파라미터로 전달받은 ProceedingJointPoint의 proceed() 메서드만 호출하면 되지만, 다른 정보들이 필요할 때가 있다
  • ProceedingJoinPoint 인터페이스가 제공하는 메서드
    • Signature getSignature() : 호출되는 메서드에 댛나 정보를 구함
    • Object getTarget() : 대상 객체를 구함
    • Object[] getArgs() : 파라미터 목록을 구함
  • org.aspectj.lang.Signature 인터페이스가 제공하는 메서드
    • String getName() : 호출되는 메서드의 이름을 구함
    • String toLongString() : 호출되는 메서드를 완전하게 표현한 문장을 구함 (메서드의 리턴 타입, 파라미터 타입이 모두 표시)
    • String toShortString() : 호출되는 메서드를 축약해서 표현한 문장을 구함 (기본 구현은 메서드의 이름만을 구함)

 

프록시 생성 방식

  • 빈 객체가 인터페이스를 상속하면, 그 인터페이스를 이용하여 프록시를 생성한다
    • 인터페이스가 아닌 실제 클래스 타입으로 getBean()을 통해 빈 객체를 구하면 오류 발생하므로 주의
  • 빈 객체가 인터페이스를 상속할 때, 인터페이스가 아닌 클래스를 이용해서 프록시를 생성하고 싶다면 아래와 같이 설정
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppCtx{
	//...
}

 

Pointcut

  • 하나의 Pointcut 에 여러 Advice를 적용할 수 있으며, 어떤 Aspect가 먼저 적용될지는 스프링 프레임워크나 자바 버전에 따라 달라질 수 있음
  • Aspect 적용 순서가 중요하다면 @Aspect 애노테이션과 함께 @Order 애노테이션을 붙여 적용 순서 결정 가능
@Aspect
@Order(1)
public class FirstAspect {
}

@Aspect
@Order(2)
public class SecondAspect {
}

 

  • @Pointcut 애노테이션이 아닌 @Around 애노테이션에 execution 명시자를 직접 지정할 수도 있음
@Aspect
public class ExamAspect {

	@Around("execution(public * package..*(..))")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
    	...
    }

}

 

  • 여러 Aspect에서 공통으로 사용하는 Pointcut이 있다면 별도 클래스에 Pointcut을 정의하고, 각 Aspect 클래스에서 해당 Pointcut을 사용하도록 구성하면 Pointcut 관리가 편해짐 - @Around("별도 클래스에 정의된 Pointcut 메서드()")
  • Pointcut을 설정한 클래스는 빈으로 등록할 필요가 없으며, @Around 애노테이션에서 해당 클래스에 접근 가능하면 해당 Pointcut 사용 가능