Spring MVC 中使用 JetCache

/ Java / 没有评论 / 1977浏览

Spring MVC 中使用 JetCache

JetCache 是一个基于 Java 的缓存系统封装,提供统一的API和注解来简化缓存的使用。 JetCache 提供了比 SpringCache 更加强大的注解,可以原生的支持 TTL、两级缓存、分布式自动刷新,还提供了 Cache 接口用于手工缓存操作。 当前有四个实现,RedisCacheTairCache (此部分未在 github 开源)、CaffeineCache (in memory) 和一个简易的LinkedHashMapCache (in memory),要添加新的实现也是非常简单的。

网上很多文章介绍 JetCache 的文章包括官方文档主要是基于 Spring Boot 的,也介绍了未使用 SpringBoot 的配置方式,但是估计很多同学还是不明白怎么在传统的 Spring MVC 的 Web 项目里使用 JetCache 吧,毕竟不是所有 Web 项目都使用 Spring Boot,接下来就一步一步的介绍使用的方法。

提示:

  • JetCache 需要 Java 8,并且指定 javac 的 -parameters 参数
  • JetCache 有 3 个重要的注解: @Cached, @CacheUpdate, @CacheInvalidate,详细文档请参考 MethodCache_CN
  • JetCache 不支持 Redis 的 Hash, List, Set 等,如果需要的话,使用 client 进行访问吧

一、Gradle 依赖

dependencies {
    compile group: 'com.alicp.jetcache', name: 'jetcache-anno', version: '2.5.13'
    compile group: 'com.alicp.jetcache', name: 'jetcache-redis', version: '2.5.13'
}

二、JVM 设置

JetCache 需要 Java 8,并且指定 javac 的 -parameters 参数,在 build.gradle 中设置 JVM 相关信息如下:

sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
[compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8'

compileJava {
    options.compilerArgs << '-Xlint:unchecked' << '-Xlint:deprecation' << '-parameters'
    options.forkOptions.jvmArgs << '-parameters'
}

三、JetCache 配置

package com.xtuer.config;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import com.alicp.jetcache.CacheBuilder;
import com.alicp.jetcache.anno.CacheConsts;
import com.alicp.jetcache.anno.config.EnableCreateCacheAnnotation;
import com.alicp.jetcache.anno.config.EnableMethodCache;
import com.alicp.jetcache.anno.support.GlobalCacheConfig;
import com.alicp.jetcache.anno.support.SpringConfigProvider;
import com.alicp.jetcache.embedded.EmbeddedCacheBuilder;
import com.alicp.jetcache.embedded.LinkedHashMapCacheBuilder;
import com.alicp.jetcache.redis.RedisCacheBuilder;
import com.alicp.jetcache.support.FastjsonKeyConvertor;
import com.alicp.jetcache.support.JavaValueDecoder;
import com.alicp.jetcache.support.JavaValueEncoder;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.util.Pool;

/**
 * JetCache 的配置
 */
@Configuration
@EnableMethodCache(basePackages = "com.xtuer.service")
@EnableCreateCacheAnnotation
public class JetCacheConfig {
    @Bean
    public Pool<Jedis> pool(){
        GenericObjectPoolConfig pc = new GenericObjectPoolConfig();
        pc.setMinIdle(2);
        pc.setMaxIdle(10);
        pc.setMaxTotal(10);
        return new JedisPool(pc, "localhost", 6379);
    }

    @Bean
    public SpringConfigProvider springConfigProvider() {
        return new SpringConfigProvider();
    }

    @Bean
    public GlobalCacheConfig config(SpringConfigProvider configProvider, Pool<Jedis> pool){
        Map<String, CacheBuilder> localBuilders = new HashMap<>();
        EmbeddedCacheBuilder localBuilder = LinkedHashMapCacheBuilder
            .createLinkedHashMapCacheBuilder()
            .keyConvertor(FastjsonKeyConvertor.INSTANCE);
        localBuilders.put(CacheConsts.DEFAULT_AREA, localBuilder);

        Map<String, CacheBuilder> remoteBuilders = new HashMap<>();
        RedisCacheBuilder remoteCacheBuilder = RedisCacheBuilder.createRedisCacheBuilder()
            .keyConvertor(FastjsonKeyConvertor.INSTANCE)
            .valueEncoder(JavaValueEncoder.INSTANCE)  // 可替换为 KryoValueEncoder.INSTANCE
            .valueDecoder(JavaValueDecoder.INSTANCE)  // 可替换为 KryoValueDecoder.INSTANCE
            .expireAfterWrite(3600, TimeUnit.SECONDS) // 全局 expire,@Cached 能够指定自己的 expire
            .jedisPool(pool);
        remoteBuilders.put(CacheConsts.DEFAULT_AREA, remoteCacheBuilder);

        GlobalCacheConfig globalCacheConfig = new GlobalCacheConfig();
        globalCacheConfig.setConfigProvider(configProvider);
        globalCacheConfig.setLocalCacheBuilders(localBuilders);
        globalCacheConfig.setRemoteCacheBuilders(remoteBuilders);
        globalCacheConfig.setStatIntervalMinutes(15);
        globalCacheConfig.setAreaInCacheName(false);

        return globalCacheConfig;
    }
}

提示: 使用 JavaValueEncoder 进行缓存的 Bean 需要实现 Serializable 接口,使用 KryoValueEncoder 进行缓存的 Bean 不需要实现 Serializable 接口,并且性能更好。

四、加载 JetCache 配置

在 Spring MVC 项目的 xml 配置文件中使用自动扫描包创建 JetCacheConfig 的对象或者手动创建,下面使用自动扫描的方式:

<context:component-scan base-package="com.xtuer.config"/>

五、Service 和 Controller

package com.xtuer.service;

import com.alicp.jetcache.anno.CacheInvalidate;
import com.alicp.jetcache.anno.Cached;
import org.springframework.stereotype.Service;

@Service
public class HelloService {
    @Cached(name = "user.", key = "#userId", expire = 600)
    public String getUsernameById(long userId) {
        System.out.println("Fetch username from DB");
        return "Bob";
    }

    @CacheInvalidate(name = "user.", key = "#userId")
    public void removeUsername(long userId) {
        System.out.println("Remove user from Redis");
    }
}
package com.xtuer.controller;

import com.xtuer.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class HelloController {
    @Autowired
    private HelloService helloService;

    /**
     * 网址: http://localhost:8080/api/cache
     */
    @GetMapping("/api/cache")
    @ResponseBody
    public String cache() {
        return helloService.getUsernameById(10);
    }

    /**
     * 网址: http://localhost:8080/api/cache-invalidate
     */
    @GetMapping("/api/cache-invalidate")
    @ResponseBody
    public String cacheInvalidate() {
        helloService.removeUsername(10);

        return "OK";
    }
}

六、缓存测试

上面就是所有需要的代码和设置,然后启动项目进行测试:

  1. 启动 Redis
  2. 启动 Web 项目
  3. 浏览器中输入 http://localhost:8080/api/cache,可以看到控制台输出 Get from db,Redis 中创建了缓存
  4. 再次刷新缓存,控制台不再输出,直接从 Redis 中读取
  5. 浏览器输入 http://localhost:8080/api/cache-invalidate,发现 Redis 中的缓存被删除了

七、补充说明

以下几点需要注意一下:

缓存信息统计:

[StatInfoLogger.java-accept:46] - jetcache stat from 2019-07-18 06:29:00,014 to 2019-07-18 06:30:00,003
cache|       qps|   rate|           get|           hit|          fail|        expire|avgLoadTime|maxLoadTime
-----+----------+-------+--------------+--------------+--------------+--------------+-----------+-----------
user.|      0.07| 75.00%|             4|             3|             0|             0|        1.0|          1
-----+----------+-------+--------------+--------------+--------------+--------------+-----------+-----------