1. 程式人生 > >Spring boot 2.0的Redis快取應用

Spring boot 2.0的Redis快取應用

範培忠 2018-04-18

  Spring Boot2.0.0.RELEASE在2018年3月1日正式釋出。2.0下對Redis的使用與之前略有不同。具體實現如下:

        一、Maven依賴和配置

        新增3個依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--spring2.0整合redis所需common-pool2-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.5.0</version>
</dependency>
<!-- 將作為Redis物件序列化器 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.47</version>
</dependency>

        application.yml配置:

spring:
    redis:
      #資料庫索引
      database: 0
      host: 127.0.0.1
      port: 6379
      password: 123
      lettuce:
        pool:
          #最大連線數
          max-active: 8
          #最大阻塞等待時間(負數表示沒限制)
          max-wait: -1
          #最大空閒
          max-idle: 8
          #最小空閒
          min-idle: 0
      #連線超時時間
      timeout: 10000

        fastjson.properties配置:

        本配置是為了解決在1.2.24後因修復漏洞導致的autotype is not support錯誤。

        具體解決辦法為宣告fastjson.properties檔案,將需要打包的類設定白名單等,如下程式碼所示。

fastjson.parser.autoTypeAccept=cn.fanpz.springbootdemoredis3.domain.entity.User

        二、自定義序列化器

        若要實現物件的快取,最好定義自己的序列化和反序列化器。使用阿里的fastjson來實現的比較多。

package cn.fanpz.springbootdemoredis3.util;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;

import java.nio.charset.Charset;

public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
    private 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 (null == t) {
            return new byte[0];
        }
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }

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

        三、新增RedisConfig配置

        在RedisConfig類裡定義RedisTemplate的bean和Redis的CacheManager。

package cn.fanpz.springbootdemoredis3.config;

import cn.fanpz.springbootdemoredis3.util.FastJsonRedisSerializer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
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.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@EnableCaching
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig extends CachingConfigurerSupport {

    @Bean(name = "redisTemplate")
    @SuppressWarnings("unchecked")
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();

        //使用fastjson序列化
        FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class);
        // value值的序列化採用fastJsonRedisSerializer
        template.setValueSerializer(fastJsonRedisSerializer);
        template.setHashValueSerializer(fastJsonRedisSerializer);
        // key的序列化採用StringRedisSerializer
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());

        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    /*@Bean
    @ConditionalOnMissingBean(StringRedisTemplate.class)
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }*/

    //快取管理器
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager
                .RedisCacheManagerBuilder
                .fromConnectionFactory(redisConnectionFactory);
        return builder.build();
    }

}

        四、定義用於測試的User類和UserService

        User類:

package cn.fanpz.springbootdemoredis3.domain.entity;

import java.io.Serializable;

public class User implements Serializable {

    private static final long serialVersionUID = -1L;

    private String username;
    private Integer age;

    public User(String username, Integer age) {
        this.username = username;
        this.age = age;
    }

    //getter和setter省略

}

        UserService:

package cn.fanpz.springbootdemoredis3.service.impl;

import cn.fanpz.springbootdemoredis3.domain.entity.User;
import cn.fanpz.springbootdemoredis3.service.UserService;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {
    @Override
    @Cacheable(value = "user", key = "'user_'+#username")
    public User getUser(String username) {
        System.out.println(username + "進入實現類獲取資料!");
        return new User("Ttomm", 22);
    }
}

        五、編寫測試類

package cn.fanpz.springbootdemoredis3;

import cn.fanpz.springbootdemoredis3.domain.entity.User;
import cn.fanpz.springbootdemoredis3.service.UserService;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;

@SuppressWarnings("unchecked")
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBootDemoRedis3ApplicationTests {
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private UserService userService;

    @Test
    //直接使用redisTemplate存取字串
    public void setAndGet() {
        redisTemplate.opsForValue().set("test:set", "testValue1");
        Assert.assertEquals("testValue1", redisTemplate.opsForValue().get("test:set"));
    }

    @Test
    //直接使用redisTemplate存取物件
    public void setAndGetAUser() {
        User user = new User("Tom", 10);
        redisTemplate.opsForValue().set("test:setUser", user);
        Assert.assertEquals(user.getUsername(), ((User) redisTemplate.opsForValue().get("test:setUser")).getUsername());
    }

    @Test
    //使用Redis快取物件,getUser只會被呼叫一次
    public void testCache() {
        User user;
        user = userService.getUser("Ttomm");
        user = userService.getUser("Ttomm");
        user = userService.getUser("Ttomm");
    }
}

        六、執行效果

        testCache()的執行效果如下,可見後臺日誌顯示方法僅被實際呼叫了一次,快取成功: