39DAYS) Spring Framework 핵심 개념(4) - AOP (Aspect Oriented Programming)

nacSeo (낙서)·2022년 12월 13일
0

◉ 학습목표

1. 타입별 Advice와 Pointcut 표현식, JoinPoint의 의미를 이해할 수 있다.
2. 애너테이션을 이용한 AOP에 대해 이해하고 활용할 수 있다.
  1. 타입별 Advice

⦿ 학습내용

☞ 어드바이스 (Advice)

✔︎ Aspect를 언제 핵심 코드에 적용할지 정의
✔︎ 부가 기능에 해당
✔︎ 특정 조인 포인트에서 애스펙트에 의해 취해지는 조치

☞ Advice 순서

✔︎ 기본적으로 순서 보장 ❌
✔︎ 순서를 지정하고 싶으면, @Aspect 적용 단위로 org.springframwork.core.annotation.@Order 애너테이션 적용

  • 어드바이스 단위가 아닌 클래스 단위로 적용 가능
  • 하나의 애스펙트에 여러 어드바이스가 존재하면 순서 보장 ❌

✔︎ 애스펙트를 별도의 클래스로 분리해야 함

☞ Advice 종류

✔︎ Before

  • 조인포인트 실행 이전에 수행
  • 타겟 메서드가 실행되기 전 처리해야할 필요가 있는 부가 기능을 메서드 호출 전에 실행
  • 일반적으로 리턴타입이 void
  • 메서드에서 예외를 발생시킬 경우, 대상 객체의 메서드가 호출 ❌
  • 예제
@Before("hello.aop.order.aop.Pointcuts.orderAndService()")
public void doBefore(JoinPoint joinPoint) {
	log.info("[before] {}", joinPoint.getSignature());
}
- 작업 흐름 변경 ❌
- 메서드 종료 시 자동으로 다음 타겟 호출 (예외 발생시 다음 코드 호출 ❌)

✔︎ After returning

  • 조인 포인트가 정상 완료 후 실행
  • 메서드가 예외 없이 실행된 이후 공통 기능 실행
  • 예제
@AfterReturning(value = "hello.aop.order.aop.Pointcuts.orderAndService()", returning = "result")
public void doReturn(JoinPoint joinPoint, Object object) {
	log.info("[return] {} return={}", joinPoint.getSignature(), result);
}
- 메서드 실행이 정상적으로 반환될 때 실행
- returning 속성에 사용된 이름은 어드바이스 메서드의 매개변수 이름과 일치해야 함
- returning 절에 지정된 타입의 값을 반환하는 메서드만 대상을 실행

✔︎ After throwing

  • 메서드가 예외를 던지는 경우 실행
  • 메서드 실행 도중 예외가 발생한 경우 공통 기능 실행
  • 예제
@AfterThrowing(value = "hello.aop.order.aop.Pointcut.orderAndService()", throwing = "ex")
public void doThrowing(JoinPoint joinPoint, Exception ex) {
	log.info("[ex] {} message={}", joinPoint.getSignature(), ex.getMessage());
}
- 메서드 실행이 예외를 던져서 종료될 때 실행
- throwing 속성에 사용된 이름은 어드바이스 메서드의 매개변수 이름과 일치해야 함
- throwing 절에 지정된 타입과 맞는 예외를 대상으로 실행

✔︎ After (finally)

  • 조인 포인트의 동작(정상 또는 예외)과는 상관없이 실행
    • 예외 동작의 finally
  • 메서드 실행 후 공통 기능 실행
  • 일반적으로 리소스를 해제할 때 사용

✔︎ Around

  • 메서드 호출 전후에 수행
    • 조인 포인트 실행 여부 선택, 반환값 변환, 예외 변환 등이 가능
  • 메서드 실행 전&후, 예외 발생 시점에 공통 기능 실행
  • 가장 강력한 어드바이스
    • 조인 포인트 실행 여부 선택 - joinPoint.proceed()
    • 전달 값 변환 - joinPoint.proceed(arg[])
    • 반환 값 변환
    • 예외 변환
    • try ~ catch ~ finally가 들어가는 구문 처리 가능
  • 첫 번째 파라미터로 ProceedingJoinPoint 사용
  • proceed()를 통해 대상 실행
  • proceed()를 통해 여러 번 실행 가능

✔︎ 추가 설명

  • @Around만 있어도 모든 기능 수행 가능
    • 가장 강력한 어드바이스, 대부분의 기능을 제공하지만 타겟 등 고려해야할 사항이 있을 때 정상적으로 작동 안될 수 있음
  • @Before, @After와 같은 어드바이스는 기능은 적으나, 원하는대로 작동되고 코드도 단순
  • 각 애너테이션을 통해 타겟 실행 전 어떤 일을 하는지 명확히 알 수 있음
  • 좋은 설계는 @Around만 사용해 모두 해결하는 것보다, 제약을 가지더라도 실수를 미연에 방지하는 것
  • 제약을 두면 문제 자체 발생 ❌, 역할 명확
  1. Pointcut 표현식

⦿ 학습내용

☞ 포인트컷 표현식

✔︎ 포인트컷은 관심 조인 포인트를 결정하므로, 어드바이스가 실행되는 시기를 제어할 수 있음
✔︎ AspectJ는 포인트컷을 편리하게 표현하기 위한 특별한 표현식 제공
ex) @Pointcut("execution(*hello.aop.order..*(..))")
✔︎ 예제

@Pointcut("execution(* transfer(..))")	// 포인트컷 표현식
private void anyOldTransfer() {}		// 포인트컷 서명
- 포인트컷 표현식은 AspectJ pointcut expression ⇒ AspectJ가 제공하는 포인트컷 표현식을 줄여 표현하는 것

☞ 포인트컷 지시자

✔︎ 포인트컷 표현식은 execution같은 포인트컷 지시자(PointCut Designator, PCD)로 시작
✔︎ 포인트컷 지시자 종류

  • execution을 가장 많이 사용, 나머지 자주 사용 ❌

☞ 포인트컷 표현식 결합

✔︎ 포인트컷 표현식은 &&, ||, !를 사용하여 결합 가능
✔︎ 이름으로 pointcut 표현식 참조 가능
✔︎ 예제

@Pointcut("execution(public * * (..))")
private void anyPublicOperation() {}	// (1)

@Pointcut("within(com.xyz.myapp.trading..*)")
private void inTrading() {}				// (2)

@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOpreation() {}		// (3)
(1) `anyPublicOpreation`은 메서드 실행 조인 포인트가 공용 메서드 실행을 나타내는 경우 일치
(2) `inTrading`은 메서드 실행이 거래 모듈에 있는 경우 일치
(3) `tradingOperation`은 메서드 실행이 거래 모듈의 공개 메서드를 나타내는 경우 일치

✔︎ 일반적인 pointcut 표현식

  • 모든 공개 메서드 실행
    • execution(public * *(..))
  • set 다음 이름으로 시작하는 모든 메서드 실행
    • execution(* set*(..))
  • AccountService 인터페이스에 의해 정의된 모든 메서드의 실행
    • execution(* com.xyz.service.AccountService.*(..))
  • service 패키지에 정의된 메서드 실행
    • execution(* com.xyz.service.*.*(..))
  • 서비스 패키지 또는 해당 하위 패키지 중 하나에 정의된 메서드 실행
    • execution(* com.xyz.service..*.*(..))
  • 서비스 패키지 내의 모든 조인포인트 (Spring AOP에서만 메서드 실행)
    • within(com.xyz.service.*)
  • 서비스 패키지 또는 하위 패키지 중 하나 내의 모든 조인포인트 (Spring AOP에서만 메서드 실행)
    • within(com.xyz.service..*)
  • AccountService 프록시가 인터페이스를 구현하는 모든 조인포인트 (Spring AOP에서만 메서드 실행)
    • this(com.xyz.service.AccountService)
  • AccountService 대상 객체가 인터페이스를 구현하는 모든 조인포인트 (Spring AOP에서만 메서드 실행)
    • target(com.xyz.service.AccountService)
  • 단일 매개변수를 사용하고 런타임에 전달된 인수가 Serializable과 같은 모든 조인포인트 (Spring AOP에서만 메서드 실행)
    • args(java.io.Serializable)
  • 대상 객체에 @Transactional 애너테이션이 있는 모든 조인포인트 (Spring AOP에서만 메서드 실행)
    • @target(org.springframework.transaction.annotation.Transactional)
  • 실행 메서드에 @Transactional 애너테이션이 있는 모든 조인포인트 (Spring AOP에서만 메서드 실행)
    • @annotation(org.springframework.transaction.annotation.Transactional)
  • 단일 매개변수를 사용하고 전달된 인수의 런타임 유형이 @Classified 애너테이션을 갖는 조인포인트 (Spring AOP에서만 메서드 실행)
    • @args(com.xyz.security.Classfied)
  • tradeService라는 이름을 가진 스프링 빈의 모든 조인포인트 (Spring AOP에서만 메서드 실행)
    • bean(tradeService)
  • 와일드 표현식 *Service라는 이름을 가진 스프링 빈의 모든 조인포인트
    • bean(*Service)
  1. JoinPoint

⦿ 학습내용

☞ AOP 적용 위치

✔︎ 메서드 실행 위치 뿐만 아니라, 다양한 위치에 적용 가능
✔︎ 적용 가능 지점 (조인포인트) : 생성자, 필드값 접근, static 메서드 접근, 메서드 실행
✔︎ AOP를 수행하는 메서드는 이 JoinPoint 인스턴스를 인자로 받음
✔︎ JoinPoint 인스턴스에서 조인포인트 지점의 정보를 얻어야 함

☞ JoinPoint

✔︎ 조인포인트는 추상적인 개념, AOP를 적용할 수 있는 지점을 의미

  • 어드바이스가 적용될 수 있는 위치, 메소드 실행, 생성자 호출, 필드 값 접근, static 메서드 접근 같은 프로그램 실행 중 지점을 나타냄
  • AspectJ를 사용해서 컴파일 시점과 클래스 로딩 시점에 적용하는 AOP는 바이트코드를 실제로 조작하기 때문에 해당 기능을 모든 지점에 적용 가능
  • 프록시 방식을 사용하는 스프링 AOP는 메서드 실행 지점에만 AOP 적용 가능
  • 프록시는 메서드 오버라이딩 개념으로 동작
  • 생성자나 static 메서드, 필드 값 접근에는 프록시 개념 적용 ❌
  • 프록시를 사용하는 스프링 AOP는 스프링 컨테이너가 관리할 수 있는 스프링 빈에만 AOP 적용 가능
  • JoinPoint 메서드는 어드바이스 종류에 따라 사용 방법이 다소 다르지만, 기본적으로 어드바이스 메소드에 매개변수로 선언하면 됨

☞ JoinPoint 인터페이스 주요 기능

✔︎ JoinPoint.getArgs() : JoinPoint에 전달된 인자를 배열로 반환
✔︎ JoinPoint.getThis() : AOP 프록시 객체를 반환
✔︎ JoinPoint.getTarget() : AOP가 적용된 대상 객체를 반환

  • 클라이언트가 호출한 비즈니스 메소드를 포함하는 비즈니스 객체를 반환

✔︎ JoinPoint.getSignature() : 조언되는 메서드에 대한 설명을 반환

  • 클라이언트가 호출한 메소드의 시그니처(리턴타입, 이름, 매개변수) 정보가 저장된 Signature 객체를 반환
  • Signature : 객체가 선언하는 모든 연산을 연산의 이름, 매개변수로 받아들이는 객체
  • Signature가 제공하는 메서드
    • String getName() : 클라이언트가 호출한 메소드의 이름을 반환
    • String toLongString() : 클라이언트가 호출한 메소드의 시그니처(리턴타입, 이름, 매개변수)를 패키지 경로까지 포함해서 반환
    • String toShortString() : 클라이언트가 호출한 메소드 시그니처를 축약한 문자열로 반환

