Spring Boot中使用Redis小结

Spring Boot 中除了对常用的关系型数据库提供了优秀的自动化支持之外,对于很多 NoSQL 数据库一样提供了自动化配置的支持,包括:Redis, MongoDB, 等。

Redis 简单介绍

Redis 是 Redis 是 Remote DIctionary Server 的缩写,是目前业界使用最广泛的内存数据存储。相比 memcached,Redis 支持更丰富的数据结构 (Memcached 完全基于内存,而 Redis 具有持久化保存特性,Redis 可以将数据写入到磁盘中 ( 以字节(0101 这样的二进制数据)的形式写入的),例如 hashes, lists, sets 等,同时支持数据持久化。除此之外,Redis 还提供一些类数据库的特性,比如事务,HA,主从库。可以说 Redis 兼具了缓存系统和数据库的一些特性,因此有着丰富的应用场景。

Spring boot 集成 Redis

添加依赖

Spring Boot 提供的数据访问框架 Spring Data Redis 基于 Jedis。可以通过引入 spring-boot-starter-redis 来配置依赖关系。

    <!-- 添加 Spring-boot-starter-redis 依赖 -->
        <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-redis</artifactId>
        </dependency>

 对 Redis 进行配置,修改配置文件 application.properties

# REDIS (RedisProperties)
# Redis 数据库索引(默认为 0)
spring.redis.database=0
# Redis 服务器地址
spring.redis.host=localhost
# Redis 服务器连接端口
spring.redis.port=6379
# Redis 服务器连接密码(默认为空)
spring.redis.password=qpc_redis
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=0

其中 spring.redis.database 的配置通常使用 0 即可,Redis 在配置的时候可以设置数据库数量,默认为 16,可以理解为数据库的 schema.

使用 Redis

使用自动配置的 StringRedisTemplate 对象进行 Redis 读写操作。

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(Application.class)
public class ApplicationTest {
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> Logger LOG = Logger.getLogger(RedisApplicationTest.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);

@Autowired
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> StringRedisTemplate stringRedisTemplate;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">@Autowired
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">private RedisTemplate&lt;Serializable, Object&gt; redisTemplate;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">@Autowired
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">private RedisService redisService;</span>
@Test public void testStringWithRedis(){stringRedisTemplate.opsForValue().set("name", "guanguan"); String val = stringRedisTemplate.opsForValue().get("name"); Assert.assertEquals("guanguan", val);} }

当然,根据StringRedisTemplate 对象命名我们可以知道该对象支持 String 类型,但是在实际的应用中,我们可能需要存入 Object 对象。那该怎么存储呢。聪明的你,肯定立刻想到了,直接把对象转成 json 格式字符串,不就可以存储了嘛。这里我使用 jackson 依赖转换成 json 数据。

 首先添加 jackson 依赖

        <!-- java json 解析依赖 -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.9.3</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.9.3</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.3</version>
        </dependency>

实现 json 转换工具类

public class JsonUtil {
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> ObjectMapper objectMapper = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ObjectMapper();

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span><span style="color: rgba(0, 0, 0, 1)"> String convertObj2String(Object object) {
    String s </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
        s </span>=<span style="color: rgba(0, 0, 0, 1)"> objectMapper.writeValueAsString(object);
    } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (JsonProcessingException e) {
        e.printStackTrace();
    }
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> s;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> &lt;T&gt; T convertString2Obj(String s, Class&lt;T&gt;<span style="color: rgba(0, 0, 0, 1)"> clazz) {
    T t </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
        t </span>=<span style="color: rgba(0, 0, 0, 1)"> objectMapper.readValue(s, clazz);
    } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (IOException e) {
        e.printStackTrace();
    }
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> t;
}

}

我们知道,RedisTemplate 是 redis 模块的核心类,是对 redis 操作的较高抽象具有丰富的特性。他关注的是序列化和连接管理,线程安全,提供了如下操作接口:

HashOperations
HyperLogLogOperations
ListOperations
SetOperations
ValueOperations
ZSetOperations

那我们就实现一个通用的 RedisService 类完成 Redis 的读写操作

@Service
public class RedisService {
   @Autowired
    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> StringRedisTemplate redisTemplate;

    </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
     * 一周有多少秒
     </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
    <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">long</span> WEEK_SECONDS = 7 * 24 * 60 * 60<span style="color: rgba(0, 0, 0, 1)">;


    </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
     * 将 key,value 存放到redis数据库中,默认设置过期时间为一周
     *
     * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> key
     * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> value
     </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> set(String key, Object value) {
        redisTemplate.opsForValue().set(key, JsonUtil.convertObj2String(value), WEEK_SECONDS, TimeUnit.SECONDS);
    }

    </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
     * 将 key,value 存放到redis数据库中,设置过期时间单位是秒
     *
     * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> key
     * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> value
     * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> expireTime
     </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> set(String key, Object value, <span style="color: rgba(0, 0, 255, 1)">long</span><span style="color: rgba(0, 0, 0, 1)"> expireTime) {
        redisTemplate.opsForValue().set(key, JsonUtil.convertObj2String(value), expireTime, TimeUnit.SECONDS);
    }

    </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
     * 判断 key 是否在 redis 数据库中
     *
     * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> key
     * </span><span style="color: rgba(128, 128, 128, 1)">@return</span>
     <span style="color: rgba(0, 128, 0, 1)">*/</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span> exists(<span style="color: rgba(0, 0, 255, 1)">final</span><span style="color: rgba(0, 0, 0, 1)"> String key) {
        </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> redisTemplate.hasKey(key);
    }

    </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
     * 获取与 key 对应的对象
     * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> key
     * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> clazz 目标对象类型
     * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> &lt;T&gt;
     * </span><span style="color: rgba(128, 128, 128, 1)">@return</span>
     <span style="color: rgba(0, 128, 0, 1)">*/</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> &lt;T&gt; T get(String key, Class&lt;T&gt;<span style="color: rgba(0, 0, 0, 1)"> clazz) {
        String s </span>=<span style="color: rgba(0, 0, 0, 1)"> get(key);
        </span><span style="color: rgba(0, 0, 255, 1)">if</span> (s == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
            </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
        }
        </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> JsonUtil.convertString2Obj(s, clazz);
    }

    </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
     * 获取 key 对应的字符串
     * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> key
     * </span><span style="color: rgba(128, 128, 128, 1)">@return</span>
     <span style="color: rgba(0, 128, 0, 1)">*/</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String get(String key) {
        </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> redisTemplate.opsForValue().get(key);
    }

    </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
     * 删除 key 对应的 value
     * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> key
     </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> delete(String key) {
        redisTemplate.delete(key);
    }

}

新建一个 User 对象

public class User implements Serializable{
</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
 * 
 </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">long</span> serialVersionUID = 3456232569272497427L<span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> id;

</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> String name;

</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> age;

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> User() {
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> User(<span style="color: rgba(0, 0, 255, 1)">int</span> id, String name, <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> age) {
    </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">();
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.id =<span style="color: rgba(0, 0, 0, 1)"> id;
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.name =<span style="color: rgba(0, 0, 0, 1)"> name;
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.age =<span style="color: rgba(0, 0, 0, 1)"> age;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> getId() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> id;
}


</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> setId(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> id) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.id =<span style="color: rgba(0, 0, 0, 1)"> id;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String getName() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> name;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setName(String name) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.name =<span style="color: rgba(0, 0, 0, 1)"> name;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> getAge() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> age;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> setAge(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> age) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.age =<span style="color: rgba(0, 0, 0, 1)"> age;
}

@Override
</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String toString() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> "User [id=" + id + ", name=" + name + ", age=" + age + "]"<span style="color: rgba(0, 0, 0, 1)">;
}

}

