SpringBoot配置Ehcache缓存

一、Ehcache介绍

EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认CacheProvider。Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,一个gzip缓存servlet过滤器,支持REST和SOAP API等特点。

主要的特性有:

  1. 快速

  2. 简单

  3. 多种缓存策略

  4. 缓存数据有两级:内存和磁盘,因此无需担心容量问题

  5. 缓存数据会在虚拟机重启的过程中写入磁盘

  6. 可以通过RMI、可插入API等方式进行分布式缓存

  7. 具有缓存和缓存管理器的侦听接口

  8. 支持多缓存管理器实例,以及一个实例的多个缓存区域

  9. 提供Hibernate的缓存实现

二、Spring缓存抽象

Spring从3.1开始定义了org.springframework.cache.Cache 和 org.springframework.cache.CacheManager 接口来统一不同的缓存技术;

  • Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;

  • Cache接口下spring提供了各种xxxCache的实现,比如EhCacheCache、RedisCache等等

  • 每次调用需要缓存功能的方法时,Spring会检查指定参数的指定目标方法是否已经被调用过;如果有缓存就直接从缓存中获取结果,没有就调用方法并缓存结果后返回给用户。下次调用则直接从缓存中获取。

1、缓存注解概念

Cache缓存接口,定义缓存操作。实现有:EhCacheCache、RedisCache等等
CacheManager 缓存管理器,管理各种缓存组件
@Cacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
@CacheEvict 清空缓存
@CachePut 保证方法被调用,又希望结果被缓存
@EnableCaching 开启基于注解的缓存

 

三、SpringBoot 添加 EhCache缓存

1、pom.xml 添加依赖

<!--ehcache 缓存-->
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>2.10.2</version>
</dependency><dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

2、resources目录下创建ehcache.xml 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
    <!-- 磁盘缓存位置 -->
    <diskStore path="E:\data"/><!-- 默认缓存 -->
    <defaultCache
            maxEntriesLocalHeap="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            maxEntriesLocalDisk="10000000"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
        <persistence strategy="localTempSwap"/>
    </defaultCache>
    
    <!-- cache 可以设置多个,例如localCache、UserCache和HelloWorldCache -->
    <cache name="localCache"
           eternal="true"
           maxElementsInMemory="100"
           maxElementsOnDisk="1000"
           overflowToDisk="true"
           diskPersistent="true"
           timeToIdleSeconds="0"
           timeToLiveSeconds="0"
           memoryStoreEvictionPolicy="LRU"/><cache name="UserCache"
           maxElementsInMemory="1000"
           eternal="false"
           timeToIdleSeconds="10"
           timeToLiveSeconds="10"
           overflowToDisk="false"
           memoryStoreEvictionPolicy="LRU"/><!-- hello world缓存 -->
    <cache name="HelloWorldCache"
           maxElementsInMemory="1000"
           eternal="false"
           timeToIdleSeconds="5"
           timeToLiveSeconds="5"
           overflowToDisk="false"
           memoryStoreEvictionPolicy="LRU"/>
    <!-- memoryStoreEvictionPolicy Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)-->
    <!-- 缓存配置
     name:缓存名称。
     maxElementsInMemory:缓存最大个数。
     eternal:对象是否永久有效,一但设置了,timeout将不起作用。
     timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
     timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
     overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。 diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
     maxElementsOnDisk:硬盘最大缓存个数。
     diskPersistent:是否缓存虚拟机重启期数据 Whether the disk
     store persists between restarts of the Virtual Machine. The default value is false.
     diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
     memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
     clearOnFlush:内存数量最大时是否清除。
     --></ehcache>

3、创建测试方法测试CacheManager

@Test
void cacheManagerTest() {
    // 1. 创建缓存管理器
    CacheManager cacheManager = CacheManager.create(this.getClass().getResourceAsStream("/ehcache.xml"));
​
    // 2. 获取ehcache.xml 中定义的 HelloWorldCache 缓存对象
    Cache cache = cacheManager.getCache("HelloWorldCache");
​
    // 3. 创建元素
    Element element = new Element("key1", "value1");
​
    // 4. 将元素添加到缓存
    cache.put(element);
​
    // 5. 获取缓存
    Element value = cache.get("key1");
    System.out.println(value);
    System.out.println(value.getObjectKey());
    System.out.println(value.getObjectValue());
​
    // 6. 删除元素
    cache.remove("key1");
    System.out.println(cache.getSize());
​
    // 7. 刷新缓存
    cache.flush();
​
    // 8. 关闭缓存管理器
    cacheManager.shutdown();
}

