SpringBoot2.1版本的个人开发框架 —— 七(集成Redis Redis工具类)

前言

本篇作为SpringBoot2.1版本的个人开发框架 子章节,请先阅读SpringBoot2.1版本的个人开发框架再次阅读本篇文章

掘金笔记地址

Redis的安装,可以参考我的学习笔记:https://blog.csdn.net/qq_36956154/article/details/84339285

项目已发布到GitHub:   项目地址,如果有帮助到你,点一点star,感谢

上一篇:日志及全局异常处理

下一篇:spring security学习笔记

七、关于为什么要用Redis的简述

win10安装redis和liunx安装redis

项目中为什么要用Redis缓存?其实在我实习的项目中是有Redis缓存的,但是我只是知道用到了Redis,如何使用的,为什么要用,这些我统统都不知道。引用网上一句话就是,我这次集成也是为了Redis而Redis了,并不是拿Redis来解决实际的问题,但是我觉得只有先学会了,才能知道在什么情况下可以用Redis来解决问题。

为什么要用Redis:

1.解决应用服务器的cpu和内存压力

2.在项目中使用 Redis,主要考虑两个角度:性能和并发。

3.我们在碰到需要执行耗时特别久,且结果不频繁变动的 SQL,就特别适合将运行结果放入缓存。这样,后面的请求就去缓存中读取,使得请求能够迅速响应。

4.在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常。这个时候,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问数据库。

5.排行榜及相关问题。排行榜(leader board)按照得分进行排序。zadd命令可以直接实现这个功能,而zrevrange命令可以用来按照得分来获取前100名的用户,zrank可以用来获取用户排名,非常直接而且操作容易。

6.计数的问题,比如点赞和转发数,通过原子递增保持计数;getset用来重置计数器;过期属性用来确认一个关键字什么时候应该删除。

而且Redis可以master-slave(主从)模式进行数据备份,在分布式的系统中,可以很好保证数据的备份,Redis会自动把主数据库(master)中的数据备份到从数据库(slave)中,关于为什么要用Redis这件事情上,除了本身自己项目中的原因;剩下的有很多原因都可以事先在网络上汲取知识以及经验。

7.1.SpringBoot的缓存支持

Spring Data Redis官方文档:https://docs.spring.io/spring-data/redis/docs/2.1.2.RELEASE/reference/html/

我们把缓存的事情放到ywh-starter-cache这个模块中来集成,在cache中的pom.xml文件中引入spring-boot-starter-data-redis依赖

<!-- redis依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>2.1.1.RELEASE</version>
</dependency>

7.2.源码级分析

为什么用户需要自己创建一个redis的配置类呢?

SpringBoot提供了对Redis的自动配置功能,在**RedisAutoConfiguration**类中默认为我们配置了客户端连接(Lettuce和Jedis),以及数据操作模板(StringRedisTemplate和RedisTemplate),下列代码有一个@ConditionalOnMissingBean和@Bean的注解,@ConditionalOnMissingBean注解判断是否执行初始化代码,即如果用户已经创建了bean,则相关的初始化代码不再执行。这导致了默认的是redisTemplate方法会被执行。

 

我们看redisTemplate这个方法和返回的RedisTemplate这个类,如下图:RedisTemplate类中有一个private RedisSerializer<?> defaultSerializer 这个私有属性,默认为NULL;所以默认的序列化是以JdkSerializationRedisSerializer的二进制数据序列化方式,据说这个会把数据变得看不懂。所以用户才需要创建一个RedisCacheConfig类来覆盖默认的配置。

如果我有分析的有不对的地方,望指正。

7.3.简单的连接

分析过后,就以两种方式来进行连接。第一种:不更改默认配置使用StringRedisTemplate和RedisTemplate

在我们写代码测试之前需要在cache模块中配置application-redis.yml文件,并且把core下的application.yml文件中的active属性添加redis 以逗号相隔这样就可在运行的时候读取application-redis.yml的内容,把application.properties修改成.yml文件进行配置。

spring:
  redis:
    # Redis数据库索引(默认为0)
    database: 0
    # Redis服务器地址
    host: 127.0.0.1
    # Redis服务器连接端口
    port: 6379
    # Redis服务器连接密码(默认为空)如果没有配置密码就不要写这个属性了
    password: 123456
    #连接池
    lettuce:
      pool:
        #连接池最大连接数(使用负值表示没有限制)
        max-active: 8
        #连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: 60000
        #连接池中的最大空闲连接
        max-idle: 8
        #连接池中的最小空闲连接
        min-idle: 0
    #连接超时时间(毫秒)
    timeout: 10000

在测试类中引入以下代码并运行就可以连接到Redis中,并且添加数据。

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 测试连接redis,并存入数据
     */
    @Test
    public void redisTest(){
        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        stringRedisTemplate.opsForValue().set("abc","测试");
        stringRedisTemplate.opsForList().leftPushAll("ywh",list);

    }

我们在用StringRedisTemplate添加的数据显示正常,也是我们人眼能读懂的,接下来我们用RedisTemplate这个类来连接Redis添加数据看一看数据是什么样的,默认的序列化JdkSerializationRedisSerializer的二进制数据序列化方式,代码跟上面差不多

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    @Test
    public void redisTest1(){
        List<String> list = new ArrayList<>();
        list.add("y");
        list.add("w");
        list.add("h");
        redisTemplate.opsForValue().set("redisTemplate","连接成功了");
        redisTemplate.opsForList().leftPushAll("redis",list);
    }

果然跟据说的情况一样,添加的数据我们人眼分辨不出来这是什么数据,所以下面我们要进行覆盖默认的配置,定制自己的序列化方式。

7.4.覆盖默认配置进行连接

在cache下创建serializer包和config包,spring为我们提供了四种序列化方式,都在org.springframework.data.redis.serializer包下,分别是:

JdkSerializationRedisSerializer、GenericJackson2JsonRedisSerializer、StringRedisSerializer、Jackson2JsonRedisSerializer。

这四种我们只使用StringRedisSerializer来序列化Key值,value值由我们自己创建的序列化类,serializer包下创建我们自定义的FastJsonRedisSerializer类,需要实现RedisSerializer接口,实现接口中的序列化方法和反序列化方法,使用的是alibaba的Fastjson实现。

package com.ywh.cache.serializer;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import org.springframework.util.Assert;

import java.nio.charset.Charset;

/**
 * CreateTime: 2018-12-19 16:51
 * ClassName: FastJsonRedisSerializer
 * Package: com.ywh.cache.serializer
 * Describe:
 * 自定义的序列化类
 * @author YWH
 */
public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
    private ObjectMapper objectMapper = new ObjectMapper();

    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private Class<T> clazz;

    public FastJsonRedisSerializer(Class<T> clazz) {
        super();
        this.clazz = clazz;
    }

    @Override
    public byte[] serialize(T t) throws SerializationException {
        if (t == null) {
            return new byte[0];
        }
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }

    @Override
    public T deserialize(byte[] bytes) throws SerializationException {
        if (bytes == null || bytes.length <= 0) {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);

        return JSON.parseObject(str, clazz);
    }
    public void setObjectMapper(ObjectMapper objectMapper) {
        Assert.notNull(objectMapper, "'objectMapper' must not be null");
        this.objectMapper = objectMapper;
    }

    protected JavaType getJavaType(Class<?> clazz) {
        return TypeFactory.defaultInstance().constructType(clazz);
    }
}

创建好自定义的序列化类,覆盖默认的配置,接下来在config包下创建RedisCacheConfig类。

package com.ywh.cache.config;

/**
 * CreateTime: 2018-12-18 23:34
 * ClassName: RedisCacheConfig
 * Package: com.ywh.cache.config
 * Describe:
 * Redis缓存配置   @EnableCaching 开启声明式缓存支持. 之后就可以使用 @Cacheable/@CachePut/@CacheEvict 注解缓存数据.
 * @author YWH
 */
@Configuration
@EnableCaching
public class RedisCacheConfig {


    private RedisConnectionFactory redisConnectionFactory;

    @Autowired
    public void setRedisConnectionFactory(RedisConnectionFactory redisConnectionFactory){
        this.redisConnectionFactory = redisConnectionFactory;
    }

    /**
     * 覆盖默认的配置
     * @return RedisTemplate
     */
    @Bean
    public RedisTemplate<String,Object> redisTemplate(){
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);


        // 设置value的序列化规则和key的序列化规则
        template.setKeySerializer(stringRedisSerializer);
        template.setValueSerializer(fastJsonRedisSerializer);
        template.setHashKeySerializer(stringRedisSerializer);
        template.setHashValueSerializer(fastJsonRedisSerializer);
        template.setDefaultSerializer(fastJsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }

    /**
     * 解决注解方式存放到redis中的值是乱码的情况
     * @param factory 连接工厂
     * @return CacheManager
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);

        // 配置注解方式的序列化
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        RedisCacheConfiguration redisCacheConfiguration =
                config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                        .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer))
                        //配置注解默认的过期时间
                        .entryTtl(Duration.ofDays(1));
        // 加入白名单   https://github.com/alibaba/fastjson/wiki/enable_autotype
        ParserConfig.getGlobalInstance().addAccept("com.ywh");
        ParserConfig.getGlobalInstance().addAccept("com.baomidou");
        return RedisCacheManager.builder(factory).cacheDefaults(redisCacheConfiguration).build();
    }


}

配置好Redis的类以后我们再次重新运行测试类,会发现值不再是乱码了。

7.5.Redis工具类

redis可以对五种类型操作,可以自己再进行扩展,工具类我们放在cache模块下创建utils包,创建RedisUtil工具类,类上添加@Component注解,交给Spring来管理,我们可以直接在其他的方法中用@AtuoWired获取。

package com.ywh.cache.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * CreateTime: 2018-12-19 17:45
 * ClassName: RedisUtil
 * Package: com.ywh.cache.utils
 * Describe:
 * Redis工具类
 *
 * @author YWH
 */
@Component
public class RedisUtil {

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    //---------------------- common --------------------------

    /**
     * 指定缓存失效时间
     * @param key key值
     * @param time 缓存时间
     * @return true设置成功,time <=0 设置失败
     */
    public void expire(String key, long time){
        if(time > 0){
            redisTemplate.expire(key,time,TimeUnit.SECONDS);
        }else{
            throw MyExceptionUtil.mxe("设置的时间不能为0或者小于0!!");
        }
    }

    /**
     * 判断key是否存在
     * @param key
     * @return true 存在  false  不存在
     */
    public Boolean existsKey(String key){
        return redisTemplate.hasKey(key);
    }

    。。。。省略代码,具体代码可前往github查看

}

7.6.错误

在整合redis的时候报以下出错误只贴出了几行,最主要的是这句话:nested exception is java.lang.NoClassDefFoundError: org/apache/commons/pool2/impl/GenericObjectPoolConfig,说找不到这个类,那是因为我根本就没有引入这个“commons-pool2”包,这个包是用来创建连接池的,可以在网上搜一搜这个包的详解。

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'redisConnectionFactory' defined in class path resource [org/springframework/boot/autoconfigure/data/redis/LettuceConnectionConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory]: Factory method 'redisConnectionFactory' threw exception; nested exception is java.lang.NoClassDefFoundError: org/apache/commons/pool2/impl/GenericObjectPoolConfig
	at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:625)
	at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:605)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1288)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1127)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:538)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:498)

 

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值