新建测试类

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(Application.class)
public class ApplicationTest {
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> Logger LOG = Logger.getLogger(ApplicationTest.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);


@Autowired
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> RedisService redisService;


@Test
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> testRedisService(){
    User user3 </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> User(2,"xiaoxiaoping",16<span style="color: rgba(0, 0, 0, 1)">);
    redisService.set(</span>"user3", user3, 1000*60l<span style="color: rgba(0, 0, 0, 1)">);
    User userV3 </span>= redisService.get("user3",User.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);
    LOG.info(</span>"userV3====="+<span style="color: rgba(0, 0, 0, 1)">userV3.toString());
}

}

测试结果

 通过使用 StringRedisTemplate 对象完全实现了对 Object 对象的存储. 通过 redis-cli.exe 可以查看到我们存储的 Object 对象是 json 格式字符串,但是当某个对象很大时, 这个 json 字符串会很冗长,那我们有没有其他方式实现呢。如果有使用过 spring-data-redis 的开发者一定熟悉 RedisTemplate<K, V> 接口,StringRedisTemplate 就相当于 RedisTemplate<String, String> 的实现。没有使用过,可以先看下 StringRedisTemplate 类源码。

public class StringRedisTemplate extends RedisTemplate<String, String> {
</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
 * Constructs a new &lt;code&gt;StringRedisTemplate&lt;/code&gt; instance. {</span><span style="color: rgba(128, 128, 128, 1)">@link</span><span style="color: rgba(0, 128, 0, 1)"> #setConnectionFactory(RedisConnectionFactory)}
 * and {</span><span style="color: rgba(128, 128, 128, 1)">@link</span><span style="color: rgba(0, 128, 0, 1)"> #afterPropertiesSet()} still need to be called.
 </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> StringRedisTemplate() {
    <span style="background-color: rgba(255, 0, 0, 1)"><strong>RedisSerializer</strong></span></span><span style="background-color: rgba(255, 0, 0, 1)"><strong>&lt;String&gt; stringSerializer = <span style="color: rgba(0, 0, 255, 1)">new</span></strong></span><span style="color: rgba(0, 0, 0, 1)"><span style="background-color: rgba(255, 0, 0, 1)"><strong> StringRedisSerializer();</strong></span>
    setKeySerializer(stringSerializer);
    setValueSerializer(stringSerializer);
    setHashKeySerializer(stringSerializer);
    setHashValueSerializer(stringSerializer);
}

</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
 * Constructs a new &lt;code&gt;StringRedisTemplate&lt;/code&gt; instance ready to be used.
 * 
 * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> connectionFactory connection factory for creating new connections
 </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> StringRedisTemplate(RedisConnectionFactory connectionFactory) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">();
    setConnectionFactory(connectionFactory);
    afterPropertiesSet();
}

</span><span style="color: rgba(0, 0, 255, 1)">protected</span> RedisConnection preProcessConnection(RedisConnection connection, <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> existingConnection) {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> DefaultStringRedisConnection(connection);
}

}

