目录
Spring Framework支持透明地向应用程序添加缓存。从本质上讲,抽象将缓存应用于方法,从而根据缓存中可用的信息减少执行次数。缓存逻辑应用透明,不会对调用者造成任何干扰。只要通过@EnableCaching
注释启用了缓存支持,Spring Boot就会自动配置缓存基础结构。下面我就介绍两个我比较常用的缓存。
啥也不管记得依赖依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
RedisCacheManager
首先呢先看下yml 依赖我就不贴了
spring:
cache:
type: redis
redis:
time-to-live: 1000s #缓存1000s的生存时间 m分 d天 h小时
redis:
host: 127.0.0.1
port: 6379
password:
jedis:
pool:
max-active: 9
max-idle: 8
max-wait: -1ms
min-idle: 0
database: 0
如果不想使用提供的默认配置希望改变 key或者value的序列化或者其他可以自定义cacheManager 注意这个注入的方式为springboot2.x以上的
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
//使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(mapper);
template.setValueSerializer(serializer);
//使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}
@Bean
public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisTemplate.getConnectionFactory());
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()));
return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
}
如果只想定义缓存的序列化可以这样
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1));
// 设置缓存有效期一小时
return RedisCacheManager
.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
.cacheDefaults(redisCacheConfiguration).build();
}
然后呢我们测试下怎么使用 首先呢写一个实体类
package com.maoxs.pojo;
import lombok.Data;
import java.io.Serializable;
@Data
public class User implements Serializable {
private Long id;
private String name;
public User() {
}
public User(Long id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("User{");
sb.append("id=").append(id);
sb.append(", name='").append(name).append('\'');
sb.append('}');
return sb.toString();
}
}
这里呢用到了一个自定义的key wiselyKeyGenerator 这里贴出代码
/**
* 设置统一的生成key的方式
* @return
*/
@Bean
public KeyGenerator wiselyKeyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append("-");
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
然后建一个service
package com.maoxs.service;
import com.maoxs.pojo.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
@CacheConfig(cacheNames = "myuser")
@Slf4j
public class UserService {
public static final Map<Long, User> users = new HashMap<>();
static {
users.put(1L, new User(1L, "我是快乐鱼"));
users.put(2L, new User(1L, "我是忧郁猫"));
users.put(3L, new User(1L, "我是昴先生"));
}
/**
* 使用自定义的key
*
* @param id
* @return
*/
@Cacheable(keyGenerator = "wiselyKeyGenerator")
public User getUserKeyGen(Long id) {
log.info("这是自定义key,redis里没有从map中读取");
return users.get(id);
}
/**
* 从缓存中读取否则读map
*
* @param id
* @return
*/
@Cacheable(key = "#id")
public User getUser(Long id) {
log.info("redis中没有,从map中读取");
return users.get(id);
}
/**
* 将方法的返回值放到缓存中
* 在方法的调用前并不会检查缓存,方法始终都会被调用。
*
* @param user
*/
@CachePut(key = "#id")
public User saveOrUpdate(Long id, User user) {
log.info("进入 saveOrUpdate 方法");
users.put(id, user);
return users.get(id);
}
/**
* 清除缓存
*
* @param id
*/
@CacheEvict(key = "#id")
public void delete(Long id) {
users.remove(id);
log.info("进入 delete 方法");
}
}
测试一下
package com.maoxs;
import com.maoxs.pojo.User;
import com.maoxs.service.UserService;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.cache.RedisCacheManager;
public class RedisCacheTest extends SpringbootCacheApplicationTests {
@Autowired
private UserService userService;
@Autowired
private RedisCacheManager redisCacheManager;
@Test
public void getUserKeyGen() {
User userKeyGen = userService.getUserKeyGen(1L);
System.out.println(userKeyGen);
}
@Test
public void getUser() {
User user = userService.getUser(1L);
System.out.println(user);
}
@Test
public void saveOrUpdate() {
User user = new User(3L, "我的的 的的的");
userService.saveOrUpdate(1L, user);
}
@Test
public void delete() {
userService.delete(1L);
}
}
首先介绍下service中几个注解是什么意思
注解 | 描述 |
---|---|
@Cacheable | 在调用方法之前,首先应该在缓存中查找方法的返回值,如果这个值能够找到,就会返回缓存的值。否则,这个方法就会被调用,返回值会放到缓存之中。 |
@CachePut | 将方法的返回值放到缓存中。在方法的调用前并不会检查缓存,方法始终都会被调用。 |
@CacheEvict | 在缓存中清除一个或多个条目。 |
@Caching | 分组的注解,能够同时应用多个其他的缓存注解。这里不做介绍有兴趣可以研究下 |
@Cacheable(根据方法的请求参数对其结果进行缓存)
- key: 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合(如:
@Cacheable(value="user",key="#userName")
) - value: 缓存的名称,必须指定至少一个(如:
@Cacheable(value="user")
或者@Cacheable(value={"user1","use2"})
) - condition: 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存(如:
@Cacheable(value = "user", key = "#id",condition = "#id < 10")
)
@CachePut(根据方法的请求参数对其结果进行缓存,和 @Cacheable
不同的是,它每次都会触发真实方法的调用)
- key: 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合(如:
@Cacheable(value="user",key="#userName")
) - value: 缓存的名称,必须指定至少一个(如:
@Cacheable(value="user")
或者@Cacheable(value={"user1","use2"})
) - condition: 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存(如:
@Cacheable(value = "user", key = "#id",condition = "#id < 10")
)
@CachEvict(根据条件对缓存进行清空)
- key: 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合(如:
@Cacheable(value="user",key="#userName")
) - value: 缓存的名称,必须指定至少一个(如:
@Cacheable(value="user")
或者@Cacheable(value={"user1","use2"})
) - condition: 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存(如:
@Cacheable(value = "user", key = "#id",condition = "#id < 10")
) - allEntries: 是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存(如:
@CacheEvict(value = "user", key = "#id", allEntries = true)
) - beforeInvocation: 是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存(如:
@CacheEvict(value = "user", key = "#id", beforeInvocation = true)
)
启动测试类,结果和我们期望的一致,可以看到第一次调用getUser,会在redis中插入缓存,因为缓存中不存在,此时,是会打印日志的。第二次调用呢是没有日志输出的,因为他是直接从缓存中获取数据,刷新缓存呢都是会进入方法内执行具体的业务代码,然后通过切面去除掉redis中缓存的数据,这里面的那个#号代表是一个spel 表达式,具体呢可以去spring官网查看详细用法。
这里说明一下 如果注解不能满足你的需求可以使用redisCacheManager 具体用法如下例子
package com.maoxs;
import com.maoxs.pojo.User;
import com.maoxs.service.UserService;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.data.redis.cache.RedisCacheManager;
public class RedisCacheTest extends SpringbootCacheApplicationTests {
@Autowired
private RedisCacheManager redisCacheManager;
@Test
public void managerTest() {
Cache cache = redisCacheManager.getCache("fulin");
cache.put("1", "我看不清楚");
Cache.ValueWrapper valueWrapper = cache.get("1");
System.out.println(valueWrapper.get());
}
}
这里为了方便大家给大家一个封装的抽象类
package com.maoxs.controller;
import org.springframework.cache.Cache;
/**
* fulin缓存抽象类
*/
public abstract class AbstractCacheSupport {
/**
* 获取缓存内容
* @param cache
* @param key
* @return
*/
protected Object getFromCache(Cache cache, String key) {
final Cache.ValueWrapper valueWrapper = cache.get(key);
return null == valueWrapper ? null : valueWrapper.get();
}
/**
* 设置缓存数据
* @param cache
* @param key
* @param value
* @return
*/
protected boolean putCache(Cache cache, String key, Object value) {
if (null == value) {
return false;
}
cache.put(key, value);
return true;
}
/**
* 删除缓存数据
* @param cache
* @param key
* @return
*/
protected boolean evictFromCache(Cache cache, Object key) {
if (null == key) {
return false;
}
cache.evict(key);
return true;
}
}
ehcache3.x、2.x
ehcache算是比较老牌的缓存了,随着时代的进步ehcache已经更新到了3.x版本,虽然今天本贴只是介绍整合spingboot的使用,但是其功能远不如此,值得大家去细细学习一波//TODO会一起分享的 ehcache学习手册
这里呢我采用的ehcache加 jsr107(jcache)的整合首先呢看下yml
spring:
cache:
type: jcache
jcache:
config: classpath:cache/ehcache.xml
logging:
level:
root: info
依赖依赖
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.5.2</version>
</dependency>
<!-- JSR107 API -->
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
</dependency>
这里使用的是xml配置 在此贴出xml
<?xml version="1.0" encoding="UTF-8"?>
<config
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns='http://www.ehcache.org/v3'
xmlns:jsr107='http://www.ehcache.org/v3/jsr107'
xsi:schemaLocation="
http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.0.xsd
http://www.ehcache.org/v3/jsr107 http://www.ehcache.org/schema/ehcache-107-ext-3.0.xsd">
<cache-template name="heap-cache">
<resources>
<heap unit="entries">2000</heap>
<offheap unit="MB">100</offheap>
</resources>
</cache-template>
<cache alias="myuser" uses-template="heap-cache">
<expiry>
<ttl unit="seconds">40</ttl>
</expiry>
</cache>
</config>
然后呢之前做redis缓存时候的类接着拿来用 切记CacheConfig中的缓存名称要跟xml文件中的cacahe 别名保持一致
package com.maoxs.service;
import com.maoxs.pojo.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
@CacheConfig(cacheNames = "myuser")
@Slf4j
public class UserService {
public static final Map<Long, User> users = new HashMap<>();
static {
users.put(1L, new User(1L, "我是快乐鱼"));
users.put(2L, new User(1L, "我是忧郁猫"));
users.put(3L, new User(1L, "我是昴先生"));
}
/**
* 使用自定义的key
*
* @param id
* @return
*/
@Cacheable(keyGenerator = "wiselyKeyGenerator")
public User getUserKeyGen(Long id) {
log.info("这是自定义key,ehcahe里没有从map中读取");
return users.get(id);
}
/**
* 从缓存中读取否则读map
*
* @param id
* @return
*/
@Cacheable(key = "#id")
public User getUser(Long id) {
log.info("ehcahe中没有,从map中读取");
return users.get(id);
}
/**
* 将方法的返回值放到缓存中
* 在方法的调用前并不会检查缓存,方法始终都会被调用。
*
* @param user
*/
@CachePut(key = "#id")
public User saveOrUpdate(Long id, User user) {
log.info("进入 saveOrUpdate 方法");
users.put(id, user);
return users.get(id);
}
/**
* 清除缓存
*
* @param id
*/
@CacheEvict(key = "#id")
public void delete(Long id) {
users.remove(id);
log.info("进入 delete 方法");
}
}
由于ehcache存在内存中所以这里测试改为接口测试
package com.maoxs.controller;
import com.maoxs.pojo.User;
import com.maoxs.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/getUserKeyGen/{id}")
public void getUserKeyGen(@PathVariable("id") Long id) {
User userKeyGen = userService.getUserKeyGen(id);
System.out.println(userKeyGen);
}
@RequestMapping("/getUser/{id}")
public void getUser(@PathVariable("id") Long id) {
User user = userService.getUser(id);
System.out.println(user);
}
@RequestMapping("/saveOrUpdate/{id}")
public void saveOrUpdate(@PathVariable("id") Long id) {
User user = new User(3L, "我的的 的的的");
userService.saveOrUpdate(id, user);
}
@RequestMapping("/delete/{id}")
public void delete(@PathVariable("id") Long id) {
userService.delete(id);
}
}
然后呢访问对应的url看对应的日志即可访问。 注解呢在上面有提到怎么使用的
注意,有时候spring cache的注解可能没有满足我们的需求,注入一下代码即可
@Bean
public JCacheCacheManager jCacheCacheManager() throws URISyntaxException {
CachingProvider provider = Caching.getCachingProvider();
JCacheCacheManager jCacheCacheManager = new JCacheCacheManager();
javax.cache.CacheManager eh107CacheManager = provider.getCacheManager(getClass().getResource("/cache/ehcache.xml").toURI(), getClass().getClassLoader());
jCacheCacheManager.setCacheManager(eh107CacheManager);
return jCacheCacheManager;
}
怎么使用呢 这样就行
@Autowired
private JCacheCacheManager jCacheCacheManager;
public void getUserByManager() {
Cache cache = jCacheCacheManager.getCache("myuser");
User user = new User(1L, "我是快乐鱼");
cache.put(1L, user);
}
顺便提一下2.x的使用 yml要改为
spring:
cache:
ehcache:
config: classpath:cache/ehcache.xml
manager配置呢应该改为这样
/**
* ehcache 主要的管理器
* @param bean
* @return
*/
@Bean
public EhCacheCacheManager ehCacheCacheManager(EhCacheManagerFactoryBean bean){
return new EhCacheCacheManager(bean.getObject());
}
@Bean
public EhCacheManagerFactoryBean ehCacheManagerFactoryBean(){
EhCacheManagerFactoryBean factoryBean = new EhCacheManagerFactoryBean();
factoryBean.setConfigLocation(new ClassPathResource("cache/ehcache.xml"));
factoryBean.setShared(true);
return factoryBean;
}
注:如果不对联系本宝宝及时改正~