首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Spring AOP 實現自定義緩存註解 @MyCacheable 完整教程

     編輯:Java 相關知識

前言

Spring 3.1+ 引入抽象緩存(@Cacheable 等),底層基於 AOP 切面攔截。本文從零手寫一個 @MyCacheable 註解及其處理切面,深度理解緩存鍵生成、切面編織、緩存存儲全流程。

1. 定義自定義註解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyCacheable {
    // 可擴展:value 指定緩存名稱、key 自定義 SpEL 表達式等
}

2. 切面實現:攔截、生成 Key、查緩存、存緩存

@Aspect
public class CacheAspect {
    private final Map<String, Object> cache = new HashMap<>(); // 生產環境換 Redis/Caffeine
    private final Logger log = LoggerFactory.getLogger(CacheAspect.class);

    @Pointcut("@annotation(MyCacheable)")
    public void cacheableMethods() {}

    @Around("cacheableMethods()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        // 1. 生成緩存 Key:類名.方法名(參數類型=值;...)
        StringBuilder key = new StringBuilder();
        key.append(pjp.getTarget().getClass().getName())
           .append(".").append(pjp.getSignature().getName())
           .append("(");
        for (Object arg : pjp.getArgs()) {
            key.append(arg.getClass().getSimpleName())
               .append("=").append(arg).append(";");
        }
        key.append(")");
        String cacheKey = key.toString();

        // 2. 查緩存
        Object cached = cache.get(cacheKey);
        if (cached != null) {
            log.debug("Cache hit: {}", cacheKey);
            return cached;
        }

        // 3. 執行目標方法
        log.debug("Cache miss, executing: {}", cacheKey);
        Object result = pjp.proceed();

        // 4. 存入緩存
        cache.put(cacheKey, result);
        log.info("Cached result: {} -> {}", cacheKey, result);
        return result;
    }
}

Key 生成規則示例

// Calculator.sum(1, 2) → caching.springaop.Calculator.sum(Integer=1;Integer=2;)

3. 業務類使用註解

public class Calculator {
    private static final Logger log = LoggerFactory.getLogger(Calculator.class);

    @MyCacheable
    public int sum(int a, int b) {
        log.info("Calculating {} + {}", a, b);
        try { Thread.sleep(3000); } catch (InterruptedException e) { /* ignore */ }
        return a + b;
    }
}

4. Spring XML 配置啟用 AOP

<beans xmlns="https://www.springframework.org/schema/beans"
       xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="https://www.springframework.org/schema/aop"
       xsi:schemaLocation="...">
    <aop:aspectj-autoproxy />
    <bean class="caching.springaop.CacheAspect" />
    <bean id="calc" class="caching.springaop.Calculator" />
</beans>

5. 測試驗證

public class App {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config.xml");
        Calculator calc = ctx.getBean("calc", Calculator.class);

        log.info("1 + 2 = {}", calc.sum(1, 2)); // 執行 3 秒,存入緩存
        log.info("1 + 2 = {}", calc.sum(1, 2)); // 瞬時返回,命中緩存
    }
}

日誌輸出:

Calculating 1 + 2
1 + 2 = 3
1 + 2 = 3  // 無 Calculating 日誌,直接返回緩存值

生產環境擴展點

維度建議替換
緩存存儲HashMap → Redis(分佈式)、Caffeine(本地高性能)
Key 生成硬編碼 → SpEL 表達式(@Cacheable(key="#root.methodName"))
過期策略無 → TTL、LRU、@CacheEvict 主動清理
序列化默認 → JSON/Protobuf 跨語言

總結

手寫 @MyCacheable 切面揭示了 Spring 緩存抽象的核心機制:註解標記 → 切面攔截 → Key 生成 → 緩存查/存 → 返回目標結果。理解此流程,有助於排查緩存不生效、Key 衝突、序列化失敗等生產問題。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved