[스프링 AOP]_03 @AOP

3 minute read

스프링 AOP: @AOP

애노테이션 기반의 스프링 @AOP

의존성 추가

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

애스팩트 정의

● @Aspect
● 빈으로 등록해야 하니까 (컴포넌트 스캔을 사용한다면) @Component도 추가.

포인트컷 정의

● @Pointcut(표현식)
● 주요 표현식
		○ execution
		○ @annotation
		○ bean
● 포인트컷 조합
		○ &&, ||, !

어드바이스 정의

● @Before
● @AfterReturning
● @AfterThrowing
● @Around

참고 * https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aoppointcuts


애노테이션 기반의 스프링 @AOP

1. 의존성 추가

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2. 애스팩트 정의

  • @Aspect
  • 빈으로 등록해야 하니까 (컴포넌트 스캔을 사용한다면) @Component도 추가.
@Component
@Aspect
public class PerAspect { ... }

두가지 정보가 필요하다. Advice(해야할 일), Pointcut(해야할일을 적용할 곳)

3. 어드바이스 정의

  • ProceedingJoinPoint : Advice가 적용되는 대상 - SimpleEventService.createEvent(), SimpleEventService.publishEvent()

  • proceed() : 메서드를 실행하는 것 (java reflection api)

    메서드가 실행되면서 에러가 발생할 수 있으니 throws Throwable 추가

타겟에 해당하는 메서드를 호출하고, 그 결과값을 리턴

단순히 메서드 수행 시간만 측정해주는 코드를 추가

public Object logPerf(ProceedingJoinPoint pjp) throws Throwable {
  long begin = System.currentTimeMillis();
  Object retVal = pjp.proceed(); //메서드 실행
  System.out.println("수행시간 : " + (System.currentTimeMillis() - begin));
  return retVal;
}

포인트컷 정의 - (1) execution으로 포인트컷 정의

  • 정의한 Advice를 어떻게 적용할 것인지 어노테이션으로 지정
  • value값으로 포인트컷을 직접 주거나 포인트컷을 정의할 수 있다.

  • execution() : 포인트컷 표현식. 어디에 적용할지 정의할 수 있다.

  • 포인트 컷을 직접 적용

    포인트 컷을 다른 어드바이스에서 재사용하지 않을 것이라면 직접 적용해서 쓰면 된다.

    ex) dev.solar.demospring51 패키지 하위에 존재하는 EventService의 모든 메서드에 Advice를 적용

@Around("execution(* dev.solar..*.EventService.*(..))")
  • 포인트 컷을 지정하면 IDE에서 이 Advice가 어디에 적용되는지 보여준다.

    포인트컷지정

    적용되는 메서드로 가보면, 어떤 Advice가 적용되는지 쉽게 확인할 수 있다.

    포인트컷대상

  • @Around
    • 메서드를 감싸는 형태로 적용된다.
    • 그 메서드 호출 자체를 로직으로 감싸고 있기 때문에, 해당 메서드 호출 전/후에 로직을 처리할 수 있다.
    • 해당 메서드 호출 시 발생한 에러를 잡아서 에러 발생 시 특정 로직을 수행하게 할 수 있다.
  • @Before
    • 메서드가 실행되기 전에 적용

전체 Aspect 코드

@Component
@Aspect
public class PerAspect {

    @Around("execution(* dev.solar..*.EventService.*(..))")
    public Object logPerf(ProceedingJoinPoint pjp) throws Throwable {
        long begin = System.currentTimeMillis();
        Object retVal = pjp.proceed(); //메서드 실행
        System.out.println("수행시간 : " + (System.currentTimeMillis() - begin));
        return retVal;
    }
}

실행 결과

Created an event
수행시간 : 1015
Published an event
수행시간 : 2001
Deleted an event
수행시간 : 0

포인트컷 정의 - (2) @annotation 으로 포인트컷 정의

  • 적용대상을 쉽게 바뀔 수 있다.

ex) 이 패키지의 모든 클래스의 모든 메서드에 적용

@Around("execution(* dev.solar..*.*(..))")

EventService.deleteEvent() 메서드에는 적용하고 싶지 않다면?

execution보다 애노테이션으로 적용하면 편리하다.

애노테이션으로 적용하기 위해서는 약간의 코드 변경 필요

1. 애노테이션 생성

  • @Retention

★ 주의사항 ★

  • RetentionPolicy를 줄때, CLASS이상으로 지정해야 한다.
  • 기본값이 CLASS이므로 따로 지정하지 않고 사용해도 괜찮지만, 잘 모를 수 있기 때문에 명시적으로 써주는 것도 좋음
  • RetentionPolicy
    • 이 애노테이션 정보를 얼마나 유지할 것인가.
    • default 설정 - CLASS
    • CLASS
      • class 파일까지 유지하겠다.
      • 즉, 컴파일하여 나온 .class파일에도 이 애노테이션 정보가 남아있다는 의미
    • SOURCE
      • 컴파일하고 나면 사라진다.
    • RUNTIME
      • (지금 굳이 runtime까지 유지할 필요는 없음)
  • @Target : Pointcut 대상 정도는 알려줘도 좋다.

  • @Documented : Java Doc 문서를 만들 때 Doc이 되도록 어노테이션 추가해도 좋음
@Documented
@Target(ElementType.METHOD) //메서드 대상
@Retention(RetentionPolicy.CLASS)
public @interface PerLogging { }

2. 성능을 측정하고자 하는 메서드에 애노테이션 추가

기본적으로 이 애노테이션을 사용하면, 바이트코드까지 남아있게 된다.

@PerLogging // <-- 추가
@Override
public void createEvent() { ... }

@PerLogging // <-- 추가
@Override
public void publishEvent() { ... }

3. Aspect에 Pointcut 정의

  • @annotation()

ex ) PerLogging 애노테이션이 붙어있는 메서드를 대상으로 이 Advice를 적용해라

@Around("@annotation(PerLogging)")

실행 결과

⇒ createEvent()와 publishEvent()에는 적용되고, deleteEvent()에는 적용되지 않아서 수행시간이 출력되지 않는다.

Created an event
수행시간 : 1013
Published an event
수행시간 : 2004
Deleted an event

@annotation 방식 장점

  • execution은 포인트컷 조합 (&&, ||, !)을 할 수는 있지만 불편
    • execution 어드바이스를 2개 만들고, 중복되는 코드를 extract 메서드로 빼내는 방법
  • 대상이 여러 곳에 흩어질 때는 각 대상에 애노테이션을 붙이는 것이 편리하다.
  • IDE 툴의 지원을 받아서 어떤 Advice가 적용되는지 알 수 있지만, 툴의 지원을 못받는 경우 애노테이션을 통해서 Advice를 짐작할 수 있기 때문
/*
 * 이 애노테이션을 사용하면 성능을 로깅해줍니다.
 */
@Documented
@Target(ElementType.METHOD) //메서드 대상
@Retention(RetentionPolicy.CLASS)
public @interface PerLogging {
}

포인트컷 정의 - (3) bean

  • 지정한 bean이 가지고 있는 모든 public 메서드에 적용된다.
@Around("bean(simpleEventService)")
public Object logPerf(ProceedingJoinPoint pjp) throws Throwable { ... }
Created an event
수행시간 : 1013
Published an event
수행시간 : 2004
Deleted an event
수행시간 : 0
  • @Before

    메서드 실행 전에 hello 출력

@Before("bean(simpleEventService)")
public void hello() {
  System.out.println("hello");
}
hello
Created an event
수행시간 : 1014
hello
Published an event
수행시간 : 2003
hello
Deleted an event
수행시간 : 0