利用 @RequestScope 进行 HTTP 请求级别的缓存

2021-08-28  本文已影响0人  蓝笔头

(1)定义一个注解,用来标识需要缓存方法。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
@Documented
public @interface CachedInRequest {
}

(2)定义一个缓存 key,用作被缓存的方法的唯一标识。

@EqualsAndHashCode
public class InvocationTarget {
    private final Class targetClass;  // 缓存方法所在的类
    private final String targetMethod; // 缓存方法的方法名
    private final Object[] args;       // 调用方法时传入的参数

    public InvocationTarget(ProceedingJoinPoint joinPoint) {
        this.targetClass = joinPoint.getSignature().getDeclaringType();
        this.targetMethod = joinPoint.getSignature().getName();
        this.args = joinPoint.getArgs();
    }

    @Override
    public String toString() {
        return String.format("%s.%s(%s)", targetClass.getName(), targetMethod, Arrays.toString(args));
    }
}

(3)新增一个缓存管理类,用来维护缓存内容。

/**
 * @RequestScope
 *      将单个 bean 定义的范围限定为单个 HTTP 请求的生命周期。
 *      也就是说,每个 HTTP 请求拥有一个被创建的 bean 实例。
 */
@Component
@RequestScope
public class RequestCacheManager {
    private final Map<InvocationTarget, Object> cache = new ConcurrentHashMap<>();

    public Optional<Object> get(InvocationTarget invocationContext) {
        return Optional.ofNullable(cache.get(invocationContext));
    }

    public void put(InvocationTarget methodInvocation, Object result) {
        cache.put(methodInvocation, result);
    }
}

(4)通过 AOP 切面拦截需要缓存的方法,对其进行缓存功能增强。

@Aspect
@Component
@RequiredArgsConstructor
public class CacheInRequestAspect {
    private final RequestCacheManager requestCacheManager;

    @Around("@annotation(CachedInRequest)")
    public Object requestCache(ProceedingJoinPoint joinPoint) throws Throwable {
        // 1. 从 ProceedingJoinPoint 中获取需要缓存方法的 key 对象
        InvocationTarget invocationTarget = new InvocationTarget(joinPoint);

        // 2. 如果缓存中已经存在结果,则直接返回,不需要调用目标方法
        Optional<Object> cachedResult = requestCacheManager.get(invocationTarget);
        if (cachedResult.isPresent()) {
            return cachedResult.get();
        }

        // 3. 缓存中不存在结果,则调用目标方法
        Object methodResult = joinPoint.proceed();
        // 4. 把方法调用结果存入到缓存中
        requestCacheManager.put(invocationTarget, methodResult);
        return methodResult;
    }
}

(5)功能测试

@Service
@Slf4j
public class CacheDemoService {

    @SneakyThrows
    public int query() {
        // 模拟业务逻辑
        Thread.sleep(1 * 1000);
        return 1;
    }

    @CachedInRequest
    public int queryCache() {
        return query();
    }
}
@RestController
@Slf4j
@RequiredArgsConstructor
public class DemoController {
    private final CacheDemoService cacheDemoService;

    @GetMapping("/query")
    public String query(@RequestParam(value = "count", defaultValue = "5") int count) {
        log.info("query 请求开始");
        for (int i = 0; i < count; ++ i) {
            log.info("cacheDemoService.query(): {}", cacheDemoService.query());
        }
        return "done";
    }

    @GetMapping("/queryCache")
    public String queryCache(@RequestParam(value = "count", defaultValue = "5") int count) {
        log.info("queryCache 请求开始");
        for (int i = 0; i < count; ++ i) {
            log.info("cacheDemoService.queryCache(): {}", cacheDemoService.queryCache());
        }
        return "done";
    }
}

调用 http://localhost:8080/query 接口,控制台输出:

2021-08-27 13:00:59.516  INFO 22452 --- [nio-8080-exec-2] com.example.demo.DemoController          : query 请求开始
2021-08-27 13:01:00.600  INFO 22452 --- [nio-8080-exec-2] com.example.demo.DemoController          : cacheDemoService.query(): 1
2021-08-27 13:01:01.613  INFO 22452 --- [nio-8080-exec-2] com.example.demo.DemoController          : cacheDemoService.query(): 1
2021-08-27 13:01:02.624  INFO 22452 --- [nio-8080-exec-2] com.example.demo.DemoController          : cacheDemoService.query(): 1
2021-08-27 13:01:03.629  INFO 22452 --- [nio-8080-exec-2] com.example.demo.DemoController          : cacheDemoService.query(): 1
2021-08-27 13:01:04.641  INFO 22452 --- [nio-8080-exec-2] com.example.demo.DemoController          : cacheDemoService.query(): 1

每隔一秒打印一次日志,说明没有被缓存。

调用 http://localhost:8080/queryCache 接口,控制台输出:

2021-08-27 13:01:18.311  INFO 22452 --- [nio-8080-exec-3] com.example.demo.DemoController          : queryCache 请求开始
2021-08-27 13:01:19.336  INFO 22452 --- [nio-8080-exec-3] com.example.demo.DemoController          : cacheDemoService.queryCache(): 1
2021-08-27 13:01:19.337  INFO 22452 --- [nio-8080-exec-3] com.example.demo.DemoController          : cacheDemoService.queryCache(): 1
2021-08-27 13:01:19.337  INFO 22452 --- [nio-8080-exec-3] com.example.demo.DemoController          : cacheDemoService.queryCache(): 1
2021-08-27 13:01:19.337  INFO 22452 --- [nio-8080-exec-3] com.example.demo.DemoController          : cacheDemoService.queryCache(): 1
2021-08-27 13:01:19.337  INFO 22452 --- [nio-8080-exec-3] com.example.demo.DemoController          : cacheDemoService.queryCache(): 1

第一次方法执行耗时 1s,后续日志间隔时间非常短,说明后续的接口都是走的缓存。

参考

上一篇 下一篇

猜你喜欢

热点阅读