Spring Boot 项目实战(四)集成 Redis
一、前言
上篇介绍了接口文档工具 Swagger 及项目监控工具 JavaMelody 的集成过程,使项目更加健壮。在 JAVA Web 项目某些场景中,我们需要用缓存解决如热点数据访问的性能问题,业界常用的中间件如 Memcached 、 Redis 等。相比 Memcached ,Redis 支持更丰富的数据结构。本篇将主要介绍在 Spring Boot 中集成 Redis 的过程。
二、集成 Redis
在 Spring Boot 中使用 Redis 有两种方式:
- 基于 RedisTemplate 类,该类是 Spring Data 提供的工具,可以直接注入使用。
- 基于 Jedis,Jedis 是 Redis 官方推荐的面向 JAVA 的客户端。
本文将介绍第一种使用方式。
2.1 引入依赖包
其实 Spring Boot 提供的父工程中已经包含了所依赖的 Redis jar 包,我们只需在相应模块引入即可。第一篇我们已经提到过 demo-common 层是公用组件层,那么 Redis 相关的声明及配置应该在该层定义。于是乎在 demo-common 层的 pom 文件中引入 Redis 的依赖包。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.2 RedisTemplate 的自动配置
其实我们现在就可以在项目中注入 RedisTemplate 并使用了,至于原因,首先看下「 RedisAutoConfiguration 」类的源码:
@Configuration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
}
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
从源码可以看出,Spring Boot 会自动帮我们生成了一个 RedisTemplate 及一个 StringRedisTemplate ,但是这个 RedisTemplate 的泛型是 <Object, Object> ,如果我们直接使用就需要处理各种类型转换。所以为了方便使用,我们需要自定义一个泛型为 <String, Object> 的 RedisTemplate 。
而 @ConditionalOnMissingBean 注解的作用是在当前 Spring 上下文中不存在某个对象时,才会自动实例化一个 Bean 。因此我们可以自定义 RedisTemplate 从而替代默认的。
2.2 自定义 Redis 配置类
Spring Data 提供了若干个 Serializer ,主要包括:
- JdkSerializationRedisSerializer — 使用 JAVA 自带的序列化机制将对象序列化为一个字符串
- OxmSerializer — 将对象序列化为 XML 字符串
- Jackson2JsonRedisSerializer — 将对象序列化为 JSON 字符串
其中 RedisTemplate 默认的序列化方式是 Jdk ,虽然是效率比较高但是序列化结果的字符串是最长的。而 JSON 由于其数据格式的紧凑型,序列化结果的字符串是最小的,即占用的内存最小。所以我们选择用 Jackson 替代默认的 Jdk 方式。
① 首先在项目父 pom 文件中定义 Jackson 的版本号且声明 Jackson 依赖。
<properties>
...省略其余部分...
<jackson.version>2.9.4</jackson.version>
</properties>
<dependencyManagement>
<dependencies>
...省略其余部分...
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
② 其次在 demo-common 层的 pom 文件中添加上述 Jackson 依赖。
<dependencies>
...省略其余部分...
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
</dependencies>
③ 最后在 demo-common 层创建 com.example.demo.common 包,添加 Redis 目录并在其中创建 RedisConfig 配置类。
package com.example.demo.common.redis;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* @author linjian
* @date 2019/3/2
*/
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
objectMapper.registerModule(new JavaTimeModule());
objectMapper.findAndRegisterModules();
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
// 使用 Jackson2JsonRedisSerialize 替换默认序列化
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(factory);
// key 采用 String 的序列化方式
redisTemplate.setKeySerializer(stringRedisSerializer);
// hash 的 key 也采用 String 的序列化方式
redisTemplate.setHashKeySerializer(stringRedisSerializer);
// value 序列化方式采用 jackson
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
// hash 的 value 序列化方式采用 jackson
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
2.3 自定义 Redis 工具类
直接使用 RedisTemplate 操作 Redis 需要很多额外的代码,最好封装成一个工具类,使用时直接注入。
① 定义一个常用的缓存时间常量类
package com.example.demo.common.redis;
/**
* @author linjian
* @date 2019/3/2
*/
public class CacheTime {
/**
* 缓存时效 5秒钟
*/
public static int CACHE_EXP_FIVE_SECONDS = 5;
/**
* 缓存时效 1分钟
*/
public static int CACHE_EXP_MINUTE = 60;
/**
* 缓存时效 5分钟
*/
public static int CACHE_EXP_FIVE_MINUTES = 60 * 5;
/**
* 缓存时效 10分钟
*/
public static int CACHE_EXP_TEN_MINUTES = 60 * 10;
/**
* 缓存时效 15分钟
*/
public static int CACHE_EXP_QUARTER_MINUTES = 60 * 15;
/**
* 缓存时效 60分钟
*/
public static int CACHE_EXP_HOUR = 60 * 60;
/**
* 缓存时效 12小时
*/
public static int CACHE_EXP_HALF_DAY = 12 * 60 * 60;
/**
* 缓存时效 1天
*/
public static int CACHE_EXP_DAY = 3600 * 24;
/**
* 缓存时效 1周
*/
public static int CACHE_EXP_WEEK = 3600 * 24 * 7;
/**
* 缓存时效 1月
*/
public static int CACHE_EXP_MONTH = 3600 * 24 * 30 * 7;
/**
* 缓存时效 永久
*/
public static int CACHE_EXP_FOREVER = 0;
}
② 定义工具类
package com.example.demo.common.redis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* @author linjian
* @date 2019/3/2
*/
@Component
public class RedisClient {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
* @return
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取剩余过期时间
*
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long ttl(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public boolean exists(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
/**
* 模糊匹配批量删除
*
* @param pattern 匹配的前缀
*/
public void deleteByPattern(String pattern) {
Set<String> keys = redisTemplate.keys(pattern);
if (!CollectionUtils.isEmpty(keys)) {
redisTemplate.delete(keys);
}
}
/**
* 设置指定 key 的值
*
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time == CacheTime.CACHE_EXP_FOREVER) {
redisTemplate.opsForValue().set(key, value);
} else {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 获取指定 key 的值
*
* @param key 键
* @return 值
*/
@SuppressWarnings("unchecked")
public <T> T get(String key) {
return key == null ? null : (T) redisTemplate.opsForValue().get(key);
}
/**
* 将 key 中储存的数字值递增
*
* @param key 键
* @param delta 要增加几(大于0)
* @return
*/
public long incr(String key, long delta) {
if (delta <= 0) {
throw new IllegalArgumentException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 将 key 中储存的数字值递减
*
* @param key 键
* @param delta 要减少几(小于0)
* @return
*/
public long decr(String key, long delta) {
if (delta <= 0) {
throw new IllegalArgumentException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
/**
* 将哈希表 key 中的字段 field 的值设为 value
*
* @param key 键
* @param field 字段
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String field, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, field, value);
if (time != CacheTime.CACHE_EXP_FOREVER) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 同时将多个 field-value (域-值)对设置到哈希表 key 中
*
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time != CacheTime.CACHE_EXP_FOREVER) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除一个或多个哈希表字段
*
* @param key 键
* @param field 字段 可以多个
*/
public void hdel(String key, Object... field) {
redisTemplate.opsForHash().delete(key, field);
}
/**
* 获取存储在哈希表中指定字段的值
*
* @param key 键
* @param field 字段
* @return 值
*/
public <T> T hget(String key, String field) {
return (T) redisTemplate.opsForHash().get(key, field);
}
/**
* 获取在哈希表中指定 key 的所有字段和值
*
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 查看哈希表 key 中,指定的字段是否存在
*
* @param key 键
* @param field 字段
* @return true 存在 false不存在
*/
public boolean hexists(String key, String field) {
return redisTemplate.opsForHash().hasKey(key, field);
}
/**
* 获取哈希表中字段的数量
*
* @param key 键
* @return 字段数量
*/
public long hlen(String key) {
try {
return redisTemplate.opsForHash().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0L;
}
}
/**
* 向集合添加一个或多个成员
*
* @param key 键
* @param time 时间(秒)
* @param values 成员 可以是多个
* @return 成功个数
*/
public long sadd(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time != CacheTime.CACHE_EXP_FOREVER) {
expire(key, time);
}
return count;
} catch (Exception e) {
e.printStackTrace();
return 0L;
}
}
/**
* 移除集合中一个或多个成员
*
* @param key 键
* @param values 成员 可以是多个
* @return 移除的个数
*/
public long srem(String key, Object... values) {
try {
return redisTemplate.opsForSet().remove(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0L;
}
}
/**
* 返回集合中的所有成员
*
* @param key 键
* @return 成员列表
*/
public <T> Set<T> smembers(String key) {
try {
return (Set<T>) redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 判断 member 元素是否是集合 key 的成员
*
* @param key 键
* @param member 成员
* @return true 存在 false不存在
*/
public boolean sismember(String key, Object member) {
try {
return redisTemplate.opsForSet().isMember(key, member);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 获取集合的成员数
*
* @param key 键
* @return 成员数
*/
public long slen(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0L;
}
}
/**
* 在列表头部添加一个值
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return boolean
*/
public boolean lpush(String key, Object value, long time) {
try {
redisTemplate.opsForList().leftPush(key, value);
if (time != CacheTime.CACHE_EXP_FOREVER) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 在列表头部添加多个值
*
* @param key 键
* @param values 值
* @param time 时间(秒)
* @return boolean
*/
public boolean lpush(String key, List<Object> values, long time) {
try {
redisTemplate.opsForList().leftPushAll(key, values);
if (time != CacheTime.CACHE_EXP_FOREVER) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 在列表尾部添加一个值
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return boolean
*/
public boolean rpush(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time != CacheTime.CACHE_EXP_FOREVER) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 在列表尾部添加多个值
*
* @param key 键
* @param values 值
* @param time 时间(秒)
* @return boolean
*/
public boolean rpush(String key, List<Object> values, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, values);
if (time != CacheTime.CACHE_EXP_FOREVER) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除列表元素
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lrem(String key, long count, Object value) {
try {
return redisTemplate.opsForList().remove(key, count, value);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引设置列表元素的值
*
* @param key 键
* @param index 索引
* @param value 值
* @return boolean
*/
public boolean lset(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 获取列表指定范围内的元素
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
* @return 元素列表
*/
@SuppressWarnings("unchecked")
public <T> List<T> lrange(String key, long start, long end) {
try {
return (List<T>) redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 通过索引获取列表中的元素
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
* @return
*/
public Object lindex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取列表长度
*
* @param key 键
* @return 列表长度
*/
public long llen(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0L;
}
}
/**
* 向有序集合添加一个成员,或者更新已存在成员的分数
*
* @param key 键
* @param time 时间(秒)
* @param member 成员
* @param score 分数
* @return
*/
public boolean zadd(String key, long time, Object member, double score) {
try {
boolean ret = redisTemplate.opsForZSet().add(key, member, score);
if (time != CacheTime.CACHE_EXP_FOREVER) {
expire(key, time);
}
return ret;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除有序集合中的一个或多个成员
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long zrem(String key, Object... values) {
try {
return redisTemplate.opsForZSet().remove(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0L;
}
}
/**
* 通过索引区间返回有序集合成指定区间内的成员 分数从低到高
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
* @return 成员集合
*/
public Set<Object> zrange(String key, long start, long end) {
try {
return redisTemplate.opsForZSet().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 通过索引区间返回有序集合成指定区间内的成员 分数从高到低
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
* @return 成员集合
*/
public Set<Object> zrevrange(String key, long start, long end) {
try {
return redisTemplate.opsForZSet().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 返回有序集合中某个成员的分数值
*
* @param key 键
* @param member 成员
* @return 分数值
*/
public double zscore(String key, Object member) {
try {
return redisTemplate.opsForZSet().score(key, member);
} catch (Exception e) {
e.printStackTrace();
return 0.0;
}
}
/**
* 判断有序集合中某个成员是否存在
*
* @param key 键
* @param member 成员
* @return true 存在 false不存在
*/
public boolean zexist(String key, Object member) {
try {
return null != redisTemplate.opsForZSet().score(key, member);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 获取有序集合的成员数
*
* @param key 键
* @return 成员数
*/
public long zlen(String key) {
try {
return redisTemplate.opsForZSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0L;
}
}
}
2.4 添加 Redis 常用配置项
在 application.properties 文件中的添加 Redis 相关的配置项:
# 数据库索引(默认为0)
spring.redis.database = 1
# 服务器地址
spring.redis.host = 127.0.0.1
# 服务器连接端口
spring.redis.port = 6379
# 服务器连接密码(默认为空)
spring.redis.password =
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait = -1
# 连接超时时间(毫秒)
spring.redis.timeout = 3000
# 连接池最大连接数
spring.redis.jedis.pool.max-active = 8
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle = 8
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle = 1
2.5 Redis 缓存测试
① 首先在 DemoService 中注入 RedisClient ,修改 test 方法将 user 对象以 user:1 为键存放到 Redis 中。
Redis 开发规范:https://yq.aliyun.com/articles/531067
package com.example.demo.biz.service.impl;
import com.example.demo.biz.service.DemoService;
import com.example.demo.common.redis.CacheTime;
import com.example.demo.common.redis.RedisClient;
import com.example.demo.dao.entity.UserDO;
import com.example.demo.dao.mapper.business.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author linjian
* @date 2019/1/15
*/
@Service
public class DemoServiceImpl implements DemoService {
@Autowired
private UserMapper userMapper;
@Autowired
private RedisClient redisClient;
@Override
public String test() {
UserDO user = userMapper.selectById(1);
redisClient.set("user:1", user, CacheTime.CACHE_EXP_FIVE_MINUTES);
return user.toString();
}
}
② 之后使用 Redis Desktop Manager 客户端连接 Redis 服务器,选择数据库「 1 」,查看刚存放的缓存。
三、结语
至此 Spring Boot 集成 Redis 的具体步骤介绍完毕,我们自定义了 Redis 的序列化方式,并通过一个简单的例子测试了 Redis 的可用性,相关代码已同步至 GitHub 。
Spring Boot 项目实战(四)集成 Redis的更多相关文章
- Spring Boot 项目实战(五)集成 Dubbo
一.前言 上篇介绍了 Redis 的集成过程,可用于解决热点数据访问的性能问题.随着业务复杂度的提高,单体应用越来越庞大,就好比一个类的代码行数越来越多,分而治之,切成多个类应该是更好的解决方法,所以 ...
- Spring Boot 项目学习 (四) Spring Boot整合Swagger2自动生成API文档
0 引言 在做服务端开发的时候,难免会涉及到API 接口文档的编写,可以经历过手写API 文档的过程,就会发现,一个自动生成API文档可以提高多少的效率. 以下列举几个手写API 文档的痛点: 文档需 ...
- Spring Boot 1.5.4集成Redis
本文示例源码,请看这里: 如何安装与配置Redis,请看这里 首先添加起步依赖: <dependency> <groupId>org.springframework.boot& ...
- Spring Boot 项目实战(六)集成 Apollo
一.前言 上篇介绍了 Spring Boot 集成 Dubbo,使我们的系统打下了分布式的基础.随着程序功能的日益复杂,程序的配置日益增多:各种功能开关.参数配置.服务器地址等:对程序配置的期望值也越 ...
- Spring Boot 项目实战(三)集成 Swagger 及 JavaMelody
一.前言 上篇介绍了 Logback 的集成过程,总体已经达到了基本可用的项目结构.本篇主要介绍两个常用工具,接口文档工具 Swagger .项目监控工具 JavaMelody 的集成步骤. 二.Sw ...
- spring boot 学习(十四)SpringBoot+Redis+SpringSession缓存之实战
SpringBoot + Redis +SpringSession 缓存之实战 前言 前几天,从师兄那儿了解到EhCache是进程内的缓存框架,虽然它已经提供了集群环境下的缓存同步策略,这种同步仍然需 ...
- Spring Boot 项目实战(二)集成 Logback
一.前言 上篇介绍了 Spring Boot Maven 多模块项目的搭建方法以及 MyBatis 的集成.通常在调试接口或者排查问题时我们主要借助于日志,一个设计合理的日志文件配置能大大降低我们的排 ...
- Spring Boot 2 实战:利用Redis的Geo功能实现查找附近的位置
1. 前言 老板突然要上线一个需求,获取当前位置方圆一公里的业务代理点.明天上线!当接到这个需求的时候我差点吐血,这时间也太紧张了.赶紧去查相关的技术选型.经过一番折腾,终于在晚上十点完成了这个需求. ...
- Spring Boot 项目实战(一)Maven 多模块项目搭建
一.前言 最近公司项目准备开始重构,框架选定为 Spring Boot ,本篇主要记录了在 IDEA 中搭建 Spring Boot Maven 多模块项目的过程. 二.软件及硬件环境 macOS S ...
随机推荐
- Confluence 6 附件存储选项
在早期的 Confluence 版本中,我们允许存储附件到 WebDav 或者 Confluence 数据库中.针对新的 Confluence 安装,我们不再支持这 2 种存储了. 本地文件系统 在默 ...
- 在 Confluence 6 中禁用 workbox 应用通知
如果你选择 不提供应用通知(does not provide in-app notifications): Confluence workbox 图标将不会可见同时用户也不能在这个服务器上访问 wor ...
- js new一个函数和直接调用函数的区别
用new和调用一个函数的区别:如果函数返回值是一个值类型(Number.String.Boolen)时,new函数将会返回这个函数的实例对象,而如果这个函数的返回值是一个引用类型(Object.Arr ...
- SpringBoot实现异步
1.创建AsyncTest类 package com.cppdy.service; import org.springframework.scheduling.annotation.Async; im ...
- D3.js+Es6+webpack构建人物关系图(力导向图),动态更新数据,点击增加节点,拖拽增加连线...
觉得不错的麻烦加个Star:https://github.com/zhangzn3/D3-Es6 在线预览地址:https://zhangzn3.github.io/D3-Es6 功能列表:1. 增加 ...
- Redis创建集群报错
Redis创建集群报错: 1:任何一个集群节点中都不能存在数据,如果有备份一下删除掉aof文件或rdb文件 2: nodes-集群端口.conf 文件存的会有报错记录,所以该文件也要删除
- python selenium-webdriver 生成测试报告
测试最后的一个重要的过程就是生成一份完整的测试报告,生成测试报告的主要是通过python的一个第三方模块HTMLTestRunner.py生成,但是生成的测试报告不是特别的美观,而且没有办法统计测试结 ...
- zeromq的安装,部署(号称最快的消息队列,消息中间件)
1:Storm作为一个实时处理的框架,产生的消息需要快速的进行处理,比如存在消息队列ZeroMQ里面. 由于消息队列ZeroMQ是C++写的,而我们的程序是运行在JVM虚拟机里面的.所以需要jzmq这 ...
- debian 下deb包的制作
http://page.renren.com/601230663/note/817856769?op=next&curTime=1333642042000
- jmeter4.x centos7部署笔记
1. jmeter依赖 java8或以上版本 安装 java : 参考 https://tecadmin.net/install-java-8-on-centos-rhel-and-fedora/ ...