 从源码分析,我们可以看出 StringRedisTemplate 实现 RedisTemplate<K, V> 接口,那我们完全可以模仿写一个 RedisTemplate<Serializable, Object> 模板类。但是 Spring boot 不支直接使用,所以根据源码,我们需要实现一个RedisSerializer<T>将来对传入对象进行序列化和反序列化。这个实现类 ObjectRedisSerializer 可以参考 StringRedisSerializer 类。另外,根据源码,可以发现,Redis 默认的序列化方式为 JdkSerializationRedisSerializer ,利用 JDK 的序列化和反序列化,持久化就是以字节(0101 这样的二进制数据)的形式写入的。

Redis 存储对象实现如下

添加 ObjectRedisSerializer 实现类,需要实现 RedisSerializer<T> 接口。

/**
 * 实现 Redis 对象的序列化接口
 * 参考:JdkSerializationRedisSerializer 源码
 * 
 */
public class ObjectRedisSerializer implements RedisSerializer<Object>{
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> Logger LOG = Logger.getLogger(ObjectRedisSerializer.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
 * 定义序列化和发序列化转化类
 </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">private</span> Converter&lt;Object, <span style="color: rgba(0, 0, 255, 1)">byte</span>[]&gt; serializer = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> SerializingConverter();
</span><span style="color: rgba(0, 0, 255, 1)">private</span> Converter&lt;<span style="color: rgba(0, 0, 255, 1)">byte</span>[], Object&gt; deserializer = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> DeserializingConverter();

</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
 * 定义转换空字节数组
 </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">byte</span>[] EMPTY_ARRAY = <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">byte</span>[0<span style="color: rgba(0, 0, 0, 1)">]; 

@Override
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">byte</span>[] serialize(Object obj) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> SerializationException {
    </span><span style="color: rgba(0, 0, 255, 1)">byte</span>[] byteArray = <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">null</span> ==<span style="color: rgba(0, 0, 0, 1)"> obj) {
        LOG.warn(</span>"Redis待序列化的对象为空."<span style="color: rgba(0, 0, 0, 1)">);
        byteArray </span>=<span style="color: rgba(0, 0, 0, 1)"> EMPTY_ARRAY;
    } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
        </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
            byteArray </span>=<span style="color: rgba(0, 0, 0, 1)"> serializer.convert(obj);
        } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e) {
            LOG.error(</span>"Redis序列化对象失败,异常:"+<span style="color: rgba(0, 0, 0, 1)">e.getMessage());
            byteArray </span>=<span style="color: rgba(0, 0, 0, 1)"> EMPTY_ARRAY;
        }
    }
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> byteArray;
}

@Override
</span><span style="color: rgba(0, 0, 255, 1)">public</span> Object deserialize(<span style="color: rgba(0, 0, 255, 1)">byte</span>[] datas) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> SerializationException {
    Object obj </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)">(isNullOrEmpty(datas)){
        LOG.warn(</span>"Redis待反序列化的对象为空."<span style="color: rgba(0, 0, 0, 1)">);
    }</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">{
        </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
            obj </span>=<span style="color: rgba(0, 0, 0, 1)"> deserializer.convert(datas);
        } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e) {
            LOG.error(</span>"Redis反序列化对象失败,异常:"+<span style="color: rgba(0, 0, 0, 1)">e.getMessage());
        }
    }
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> obj;
}

</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span> isNullOrEmpty(<span style="color: rgba(0, 0, 255, 1)">byte</span><span style="color: rgba(0, 0, 0, 1)">[] datas){
  </span><span style="color: rgba(0, 0, 255, 1)">return</span> (<span style="color: rgba(0, 0, 255, 1)">null</span> == datas)|| (datas.length == 0<span style="color: rgba(0, 0, 0, 1)">);
}

}

创建 RedisConfig 配置类,将 RedisTemplate 的 setValueSerializer 设置成 ObjectRedisSerializer 转换类。

@Configuration
public class RedisConfig {

// /**
// * 连接 redis 需要 RedisConnection 和 RedisConnectionFactory,
// * RedisConnection 是通过 RedisConnectionFactory 进行创建
// * RedisConnection 提供较低级的数据操作 (byte arrays)
// */
// @Bean
// RedisConnectionFactory initJedisConnectionFactory(){
// //在这里设置 redis 连接对象配置
// return new JedisConnectionFactory();
// }

<span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
 * 配置RedisTemplate实例
 * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> factory
 * </span><span style="color: rgba(128, 128, 128, 1)">@return</span>
 <span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
@Bean
</span><span style="color: rgba(0, 0, 255, 1)">public</span> RedisTemplate&lt;Serializable, Object&gt;<span style="color: rgba(0, 0, 0, 1)"> redisTemplate(RedisConnectionFactory connectionFactory) {
    RedisTemplate</span>&lt;Serializable, Object&gt; template = <span style="color: rgba(0, 0, 255, 1)">new</span> RedisTemplate&lt;Serializable, Object&gt;<span style="color: rgba(0, 0, 0, 1)">();

template.setConnectionFactory(connectionFactory);
template.afterPropertiesSet();

template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(
new ObjectRedisSerializer());
return template;
}
}

需要注意几点:

在添加 RedisConfig 配置时,因为连接 redis 需要 RedisConnection 和 RedisConnectionFactory,RedisConnection 是通过 RedisConnectionFactory 进行创建若注入 JedisConnnectionFactory,如果我们 Redis 设置了密码,在重新注入 RedisConnectionFactory(如上注释代码),就会报错如下:

org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:193)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: redis.clients.jedis.exceptions.JedisDataException: NOAUTH Authentication required.
    at redis.clients.jedis.Protocol.processError(Protocol.java:117)
    at redis.clients.jedis.Protocol.process(Protocol.java:151)
    at redis.clients.jedis.Protocol.read(Protocol.java:205)
    at redis.clients.jedis.Connection.readProtocolWithCheckingBroken(Connection.java:297)
    at redis.clients.jedis.Connection.getStatusCodeReply(Connection.java:196)
    at redis.clients.jedis.BinaryJedis.set(BinaryJedis.java:126)
    at org.springframework.data.redis.connection.jedis.JedisConnection.set(JedisConnection.java:1136)
    ... 36 more

根据 StringRedisTemplate 源码,在注入 RedisTemplate<Serializable, Object> 直接使用默认的连接对象即可。设置如下代码:

template.setConnectionFactory(connectionFactory);
template.afterPropertiesSet();

或者我们注入 RedisConnectionFactory 设置连接属性应该也是可以的,有兴趣可以尝试下。

创建测试类

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(Application.class)
public class ApplicationTest {
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> Logger LOG = Logger.getLogger(ApplicationTest.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);

@Autowired
</span><span style="color: rgba(0, 0, 255, 1)">private</span> RedisTemplate&lt;Serializable, Object&gt;<span style="color: rgba(0, 0, 0, 1)"> redisTemplate;

@Test
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> testObjectWithRedis(){
    User user1 </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> User(1,"guanguan",18<span style="color: rgba(0, 0, 0, 1)">);
    redisTemplate.opsForValue().set(</span>"user1"<span style="color: rgba(0, 0, 0, 1)">, user1);
    
    User userV1 </span>= (User)redisTemplate.opsForValue().get("user1"<span style="color: rgba(0, 0, 0, 1)">);
    LOG.info(</span>"userV1====="+<span style="color: rgba(0, 0, 0, 1)">userV1.toString());
    
    User user2 </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> User(2,"xiaoyan",16<span style="color: rgba(0, 0, 0, 1)">);
    redisTemplate.opsForValue().set(</span>"user2"<span style="color: rgba(0, 0, 0, 1)">, user2);
    
    User userV2 </span>= (User)redisTemplate.opsForValue().get("user2"<span style="color: rgba(0, 0, 0, 1)">);
    LOG.info(</span>"user2====="+<span style="color: rgba(0, 0, 0, 1)">userV2.toString());
    
    User user3 </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> User(3,"xiaoxiaoping",18<span style="color: rgba(0, 0, 0, 1)">);
    redisTemplate.opsForValue().set(</span>"user3"<span style="color: rgba(0, 0, 0, 1)">, user3);
    
    User userV3 </span>= (User)redisTemplate.opsForValue().get("user3"<span style="color: rgba(0, 0, 0, 1)">);
    LOG.info(</span>"userV3====="+<span style="color: rgba(0, 0, 0, 1)">userV3.toString());
    
}

}

测试结果:

 

可以看出,是以字节方式存储的。