Redis 19 SpringBoot集成
概述
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集成的更多相关文章
- 【redis】-- springboot集成redis及使用
springboot自动配置的redis并不是特别好用,所以需要我们使用原生的jedis, 1.添加依赖 2.在application文件中配置 # Redis服务器地址 redis.host= # ...
- springboot集成redis(mybatis、分布式session)
安装Redis请参考:<CentOS快速安装Redis> 一.springboot集成redis并实现DB与缓存同步 1.添加redis及数据库相关依赖(pom.xml) <depe ...
- 【springBoot】springBoot集成redis的key,value序列化的相关问题
使用的是maven工程 springBoot集成redis默认使用的是注解,在官方文档中只需要2步; 1.在pom文件中引入即可 <dependency> <groupId>o ...
- SpringBoot集成redis的key,value序列化的相关问题
使用的是maven工程 springBoot集成redis默认使用的是注解,在官方文档中只需要2步; 1.在pom文件中引入即可 <dependency> <groupId>o ...
- Windows环境下springboot集成redis的安装与使用
一,redis安装 首先我们需要下载Windows版本的redis压缩包地址如下: https://github.com/MicrosoftArchive/redis/releases 连接打开后如下 ...
- springBoot集成Redis遇到的坑(择库)源码分析为什么择库失败
提示: springboot提供了一套链接redis的api,也就是个jar包,用到的连接类叫做LettuceConnectionConfiguration,所以我们引入pom时是这样的 <de ...
- 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 ...
- SpringBoot | 集成Redis
Windows下安装: https://github.com/MicrosoftArchive/redis/releases zip下就解包到自定义目录下,msi就跟着步骤安装 进入安装目录下运行命令 ...
- springboot集成redis使用redis作为session报错ClassNotFoundException类RememberMeServices
springboot 集成redis使用redis作为缓存,会报错的问题. 错误信息: java.lang.IllegalStateException: Error processing condit ...
- 基于Springboot集成security、oauth2实现认证鉴权、资源管理
1.Oauth2简介 OAuth(开放授权)是一个开放标准,允许用户授权第三方移动应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容,OAu ...
随机推荐
- AI 让观众成为 3D 版《老友记》的导演了?
<老友记>上线 3D 版了? 允许用户旋转镜头,且从近景切换到全景观看故事? 今年出炉的 3D 方向 AI 项目 SitCom3D,能够自动补齐<老友记>原剧中的三维拍摄空间, ...
- 第122篇: JS函数一些基本概念
好家伙,本篇为<JS高级程序设计>第十章"函数"学习笔记 1.函数的三种定义方式:函数表达式.函数声明及箭头函数 函数声明: function sum(a) { ret ...
- SpringMvc-<context:component-scan>使用说明
在xml配置了这个标签后,spring可以自动去扫描base-package下面或者子包下面的java文件,如果扫描到有@Component @Controller@Service等这些注解的类,则把 ...
- linux系统信息命令笔记
1,时间和日期 2,磁盘信息 4,进程概念介绍 4.1,ps 基本命令使用 ps aux 显示内容太多了.一般用ps a 或 ps au 4.2, top命令的基本使用 top 可以动态的显示运行中的 ...
- linux用户权限相关命令笔记
1,用户 和 权限 的基本概念 1.1 ls 扩展 ls -l 1.2 chmod 简单使用(重要) + 是加权限, - 是减权限 chmod 修改文件权限 chmod 修改目录权限: 想要在目录下执 ...
- 50条MAUI踩坑记
1. 目录结构: (1)_imports.razor是一个全局using namespace的地方 (2)Platforms下的代码,虽然都放在同一个项目下,但是Platforms\Android下的 ...
- Android Handler实现子线程与子线程、主线程之间通信
一.子线程向主线程传值: 首选在主线程里创建一个Handler 1 Handler mHandler = new Handler(){ 2 @Override 3 public void handle ...
- 2022年RPA行业发展十大趋势,六千字长文助你看懂RPA
2022年RPA行业发展十大趋势,六千字长文助你看懂RPA 2022年RPA行业如何发展?十大趋势助你看懂RPA行业未来 这里有2022年RPA行业发展的十大趋势,关注RPA的朋友定要收藏! 文/王吉 ...
- 3、zookeeper在java使用的API
引入maven包 <dependency> <groupId>com.101tec</groupId> <artifactId>zkclient< ...
- XAF EFCore 示例
前言 在DEV官方建议创建新的XAF项目推荐选择EFCore时,我也第一时间创建了XAF的EFCore项目,这也是我第一次创建这个类型的项目,之前一直使用XPO,避免不了要对比一下.如果熟悉XPO但不 ...