前言
缓存(Caching)可以存储经常会用到的信息,在需要的时候,直接返回这些信息。Spring对缓存的支持有两种方式:1)注解驱动的缓存;2)XML声明的缓存;本文使用第二种方式来实际进行配置。
本文内容
- 缓存内容详解
- 配置Ehcache缓存
- 配置Mybatis Generator
缓存内容详解
使用 @EnableCaching
和<cache:annotation-driven/>
的工作方式是相同的,都会创建一个切面并触发Spring缓存注解的切点。根据所使用的注解以及缓存的状态,这个切面会从缓存中获取数据,将数据添加到缓存之中或者从缓存中移除某个值。在启用注解驱动的缓存时,还声明了一个缓存管理器的bean。缓存管理器是Spring缓存抽象的核心,能够与多个流行的缓存实现进行集成。
如何启用缓存
/**
* @Description 使用@EnableCaching启用注解驱动的缓存
* @Author Mr.wang
* @Date 2019/3/9
*/
@Configuration
@EnableCaching // 启用缓存
public class CachingConfig {
@Bean
public CacheManager cacheManager() { // 声明缓存管理器
return new ConcurrentMapCacheManager();
}
}
XML方式配置方式如下
<!--Ehcache配置-->
<!-- 启用缓存 -->
<cache:annotation-driven />
<!-- 声明缓存管理器 -->
<bean id="cacheManager" class="org.springframework.cache.concurrent.ConcurrentMapCacheManager" />
ConcurrentMapCacheManager 使用 java.util.concurrent.ConcurrentHashMap 作为缓存存储,这个缓存存储是基于内存的,因此其生命周期是与应用关联的,不太适合于生产级别的大型企业级应用程序,因此有其他的缓存管理器方案可供使用。
配置缓存管理器
Spring中内置了如下几种缓存管理器实现:
- SimpleCacheManager
- NoOpCacheManager
- ConcurrentMapCacheManager
- CompositeCacheManager
- EhCacheCacheManager
...
SpringData提供的缓存管理器:
- RedisCacheManager(来自于Spring Data Redis项目)
- GemfireCacheManager(来自于Spring Data GemFire项目)
import net.sf.ehcache.CacheManager;
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;
/**
* @Description 使用@EnableCaching启用注解驱动的缓存
* @Author Mr.wang
* @Date 2019/3/9
*/
@Configuration
@EnableCaching // 启用缓存
public class CachingConfig {
@Bean
public EhCacheCacheManager cacheManager(CacheManager cm) { // 配置EhCacheCacheManager
return new EhCacheCacheManager(cm);
}
@Bean
public EhCacheManagerFactoryBean ehcache() {
EhCacheManagerFactoryBean ehCacheFactoryBean = new EhCacheManagerFactoryBean();
ehCacheFactoryBean.setConfigLocation(new ClassPathResource(""));
return ehCacheFactoryBean;
}
}
也可以通过CompositeCacheManager使用多个缓存管理器。
一、为方法添加注解支持缓存
以下注解如果放在单独的方法上时,注解所描述的缓存行为只作用于这个方法上,如果放在类级别,那么缓存行为就会应用到这个类的所有方法上。
注解 | 描述 |
---|---|
@Cacheable | Spring在调用方法之前,首先应该在缓存中查找方法的返回值。如果这个值能够找到,就会返回缓存的值。否则,这个方法就会被调用,返回值就会放到缓存之中 |
@CachePut | Spring应该将方法的返回值放到缓存中,在方法的调用前并不会检查缓存,方法始终会被调用 |
@CacheEvict | Spring应该在缓存中清除一个或多个条目 |
@Caching | 一个分组的注解,能够同时应用多个其他的缓存注解 |
填充缓存
@Cacheable和@CachePut注解都可以填充缓存。
示例:
@Cacheable(value="spittleCache")
Spittle findOne(long id){
// ...
}
自定义缓存key
默认的缓存key要基于方法的参数来确定,参数是一个map,那么其缓存的key也是这个map,因此需要把默认的key改为map的key,而不是这个map,所以需要自定义缓存key。
Spring 提供了多个用来定义缓存规则的SpEL扩展
表达式 | 描述 |
---|---|
#root.args | 传递给缓存方法的参数,形式为数组 |
#root.caches | 该方法执行时所对应的缓存,形式为数组 |
#root.target | 目标对象 |
#root.targetClass | 目标对象的类,是#root.target.class的简写形式 |
#root.method | 缓存方法 |
#root.methodName | 缓存方法的名字,是#root.method.name的简写形式 |
#result | 方法调用的返回值(不能用在@Cacheable主街上) |
#Argument | 任意的方法参数名(如#argName)或参数索引(如#a0或#p0) |
@Cacheable(value="spittleCache" key="#result.id")
Spittle save(Spittle spittle);
条件化缓存
前面说的是通过为方法添加Spring的缓存注解,Spring就会围绕着这个方法创建一个缓存切面。条件化缓存是说将缓存功能关闭的一些场景。@Cacheable和@CachePut提供两个属性用以实现条件化缓存:unless和condition。如果unless属性的SpEL表达式计算结果为true,缓存方法返回的数据不会放到缓存中。如果condition属性的SpEL表达式计算结果为false,对于这个方法缓存就会被禁用掉。
unless属性只能阻止将对象放进缓存,但是在这个方法调用的时候,依然会去缓存中进行查找。如果condition的表达式计算结果为false,这个方法调用时,缓存是被禁用的,也就不会去缓存中查找,返回值也不会放进缓存中。
例:
// unless用法,对于属性中包含NoCache的Spittle对象,不对其进行缓存
@Cacheable(value="spittleCache" unless="#result.message.contains('NoCache')")
Spittle findOne(long id);
// condition用法,如果findOne方法调用时,参数值小于10,不会在缓存中进行查找,返回的数据也不会放进缓存中
@Cacheable(value="spittleCache"
unless="#result.message.contains('NoCache')"
condition="#id >= 10")
Spittle findOne(long id);
移除缓存条目
@CacheEvict不会往缓存中添加任何东西,且如果带有该注解的方法被调用的话,缓存中的一个或更多的条目会被移除。
@CacheEvict("spittleCache")
void remove(long spittleId);
注意:@Cacheable和@CacheEvict必须应用在返回值非void的方法上,@CacheEvict可以放在任意的方法上,甚至void方法
@Cacheable注解的属性,指定哪些缓存条目应该被移除掉
属性 | 类型 | 描述 |
---|---|---|
value | String[] | 要使用的缓存名称 |
key | String | SpEL表达式,用来计算自定义的缓存key |
condition | String | SpEL表达式,如果得到的值是false的话,缓存不会应用到方法调用上 |
allEntries | boolean | 如果为true的话,特定缓存的所有条目都会被移除掉 |
beforeInvoication | boolean | 如果为true的话,在方法调用之前移除条目,如果为false(默认值)的话,在方法成功调用之后再移除条目 |
二、使用XML声明缓存
元素 | 描述 |
---|---|
<cache:annotation-driven> | 启用注解驱动的缓存,等同于Java配置中的@EnableCaching |
<cache:advice> | 定义缓存通知。结合<aop:advisor>,将通知应用到切点上 |
<cache:caching> | 在缓存通知中定义缓存规则 |
<cache:cacheable> | 指明某个方法要进行缓存。等同于@Cacheable注解 |
<cache:cache-put> | 指明某个方法要填充缓存,但不会考虑缓存中时候已有匹配的值,等同于@CachePut注解 |
<cache:cache-evict> | 指明某个方法要从缓存中移除一项或多项,等同于@CacheEvict注解 |
填充缓存
<cache:cacheable>
<!--示例-->
<cache:cacheable cache="spittleCache" method="findRecent" />
自定义缓存key
<cache:cache-put cache="spittleCache" method="save" key="#result.id"/>
移除缓存条目
<cache:cache-evict cache="spittleCache" method="remove" />
配置Ehcache缓存
Ehcache的主要特性有:
- 快速、精干,简单;
- 多种缓存策略;
- 缓存数据有两级:内存和磁盘,因此无需担心容量问题;
- 缓存数据会在虚拟机重启的过程中写入磁盘;
- 可以通过RMI、可插入API等方式进行分布式缓存;
- 具有缓存和缓存管理器的侦听借口;
- 支持多缓存管理器实例,以及一个实例的多个缓存区域;
- 提供Hibernate 的缓存实现;
上面的声明应该改为以下方式
pom.xml添加配置
<!-- Ehcache -->
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.6.3</version>
</dependency>
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.3.5</version>
</dependency>
添加spring-ehcache.xml并配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">
<description>Ehcache缓存配置文件</description>
<!--Ehcache配置-->
<!-- 启用缓存 -->
<cache:annotation-driven cache-manager="cacheManager"/>
<!-- 声明缓存管理器 -->
<bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation" value="classpath:config/ehcache.xml"/>
<!-- true:一个cacheManager对象共享,false:多个对象独立 -->
<property name="shared" value="true"/> <!-- 这里是关键!!!没有必错 -->
</bean>
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
<property name="cacheManager" ref="ehcache"/>
</bean>
</beans>
spring-config.xml中添加配置
<!-- 加载Ehcache缓存配置文件 -->
<import resource="classpath:config/spring-ehcache.xml"/>
在resources/config文件夹下添加ehcache.xml并添加配置
<?xml version="1.0" encoding="UTF-8"?>
<!-- 这个name和updateCheck 都按这样配置最好,不然启动时会在前台报错403,IOException-->
<ehcache name="ehcache" updateCheck="false">
<!-- 缓存位置 java.io.tmpdir:Java临时目录 -->
<diskStore path="java.io.tmpdir/ehcache"/>
<!-- 默认缓存 -->
<defaultCache
maxEntriesLocalHeap="10000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="100"
timeToLiveSeconds="120"
diskPersistent="false"
maxEntriesLocalDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
<!--name:Cache的唯一标识
maxElementsInMemory:内存中最大缓存对象数。
maxElementsOnDisk:磁盘中最大缓存对象数,若是0表示无穷大。
eternal:Element是否永久有效,一但设置了,timeout将不起作用。
overflowToDisk:配置此属性,当内存中Element数量达到maxElementsInMemory时,Ehcache将会Element写到磁盘中。
timeToIdleSeconds:设置Element在失效前的允许闲置时间。仅当element不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds:设置Element在失效前允许存活时间。最大时间介于创建时间和失效时间之间。仅当element不是永久有效时使用,默认是0.,也就是element存活时间无穷大。
diskPersistent:是否缓存虚拟机重启期数据。(Server重启时将缓存序列化到本地,后再加载,保证缓存在重启后依然有效)。
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。这里比较遗憾,Ehcache并没有提供一个用户定制策略的接口,仅仅支持三种指定策略,感觉做的不够理想。-->
<!-- 系统缓存,用来缓存菜单、字典等 -->
<cache name="systemCatch"
maxElementsInMemory="1000"
eternal="false"
timeToIdleSeconds="10"
timeToLiveSeconds="30"
overflowToDisk="true"
diskPersistent="false"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>
这三个文件配置完了之后,Ehcache的配置也就完成了,下面我们说一下测试的方法。
测试缓存配置
SysMenuServiceImpl.java 中添加如下代码。
@Service
public class SysMenuServiceImpl extends BaseService<SysMenu> implements SysMenuService {
private static final Logger logger = Logger.getLogger(SysMenuServiceImpl.class);
@Autowired
SysMenuDao sysMenuDao;
// value对应ehcache.xml中的cache name,key就是这个缓存条目的key
@Cacheable(value="systemCatch", key="'SysMenuServiceImpl.systemCatch'")
@Override
public List<SysMenu> findMenuList(SysMenu sysMenu) {
List<SysMenu> sysMenus = sysMenuDao.queryMenuList(sysMenu);
Long timestamp = System.currentTimeMillis();
logger.error("timestamp>>>>>>>>>>>>>>>>>>>>>>>>>" + timestamp);
return sysMenus;
}
}
SysMenuController.java 中添加如下代码,观察start 和 end方法之间会不会打印sql以及中间打印时间的日志。
@RequestMapping(value = "sysMenuList")
@ResponseBody
public List<SysMenu> getSysMenuList() {
SysMenu sysMenu = new SysMenu();
sysMenu.setMenuId("01");
logger.info("-----------------------------------------------findMenuList start-----------------------------------");
List<SysMenu> menuList = sysMenuService.findMenuList(sysMenu);
logger.info("-----------------------------------------------findMenuList end-----------------------------------");
return menuList;
}
具体效果如下:
相关文章推荐
源码下载
该项目持续更新中,会在代码以及该文档里面详细注释和介绍。项目托管在<code>码云</code>开源平台上,持续更新项目源码链接:
https://gitee.com/nelucifer/ssm-note,点击<code>克隆/下载</code>获取该项目。
网友评论