运行结果如下:

[ key = key1, value=value1, version=1, hitCount=1, CreationTime = 1630287141405, LastAccessTime = 1630287141406 ]
key1
value1
0

四、EhCache封装

1、封装CacheManagerHelper

package com.example.ehcachedemo.helper;
​
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
​
/*
 * Ehcache 缓存管理对象单例
 * 磁盘 + 内存
 * */
public class CacheManagerHelper {
    private final String EHCAHE_PATH = "/ehcache.xml";
    private final String CACHE_NAME = "localCache";
    private CacheManager manager;
    private Cache cache;
​
    private CacheManagerHelper() {
        init();
    }
​
    private static class SingletonInstance {
        private static final CacheManagerHelper singleton = new CacheManagerHelper();
    }
​
    public static CacheManagerHelper getInstance() {
        return SingletonInstance.singleton;
    }
​
    /**
     * 每次开始使用缓存对象需要初始化
     */
    public void init() {
        manager = CacheManager.create(this.getClass().getResourceAsStream(EHCAHE_PATH));
        cache = manager.getCache(CACHE_NAME);
    }
​
    /**
     * 把key放入缓存中
     */
    public void put(String key, Object value) {
        cache.put(new Element(key, value));
        flush();
    }
​
    /**
     * 根据key获取缓存元素
     */
    public Object get(String key) {
        Element element = cache.get(key);
        return element != null ? element.getObjectValue() : null;
    }
​
    /**
     * 根据key移除缓存
     */
    public void remove(String key) {
        cache.remove(key);
        flush();
    }
​
    /**
     * 构建内存与磁盘的关系
     */
    public void flush() {
        cache.flush();
    }
​
    /**
     * 关闭缓存管理器
     */
    public void shutdown() {
        manager.shutdown();
    }
}

2、新建User类和UserController测试CacheManagerHelper

User:

package com.example.ehcachedemo.bean;
​
public class User {
​
    public User() {
​
    }
​
    public User(String userId, String name, int age) {
        this.userId = userId;
        this.name = name;
        this.age = age;
    }
​
    private String userId;
    private String name;
    private int age;
​
    //region getter and setter
    public String getUserId() {
        return userId;
    }
​
    public void setUserId(String userId) {
        this.userId = userId;
    }
​
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
​
    public int getAge() {
        return age;
    }
​
    public void setAge(int age) {
        this.age = age;
    }
    //endregion
​
    @Override
    public String toString() {
​
        return "User{" +
                "userId='" + userId + '\'' +
                ", name='" + name + '\'' +
                ", age=" + this.age +
                '}';
    }
}

UserController

package com.example.ehcachedemo.controller;
​
import com.example.ehcachedemo.bean.User;
import com.example.ehcachedemo.helper.CacheManagerHelper;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
​
@RestController
@RequestMapping(value = "/user")
public class UserController {
​
    /**
     * 通过CacheManagerHelper获取缓存信息
     * @param id 访问id
     * */
    @GetMapping("byId/{id}")
    public String getUserById(@PathVariable String id) {
        String result = (String) CacheManagerHelper.getInstance().get(id);
        if (result == null) {
            User user = new User(id, "张三", (int) (Math.random() * 100));
            result = user.toString();
            CacheManagerHelper.getInstance().put(id, result);
        }
        return result;
    }
}

3、封装EhCacheConfiguration类,用于注解方式使用Ehcache

package com.example.ehcachedemo.configuration;
​
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
​
@Configuration
@EnableCaching
public class EhCacheConfiguration {
    @Bean
    public EhCacheManagerFactoryBean cacheManagerFactoryBean() {
        EhCacheManagerFactoryBean bean = new EhCacheManagerFactoryBean();
        bean.setConfigLocation(new ClassPathResource("ehcache.xml"));
        bean.setShared(true);
        return bean;
    }
​
    @Bean
    public EhCacheCacheManager ehCacheCacheManager(EhCacheManagerFactoryBean bean) {
        return new EhCacheCacheManager(bean.getObject());
    }
}

