授课语音

手写自定义缓存注解,灵活处理缓存问题

缓存是一种常见的优化技术,广泛应用于提升系统性能,减少对数据库的访问压力。在 Java 中,借助注解,我们可以灵活地实现缓存的功能。通过自定义缓存注解,我们不仅能简化缓存操作,还能根据需求灵活地控制缓存策略。本文将介绍如何手写自定义缓存注解,并在 Java 中应用该注解来处理缓存问题。


1. 缓存的基本概念

缓存(Cache)是一种临时存储介质,用于存放经常访问的数据,从而提高数据读取的效率。缓存的常见应用场景包括:

  • 数据库查询缓存:缓存数据库查询结果,减少数据库访问频率。
  • API 调用缓存:缓存 API 返回的数据,避免重复请求相同的数据。
  • 计算结果缓存:缓存计算过程的中间结果,避免重复计算。

缓存的实现方式有很多,常见的有:

  • 内存缓存(如 Redis、EhCache 等)
  • 本地缓存(如 Guava Cache)

2. 自定义缓存注解的优势

在 Java 开发中,使用注解来管理缓存具有以下优点:

  1. 简洁性:通过注解,缓存逻辑的实现可以与业务逻辑分离,代码更简洁。
  2. 灵活性:可以根据不同需求(如缓存失效时间、缓存更新策略等)自定义注解,实现灵活的缓存管理。
  3. 可维护性:自定义缓存注解可以统一管理缓存逻辑,方便后期的修改与优化。

3. 自定义缓存注解的实现

3.1 创建缓存注解

首先,我们需要定义一个自定义的缓存注解,该注解可以用于方法级别的缓存控制。注解包含以下信息:

  • value:缓存的键(可使用表达式生成唯一的缓存键)。
  • expire:缓存的过期时间(单位:秒)。
  • clear:是否在每次方法调用后清除缓存。
package com.example.cache;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 定义一个自定义的缓存注解
@Target(ElementType.METHOD)  // 该注解可以作用于方法
@Retention(RetentionPolicy.RUNTIME)  // 运行时保留,方便反射读取
public @interface Cacheable {
    String value() default "";  // 缓存的键
    int expire() default 3600;  // 缓存过期时间(默认1小时)
    boolean clear() default false;  // 是否清除缓存
}

3.2 实现缓存管理器

接下来,我们需要创建一个缓存管理器类,负责根据注解的信息进行缓存操作。为了简单起见,我们这里使用 Map 作为缓存存储。

package com.example.cache;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class CacheManager {

    // 使用 ConcurrentHashMap 来存储缓存,保证线程安全
    private static final Map<String, CacheItem> cache = new ConcurrentHashMap<>();

    // 缓存数据项
    static class CacheItem {
        Object value;
        long timestamp;  // 缓存的时间戳
    }

    // 获取缓存
    public static Object getCache(String key) {
        CacheItem item = cache.get(key);
        if (item != null && System.currentTimeMillis() - item.timestamp < 3600 * 1000) {
            return item.value;  // 如果缓存未过期,返回缓存数据
        }
        return null;  // 如果缓存已过期或不存在
    }

    // 设置缓存
    public static void setCache(String key, Object value, int expire) {
        CacheItem item = new CacheItem();
        item.value = value;
        item.timestamp = System.currentTimeMillis();
        cache.put(key, item);  // 将数据放入缓存
    }

    // 清除缓存
    public static void clearCache(String key) {
        cache.remove(key);  // 删除指定缓存
    }
}

3.3 创建缓存切面(Aspect)

为了实现缓存注解的自动处理,我们使用 AOP(面向切面编程)来拦截标注了 @Cacheable 注解的方法,在方法执行前后进行缓存操作。

package com.example.cache;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class CacheAspect {

    // 定义切点,匹配所有被 @Cacheable 注解标记的方法
    @Pointcut("@annotation(com.example.cache.Cacheable)")
    public void cacheableMethods() {}

    // 在方法执行之前进行缓存查询
    @Around("cacheableMethods() && @annotation(cacheable)")
    public Object handleCache(ProceedingJoinPoint joinPoint, Cacheable cacheable) throws Throwable {
        String cacheKey = cacheable.value();  // 获取缓存的键
        Object cachedValue = CacheManager.getCache(cacheKey);  // 尝试从缓存中获取数据

        if (cachedValue != null) {
            return cachedValue;  // 如果缓存命中,直接返回缓存数据
        }

        // 如果缓存未命中,执行原方法并缓存结果
        Object result = joinPoint.proceed();
        CacheManager.setCache(cacheKey, result, cacheable.expire());  // 将结果存入缓存
        return result;
    }

    // 在方法执行后进行缓存清除(如果需要)
    @After("cacheableMethods() && @annotation(cacheable)")
    public void handleCacheClear(Cacheable cacheable) {
        if (cacheable.clear()) {
            String cacheKey = cacheable.value();  // 获取缓存键
            CacheManager.clearCache(cacheKey);  // 清除缓存
        }
    }
}

3.4 配置 Spring AOP

为了让自定义的切面生效,需要在 Spring 配置中启用 AOP 支持。

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan(basePackages = "com.example.cache")
@EnableAspectJAutoProxy  // 启用 Spring AOP
public class AppConfig {
}

4. 使用自定义缓存注解

在业务逻辑中,我们可以使用自定义的 @Cacheable 注解来标记需要缓存的方法。

package com.example.service;

import com.example.cache.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    // 使用自定义缓存注解
    @Cacheable(value = "user_1", expire = 3600, clear = true)
    public String getUserInfo(int userId) {
        // 模拟从数据库获取用户信息
        return "User info for userId: " + userId;
    }
}

4.1 业务逻辑

  • 上述示例中,getUserInfo 方法使用了 @Cacheable 注解,表示该方法的结果会被缓存,并且缓存的键是 user_1
  • 如果该方法被多次调用,缓存的结果会被重复使用,避免每次都查询数据库。
  • clear = true 表示方法执行后会清除缓存(可以根据需要灵活配置)。

5. 总结

通过自定义注解和 AOP,Java 开发者可以轻松地实现灵活的缓存管理。使用缓存注解,不仅可以简化代码结构,还能根据不同的业务需求调整缓存策略。自定义缓存注解结合 AOP 的应用,使得缓存的实现变得更加模块化,符合面向切面编程的思想,从而提高了系统的可维护性和扩展性。

去1:1私密咨询

系列课程: