首页 > 数据库 >Spring Boot缓存实战 Redis 设置有效时间和自动刷新缓存-2

Spring Boot缓存实战 Redis 设置有效时间和自动刷新缓存-2

时间:2022-11-04 14:03:36浏览次数:69  
标签:缓存 return Spring Redis preloadSecondTime key redisOperations public


问题

上一篇​​Spring Boot Cache + redis 设置有效时间和自动刷新缓存,时间支持在配置文件中配置​​,说了一种时间方式,直接扩展注解的Value值,如:

@Override
@Cacheable(value = "people#${select.cache.timeout:1800}#${select.cache.refresh:600}", key = "#person.id", sync = true)
public Person findOne(Person person, String a, String[] b, List<Long> c) {
Person p = personRepository.findOne(person.getId());
System.out.println("为id、key为:" + p.getId() + "数据做了缓存");
System.out.println(redisTemplate);
return p;
}

@Override
@Cacheable(value = "people#${select.cache.timeout:1800}#${select.cache.refresh:600}", key = "#person.id", sync = true)
public Person findOne(Person person, String a, String[] b, List<Long> c) {
Person p = personRepository.findOne(person.getId());
System.out.println("为id、key为:" + p.getId() + "数据做了缓存");
System.out.println(redisTemplate);
return p;
}

但是这种方式有一个弊端就是破坏了原有Spring Cache架构,导致如果后期想换缓存就会去改很多代码。

解决思路

RedisCacheManager可以在配置CacheManager的Bean的时候指定过期时间,如:

@Bean
public RedisCacheManager cacheManager(RedisTemplate<String, Object> redisTemplate) {
RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate);
// 开启使用缓存名称最为key前缀
redisCacheManager.setUsePrefix(true);
//这里可以设置一个默认的过期时间 单位是秒
redisCacheManager.setDefaultExpiration(redisDefaultExpiration);

// 设置缓存的过期时间
Map<String, Long> expires = new HashMap<>();
expires.put("people", 1000);
redisCacheManager.setExpires(expires);

return redisCacheManager;
}

@Bean
public RedisCacheManager cacheManager(RedisTemplate<String, Object> redisTemplate) {
RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate);
// 开启使用缓存名称最为key前缀
redisCacheManager.setUsePrefix(true);
//这里可以设置一个默认的过期时间 单位是秒
redisCacheManager.setDefaultExpiration(redisDefaultExpiration);

// 设置缓存的过期时间
Map<String, Long> expires = new HashMap<>();
expires.put("people", 1000);
redisCacheManager.setExpires(expires);

return redisCacheManager;
}

我们借鉴一下redisCacheManager.setExpires(expires)思路,进行扩展。直接新建一个CacheTime类,来存过期时间和自动刷新时间。

在RedisCacheManager调用getCache(name)获取缓存的时候,当没有找到缓存的时候会调用getMissingCache(String cacheName)来新建缓存。在新建缓存的时候我们可以在扩展的Map<String, CacheTime> cacheTimes里面根据key获取CacheTime进而拿到有效时间和自动刷新时间。

具体实现

我们先新建CacheTime类

CacheTime:

/**
* @author yuhao.wang
*/
public class CacheTime {
public CacheTime(long preloadSecondTime, long expirationSecondTime) {
this.preloadSecondTime = preloadSecondTime;
this.expirationSecondTime = expirationSecondTime;
}

/**
* 缓存主动在失效前强制刷新缓存的时间
* 单位:秒
*/
private long preloadSecondTime = 0;

/**
* 缓存有效时间
*/
private long expirationSecondTime;

public long getPreloadSecondTime() {
return preloadSecondTime;
}

public long getExpirationSecondTime() {
return expirationSecondTime;
}
}

/**
* @author yuhao.wang
*/
public class CacheTime {
public CacheTime(long preloadSecondTime, long expirationSecondTime) {
this.preloadSecondTime = preloadSecondTime;
this.expirationSecondTime = expirationSecondTime;
}

/**
* 缓存主动在失效前强制刷新缓存的时间
* 单位:秒
*/
private long preloadSecondTime = 0;

/**
* 缓存有效时间
*/
private long expirationSecondTime;

public long getPreloadSecondTime() {
return preloadSecondTime;
}

public long getExpirationSecondTime() {
return expirationSecondTime;
}
}

扩展一下RedisCache类

和上一篇的CustomizedRedisCache类一样,主要解决:

  • 获取缓存的在大并发下的一个bug,​​详情​
  • 在获取缓存的时候判断一下缓存的过期时间和自动刷新时间,根据这个值去刷新缓存

CustomizedRedisCache:

/**
* 自定义的redis缓存
*
* @author yuhao.wang
*/
public class CustomizedRedisCache extends RedisCache {

private static final Logger logger = LoggerFactory.getLogger(CustomizedRedisCache.class);

private CacheSupport getCacheSupport() {
return SpringContextUtils.getBean(CacheSupport.class);
}

private final RedisOperations redisOperations;

private final byte[] prefix;

/**
* 缓存主动在失效前强制刷新缓存的时间
* 单位:秒
*/
private long preloadSecondTime = 0;

/**
* 缓存有效时间
*/
private long expirationSecondTime;

public CustomizedRedisCache(String name, byte[] prefix, RedisOperations<? extends Object, ? extends Object> redisOperations, long expiration, long preloadSecondTime) {
super(name, prefix, redisOperations, expiration);
this.redisOperations = redisOperations;
// 指定有效时间
this.expirationSecondTime = expiration;
// 指定自动刷新时间
this.preloadSecondTime = preloadSecondTime;
this.prefix = prefix;
}

public CustomizedRedisCache(String name, byte[] prefix, RedisOperations<? extends Object, ? extends Object> redisOperations, long expiration, long preloadSecondTime, boolean allowNullValues) {
super(name, prefix, redisOperations, expiration, allowNullValues);
this.redisOperations = redisOperations;
// 指定有效时间
this.expirationSecondTime = expiration;
// 指定自动刷新时间
this.preloadSecondTime = preloadSecondTime;
this.prefix = prefix;

}

/**
* 重写get方法,获取到缓存后再次取缓存剩余的时间,如果时间小余我们配置的刷新时间就手动刷新缓存。
* 为了不影响get的性能,启用后台线程去完成缓存的刷。
* 并且只放一个线程去刷新数据。
*
* @param key
* @return
*/
@Override
public ValueWrapper get(final Object key) {
RedisCacheKey cacheKey = getRedisCacheKey(key);
String cacheKeyStr = getCacheKey(key);
// 调用重写后的get方法
ValueWrapper valueWrapper = this.get(cacheKey);

if (null != valueWrapper) {
// 刷新缓存数据
refreshCache(key, cacheKeyStr);
}
return valueWrapper;
}

/**
* 重写父类的get函数。
* 父类的get方法,是先使用exists判断key是否存在,不存在返回null,存在再到redis缓存中去取值。这样会导致并发问题,
* 假如有一个请求调用了exists函数判断key存在,但是在下一时刻这个缓存过期了,或者被删掉了。
* 这时候再去缓存中获取值的时候返回的就是null了。
* 可以先获取缓存的值,再去判断key是否存在。
*
* @param cacheKey
* @return
*/
@Override
public RedisCacheElement get(final RedisCacheKey cacheKey) {

Assert.notNull(cacheKey, "CacheKey must not be null!");

// 根据key获取缓存值
RedisCacheElement redisCacheElement = new RedisCacheElement(cacheKey, fromStoreValue(lookup(cacheKey)));
// 判断key是否存在
Boolean exists = (Boolean) redisOperations.execute(new RedisCallback<Boolean>() {

@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
return connection.exists(cacheKey.getKeyBytes());
}
});

if (!exists.booleanValue()) {
return null;
}

return redisCacheElement;
}