✔︎ JoinPoint.toString() : 조언되는 방법에 대한 유용한 설명을 인쇄

☞ ProceedingJoinPoint 인터페이스 주요 기능

✔︎ proceed() : 다음 어드바이스나 타겟을 호출

4.애너테이션(Annotation)을 이용한 AOP

⦿ 학습내용

☞ @AspectJ

✔︎ @AspectJ는 애너테이션이 있는 일반 Java 클래스로 관점을 선언하는 스타일
✔︎ AOP 런타임은 여전히 순수한 스프링 AOP이며, AspectJ 컴파일러나 위버에 의존 ❌
✔︎ Spring 설정에서 @AspectJ aspect를 사용하기 위해서는 @AspectJ aspect에 기반한 Spring AOP 설정과 이러한 aspect에 의해 조언되는 자동 프록시 빈에 대한 Spring 빈을 활성화해야함
✔︎ @AspectJ 지원은 XML 또는 Java 스타일 설정으로 활성화 가능

  • Java 설정으로 @AspectJ 지원 활성화 방법
    • @Configuration으로 @Aspect 지원을 활성화하려면 @EnableAspectJAutoProxy 애너테이션 추가
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}
  • XML 설정으로 @AspectJ 지원 활성화 방법
    • XML 기반 구성으로 @AspectJ 지원을 활성화하려면 aop:aspectj-autoproxy 요소 사용
<aop:aspectj-autoproxy/>

☞ Aspect 선언

✔︎ @AspectJ 지원이 활성화되면 @AspectJ 관점(@Aspect)이 있는 클래스로 애플리케이션 컨텍스트에 정의된 모든 빈이 Spring에서 자동으로 감지되고 Spring AOP를 구성하는데 사용

<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
  <!-- configure properties of the aspect here -->
</bean>
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class NotVeryUserfulAspect {

}

☞ Pointcut 선언

✔︎ 포인트컷은 관심 조인포인트를 결정하므로 어드바이스가 실행되는 시기 제어 가능
✔︎ Spring AOP는 Spring Bean에 대한 메소드 실행 조인포인트만 지원하므로, Pointcut은 Spring Bean의 메소드 실행과 일치하는 것으로 생각할 수 있음
✔︎ pointcut 선언은 이름과 매개변수를 포함하는 서명과 우리가 관심있는 메소드 실행을 정확히 결정하는 pointcut 표현식의 두 부분으로 구성
✔︎ pointcut 표현식은 @Pointcut 어노테이션을 사용하여 표시

☞ Advice 선언

✔︎ 어드바이스는 포인트컷 표현식과 연관되며, 포인트컷과 일치하는 메서드 실행 전후 또는 전후에 실행
✔︎ pointcut 표현식은 명명된 pointcut에 대한 단순 참조이거나 제자리에 선언된 pointcut 표현식일 수도 있음

◉ 느낀 점

☞ 어제 DI보다 수월하다 말한 것 같은데 취소다... 너무 생소한 용어들과 이론들이기에 머릿 속이 막 복잡하다 🤯 그렇지만 크루님께서는 결코 어려운 개념이 아니라고 하신다 ,, (처음이라 생소한 것 뿐..) 쫄지말고 실습과 추가 정보들을 찾아보면서 좀 더 공부해보자...!!!

TMI ) 요즘 식사를 하면서 미생이라는 드라마를 보는데 동기부여를 많이 받는 것 같다.
오늘도 고생했고 내일도 화이팅이다 :)

인생은 끝없는 반복.
반복에 지치지 않는 자가 성취한다.                   드라마 '미생' 중에서 ...

◉ 내일의 키워드

・ 기술면접 (With Pair)
・ 미니 잡서칭
・ Section_2 회고
profile
백엔드 개발자 김창하입니다 🙇‍♂️

0개의 댓글