spring boot集成redis以及redis的自动装配

Redis java客户端

Jedis

  • Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持;
  • Jedis中的方法调用是比较底层的暴露的Redis的API,也即Jedis中的Java方法基本和Redis的API保持着一致,了解Redis的API,也就能熟练的使用Jedis。
  • Jedis在实现上是直接连接的redis server,如果在多线程环境下是非线程安全的,这个时候只有使用连接池,为每个Jedis实例增加物理连接(像BIO)
  • 使用阻塞的I/O,且其方法调用都是同步的,程序流需要等到sockets处理完I/O才能执行,不支持异步。Jedis仅支持基本的数据类型如:String、Hash、List、Set、Sorted Set。

为什么说Jedis是非线程安全的呢?

因为发送命令和获取返回值时使用全局变量RedisOutputStream和RedisInputStream(redis.clients.jedis.Connection类中)。

当不同的线程在set和get的时候,有可能会出现线程A的set()的响应流,被线程B的get()作为返回了,所以出现了线程安全问题。

通过JedisPool连接池去管理实例,在多线程情况下让每个线程有自己的独立的jedis实例,从而避免了线程安全问题。

Lettuce

  • Lettuce 是一个可伸缩的线程安全的 Redis 客户端,支持同步、异步和响应式模式。多个线程可以共享一个连接实例,而不必担心多线程并发问题。支持Redis 的高级功能,如 Sentinel,集群,流水线,自动重新连接和 Redis 数据模型
  • Lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问,因为StatefulRedisConnection是线程安全的,所以一个连接实例(StatefulRedisConnection)就可以满足多线程环境下的并发访问,当然这个也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例.
  • 高级Redis客户端,用于线程安全同步,异步和响应使用,支持集群,Sentinel,管道和编码器。
  • 基于Netty框架的事件驱动的通信层(NIO),其方法调用是异步的。Lettuce的API是线程安全的,所以可以操作单个Lettuce连接来完成各种操作。
  • springboot2.0之后,原来使用的Jedis 被替换为Lettuce

Redisson

  • Redisson实现了分布式和可扩展的Java数据结构,提供很多分布式相关操作服务,例如,分布式锁,分布式集合,可通过Redis支持延迟队列。和Jedis相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。Redisson的宗旨是促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。
  • Redisson中的方法则是进行比较高的抽象,每个方法调用可能进行了一个或多个Redis方法调用。
  • 基于Netty框架的事件驱动的通信层,其方法调用是异步的。Redisson的API是线程安全的,所以可以操作单个Redisson连接来完成各种操作。
  • Redisson不仅提供了一系列的分布式Java常用对象,基本可以与Java的基本数据结构通用,还提供了许多分布式服务,其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service)。

Lettuce实现步骤

引入依赖

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

application.properties 配置文件

spring.redis.host=xxx.xxx.xxx.xxx
spring.redis.port=6379
spring.redis.password=
#连接超时时间(毫秒)
spring.redis.timeout=10000ms
# 连接池最大连接数(使用负值表示没有限制) 默认 8
spring.redis.lettuce.pool.max-active=8
# 连接池中的最大空闲连接 默认 8
spring.redis.lettuce.pool.max-idle=8
# 连接池最大阻塞等待时间,单位毫秒(使用负值表示没有限制) 默认 -1
spring.redis.lettuce.pool.max-wait=1000ms
# 连接池中的最小空闲连接 默认 0
spring.redis.lettuce.pool.min-idle=0
spring.redis.lettuce.shutdown-timeout=100ms

SpringbootRedisApplicationTests :

自动注入容器中的redisTemplate 实例(在redis的自动装配里面会给spring容器注入一个redisTemplate实例),而redisTemplate 是封装了对redis数据库的一系列操作

@SpringBootTest
class SpringbootRedisApplicationTests {

    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;

    @Test
    void contextLoads() {
        System.out.println(redisTemplate);
        redisTemplate.opsForValue().set("name","xt");
        System.out.println(redisTemplate.opsForValue().get("name"));
    }
}

在这里插入图片描述
成功连接上服务器上的redis完成基本操作

在不是应用基本数据结构的情况下,比如实体类的情况下,我们需要对实体类进行序列化

User 类

(使用lombok插件,减少手写setter/getter,constructor方法)

@Component
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
    private String name;
    private int age;
    private String sex;
}

SpringbootRedisApplicationTests 新加一个test方法

 @Test
  public void test() throws JsonProcessingException {
      User user = new User("xt",23,"男");
      //将对象转化为json格式的字符串
      String jsonuser = new ObjectMapper().writeValueAsString(user);
      System.out.println(jsonuser);
      
      redisTemplate.opsForValue().set("userXT",jsonuser);
      System.out.println(redisTemplate.opsForValue().get("userXT"));

  }

在这里插入图片描述
如果不传入序列化后的key,value,或者不能实现序列化的key,value,就会引发以下异常
在这里插入图片描述
要缓存的JavaBean必须实现Serializable接口,因为Spring会将对象先序列化再存入 Redis
使用springboot-data-redis,默认情况下是使用org.springframework.data.redis.serializer.JdkSerializationRedisSerializer这个类来做序列化