/**
* 刷新缓存数据
*/
private void refreshCache(Object key, String cacheKeyStr) {
Long ttl = this.redisOperations.getExpire(cacheKeyStr);
if (null != ttl && ttl <= CustomizedRedisCache.this.preloadSecondTime) {
// 尽量少的去开启线程,因为线程池是有限的
ThreadTaskUtils.run(new Runnable() {
@Override
public void run() {
// 加一个分布式锁,只放一个请求去刷新缓存
RedisLock redisLock = new RedisLock((RedisTemplate) redisOperations, cacheKeyStr + "_lock");
try {
if (redisLock.lock()) {
// 获取锁之后再判断一下过期时间,看是否需要加载数据
Long ttl = CustomizedRedisCache.this.redisOperations.getExpire(cacheKeyStr);
if (null != ttl && ttl <= CustomizedRedisCache.this.preloadSecondTime) {
// 通过获取代理方法信息重新加载缓存数据
CustomizedRedisCache.this.getCacheSupport().refreshCacheByKey(CustomizedRedisCache.super.getName(), cacheKeyStr);
}
}
} catch (Exception e) {
logger.info(e.getMessage(), e);
} finally {
redisLock.unlock();
}
}
});
}
}

public long getExpirationSecondTime() {
return expirationSecondTime;
}


/**
* 获取RedisCacheKey
*
* @param key
* @return
*/
public RedisCacheKey getRedisCacheKey(Object key) {

return new RedisCacheKey(key).usePrefix(this.prefix)
.withKeySerializer(redisOperations.getKeySerializer());
}

/**
* 获取RedisCacheKey
*
* @param key
* @return
*/
public String getCacheKey(Object key) {
return new String(getRedisCacheKey(key).getKeyBytes());
}
}

/**
* 自定义的redis缓存
*
* @author yuhao.wang
*/
public class CustomizedRedisCache extends RedisCache {

private static final Logger logger = LoggerFactory.getLogger(CustomizedRedisCache.class);

private CacheSupport getCacheSupport() {
return SpringContextUtils.getBean(CacheSupport.class);
}

private final RedisOperations redisOperations;

private final byte[] prefix;

/**
* 缓存主动在失效前强制刷新缓存的时间
* 单位:秒
*/
private long preloadSecondTime = 0;

/**
* 缓存有效时间
*/
private long expirationSecondTime;

public CustomizedRedisCache(String name, byte[] prefix, RedisOperations<? extends Object, ? extends Object> redisOperations, long expiration, long preloadSecondTime) {
super(name, prefix, redisOperations, expiration);
this.redisOperations = redisOperations;
// 指定有效时间
this.expirationSecondTime = expiration;
// 指定自动刷新时间
this.preloadSecondTime = preloadSecondTime;
this.prefix = prefix;
}

public CustomizedRedisCache(String name, byte[] prefix, RedisOperations<? extends Object, ? extends Object> redisOperations, long expiration, long preloadSecondTime, boolean allowNullValues) {
super(name, prefix, redisOperations, expiration, allowNullValues);
this.redisOperations = redisOperations;
// 指定有效时间
this.expirationSecondTime = expiration;
// 指定自动刷新时间
this.preloadSecondTime = preloadSecondTime;
this.prefix = prefix;

}

/**
* 重写get方法,获取到缓存后再次取缓存剩余的时间,如果时间小余我们配置的刷新时间就手动刷新缓存。
* 为了不影响get的性能,启用后台线程去完成缓存的刷。
* 并且只放一个线程去刷新数据。
*
* @param key
* @return
*/
@Override
public ValueWrapper get(final Object key) {
RedisCacheKey cacheKey = getRedisCacheKey(key);
String cacheKeyStr = getCacheKey(key);
// 调用重写后的get方法
ValueWrapper valueWrapper = this.get(cacheKey);

if (null != valueWrapper) {
// 刷新缓存数据
refreshCache(key, cacheKeyStr);
}
return valueWrapper;
}

/**
* 重写父类的get函数。
* 父类的get方法,是先使用exists判断key是否存在,不存在返回null,存在再到redis缓存中去取值。这样会导致并发问题,
* 假如有一个请求调用了exists函数判断key存在,但是在下一时刻这个缓存过期了,或者被删掉了。
* 这时候再去缓存中获取值的时候返回的就是null了。
* 可以先获取缓存的值,再去判断key是否存在。
*
* @param cacheKey
* @return
*/
@Override
public RedisCacheElement get(final RedisCacheKey cacheKey) {

Assert.notNull(cacheKey, "CacheKey must not be null!");

// 根据key获取缓存值
RedisCacheElement redisCacheElement = new RedisCacheElement(cacheKey, fromStoreValue(lookup(cacheKey)));
// 判断key是否存在
Boolean exists = (Boolean) redisOperations.execute(new RedisCallback<Boolean>() {

@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
return connection.exists(cacheKey.getKeyBytes());
}
});

if (!exists.booleanValue()) {
return null;
}

return redisCacheElement;
}

/**
* 刷新缓存数据
*/
private void refreshCache(Object key, String cacheKeyStr) {
Long ttl = this.redisOperations.getExpire(cacheKeyStr);
if (null != ttl && ttl <= CustomizedRedisCache.this.preloadSecondTime) {
// 尽量少的去开启线程,因为线程池是有限的
ThreadTaskUtils.run(new Runnable() {
@Override
public void run() {
// 加一个分布式锁,只放一个请求去刷新缓存
RedisLock redisLock = new RedisLock((RedisTemplate) redisOperations, cacheKeyStr + "_lock");
try {
if (redisLock.lock()) {
// 获取锁之后再判断一下过期时间,看是否需要加载数据
Long ttl = CustomizedRedisCache.this.redisOperations.getExpire(cacheKeyStr);
if (null != ttl && ttl <= CustomizedRedisCache.this.preloadSecondTime) {
// 通过获取代理方法信息重新加载缓存数据
CustomizedRedisCache.this.getCacheSupport().refreshCacheByKey(CustomizedRedisCache.super.getName(), cacheKeyStr);
}
}
} catch (Exception e) {
logger.info(e.getMessage(), e);
} finally {
redisLock.unlock();
}
}
});
}
}

public long getExpirationSecondTime() {
return expirationSecondTime;
}


/**
* 获取RedisCacheKey
*
* @param key
* @return
*/
public RedisCacheKey getRedisCacheKey(Object key) {

return new RedisCacheKey(key).usePrefix(this.prefix)
.withKeySerializer(redisOperations.getKeySerializer());
}

/**
* 获取RedisCacheKey
*
* @param key
* @return
*/
public String getCacheKey(Object key) {
return new String(getRedisCacheKey(key).getKeyBytes());
}
}

扩展RedisCacheManager

主要扩展通过getCache(String name)方法获取缓存的时候,当没有找到缓存回去调用getMissingCache(String cacheName)来新建缓存。

CustomizedRedisCacheManager:

