지난 글에서 AOP 이론에 대해 공부해보았고 오늘은 코드를 보면서 사용법을 공부해보고자 한다.
implementation 'org.springframework.boot:spring-boot-starter-aop'
@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;
}
Aspect 선언
@Aspect 어노테이션을 붙이고 @Component를 붙여서 빈으로 등록한다.
@Around는 타켓 메서드를 감싸서 특정 @Advice를 실행한다는 의미이다.
PointCut 선언
Aespect를 선언했으니 이제 어떤 JoinPoint에서 Advice를 실행시킬 것인지 PointCut을 선언해야한다.
PointCut의 기본 형식은 execution(접근제어자 리턴타입 클래스.메소드(파라미터)) 이다.
하지만 난 특정 메소드에서만 사용하도록 설정할 것이다.(적용 방식 3번)
@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 적용방식에는 여러 가지가 있는데 좀 더 자세히 보고자 한다.
@Component
@Aspect
public class Aspect {
@Around("bean(simpleItemService)")
public Object process(ProceedingJoinPoint pjp) throws Throwable{}
@Component
public class SimpleItemService implements EventService {}
@Component
@Aspect
public class Aspect {
@Around("execution(* com.test..*.ItemService.*(..))")
public Object process(ProceedingJoinPoint pjp) throws Throwable{
}
}
@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는 아직 어렵다... 좀 더 공부해 보아야겠다.