所以你也可以给实体类上实现Serializable接口

public class User implements Serializable

自定义工具类

我们可以自己封装一个工具类,以redisTemplate的API为基础,再度封装。先在工具类中注入spring容器中的redisTemplate

@Component
public final class RedisUtil {

    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    // =============================common============================
    /**
     * 指定缓存失效时间
     * @param key  键
     * @param time 时间(秒)
     */
    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 getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }


    /**
     * 判断key是否存在
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(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((Collection<String>) CollectionUtils.arrayToList(key));
            }
        }
    }


    // ============================String=============================

    /**
     * 普通缓存获取
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */

    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 普通缓存放入并设置时间
     * @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 > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 递增
     * @param key   键
     * @param delta 要增加几(大于0)
     */
    public long incr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }


    /**
     * 递减
     * @param key   键
     * @param delta 要减少几(小于0)
     */
    public long decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }


    // ================================Map=================================

    /**
     * HashGet
     * @param key  键 不能为null
     * @param item 项 不能为null
     */
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 获取hashKey对应的所有键值
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet
     * @param key 键
     * @param map 对应多个键值
     */
    public boolean hmset(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
}

在使用的地方注入redisUtil 实例即可,@Component标注的RedisUtil, Spring 会帮我们生成redisUtil实例

@SpringBootTest
class SpringbootRedisApplicationTests {

    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;

    @Autowired
    private RedisUtil redisUtil;

    @Test
    void contextLoads() {
        System.out.println(redisTemplate);

        redisTemplate.opsForValue().set("name","xt");
        System.out.println(redisTemplate.opsForValue().get("name"));
    }

    @Test
    public void test() throws JsonProcessingException {
        User user = new User("xt",23,"男");
        //将对象转化为json格式的字符串
//        String jsonuser = new ObjectMapper().writeValueAsString(user);
//        System.out.println(jsonuser);
        redisTemplate.setKeySerializer(new StringRedisSerializer());

        redisTemplate.opsForValue().set("userXT", user);
        System.out.println(redisTemplate.opsForValue().get("userXT"));

    }

    @Test
    public void testRedisUtils(){
        System.out.println(redisUtil);
        User user = new User("xt",23,"男");
        redisUtil.set("userXT",user);

        System.out.println(redisUtil.get("userXT"));

    }


}

扩展阅读

META-INF/spring.factories文件的作用是什么
查看 spring-data-redis jar包的信息包文件夹META-INF下的 spring.factories,仅有一个 RepositoryFactorySupport 并无自动装配。

org.springframework.data.repository.core.support.RepositoryFactorySupport=org.springframework.data.redis.repository.support.RedisRepositoryFactory

在下载的jar包文件中,我们可以看到有个spring-boot-autoconfigure jar包
在 jar包的信息包文件夹META-INF中的spring.factories文件
里面有这样的一部分配置,key是org.springframework.boot.autoconfigure.EnableAutoConfiguration,value是各个以逗号隔开的*AutoConfiguration,结合spring-factories的运行原理,我们就可以知道所有的自动配置是从这里开始加载的。
可以在这个文件中找到redis的自动配置类RedisAutoConfiguration
在这里插入图片描述

把鼠标放到自动配置类RedisAutoConfiguration上 按 Ctrl-B 进入,看见他会帮我们new一个RedisTemplate实例(redisTemplate)放到spring的IOC容器中

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
    public RedisAutoConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean(
        name = {"redisTemplate"}
    )
    //当这个redisTemplate bean实例不存在,下面的这个方法就生效
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        return new StringRedisTemplate(redisConnectionFactory);
    }
}

@Configuration
用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。
proxyBeanMethods参数设置为false时即为:Lite 轻量级模式。该模式下注入容器中的同一个组件无论被取出多少次都是不同的bean实例,即多实例对象,在该模式下SpringBoot每次启动会跳过检查容器中是否存在该组件

什么时候用Full全模式,什么时候用Lite轻量级模式?
当在你的同一个Configuration配置类中,注入到容器中的bean实例之间有依赖关系时,建议使用Full全模式;当在你的同一个Configuration配置类中,注入到容器中的bean实例之间没有依赖关系时,建议使用Lite轻量级模式,以提高springboot的启动速度和性能

@ConditionalOnClass表示只有classpath中能找到RedisOperations.class时,RedisAutoConfiguration这个类才会被spring容器实例化,这其实也解释了为什么我们有时候只需要在pom.xml添加某些依赖包,某些功能就自动打开了

关与 @EnableConfigurationProperties 注解

@EnableConfigurationProperties引入了RedisProperties的配置,将使用了@ConfigurationProperties 注解的RedisProperties类生效,注入到spring的容器中

@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})

@EnableConfigurationProperties({RedisProperties.class})

Ctrl-B 进入RedisProperties类,我们可以看到他里面封装了很多属性,host和port有默认值
我们也可以发现application.properties中的前缀为spring.redis的配置是用在这里面的
注解@ConfigurationProperties
在这里插入图片描述
redis 配置由 springBoot 集成,在 spring-boot-autoconfigure 中配置,spring.factories 中 EnableAutoConfiguration 指定自动配置类 RedisAutoConfiguration 进行自动配置,spring-configuration-metadata.properties 指定了配置元数据 RedisAutoConfiguration ConditionalOnClass 配置条件 RedisOperations,当引入 spring-data-redis 时存在类路径 RedisOperations 进行 redis 的自动加载。redis 自动配置使用 Lettuce 作为 client 进行连接,配置了默认的 bean redisTemplate、StringRedisTemplate。

RedisTemplate

@ConditionalOnMissingBean表示在Spring容器中如果有一个Bean的name是redisTemplate那将不需要再执行被此注解修饰的代码块,也就是此方法。

如果我们自己写了一个redisTemplate bean,那么我们获取的就是自己写的那个bean 实例。

redisTemplate 里面通过set注入了一个redisConnectionFactory

    @Bean
    @ConditionalOnMissingBean(
        name = {"redisTemplate"}
    )
    //当这个redisTemplate bean实例不存在,下面的这个方法就生效
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

redisConnectionFactory是一个接口,里面提供了两个实现
在这里插入图片描述
分别是JedisConnectionFactory 和 LettuceConnectionFactory

进入JedisConnectionFactory类,点击右上角悬浮条下载源码
可以发现很多类会爆红,无法引入(springboot 2.0 已经抛弃 Jedis)
在这里插入图片描述
但是LettuceConnectionFactory不会出现这种情况
在这里插入图片描述

RedisTemplate 序列化

要缓存的JavaBean必须实现Serializable接口,因为Spring会将对象先序列化再存入 Redis

RedisTemplate 有如下字段需要序列化

@SuppressWarnings("rawtypes") private @Nullable RedisSerializer keySerializer = null;
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer valueSerializer = null;
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer hashKeySerializer = null;
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer hashValueSerializer = null;

默认的序列化方式是JDK序列化

 if (defaultSerializer == null) {
	defaultSerializer = new JdkSerializationRedisSerializer(
			classLoader != null ? classLoader : this.getClass().getClassLoader());
}

if (enableDefaultSerializer) {
	if (keySerializer == null) {
		keySerializer = defaultSerializer;
		defaultUsed = true;
	}
	if (valueSerializer == null) {
		valueSerializer = defaultSerializer;
		defaultUsed = true;
	}
	if (hashKeySerializer == null) {
		hashKeySerializer = defaultSerializer;
		defaultUsed = true;
	}
	if (hashValueSerializer == null) {
		hashValueSerializer = defaultSerializer;
		defaultUsed = true;
	}
}

当然你也可以自己修改redisTemplate指定的序列化实现类

public void setKeySerializer(RedisSerializer<?> serializer) {
	this.keySerializer = serializer;
}

RedisSerializer是一个Redis序列化的接口,我们可以看到这个接口又有很多的实现类
在这里插入图片描述

References:

  • https://blog.csdn.net/liujun03/article/details/82891784
  • https://www.bilibili.com/video/BV1S54y1R7SB?p=25
  • https://xie.infoq.cn/article/59431718520a87b1a59b5ef9a
  • https://cloud.tencent.com/developer/article/1500854
  • https://www.cnblogs.com/myitnews/p/13733882.html
  • https://blog.csdn.net/song_java/article/details/86509971
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据提供的引用内容,spring-boot-starter-data-redisSpring Boot中用于自动装配Redis的starter包。它包含了自动装配所需的类和注解等。当我们在项目的pom.xml文件中引入spring-boot-starter-data-redis包时,Spring Boot自动根据配置文件中的相关配置信息来完成Redis自动装配。 具体来说,spring-boot-starter-data-redis使用了RedisAutoConfiguration类来实现自动装配。该类通过读取配置文件中的相关配置信息,例如主机名、端口号、密码等,来创建Redis连接工厂和RedisTemplate等实例。这些实例可以在应用程序中直接使用,而无需手动配置和初始化。 下面是一个示例代码,展示了如何使用spring-boot-starter-data-redis进行自动装配: ```java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.data.redis.core.RedisTemplate; @SpringBootApplication public class RedisApplication { private final RedisTemplate<String, String> redisTemplate; public RedisApplication(RedisTemplate<String, String> redisTemplate) { this.redisTemplate = redisTemplate; } public static void main(String[] args) { SpringApplication.run(RedisApplication.class, args); } // 在需要使用Redis的地方,可以直接注入RedisTemplate实例,并进行操作 // 例如: // redisTemplate.opsForValue().set("key", "value"); // String value = redisTemplate.opsForValue().get("key"); } ``` 通过上述代码,我们可以看到,在Spring Boot应用程序中,我们只需要在需要使用Redis的地方注入RedisTemplate实例,就可以直接使用Redis的相关操作方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值