/**
* 自定义的redis缓存管理器
* 支持方法上配置过期时间
* 支持热加载缓存:缓存即将过期时主动刷新缓存
*
* @author yuhao.wang
*/
public class CustomizedRedisCacheManager extends RedisCacheManager {

private static final Logger logger = LoggerFactory.getLogger(CustomizedRedisCacheManager.class);

/**
* 父类dynamic字段
*/
private static final String SUPER_FIELD_DYNAMIC = "dynamic";

/**
* 父类cacheNullValues字段
*/
private static final String SUPER_FIELD_CACHENULLVALUES = "cacheNullValues";

RedisCacheManager redisCacheManager = null;

// 0 - never expire
private long defaultExpiration = 0;
private Map<String, CacheTime> cacheTimes = null;

public CustomizedRedisCacheManager(RedisOperations redisOperations) {
super(redisOperations);
}

public CustomizedRedisCacheManager(RedisOperations redisOperations, Collection<String> cacheNames) {
super(redisOperations, cacheNames);
}

public RedisCacheManager getInstance() {
if (redisCacheManager == null) {
redisCacheManager = SpringContextUtils.getBean(RedisCacheManager.class);
}
return redisCacheManager;
}

/**
* 获取过期时间
*
* @return
*/
public long getExpirationSecondTime(String name) {
if (StringUtils.isEmpty(name)) {
return 0;
}

CacheTime cacheTime = null;
if (!CollectionUtils.isEmpty(cacheTimes)) {
cacheTime = cacheTimes.get(name);
}
Long expiration = cacheTime != null ? cacheTime.getExpirationSecondTime() : defaultExpiration;
return expiration < 0 ? 0 : expiration;
}

/**
* 获取自动刷新时间
*
* @return
*/
private long getPreloadSecondTime(String name) {
// 自动刷新时间,默认是0
CacheTime cacheTime = null;
if (!CollectionUtils.isEmpty(cacheTimes)) {
cacheTime = cacheTimes.get(name);
}
Long preloadSecondTime = cacheTime != null ? cacheTime.getPreloadSecondTime() : 0;
return preloadSecondTime < 0 ? 0 : preloadSecondTime;
}

/**
* 创建缓存
*
* @param cacheName 缓存名称
* @return
*/
public CustomizedRedisCache getMissingCache(String cacheName) {

// 有效时间,初始化获取默认的有效时间
Long expirationSecondTime = getExpirationSecondTime(cacheName);
// 自动刷新时间,默认是0
Long preloadSecondTime = getPreloadSecondTime(cacheName);

logger.info("缓存 cacheName:{},过期时间:{}, 自动刷新时间:{}", cacheName, expirationSecondTime, preloadSecondTime);
// 是否在运行时创建Cache
Boolean dynamic = (Boolean) ReflectionUtils.getFieldValue(getInstance(), SUPER_FIELD_DYNAMIC);
// 是否允许存放NULL
Boolean cacheNullValues = (Boolean) ReflectionUtils.getFieldValue(getInstance(), SUPER_FIELD_CACHENULLVALUES);
return dynamic ? new CustomizedRedisCache(cacheName, (this.isUsePrefix() ? this.getCachePrefix().prefix(cacheName) : null),
this.getRedisOperations(), expirationSecondTime, preloadSecondTime, cacheNullValues) : null;
}

/**
* 根据缓存名称设置缓存的有效时间和刷新时间,单位秒
*
* @param cacheTimes
*/
public void setCacheTimess(Map<String, CacheTime> cacheTimes) {
this.cacheTimes = (cacheTimes != null ? new ConcurrentHashMap<String, CacheTime>(cacheTimes) : null);
}

/**
* 设置默认的过去时间, 单位:秒
*
* @param defaultExpireTime
*/
@Override
public void setDefaultExpiration(long defaultExpireTime) {
super.setDefaultExpiration(defaultExpireTime);
this.defaultExpiration = defaultExpireTime;
}

@Deprecated
@Override
public void setExpires(Map<String, Long> expires) {

}
}

/**
* 自定义的redis缓存管理器
* 支持方法上配置过期时间
* 支持热加载缓存:缓存即将过期时主动刷新缓存
*
* @author yuhao.wang
*/
public class CustomizedRedisCacheManager extends RedisCacheManager {

private static final Logger logger = LoggerFactory.getLogger(CustomizedRedisCacheManager.class);

/**
* 父类dynamic字段
*/
private static final String SUPER_FIELD_DYNAMIC = "dynamic";

/**
* 父类cacheNullValues字段
*/
private static final String SUPER_FIELD_CACHENULLVALUES = "cacheNullValues";

RedisCacheManager redisCacheManager = null;

// 0 - never expire
private long defaultExpiration = 0;
private Map<String, CacheTime> cacheTimes = null;

public CustomizedRedisCacheManager(RedisOperations redisOperations) {
super(redisOperations);
}

public CustomizedRedisCacheManager(RedisOperations redisOperations, Collection<String> cacheNames) {
super(redisOperations, cacheNames);
}

public RedisCacheManager getInstance() {
if (redisCacheManager == null) {
redisCacheManager = SpringContextUtils.getBean(RedisCacheManager.class);
}
return redisCacheManager;
}

/**
* 获取过期时间
*
* @return
*/
public long getExpirationSecondTime(String name) {
if (StringUtils.isEmpty(name)) {
return 0;
}

CacheTime cacheTime = null;
if (!CollectionUtils.isEmpty(cacheTimes)) {
cacheTime = cacheTimes.get(name);
}
Long expiration = cacheTime != null ? cacheTime.getExpirationSecondTime() : defaultExpiration;
return expiration < 0 ? 0 : expiration;
}

/**
* 获取自动刷新时间
*
* @return
*/
private long getPreloadSecondTime(String name) {
// 自动刷新时间,默认是0
CacheTime cacheTime = null;
if (!CollectionUtils.isEmpty(cacheTimes)) {
cacheTime = cacheTimes.get(name);
}
Long preloadSecondTime = cacheTime != null ? cacheTime.getPreloadSecondTime() : 0;
return preloadSecondTime < 0 ? 0 : preloadSecondTime;
}

/**
* 创建缓存
*
* @param cacheName 缓存名称
* @return
*/
public CustomizedRedisCache getMissingCache(String cacheName) {

// 有效时间,初始化获取默认的有效时间
Long expirationSecondTime = getExpirationSecondTime(cacheName);
// 自动刷新时间,默认是0
Long preloadSecondTime = getPreloadSecondTime(cacheName);

logger.info("缓存 cacheName:{},过期时间:{}, 自动刷新时间:{}", cacheName, expirationSecondTime, preloadSecondTime);
// 是否在运行时创建Cache
Boolean dynamic = (Boolean) ReflectionUtils.getFieldValue(getInstance(), SUPER_FIELD_DYNAMIC);
// 是否允许存放NULL
Boolean cacheNullValues = (Boolean) ReflectionUtils.getFieldValue(getInstance(), SUPER_FIELD_CACHENULLVALUES);
return dynamic ? new CustomizedRedisCache(cacheName, (this.isUsePrefix() ? this.getCachePrefix().prefix(cacheName) : null),
this.getRedisOperations(), expirationSecondTime, preloadSecondTime, cacheNullValues) : null;
}

/**
* 根据缓存名称设置缓存的有效时间和刷新时间,单位秒
*
* @param cacheTimes
*/
public void setCacheTimess(Map<String, CacheTime> cacheTimes) {
this.cacheTimes = (cacheTimes != null ? new ConcurrentHashMap<String, CacheTime>(cacheTimes) : null);
}

/**
* 设置默认的过去时间, 单位:秒
*
* @param defaultExpireTime
*/
@Override
public void setDefaultExpiration(long defaultExpireTime) {
super.setDefaultExpiration(defaultExpireTime);
this.defaultExpiration = defaultExpireTime;
}

@Deprecated
@Override
public void setExpires(Map<String, Long> expires) {

}
}

在Config中配置RedisCacheManager

@Bean
public RedisCacheManager cacheManager(RedisTemplate<String, Object> redisTemplate) {
CustomizedRedisCacheManager redisCacheManager = new CustomizedRedisCacheManager(redisTemplate);
// 开启使用缓存名称最为key前缀
redisCacheManager.setUsePrefix(true);
//这里可以设置一个默认的过期时间 单位是秒
redisCacheManager.setDefaultExpiration(redisDefaultExpiration);

// 设置缓存的过期时间和自动刷新时间
Map<String, CacheTime> cacheTimes = new HashMap<>();
cacheTimes.put("people", new CacheTime(selectCacheTimeout, selectCacheRefresh));
cacheTimes.put("people1", new CacheTime(120, 115));
cacheTimes.put("people2", new CacheTime(120, 115));
redisCacheManager.setCacheTimess(cacheTimes);

return redisCacheManager;
}

@Bean
public RedisCacheManager cacheManager(RedisTemplate<String, Object> redisTemplate) {
CustomizedRedisCacheManager redisCacheManager = new CustomizedRedisCacheManager(redisTemplate);
// 开启使用缓存名称最为key前缀
redisCacheManager.setUsePrefix(true);
//这里可以设置一个默认的过期时间 单位是秒
redisCacheManager.setDefaultExpiration(redisDefaultExpiration);

// 设置缓存的过期时间和自动刷新时间
Map<String, CacheTime> cacheTimes = new HashMap<>();
cacheTimes.put("people", new CacheTime(selectCacheTimeout, selectCacheRefresh));
cacheTimes.put("people1", new CacheTime(120, 115));
cacheTimes.put("people2", new CacheTime(120, 115));
redisCacheManager.setCacheTimess(cacheTimes);

return redisCacheManager;
}

剩余的创建切面来缓存方法信息请看​​上篇​

源码地址:
​​​https://github.com/wyh-spring-ecosystem-student/spring-boot-student/tree/releases​

spring-boot-student-cache-redis-2 工程

为监控而生的多级缓存框架 layering-cache这是我开源的一个多级缓存框架的实现,如果有兴趣可以看一下。

 

标签:缓存,return,Spring,Redis,preloadSecondTime,key,redisOperations,public
From: https://blog.51cto.com/u_15861563/5823716

相关文章

  • Spring Boot缓存实战 Redis + Caffeine 实现多级缓存
    在前文我们介绍了如何使用Redis或者Caffeine来做缓存。​​SpringBoot缓存实战Redis设置有效时间和自动刷新缓存-2​​​​SpringBoot缓存实战Caffeine​​问题描述:通......
  • 基于redis实现的扣减库存
    在日常开发中有很多地方都有类似扣减库存的操作,比如电商系统中的商品库存,抽奖系统中的奖品库存等。解决方案使用mysql数据库,使用一个字段来存储库存,每次扣减库存去更新这个......
  • Spring Bean 的注册和注入的几种常用方式和区别
    Spring注册Bean:包扫描+组件标注注解(@Controller、@Service、@Repository、@Component),一般项目里面使用。使用@Bean注解,一般导入第三方组件的时候使用。使用@Import注解,一......
  • Spring Boot 运行原理 - 实例分析(HttpEncodingAutoConfiguration)
    在了解了SpringBoot的运作原理和主要注解后,现在来简单的分析一个SpringBoot内置的自动配置功能:http的编码配置。我们在常规项目中配置Http编码的时候是在web.xml添加一......
  • Spring Boot对Web开发的支持
    Web开发是开发中至关重要的一部分,Web开发的核心内容主要包括内嵌Servlet容器和SpringMVC。SpringBoot对Web开发的支持springBoot提供了spring-boot-starter-web为web开......
  • Spring Boot与Thymeleaf集成
    JSP在内嵌Servlet容器上运行有些问题(内嵌Tomcate、Jetty不支持以jar的形式运行JSP,Undertow不支持JSP)。SpringBoot默认使用Thymeleaf模板,因为Thymeleaf模板提供了完美的S......
  • Spring Boot 常用应用属性配置列表
    #===================================================================#COMMONSPRINGBOOTPROPERTIES##Thissamplefileisprovidedasaguideline.DoNOT......
  • 基于redis 实现分布式锁(二)
    分布式锁的解决方式基于数据库表做乐观锁,用于分布式锁。(适用于小并发)使用memcached的add()方法,用于分布式锁。使用memcached的cas()方法,用于分布式锁。(不常用)使用redis......
  • @Cacheable注解实现Redis缓存
    SpringBoot缓存之@Cacheable介绍简介介绍:Spring从3.1开始就引入了对Cache的支持。定义了org.springframework.cache.Cache和org.springframework.cache.Cach......
  • SpringCloud(六) - RabbitMQ安装,三种消息发送模式,消息发送确认,消息消费确认(自动,手动)
    1、安装erlang语言环境1.1创建erlang安装目录mkdirerlang1.2上传解压压缩包上传到:/root/解压缩#tar-zxvfotp_src_22.0.tar.gz1.3进入解压缩目录,指定......