[Spring] AOP - 실전편

yeonjoo913·2023년 7월 14일
0

Spring

목록 보기
10/19

지난 글에서 AOP 이론에 대해 공부해보았고 오늘은 코드를 보면서 사용법을 공부해보고자 한다.

의존성

implementation 'org.springframework.boot:spring-boot-starter-aop'

사용 방법

  1. Target 선언
    @Inherited : 자식 클래스에서 부모클래스에 선언된 어노테이션을 상속받을 수 있다.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
@Documented
public @interface Cacheable {

    @AliasFor("value") String name() default "";
    @AliasFor("name") String value() default "";
    String key() default "";
    long expireSecond() default 36000L;
    boolean hasClassAndMethodNamePrefix() default false;
    boolean onlyPublic() default false;
}
  1. Aspect 선언
    @Aspect 어노테이션을 붙이고 @Component를 붙여서 빈으로 등록한다.
    @Around는 타켓 메서드를 감싸서 특정 @Advice를 실행한다는 의미이다.

  2. PointCut 선언
    Aespect를 선언했으니 이제 어떤 JoinPoint에서 Advice를 실행시킬 것인지 PointCut을 선언해야한다.
    PointCut의 기본 형식은 execution(접근제어자 리턴타입 클래스.메소드(파라미터)) 이다.
    하지만 난 특정 메소드에서만 사용하도록 설정할 것이다.(적용 방식 3번)

  • joinPoint.proceed() : 타켓 메소드 실행
  • joinPoint.getSignature() / signature.getMethod() : 타켓 메소드 가져오기
  • cacheable.name() : @Cacheable 어노테이션의 value값 (ex) TEST:ITEM:getItem)
  • joinPoint.getArgs() : @Cacheable 어노테이션의 key 값
@Component
@Aspect
@RequiredArgsConstructor
public class CacheAspect {
    private final RedisTemplate<String, Object> redisTemplate;

    @Around("@annotation(Cacheable)")
    public Object cacheableProcess(ProceedingJoinPoint joinPoint) throws Throwable {

        Cacheable cacheable = getCacheable(joinPoint);
        final String cacheKey = generateKey(cacheable.name(), joinPoint);

        Object[] parameterValues = joinPoint.getArgs();

        if (Boolean.TRUE.equals(redisTemplate.hasKey(cacheKey)) && !confirmBypass() && (!cacheable.onlyPublic())) {
            return redisTemplate.opsForValue().get(cacheKey);
        }

        final Object methodReturnValue = joinPoint.proceed();
        final long cacheTTL = cacheable.expireSecond();
        if (cacheTTL < 0) {
            redisTemplate.opsForValue().set(cacheKey, methodReturnValue);
        } else {
            redisTemplate.opsForValue().set(cacheKey, methodReturnValue, cacheTTL, TimeUnit.SECONDS);
        }

        return methodReturnValue;
    }


		private Cacheable getCacheable(ProceedingJoinPoint joinPoint) {
				
        final MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        final Method method = signature.getMethod();

        return AnnotationUtils.getAnnotation(method, Cacheable.class);
    }

    private String generateKey(String name, ProceedingJoinPoint joinPoint) {

        String generatedKey = StringUtils.arrayToCommaDelimitedString(joinPoint.getArgs());

        return String.format("%s:%s", name, generatedKey);
    }
}

간단하게 위와 같은 방식으로 AOP를 설정해서 사용 할 수 있다.
@Advice 적용방식에는 여러 가지가 있는데 좀 더 자세히 보고자 한다.

@Advice 적용방식

  1. 빈의 모든 메소드에 적용하는 방식
@Component
@Aspect
public class Aspect {

@Around("bean(simpleItemService)")
public Object process(ProceedingJoinPoint pjp) throws Throwable{}
@Component
public class SimpleItemService implements EventService {}
  1. 경로 지정 방식
    ItemService 객체의 모든 메소드에 Aspect를 적용하겠다는 의미이다.
@Component
@Aspect
public class Aspect {

@Around("execution(* com.test..*.ItemService.*(..))")
public Object process(ProceedingJoinPoint pjp) throws Throwable{
	}
}
  1. 어노테이션이 붙은 포인트에서 실행하는 방식
    위에서 보인 코드 예시 또한 이 3번 방식을 사용하였다.
    특정 컨트롤러에 어노테이션을 붙여 해당 포인트에서만 Aspect가 적용되도록 설정할 수 있다.
@Component
@Aspect
public class Aspect {

@Around("@annotation(Cacheable)")
public Object process(ProceedingJoinPoint pjp) throws Throwable{
	}
}
@GetMapping
@Cacheable(storeId = "#itemId")
public ResponseEntity<CommonResponse<Object>> getCouponList(
        @PathVariable("item-id") Long itemId) {
				...
}

이와 같은 방식으로 AOP를 원하는 곳에 적용할 수 있다.
AOP는 아직 어렵다... 좀 더 공부해 보아야겠다.

profile
주니어 백엔드 개발자. 까먹는다 기록하자!

0개의 댓글