Application启动类需要加入  @EnableCaching 

4、UserController 注解方式使用EhCache

    /**
     * 通过注解方式,如果缓存存在,则从缓存获取数据,否则从方法体获取,并更新到缓存中
     * @param id 访问id
     * */
    @GetMapping("ByIdWithInject/{id}")
    @Cacheable(value = "localCache", key = "#id")
    public String getUserByIdWithInject(@PathVariable String id) {
        User user = new User(id, "张三", (int) (Math.random() * 100));
        return user.toString();
    }

5、@Cacheable、@CachePut、@CacheEvict的使用

在Service使用@Cacheable、@CachePut、@CacheEvict

package com.example.ehcachedemo.service;
​
import com.example.ehcachedemo.bean.User;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
​
import java.util.Date;
​
@Service
@Component
public class UserServiceImpl implements UserService {
​
    /**
     * @param userId
     * @return
     * @Cacheable: 1.方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取;
     * 2.去Cache中查找缓存的内容,使用一个key,默认就是方法的参数;
     * 3.没有查到缓存就调用目标方法
     * 4.将目标方法返回的结果,放进缓存中
     * 5.condition:指定符合条件的情况下才缓存;
     */
    @Cacheable(value = "UserCache", key = "#userId", condition = "#userId!=''")
    @Override
    public User findById(String userId) {
        System.out.println(new Date().getTime() + "进入UserService.findById,当前userId为:" + userId);
        return new User(userId, "张三", (int) (Math.random() * 100));
    }
​
    /**
     * @param user
     * @return
     * @CachePut:既调用方法,又更新缓存数据;同步更新缓存 运行时机:
     * 1、先调用目标方法
     * 2、将目标方法的结果缓存起来
     */
    @CachePut(value = "UserCache", key = "#user.userId")
    @Override
    public User updateUser(User user) {
        System.out.println(new Date().getTime() + "进入UserService.updateUser,当前userId为:" + userId);
        return new User(userId, "张三", (int) (Math.random() * 100));
    }
​
    /**
     * @param userId
     * @CacheEvict:缓存清除 key:指定要清除的数据
     * beforeInvocation = true:代表清除缓存操作是在方法运行之前执行,无论方法是否出现异常,缓存都清除
     */
    @CacheEvict(value = "UserCache", key = "#userId", beforeInvocation = true)
    @Override
    public void deleteUser(String userId) {
        System.out.println(new Date().getTime() + "进入UserService.deleteUser,当前userId为:" + userId);
    }
}

测试代码

    @Autowired
    UserService userService;
​
    @Test
    void userTest() throws InterruptedException {
        // 1.新增
        User user1 = new User("123", "张三", (int) (Math.random() * 100));
        userService.updateUser(user1);
        System.out.println("初始:" + user1);
​
        // 2.查询
        user1 = userService.findById("123");
        System.out.println("查询:" + user1);
​
        // 3.清除
        userService.deleteUser("123");
        System.out.println("已清除缓存");
​
        // 4.查询
        user1 = userService.findById("123");
        System.out.println("查询:" + user1);
​
        System.out.println("休眠10秒后重新查询");
        Thread.sleep(10 * 1000);  // 休眠10秒,测试是否过期重新获取
        user1 = userService.findById("123");
        System.out.println(user1);
    }

测试结果

1630291225178进入UserService.updateUser,当前userId为:123
初始:User{userId='123', name='张三', age=82}
查询:User{userId='123', name='张三', age=82}
1630291225232进入UserService.deleteUser,当前userId为:123
已清除缓存
1630291225232进入UserService.findById,当前userId为:123
查询:User{userId='123', name='张三', age=21}
休眠10秒后重新查询
1630291235234进入UserService.findById,当前userId为:123
User{userId='123', name='张三', age=13}

五、参考文档

https://blog.csdn.net/huang_wei_cai/article/details/105293166

https://www.jianshu.com/p/154c82073b07

posted @ 2021-08-30 14:13  为之守望  阅读(2498)  评论(0编辑  收藏  举报