Docs Java Spring

Spring AOP & Cache

AOP

AOP(Cross-Cutting)

핵심적인 기능에서 부가적인 기능을 분리한다. 분리한 부가기능을 Aspect라는 모듈 형태로 만들어서 설계하고 개발하는 방법

Aspect 구성

Aspect: 부가기능을 담고 있는 객체

Advise: 실질적인 부가기능을 담은 구현체, Aspect는 ‘무엇’을 ‘언제’ 할지를 정의

@Around, @Before, @After, @AfterReturning, @AfterThrowing PointCut: 부가 기능이 적용되어야 할 대상(메소드)을 선정하는 방법

execution(), @annotion, 등등 execution(리턴 타입 타겟이 되는 메소드 argument-매개변수) ex) execution(* com.dashboard.service.DashboardService(..)) JointPoint: Advise가 적용될 위치, controller에서 정의된 method들의 args(매개변수)를 담고 있는 객체

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;


@Aspect
public class UserAuthAspect {
    private static final Logger logger = LoggerFactory.getLogger(AccountAuthAspect.class);

    @Autowired
    private UserAuthService userAuthService;


    @Pointcut("execution(* com.demo.dashboard.service.DashboardService.getBoard(..))")
    public void dashBoardAccountAuth() {}

    @Pointcut("execution(* com.demo.billing.service.BillingService.*(..))")
    public void billingAccountAuth() {}

    @Before("dashBoardAccountAuth() || billingAccountAuth()")
    public void before(JoinPoint joinPoint) {
        Object[] obj = joinPoint.getArgs();
        CommonReqModel model  = (CommonReqModel) obj[0];
        boolean hasDefaultAuth = userAuthService.getAuthInfo(model);
        model.setHasDefaultAuth(hasDefaultAuth);
    }
}

Cache

Cache 추상화 이해하기

핵심부분에서 추상화는 Java method에 캐싱을 적용함으로써 캐시에 보관된 정보로 메서드의 실행 횟수를 줄여준다. 즉 대상 메서드가 실행될때마다 추상화가 해당 메서드가 같은 인자로 이미 실행되었는 확인하는 캐싱 동작을 적용한다. 해당 데이터가 존재한다면 메서드를 실행하지 않고 결과를 반환하고 존재하지 않는다면 메서드를 실행하고 그 결과를 캐싱한 뒤에 사용자에게 반환해서 다음번 호출시에 사용 할 수 있게 한다.

Spring cache는 cache 추상화를 지원하는데 EhCache, Redis, Couchbase 등 캐시 저장소와 빠르게 연동하여 bean으로 설정 할 수 있도록 도와준다.


캐싱 전략의 종류:

1. 애플리케이션 레벨 캐시:

@Cacheable 어노테이션을 사용하여 메서드의 결과를 캐시합니다. 이 방식은 반복적인 요청에 대해 동일한 결과를 반환하도록 하여 데이터베이스 쿼리 호출을 줄입니다.

@Cacheable(value = "userCache", key = "#userId")
public User getUserById(Long userId) {
    return userRepository.findById(userId);
}

2. Redis 캐시:

Redis는 빠른 읽기 성능을 제공하는 In-memory 데이터 저장소로, 자주 조회되는 데이터를 저장하기에 적합합니다. Spring Boot에서는 Spring Data Redis를 통해 Redis와의 연동이 가능합니다. Redis를 사용하면, 자주 조회되는 데이터가 메모리에 저장되어 빠르게 응답을 처리할 수 있습니다. 캐시 만료 기간(1시간)을 설정하여 데이터가 오래된 경우에는 자동으로 갱신될 수 있도록 합니다.


선언적인 어노테이션 기반의 캐싱

@Cacheable

메소드에 지정 가능하고 지정된 메서드의 캐시 설정에 따라 데이터가 한번 생성되면 데이터가 캐싱되며, 다음 호출 시에 캐시에 저장된 데이터가 리턴된다.
@Cacheable 설정 옵션은 다음과 같다.

  • value: 캐싱 공간의 대표 명칭
  • key: Spring Expression Language(SpEl)으로 key생성을 지정 (지정하지 않으면 모든 파라미터를 조합한 해시코드 값을 키로 생성)
  • condition: 조건부 캐싱. SpEL로 지정하고 표현식이 true면 메서드를 캐시

사용가능한 SpEL evaluation context:

이름위치설명예시
methodNameroot object호출되는 메서드의 이름#root.methodName
methodroot object호출되는 메서#root.method.name
targetroot object호출되는 대상 객체#root.target
targetClassroot object호출되는 대상 클래스#root.targetClass
argsroot object대상을 호출하는데 사용한 인자(배열)#root.args[0]
cachesroot object현재 실행된 메서드 캐시의 컬렉션#root.caches[0].name
argument nameevaluation context메서드 인자의 이름. 어떤 이유로든 이름을 사용할 수 없다면(예: 디버깅 정보가 없는 경우) a<#arg>에서 인자 이름을 사용할 수도 있고 #arg은 (0부터 시작하는) 인자의 인덱스를 의미한다.iban나 a0 (p0를 사용하거나 별칭으로 p<#arg> 형식을 사용할 수 있다)

유의해야 할 점: @Cacheable은 Spring AOP를 이용하기 때문에 동일한 클래스 내 @Cacheable이 설정된 메서드를 호출할 경우 캐싱된 결과를 가져오지 못하고 메서드를 재실행 한다. 이를 해결하기 위한 방법은 internal call이 아닌 Proxy Bean를 참조하여 캐싱된 데이터를 받아 오는 것이다.


캐시 무효화 전략:

@CacheEvict 어노테이션을 사용하여 캐시된 데이터를 삭제하거나 갱신할 수 있습니다. 예를 들어, 데이터를 수정하거나 삭제하는 API에서 해당 데이터의 캐시를 무효화하여 최신 데이터를 반영하도록 합니다.

캐시 갱신 전략:

@CachePut 어노테이션을 사용하여 메서드를 실행하고 캐시를 갱신할 수 있습니다. 예를 들어, 데이터가 변경될 때 캐시도 함께 업데이트하는 방식입니다.

캐싱 전략 적용 시 고려사항:

  • 캐시 만료 시간: 데이터를 언제 캐시에서 제거할지, 즉 캐시 만료 시간을 어떻게 설정할지 결정해야 합니다. 데이터 변경 주기나 중요도에 따라 설정을 다르게 해야 합니다.
  • 캐시 일관성: 데이터 변경 시 캐시가 일관성 있게 갱신되는지 확인해야 합니다. 이를 위해 @CacheEvict와 @CachePut을 적절히 사용하여 캐시를 무효화하고 갱신할 수 있습니다.

[참고] https://ifuwanna.tistory.com/202