目录
3.创建一个ApplicationContextHolder:
6.1 应用场景: 将系统中相关的多种单据信息放入缓存中,并实现添加至缓存,从缓存中移除部门和应用启动时预加载数据到缓存中 等操作
6.2 新建一个抽象类:BaseCacheLoaderOnStartup.java 作用:应用启动时预加载数据到缓存中
6.3 新建一个类:CacheInitializeListener.java 作用:缓存初始化监听
6.4 新建一个类继承上面的抽象类:BillInfoCacheLoader.java
6.5 新建一个单据信息表Service接口类:IBillInfoService.java
6.6 新建一个单据信息表Service实现类:BillInfoServiceImpl.java 此处只写了在这个类中的相关使用方法
1.Ehcache简介
EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。Ehcache是一种广泛使用的开 源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,一个gzip缓存servlet过滤器,支持REST和SOAP api等特点。
1.1优点
1. 快速
2. 简单
3. 多种缓存策略
4. 缓存数据有两级:内存和磁盘,因此无需担心容量问题
5. 缓存数据会在虚拟机重启的过程中写入磁盘
6. 可以通过RMI、可插入API等方式进行分布式缓存
7. 具有缓存和缓存管理器的侦听接口
8. 支持多缓存管理器实例,以及一个实例的多个缓存区域
9. 提供Hibernate的缓存实现
1.2缺点
1. 使用磁盘Cache的时候非常占用磁盘空间:这是因为DiskCache的算法简单,该算法简单也导致Cache的效率非常高。它只是对元素直接追加存储。因此搜索元素的时候非常的快。如果使用DiskCache的,在很频繁的应用中,很快磁盘会满。
2. 不能保证数据的安全:当突然kill掉java的时候,可能会产生冲突,EhCache的解决方法是如果文件冲突了,则重建cache。这对于Cache 数据需要保存的时候可能不利。当然,Cache只是简单的加速,而不能保证数据的安全。如果想保证数据的存储安全,可以使用Bekeley DB Java Edition版本。这是个嵌入式数据库。可以确保存储安全和空间的利用率。
1.3其他
EhCache的分布式缓存有传统的RMI,1.5版的JGroups,1.6版的JMS。分布式缓存主要解决集群环境中不同的服务器间的数据的同步问题。
使用Spring的AOP进行整合,可以灵活的对方法的返回结果对象进行缓存。
2.详细实例讲解
本实例的环境 vue+springBoot+myBatis+maven
2.1相关依赖pom.xml
<!-- ehcache缓存 -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
2.2添加ehcache配置文件ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="ehcache.xsd"
updateCheck="false" monitoring="autodetect"
dynamicConfig="true">
<!-- 指定一个文件目录,当EhCache把数据写到硬盘上时,将把数据写到这个文件目录下 -->
<!-- <diskStore path="java.io.tmpdir"/> -->
<diskStore path="user.dir/log/tomcat"/> <!-- 修改ehcache缓存路径 user.dir表示项目根目录 -->
<cacheManagerEventListenerFactory class="" properties=""/>
<!-- 设定缓存的默认数据过期策略 -->
<!--
Default Cache configuration. These settings will be applied to caches
created programmatically using CacheManager.add(String cacheName).
This element is optional, and using CacheManager.add(String cacheName) when
its not present will throw CacheException
The defaultCache has an implicit name "default" which is a reserved cache name.
默认的缓存策略
maxEntriesLocalHeap:堆内存中最大缓存对象数,0没有限制
eternal:对象是否永久有效
timeToIdleSeconds:设当缓存闲置 n 秒后销毁
timeToLiveSeconds:当缓存存活 n 秒后销毁
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
maxEntriesLocalDisk:磁盘中的最大对象数,默认为0不限制
diskExpiryThreadIntervalSeconds:磁盘中对象检测线程运行时间间隔。标识对象状态的线程多长时间运行一次。
memoryStoreEvictionPolicy:缓存策略.默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)
-->
<defaultCache
maxEntriesLocalHeap="10000"
maxEntriesLocalDisk="10000000"
eternal="false"
diskSpoolBufferSizeMB="30"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap"/>
<cacheEventListenerFactory
class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"
properties="replicateAsynchronously=true,
replicatePuts=true,
replicateUpdates=true,
replicateUpdatesViaCopy=true,
replicateRemovals=true"/>
<bootstrapCacheLoaderFactory
class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory"/>
</defaultCache>
<!--
Sample caches. Following are some example caches. Remove these before use.
-->
<!--
Sample cache named sampleCache1
This cache contains a maximum in memory of 10000 elements, and will expire
an element if it is idle for more than 5 minutes and lives for more than
10 minutes.
If there are more than 10000 elements it will overflow to the
disk cache, which in this configuration will go to wherever java.io.tmp is
defined on your system. On a standard Linux system this will be /tmp"
-->
<cache name="Cache_FMP"
maxEntriesLocalHeap="10000"
maxEntriesLocalDisk="10000000"
eternal="false"
overflowToDisk="true"
diskSpoolBufferSizeMB="1024"
timeToIdleSeconds="86400"
timeToLiveSeconds="86400"
memoryStoreEvictionPolicy="LRU"
transactionalMode="off">
<cacheEventListenerFactory
class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"
properties="replicateAsynchronously=true,
replicatePuts=true,
replicateUpdates=true,
replicateUpdatesViaCopy=true,
replicateRemovals=true"/>
<bootstrapCacheLoaderFactory
class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory"/>
</cache>
</ehcache>
相关属性介绍:
cache元素的属性:
- name:缓存名称
- maxElementsInMemory:内存中最大缓存对象数
- maxElementsOnDisk:硬盘中最大缓存对象数,若是0表示无穷大
- eternal:true表示对象永不过期,此时会忽略timeToIdleSeconds和timeToLiveSeconds属性,默认为false
- overflowToDisk:true表示当内存缓存的对象数目达到了
- maxElementsInMemory界限后,会把溢出的对象写到硬盘缓存中。注意:如果缓存的对象要写入到硬盘中的话,则该对象必须实现了Serializable接口才行。
- diskSpoolBufferSizeMB:磁盘缓存区大小,默认为30MB。每个Cache都应该有自己的一个缓存区。
- diskPersistent:是否缓存虚拟机重启期数据,是否持久化磁盘缓存,当这个属性的值为true时,系统在初始化时会在磁盘中查找文件名 为cache名称,后缀名为index的文件,这个文件中存放了已经持久化在磁盘中的cache的index,找到后会把cache加载到内存,要想把 cache真正持久化到磁盘,写程序时注意执行net.sf.ehcache.Cache.put(Element element)后要调用flush()方法。
- diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认为120秒
- timeToIdleSeconds: 设定允许对象处于空闲状态的最长时间,以秒为单位。当对象自从最近一次被访问后,如果处于空闲状态的时间超过了timeToIdleSeconds属性 值,这个对象就会过期,EHCache将把它从缓存中清空。只有当eternal属性为false,该属性才有效。如果该属性值为0,则表示对象可以无限 期地处于空闲状态
- timeToLiveSeconds:设定对象允许存在于缓存中的最长时间,以秒为单位。当对象自从被存放到缓存中后,如果处于缓存中的时间超过了 timeToLiveSeconds属性值,这个对象就会过期,EHCache将把它从缓存中清除。只有当eternal属性为false,该属性才有 效。如果该属性值为0,则表示对象可以无限期地存在于缓存中。timeToLiveSeconds必须大于timeToIdleSeconds属性,才有 意义
- memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
3.创建一个ApplicationContextHolder:
一般情况下,使用SpringMVC/SpringBoot的时候,各种bean注册到Spring容器里了,然后在需要这个bean的地方,使用@Autowired或者@Resource标注的bean都可以被自动注入。 但是在某些场景下,需要手动注入。比如在一个Util里面,这个Util里面的方法都是static的,这个时候,如果需要获取Spring容器中的某个bean,或者获取到ApplicationContext, 这个时候,就需要一个ApplicationContext Holder的东西,这里命名为AppContextHolder其实Spring里有一个接口就是为了这个应用场景而生的ApplicationContextAware, 源码如下:
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* Spring应用上下文帮助类
*
* @author L
*
*/
@Component
public class ApplicationContextHolder implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ApplicationContextHolder.applicationContext = applicationContext;
}
/**
* 根据bean类型获取bean实例
*
* @param beanClass
* bean的类型
* @return
* @throws BeansException
*/
public static <T> T getBean(Class<T> beanClass) throws BeansException {
return applicationContext.getBean(beanClass);
}
/**
* 根据bean的名称获取bean实例
*
* @param beanId
* bean的名称
* @return
* @throws BeansException
*/
public static Object getBean(String beanId) throws BeansException {
if (beanId == null || beanId.trim().length() < 1) {
return null;
}
return applicationContext.getBean(beanId);
}
/**
* 根据bean类型(可以是接口类型),获取所有的这种类型的bean的名称及其对应的实例
*
* @param beanClass
* bean的类型
* @return
*/
public static <T> Map<String, T> getBeanTypes(Class<T> beanClass) {
Map<String, T> map = applicationContext.getBeansOfType(beanClass);
return map;
}
}
4.创建一个CacheUtils.java 工具类
import java.io.Serializable;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.Cache.ValueWrapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
import com.stream.core.context.ApplicationContextHolder;
import com.stream.core.util.StringUtil;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class CacheUtils {
/**
* 缓存区名称,这里与<code>ehcache.xml</code>配置文件中配置的缓存区名称一致
*/
private static final String CACHE_NAME = "Cache_FMP";
/**
* 从缓存区<code>CACHE_NAME</code>中获取缓存数据
*
* @param cacheKey
* 缓存键
* @return
*/
public static Object getCache(Serializable cacheKey) {
return getCache(CACHE_NAME, cacheKey);
}
/**
* 缓存数据到缓存区<code>CACHE_NAME</code>中
*
* @param cacheKey
* 缓存键
* @param cacheValue
* 缓存数据
* @return
*/
public static boolean putCache(Serializable cacheKey, Serializable cacheValue) {
return putCache(CACHE_NAME, cacheKey, cacheValue);
}
/**
* 从缓存区<code>CACHE_NAME</code>中移除缓存
*
* @param cacheKey
* 缓存键
* @return
*/
public static boolean evictCache(Serializable cacheKey) {
return evictCache(CACHE_NAME, cacheKey);
}
/**
* 从缓存区中根据键获取缓存数据
*
* @param cacheName
* 缓存区名称
* @param cacheKey
* 缓存键
* @return
*/
public static Object getCache(String cacheName, Serializable cacheKey) {
Cache cache = getCache(cacheName);
if (cache != null) {
ValueWrapper valueWrapper = cache.get(cacheKey);
return valueWrapper != null ? valueWrapper.get() : null;
}
return null;
}
/**
* 缓存数据到缓存区中
*
* @param cacheName
* 缓存区名称
* @param cacheKey
* 缓存键
* @param cacheValue
* 缓存数据
* @return
*/
public static boolean putCache(String cacheName, Serializable cacheKey, Serializable cacheValue) {
if(StringUtil.isEmpty(cacheName)) {
cacheName = CACHE_NAME;
}
Cache cache = getCache(cacheName);
if (cache != null) {
cache.put(cacheKey, cacheValue);
return true;
}
return false;
}
/**
* 从缓存区中移除缓存
*
* @param cacheName
* 缓存区名称
* @param cacheKey
* 缓存键
* @return
*/
public static boolean evictCache(String cacheName, Serializable cacheKey) {
Cache cache = getCache(cacheName);
if (cache != null) {
cache.evict(cacheKey);
return true;
}
return false;
}
private static Cache getCache(String cacheName) {
CacheManager cacheManager = ApplicationContextHolder.getBean(CacheManager.class);
Cache retCache = cacheManager.getCache(cacheName);
if(retCache==null) {
EhCacheManagerFactoryBean factoryBean = ApplicationContextHolder.getBean(EhCacheManagerFactoryBean.class);
factoryBean.getObject().addCache(cacheName);
retCache = cacheManager.getCache(cacheName);
}
return retCache;
}
}
5.新建一个缓存参数类:SysConstant.java
注:如果缓存项名称为大写,缓存项名可能会乱码,可以将缓存项名称改为小写
package com.stream.common.constants;
public class SysConstant {
/** ******************** 缓存参数 begin **************************************************************************************************** **/
public static final String CACHE_BILL_INFO = "Cache_BILL_INFO"; // BILL_INFO 单据缓存
/* ********** 部门 ********** */
public static final String CACHE_DEPT = "Cache_DEPT"; // DEPT 部门缓存
/* ********** 岗位 ********** */
public static final String CACHE_POST = "Cache_POST"; // POST 岗位缓存
/* ********** 人员 ********** */
public static final String CACHE_EMP = "Cache_EMP"; // EMP 人员缓存
/** ******************** 缓存参数 end **************************************************************************************************** **/
}
6.缓存的使用
6.1 应用场景: 将系统中相关的多种单据信息放入缓存中,并实现添加至缓存,从缓存中移除部门和应用启动时预加载数据到缓存中 等操作
6.2 新建一个抽象类:BaseCacheLoaderOnStartup.java 作用:应用启动时预加载数据到缓存中
package com.stream.core.cache;
import java.io.Serializable;
import java.util.Map;
import org.apache.commons.collections.MapUtils;
/**
* 应用启动时缓冲加载器
*
* @author L
*
*/
public abstract class BaseCacheLoaderOnStartup {
/**
* 应用启动时预加载数据到缓存中
*/
protected void loadCacheOnStartup() {
Map<String, Serializable> dataMap = this.getData();
if (MapUtils.isEmpty(dataMap)) {
return;
}
String cacheName = this.getCacheName(); // 获得缓存名称
for(Map.Entry<String, Serializable> entry : dataMap.entrySet()) {
CacheUtils.putCache(cacheName, entry.getKey(), entry.getValue());
}
}
/**
* 子类重写该方法,表示将要缓存起来的数据<br>
* 这里需要注意的是:重写了该方法的子类需要将自己的实例交给spring容器进行管理,如:
* <pre> {@code
*
@Component
public class MyCacheLoader extends BaseCacheLoaderOnStartup {
@Override
protected Map<String, Serializable> getData() {
Map<String, Serializable> map = new HashMap<>();
map.put("$CACHE_1001$", "郭靖");
map.put("$CACHE_1002$", "杨过");
map.put("$CACHE_1003$", "乔峰");
return map;
}
}
*
* }</pre>
*
* @return
*/
protected abstract Map<String, Serializable> getData();
/**
* 自定义缓存名称
* @return
*/
protected abstract String getCacheName();
}
6.3 新建一个类:CacheInitializeListener.java 作用:缓存初始化监听
package com.stream.core.cache;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.collections.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import com.stream.core.context.ApplicationContextHolder;
/**
* 缓存初始化监听
*
* @author L
*
*/
@Component
public class CacheInitializeListener implements ApplicationListener<ContextRefreshedEvent> {
private static final Logger logger = LoggerFactory.getLogger(CacheInitializeListener.class);
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (event.getApplicationContext().getParent() == null) {
logger.info("====== CacheInitializer onApplicationEvent ContextRefreshedEvent, initialize caches ========");
Map<String, BaseCacheLoaderOnStartup> cacheLoaders = ApplicationContextHolder.getBeanTypes(BaseCacheLoaderOnStartup.class);
logger.info("cacheLoaders: {}", cacheLoaders);
if (MapUtils.isEmpty(cacheLoaders)) {
return;
}
Iterator<Entry<String, BaseCacheLoaderOnStartup>> iter = cacheLoaders.entrySet().iterator();
while (iter.hasNext()) {
Entry<String, BaseCacheLoaderOnStartup> entry = iter.next();
BaseCacheLoaderOnStartup cacheLoader = entry.getValue();
cacheLoader.loadCacheOnStartup();
}
}
}
}
6.4 新建一个类继承上面的抽象类:BillInfoCacheLoader.java
package com.stream.bill.cache;
import java.io.Serializable;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.stream.bill.service.IBillInfoService;
import com.stream.common.constants.SysConstant;
import com.stream.core.cache.BaseCacheLoaderOnStartup;
@Component
public class BillInfoCacheLoader extends BaseCacheLoaderOnStartup {
@Autowired IBillInfoService billInfoService;
@Override
protected Map<String, Serializable> getData() {
return billInfoService.getCacheMap();
}
/**
* 缓存名称
*/
@Override
protected String getCacheName() {
return SysConstant.CACHE_BILL_INFO;
}
}
6.5 新建一个单据信息表Service接口类:IBillInfoService.java
package com.stream.bill.service;
import java.io.Serializable;
import java.util.Map;
/**
* 单据信息表Service接口类
* @author CodeAutoGenerator
*
*/
public interface IBillInfoService {
/**
* 获得缓存集合
* @return
*/
public Map<String, Serializable> getCacheMap();
}
6.6 新建一个单据信息表Service实现类:BillInfoServiceImpl.java 此处只写了在这个类中的相关使用方法
/** ******************** 缓存方法 begin **************************************************************************************************** **/
/**
* 获得缓存集合
* @return
*/
@Override
public Map<String, Serializable> getCacheMap() {
/** 待返回数据 **/
Map<String, Serializable> retMap = new HashMap<String, Serializable>();
/** 查询所有单据数据 **/
List<TBillInfo> billInfoList = billInfoMapper.selectByMap(new HashMap<String, Object>());
/** 封装缓存数据 **/
for(int i=0; billInfoList!=null && billInfoList.size()>0 && i<billInfoList.size(); i++) {
TBillInfo billInfo = billInfoList.get(i);
retMap.put(billInfo.getId(), billInfo);
}
/** 返回成功数据 **/
return retMap;
}
/**
* 添加至缓存
* @param billInfo
*/
private void addToCache(TBillInfo billInfo) {
if(billInfo!=null) {
CacheUtils.putCache(SysConstant.CACHE_BILL_INFO, billInfo.getId(), billInfo);
}
}
/**
* 从缓存中移除单据
* @param billInfoId 单据
*/
private void removeFromCache(Serializable billInfoId) {
CacheUtils.evictCache(SysConstant.CACHE_BILL_INFO, billInfoId);
}
/** ******************** 缓存方法 end **************************************************************************************************** **/
参考资料: