概述

SpringBoot 整合 Redis 是使用 SpringData 实现的。

SpringData 是与 SpringBoot 齐名的顶级项目,整合了对常用数据库的模板型操作。

在 SpringBoot 2.x 之后,Jedis 被 Lettuce 替代了。

Jedis

采用的直连,多个线程操作的话,是不安全的。

如果想要避免不安全,就要使用 Jedis pool 连接池解决。

这样是有一些弊端的,比如线程数量太多了,Redis 服务就比较庞大,而且它是阻塞的。

Lettuce

底层采用 Netty,实例可以在多个线程中进行共享。

不存在线程不安全的情况,可以减少线程数量。

使用

导入依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

yaml 配置

spring:
redis:
host: 47.100.222.85
port: 6379
jedis:
pool:
max-active: 8
max-wait: -1ms
max-idle: 500
min-idle: 0
lettuce:
shutdown-timeout: 0ms

测试

@Resource
private RedisTemplate redisTemplate; @Test
void contextLoads() {
redisTemplate.opsForValue().set("myKey", "myValue");
System.out.println(redisTemplate.opsForValue().get("myKey"));
}

输出为:myValue,连接成功。

源码分析

RedisAutoConfiguration

@AutoConfiguration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
} @Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// 默认的 RedisTemplate 没有过多的设置,Redis 对象都是需要序列化的
// 两个泛型都是 Object 的类型,我们使用需要强制转换,很不方便,预期是 <String, Object>
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
} @Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
// 由于 String 是 Redis 最常使用的类型,所以说单独提出来了一个 Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
return new StringRedisTemplate(redisConnectionFactory);
}
}

通过源码可以看出,SpringBoot 自动帮我们在容器中生成了一个 RedisTemplate 和一个 StringRedisTemplate。

但是,这个 RedisTemplate 的泛型是 <Object, Object>,写代码不方便,需要写好多类型转换的代码。

我们需要一个泛型为 <String, Object> 形式的 RedisTemplate。

并且,这个 RedisTemplate 没有设置数据存在 Redis 时,key 及 value 的序列化方式。

由 @ConditionalOnMissingBean 可以看出,如果 Spring 容器中有了自定义的 RedisTemplate 对象,自动配置的 RedisTemplate 不会实例化。

因此我们可以直接自己写个配置类,配置 RedisTemplate。

集成

Redis 配置类

import cn.hutool.core.date.DatePattern;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter; /**
* Redis 配置类
*/
@Configuration
@EnableCaching
@AutoConfigureBefore(RedisAutoConfiguration.class)
public class RedisConfig extends CachingConfigurerSupport { @Value("${spring.redis.database}")
private int database; @Bean
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 设置数据库
connectionFactory.setDatabase(database);
// 刷新配置
connectionFactory.afterPropertiesSet();
// 重置连接
connectionFactory.resetConnection();
// 设置连接
template.setConnectionFactory(connectionFactory);
// 使用Jackson2JsonRedisSerialize 替换默认序列化
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.activateDefaultTyping(
om.getPolymorphicTypeValidator(),
ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.PROPERTY);
om.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
this.registerLocalDateTime(om);
serializer.setObjectMapper(om);
// key采用String的序列化方式
template.setKeySerializer(new StringRedisSerializer());
// value序列化方式采用jackson
template.setValueSerializer(serializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
// hash的value序列化方式采用jackson
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
// 添加对事务的支持
template.setEnableTransactionSupport(true);
return template;
} /**
* 处理时间类型
*
* @param objectMapper 待序列化对象
*/
private void registerLocalDateTime(ObjectMapper objectMapper) {
// 设置java.util.Date时间类的序列化以及反序列化的格式
objectMapper.setDateFormat(new SimpleDateFormat(DatePattern.NORM_DATETIME_PATTERN)); JavaTimeModule timeModule = new JavaTimeModule();
// LocalDateTime
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN);
timeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(dateTimeFormatter));
timeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(dateTimeFormatter));
// LocalDate
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN);
timeModule.addSerializer(LocalDate.class, new LocalDateSerializer(dateFormatter));
timeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(dateFormatter));
// LocalTime
DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN);
timeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(timeFormatter));
timeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(timeFormatter));
objectMapper.registerModule(timeModule); } }

直接用 RedisTemplate 操作 Redis,比较繁琐。

因此直接封装好一个 RedisUtils,这样写代码更方便点。

这个 RedisUtils 交给Spring容器实例化,使用时直接注解注入。

Redis 工具类

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnectionCommands;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit; /**
* Redis 工具类
*/
@Component
public class RedisUtils { private static RedisTemplate<String, Object> redisTemplate; @Autowired
public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
RedisUtils.redisTemplate = redisTemplate;
} /**
* 连接测试
*
* @return 测试结果
*/
public static String ping() {
return redisTemplate.execute(RedisConnectionCommands::ping);
} /**
* 切换数据库
*
* @param dbIndex 数据库编号(默认有16个数据库,编号在0-15之间)
*/
public static void select(int dbIndex) {
// 获取连接工厂
LettuceConnectionFactory connectionFactory = (LettuceConnectionFactory) redisTemplate.getConnectionFactory();
if (ObjUtil.isNull(connectionFactory)) {
return;
}
// 设置数据库
connectionFactory.setDatabase(dbIndex);
// 刷新配置
connectionFactory.afterPropertiesSet();
redisTemplate.setConnectionFactory(connectionFactory);
} /**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒),指定为-1代表永久有效
*/
public static boolean expire(String key, long time) {
return Boolean.TRUE.equals(redisTemplate.expire(key, time, TimeUnit.SECONDS));
} /**
* 根据key获取过期时间
*
* @param key 键
* @return 时间(秒)。-1:永久有效;-2:该键已过期或不存在
*/
public static long getExpire(String key) {
Long expire = redisTemplate.getExpire(key, TimeUnit.SECONDS);
return ObjUtil.isNull(expire) ? 0 : expire;
} /**
* 判断key是否存在
*
* @param key 键
* @return 结果
*/
public static boolean has(String key) {
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
} /**
* 删除缓存
*
* @param key 键,可以传一个或多个值
*/
public static void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollUtil.toList(key));
}
}
} /**
* 字符串缓存取值(不能获取token值,因为其自带加密,无法解析)
*
* @param key 键
* @return 值
*/
public static Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
} /**
* 字符串缓存设值
*
* @param key 键
* @param value 值
*/
public static void set(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
} /**
* 只有在 key 不存在时设置 key 的值
*
* @param key 键
* @param value 值
*/
public static void setIfAbsent(String key, String value) {
redisTemplate.opsForValue().setIfAbsent(key, value);
} /**
* 字符串缓存设值并设置生效时间
*
* @param key 键
* @param value 值
* @param time 时间(秒),time要大于0,如果time小于等于0,将设置无限期
*/
public static void set(String key, Object value, long time) {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
} /**
* 递增
*
* @param key 键
* @param num 要增加几
*/
public static long inc(String key, long num) {
Long increment = redisTemplate.opsForValue().increment(key, num);
return increment == null ? 0 : increment;
} /**
* 递减
*
* @param key 键
* @param num 要减少几
*/
public static long dec(String key, long num) {
Long decrement = redisTemplate.opsForValue().decrement(key, num);
return decrement == null ? 0 : decrement;
} /**
* 获取list缓存的内容
*
* @param key 键
* @return 列表
*/
public static List<Object> getList(String key) {
return redisTemplate.opsForList().range(key, 0, -1);
} /**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束。0到-1代表所有值
*/
public static List<Object> getList(String key, long start, long end) {
return redisTemplate.opsForList().range(key, start, end);
} /**
* 获取list缓存的长度
*
* @param key 键
*/
public static long getListSize(String key) {
Long size = redisTemplate.opsForList().size(key);
return size == null ? 0 : size;
} /**
* 获取list中的值
*
* @param key 键
* @param index 索引index>=0时,0:表头,1:第二个元素,依次类推;<br>
* 索引index<0时,-1:表尾,-2:倒数第二个元素,依次类推
*/
public static Object getList(String key, long index) {
return redisTemplate.opsForList().index(key, index);
} /**
* 将list放入缓存
*
* @param key 键
* @param value 值
*/
public static void setList(String key, Object value) {
redisTemplate.opsForList().rightPush(key, value);
} /**
* 将list放入缓存并设置生效时间
*
* @param key 键
* @param value 值
* @param time 时间(秒)
*/
public static void setList(String key, Object value, long time) {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0) {
expire(key, time);
}
} /**
* 将list放入列表缓存
*
* @param key 键
* @param value 值
*/
public static void setList(String key, List<Object> value) {
redisTemplate.opsForList().rightPushAll(key, value);
} /**
* 将list放入列表缓存并设置生效时间
*
* @param key 键
* @param value 值
* @param time 时间(秒)
*/
public static void setList(String key, List<Object> value, long time) {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0) {
expire(key, time);
}
} /**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
*/
public static void setList(String key, long index, Object value) {
redisTemplate.opsForList().set(key, index, value);
} /**
* 移除n个值为value的
*
* @param key 键
* @param count 移除多少个
* @param value 值
*/
public static void delList(String key, long count, Object value) {
redisTemplate.opsForList().remove(key, count, value);
} /**
* 左弹出list缓存的内容
*
* @param key 键
*/
public static void popList(String key) {
redisTemplate.opsForList().leftPop(key);
} /**
* 根据key获取Set中的所有值
*
* @param key 键
*/
public static Set<Object> getSet(String key) {
return redisTemplate.opsForSet().members(key);
} /**
* 随机获取变量中的元素
*
* @param key 键
* @return value值
*/
public static Object randomSet(String key) {
return redisTemplate.opsForSet().randomMember(key);
} /**
* 获取set缓存的长度
*
* @param key 键
*/
public static long getSetSize(String key) {
Long size = redisTemplate.opsForSet().size(key);
return size == null ? 0 : size;
} /**
* 将数据放入set缓存
*
* @param key 键
* @param values 值,可以是多个
*/
public static void setSet(String key, Object... values) {
redisTemplate.opsForSet().add(key, values);
} /**
* 将set数据放入缓存并设置生效时间
*
* @param key 键
* @param time 时间(秒)
* @param values 值,可以是多个
*/
public static void setSetTime(String key, long time, Object... values) {
redisTemplate.opsForSet().add(key, values);
if (time > 0) {
expire(key, time);
}
} /**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return 结果
*/
public static boolean hasSet(String key, Object value) {
return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(key, value)); } /**
* 移除值为value的
*
* @param key 键
* @param values 值,可以是多个
*/
public static void delSet(String key, Object... values) {
redisTemplate.opsForSet().remove(key, values);
} /**
* 添加元素,有序集合是按照元素的score值由小到大排列
*
* @param key 键
* @param value 对象名称
* @param score 数据值
*/
public static void setZset(String key, TypedTuple<Object> value, double score) {
redisTemplate.opsForZSet().add(key, value, score);
} /**
* 添加元素,向集合中插入多个元素
*
* @param key 键
* @param values set集合
*/
public static void setZset(String key, Set<TypedTuple<Object>> values) {
redisTemplate.opsForZSet().add(key, values);
} /*
* 获取元素集合,从小到大排序
* @param key
* @param start 开始位置
* @param end 结束位置, -1查询所有
* @return 元素集合
*/
public static Set<Object> getZset(String key, long start, long end) {
return redisTemplate.opsForZSet().range(key, start, end);
} /**
* 从集合中删除指定元素
*
* @param key 键
* @param values 值
*/
public static void delZset(String key, Object... values) {
redisTemplate.opsForZSet().remove(key, values);
} /**
* 增加元素的score值,并返回增加后的值
*
* @param key 键
* @param value 值
* @param num 增加多少score值
* @return 增加后的score值
*/
public static double incScoreZset(String key, String value, double num) {
Double result = redisTemplate.opsForZSet().incrementScore(key, value, num);
return ObjUtil.isNull(result) ? 0 : result;
} /**
* 返回元素在集合的排名,有序集合是按照元素的score值由小到大排列
*
* @param key 键
* @param value 值
* @return 0表示第一位
*/
public static long rankZset(String key, Object value) {
Long result = redisTemplate.opsForZSet().rank(key, value);
return ObjUtil.isNull(result) ? 0 : result;
} /**
* 获取集合大小
*
* @param key 键
* @return 集合大小
*/
public static long getZsetSize(String key) {
Long result = redisTemplate.opsForZSet().size(key);
return ObjUtil.isNull(result) ? 0 : result;
} /**
* 获取键对应的所有值
*
* @param key 键
* @return 对应的多个键值
*/
public static Map<Object, Object> getHash(String key) {
return redisTemplate.opsForHash().entries(key);
} /**
* 根据键和项取值
*
* @param key 键
* @param item 项
*/
public static Object getHash(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
} /**
* 设值
*
* @param key 键
* @param map 对应多个键值
*/
public static void setHash(String key, Map<String, Object> map) {
redisTemplate.opsForHash().putAll(key, map);
} /**
* 设值
*
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
*/
public static void setHash(String key, Map<String, Object> map, long time) {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
} /**
* 向一张hash表中放入数据,如果不存在则创建
*
* @param key 键
* @param item 项
* @param value 值
*/
public static void setHash(String key, String item, Object value) {
redisTemplate.opsForHash().put(key, item, value);
} /**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
*/
public static void setHash(String key, String item, Object value, long time) {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
} /**
* 判断hash表中是否有该项的值
*
* @param key 键,不能为null
* @param item 项,不能为null
* @return 结果
*/
public static boolean hasHash(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
} /**
* 删除hash表中的值
*
* @param key 键,不能为null
* @param item 项,可以是多个,不能为null
*/
public static void delHash(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
} /**
* hash递增,如果不存在,就会创建一个,并把新增后的值返回
*
* @param key 键
* @param item 项
* @param num 要增加几(大于0)
*/
public static double incHash(String key, String item, double num) {
return redisTemplate.opsForHash().increment(key, item, num);
} /**
* hash递减
*
* @param key 键
* @param item 项
* @param num 要减少几(小于0)
*/
public static double decHash(String key, String item, double num) {
return redisTemplate.opsForHash().increment(key, item, -num);
} }

参考

https://www.bilibili.com/video/BV1S54y1R7SB?spm_id_from=333.999.0.0

版本

6.2.6

Redis 19 SpringBoot集成的更多相关文章

  1. 【redis】-- springboot集成redis及使用

    springboot自动配置的redis并不是特别好用,所以需要我们使用原生的jedis, 1.添加依赖 2.在application文件中配置 # Redis服务器地址 redis.host= # ...

  2. springboot集成redis(mybatis、分布式session)

    安装Redis请参考:<CentOS快速安装Redis> 一.springboot集成redis并实现DB与缓存同步 1.添加redis及数据库相关依赖(pom.xml) <depe ...

  3. 【springBoot】springBoot集成redis的key,value序列化的相关问题

    使用的是maven工程 springBoot集成redis默认使用的是注解,在官方文档中只需要2步; 1.在pom文件中引入即可 <dependency> <groupId>o ...

  4. SpringBoot集成redis的key,value序列化的相关问题

    使用的是maven工程 springBoot集成redis默认使用的是注解,在官方文档中只需要2步; 1.在pom文件中引入即可 <dependency> <groupId>o ...

  5. Windows环境下springboot集成redis的安装与使用

    一,redis安装 首先我们需要下载Windows版本的redis压缩包地址如下: https://github.com/MicrosoftArchive/redis/releases 连接打开后如下 ...

  6. springBoot集成Redis遇到的坑(择库)源码分析为什么择库失败

    提示: springboot提供了一套链接redis的api,也就是个jar包,用到的连接类叫做LettuceConnectionConfiguration,所以我们引入pom时是这样的 <de ...

  7. Springboot集成mybatis(mysql),mail,mongodb,cassandra,scheduler,redis,kafka,shiro,websocket

    https://blog.csdn.net/a123demi/article/details/78234023  : Springboot集成mybatis(mysql),mail,mongodb,c ...

  8. SpringBoot | 集成Redis

    Windows下安装: https://github.com/MicrosoftArchive/redis/releases zip下就解包到自定义目录下,msi就跟着步骤安装 进入安装目录下运行命令 ...

  9. springboot集成redis使用redis作为session报错ClassNotFoundException类RememberMeServices

    springboot 集成redis使用redis作为缓存,会报错的问题. 错误信息: java.lang.IllegalStateException: Error processing condit ...

  10. 基于Springboot集成security、oauth2实现认证鉴权、资源管理

    1.Oauth2简介 OAuth(开放授权)是一个开放标准,允许用户授权第三方移动应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容,OAu ...

随机推荐

  1. 【转载】重装系统小贴士:ssh、vscode免密登录

    ssh安装 apt install openssh-server 然后到cd /etc/ssh下找sshd_config文件,打开把允许远程root登录的选项改为yes 重启ssh服务:/etc/in ...

  2. 【Azure Kubernetes】通过 kubelogin 进行非交互式登录AKS

    问题描述 当对AKS的登录方式(认证和授权)从"Local Account with Kubernetes RBAC "改变为"Azure AD authenticati ...

  3. 【Azure 存储服务】Azure Blob Storage SDK 升级失败,遇见 Unsatisfied Dependency Exception 和 Unexpected Length Exception

    问题描述 在升级Java Azure Blob Storage SDK的过程中,先后遇见了 UnsatisfiedDependencyException 和 UnexpectedLengthExcep ...

  4. Spring Cloud跟Dubbo区别?

    Spring Cloud是一个微服务框架,提供了微服务中很多功能组件,Dubbo一开始时RPC调用框架,核心是解决服务调用间的问题, Spring Cloud是一个大而全的框架,Dubbo更侧重于服务 ...

  5. [笔记]git pull vs git pull --rebase

    git pull vs git pull -rebase 背景 最近在实际开发过程,之前一直使用git pull 去更新该分支的代码,之前认为一旦pull 操作产生新的节点是对合并操作的一个记录,但是 ...

  6. Java面向对象(中)--super/多态/向下转型/equals/toString/包装类/单元测试工具

    java对象 方法重写 子类继承父类以后,可以对父类同名同参数的方法,进行覆盖操作 重写后,当创建子类对象以后,通过子类对象调用子父类中同名同参数的方法时,执行的是子类重写父类的方法. 如何区分方法重 ...

  7. 使用PdfSharp从模板生成Pdf文件

    ​ 最近在做一个生成文档的需求.通过先制作一个包含各字段占位符的文档模板,导入这个模板并填写内容替换掉占位符,再输出成最终文件. 由于版式固定,安全性更好,业务上常用Pdf作为最终标准化的格式, 在. ...

  8. HttpClientHandler VS SocketsHttpHandler

    .NET Framework 和 .NET Core 2.0 及更低版本中由 HttpClient 使用的默认消息处理程序为HttpClientHandler. 从 .NET Core 2.1 开始, ...

  9. C# DiagnosticSource and DiagnosticListener

    class Program { private static readonly DiagnosticSource testDiagnosticListener = new DiagnosticList ...

  10. Swagger (API框架,API 文档 与API 定义同步更新)

    1.依赖 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring ...