spring boot 集成 redis lettuce

1、簡介

spring boot框架中已經集成了redis,在1.x.x的版本時默認使用的jedis客戶端,如今是2.x.x版本默認使用的lettuce客戶端,兩種客戶端的區別以下java

# Jedis和Lettuce都是Redis Client

# Jedis 是直連模式,在多個線程間共享一個 Jedis 實例時是線程不安全的,
# 若是想要在多線程環境下使用 Jedis,須要使用鏈接池,
# 每一個線程都去拿本身的 Jedis 實例,當鏈接數量增多時,物理鏈接成本就較高了。
# Lettuce的鏈接是基於Netty的,鏈接實例能夠在多個線程間共享,
# 因此,一個多線程的應用可使用同一個鏈接實例,而不用擔憂併發線程的數量。
# 固然這個也是可伸縮的設計,一個鏈接實例不夠的狀況也能夠按需增長鏈接實例。

# 經過異步的方式可讓咱們更好的利用系統資源,而不用浪費線程等待網絡或磁盤I/O。
# Lettuce 是基於 netty 的,netty 是一個多線程、事件驅動的 I/O 框架,
# 因此 Lettuce 能夠幫助咱們充分利用異步的優點。

因爲個人項目是spring boot 2.0.4的,因此我是用lettuce來配置,在個人這個文章裏面和其餘文章不同的地方是,其餘文章直接把cache操做類放在跟spring boot同一個模塊中web

而實際開發時,這種緩存類都是獨立放在common模塊中的,因此Autowired就失效了,使用其餘方式進行注入redis

如下是個人項目結構:spring

 

2、Common模塊代碼

一、先在pom中引入redis及其它jar包數據庫

<dependencies>        
        <!-- spring boot redis 緩存引入 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.0.4.RELEASE</version>
        </dependency>
        <!-- lettuce pool 緩存鏈接池 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.5.0</version>
        </dependency>
        <!-- jackson json 優化緩存對象序列化 -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.6</version>
        </dependency>
    </dependencies>

二、編寫緩存配置類CacheConfig用於調優緩存默認配置,RedisTemplate<String, Object>的類型兼容性更高apache

你們能夠看到在redisTemplate()這個方法中更換掉了Redis默認的序列化方式json

spring-data-redis中序列化類有如下幾個:緩存

  • GenericToStringSerializer:能夠將任何對象泛化爲字符創並序列化
  • Jackson2JsonRedisSerializer:序列化Object對象爲json字符創(與JacksonJsonRedisSerializer相同)
  • JdkSerializationRedisSerializer:序列化java對象
  • StringRedisSerializer:簡單的字符串序列化

JdkSerializationRedisSerializer序列化安全

被序列化對象必須實現Serializable接口,被序列化除屬性內容還有其餘內容,長度長且不易閱讀服務器

存儲內容以下:

"\xac\xed\x00\x05sr\x00!com.oreilly.springdata.redis.User\xb1\x1c \n\xcd\xed%\xd8\x02\x00\x02I\x00\x03ageL\x00\buserNamet\x00\x12Ljava/lang/String;xp\x00\x00\x00\x14t\x00\x05user1"

JacksonJsonRedisSerializer序列化

被序列化對象不須要實現Serializable接口,被序列化的結果清晰,容易閱讀,並且存儲字節少,速度快

存儲內容以下:

"{\"userName\":\"user1\",\"age\":20}"

StringRedisSerializer序列化

通常若是key、value都是string字符串的話,就是用這個就能夠了

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;

import java.lang.reflect.Method;

/**
 * 緩存配置-使用Lettuce客戶端,自動注入配置的方式
 */
@Configuration
@EnableCaching //啓用緩存
public class CacheConfig extends CachingConfigurerSupport {

    /**
     * 自定義緩存key的生成策略。默認的生成策略是看不懂的(亂碼內容) 經過Spring 的依賴注入特性進行自定義的配置注入而且此類是一個配置類能夠更多程度的自定義配置
     *
     * @return
     */
    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                StringBuilder sb = new StringBuilder();
                sb.append(target.getClass().getName());
                sb.append(method.getName());
                for (Object obj : params) {
                    sb.append(obj.toString());
                }
                return sb.toString();
            }
        };
    }

    /**
     * 緩存配置管理器
     */
    @Bean
    public CacheManager cacheManager(LettuceConnectionFactory factory) {
        //以鎖寫入的方式建立RedisCacheWriter對象
        RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(factory);
        /*
        設置CacheManager的Value序列化方式爲JdkSerializationRedisSerializer,
        但其實RedisCacheConfiguration默認就是使用
        StringRedisSerializer序列化key,
        JdkSerializationRedisSerializer序列化value,
        因此如下注釋代碼就是默認實現,不必寫,直接註釋掉
         */
        // RedisSerializationContext.SerializationPair pair = RedisSerializationContext.SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(this.getClass().getClassLoader()));
        // RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
        //建立默認緩存配置對象
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        RedisCacheManager cacheManager = new RedisCacheManager(writer, config);
        return cacheManager;
    }

    /**
     * 獲取緩存操做助手對象
     *
     * @return
     */
    @Bean
    public RedisTemplate<String, String> redisTemplate(LettuceConnectionFactory factory) {
        //建立Redis緩存操做助手RedisTemplate對象
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(factory);
        //如下代碼爲將RedisTemplate的Value序列化方式由JdkSerializationRedisSerializer更換爲Jackson2JsonRedisSerializer
        //此種序列化方式結果清晰、容易閱讀、存儲字節少、速度快,因此推薦更換
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;//StringRedisTemplate是RedisTempLate<String, String>的子類
    }
}

三、編寫緩存操做提供類CacheProvider,用於給開發提供緩存操做

import com.google.gson.Gson;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;

/**
 * 緩存提供類
 */
public class CacheProvider {

    //因爲當前class不在spring boot框架內(不在web項目中)因此沒法使用autowired,使用此種方法進行注入
    private static RedisTemplate<String, String> template = (RedisTemplate<String, String>) SpringBeanUtil.getBean("redisTemplate");

    public static <T> boolean set(String key, T value) {
        Gson gson = new Gson();
        return set(key, gson.toJson(value));
    }

    public static boolean set(String key, String value, long validTime) {
        boolean result = template.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer<String> serializer = template.getStringSerializer();
                connection.set(serializer.serialize(key), serializer.serialize(value));
                connection.expire(serializer.serialize(key), validTime);
                return true;
            }
        });
        return result;
    }

    public static <T> T get(String key, Class<T> clazz) {
        Gson gson = new Gson();
        return gson.fromJson(get(key), clazz);
    }

    public static String get(String key) {
        String result = template.execute(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer<String> serializer = template.getStringSerializer();
                byte[] value = connection.get(serializer.serialize(key));
                return serializer.deserialize(value);
            }
        });
        return result;
    }

    public static boolean del(String key) {
        return template.delete(key);
    }
}

四、此時你會發現咱們並無用Autowired作自動注入,而是用SpringBeanUtil.getBean("redisTemplate")本身寫的類進行注入,

由於這個Common模塊並不在Spring boot框架內,自動注入無效,因此改用這個

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class SpringBeanUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext = null;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (SpringBeanUtil.applicationContext == null) {
            SpringBeanUtil.applicationContext = applicationContext;
        }
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    /**
     * 經過Bean名字獲取Bean
     *
     * @param beanName
     * @return
     */
    public static Object getBean(String beanName) {
        return getApplicationContext().getBean(beanName);
    }

    /**
     * 經過Bean類型獲取Bean
     *
     * @param beanClass
     * @param <T>
     * @return
     */
    public static <T> T getBean(Class<T> beanClass) {
        return getApplicationContext().getBean(beanClass);
    }

    /**
     * 經過Bean名字和Bean類型獲取Bean
     *
     * @param beanName
     * @param beanClass
     * @param <T>
     * @return
     */
    public static <T> T getBean(String beanName, Class<T> beanClass) {
        return getApplicationContext().getBean(beanName, beanClass);
    }
}

五、如今Common模塊就編寫完成了,你們能夠發現CacheConfig類中使用的自動讀取配置文件的方式,如下再提供一種手動配置的方式

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.*;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.lang.reflect.Method;
import java.time.Duration;

/**
 * 緩存配置-使用Lettuce客戶端,手動注入配置的方式
 */
@Configuration
@EnableCaching //啓用緩存
@ConfigurationProperties(prefix = "spring.redis") //指明配置節點
public class CacheConfigLettuceManual extends CachingConfigurerSupport {

    // Redis服務器地址
    @Value("${spring.redis.host}")
    private String host;
    // Redis服務器鏈接端口
    @Value("${spring.redis.port}")
    private Integer port;
    // Redis數據庫索引(默認爲0)
    @Value("${spring.redis.database}")
    private Integer database;
    // Redis服務器鏈接密碼(默認爲空)
    @Value("${spring.redis.password}")
    private String password;
    // 鏈接超時時間(毫秒)
    @Value("${spring.redis.timeout}")
    private Integer timeout;

    // 鏈接池最大鏈接數(使用負值表示沒有限制)
    @Value("${spring.redis.lettuce.pool.max-active}")
    private Integer maxTotal;
    // 鏈接池最大阻塞等待時間(使用負值表示沒有限制)
    @Value("${spring.redis.lettuce.pool.max-wait}")
    private Integer maxWait;
    // 鏈接池中的最大空閒鏈接
    @Value("${spring.redis.lettuce.pool.max-idle}")
    private Integer maxIdle;
    // 鏈接池中的最小空閒鏈接
    @Value("${spring.redis.lettuce.pool.min-idle}")
    private Integer minIdle;
    // 關閉超時時間
    @Value("${spring.redis.lettuce.shutdown-timeout}")
    private Integer shutdown;

    /**
     * 自定義緩存key的生成策略。默認的生成策略是看不懂的(亂碼內容) 經過Spring 的依賴注入特性進行自定義的配置注入而且此類是一個配置類能夠更多程度的自定義配置
     *
     * @return
     */
    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                StringBuilder sb = new StringBuilder();
                sb.append(target.getClass().getName());
                sb.append(method.getName());
                for (Object obj : params) {
                    sb.append(obj.toString());
                }
                return sb.toString();
            }
        };
    }

    /**
     * 緩存配置管理器
     */
    @Bean
    @Override
    public CacheManager cacheManager() {
        //以鎖寫入的方式建立RedisCacheWriter對象
        RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(getConnectionFactory());
        /*
        設置CacheManager的Value序列化方式爲JdkSerializationRedisSerializer,
        但其實RedisCacheConfiguration默認就是使用
        StringRedisSerializer序列化key,
        JdkSerializationRedisSerializer序列化value,
        因此如下注釋代碼就是默認實現,不必寫,直接註釋掉
         */
        // RedisSerializationContext.SerializationPair pair = RedisSerializationContext.SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(this.getClass().getClassLoader()));
        // RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
        //建立默認緩存配置對象
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        RedisCacheManager cacheManager = new RedisCacheManager(writer, config);
        return cacheManager;
    }

    /**
     * 獲取緩存操做助手對象
     *
     * @return
     */
    @Bean
    public RedisTemplate<String, String> redisTemplate() {
        //建立Redis緩存操做助手RedisTemplate對象
        RedisTemplate<String, String> template = new RedisTemplate<>();
        template.setConnectionFactory(getConnectionFactory());
        //如下代碼爲將RedisTemplate的Value序列化方式由JdkSerializationRedisSerializer更換爲Jackson2JsonRedisSerializer
        //此種序列化方式結果清晰、容易閱讀、存儲字節少、速度快,因此推薦更換
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.setKeySerializer(new StringRedisSerializer());//RedisTemplate對象須要指明Key序列化方式,若是聲明StringRedisTemplate對象則不須要
        //template.setEnableTransactionSupport(true);//是否啓用事務
        template.afterPropertiesSet();
        return template;
    }

    /**
     * 獲取緩存鏈接
     *
     * @return
     */
    @Bean
    public RedisConnectionFactory getConnectionFactory() {
        //單機模式
        RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
        configuration.setHostName(host);
        configuration.setPort(port);
        configuration.setDatabase(database);
        configuration.setPassword(RedisPassword.of(password));
        //哨兵模式
        //RedisSentinelConfiguration configuration1 = new RedisSentinelConfiguration();
        //集羣模式
        //RedisClusterConfiguration configuration2 = new RedisClusterConfiguration();
        LettuceConnectionFactory factory = new LettuceConnectionFactory(configuration, getPoolConfig());
        //factory.setShareNativeConnection(false);//是否容許多個線程操做共用同一個緩存鏈接,默認true,false時每一個操做都將開闢新的鏈接
        return factory;
    }

    /**
     * 獲取緩存鏈接池
     *
     * @return
     */
    @Bean
    public LettucePoolingClientConfiguration getPoolConfig() {
        GenericObjectPoolConfig config = new GenericObjectPoolConfig();
        config.setMaxTotal(maxTotal);
        config.setMaxWaitMillis(maxWait);
        config.setMaxIdle(maxIdle);
        config.setMinIdle(minIdle);
        LettucePoolingClientConfiguration pool = LettucePoolingClientConfiguration.builder()
                .poolConfig(config)
                .commandTimeout(Duration.ofMillis(timeout))
                .shutdownTimeout(Duration.ofMillis(shutdown))
                .build();
        return pool;
    }

}
View Code

 

3、Web模塊代碼

這裏只是一個調用方,使用spring boot先添加pom.xml信息

<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.0.3.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>javademo.tyh</groupId>
            <artifactId>javademo-tyh-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

添加配置文件及裏面的配置

spring.application.name=javademo-tyh-job
server.port=15000

#redis
# Redis服務器地址
spring.redis.host=10.11.12.237
# Redis服務器鏈接端口
spring.redis.port=6379
# Redis數據庫索引(默認爲0)
spring.redis.database=0
# Redis服務器鏈接密碼(默認爲空)
spring.redis.password=
# 鏈接超時時間(毫秒)
spring.redis.timeout=10000

# 如下鏈接池已在SpringBoot2.0不推薦使用
#spring.redis.pool.max-active=8
#spring.redis.pool.max-wait=-1
#spring.redis.pool.max-idle=8
#spring.redis.pool.min-idle=0

# Jedis
#spring.redis.jredis.max-active=8
#spring.redis.jredis.max-wait=10000
#spring.redis.jredis.max-idle=8
#spring.redis.jredis.min-idle=0

# Lettuce
# 鏈接池最大鏈接數(使用負值表示沒有限制)
spring.redis.lettuce.pool.max-active=8
# 鏈接池最大阻塞等待時間(使用負值表示沒有限制)
spring.redis.lettuce.pool.max-wait=10000
# 鏈接池中的最大空閒鏈接
spring.redis.lettuce.pool.max-idle=8
# 鏈接池中的最小空閒鏈接
spring.redis.lettuce.pool.min-idle=0
# 關閉超時時間
spring.redis.lettuce.shutdown-timeout=100

啓動main()方法

SpringBoot在寫啓動類的時候若是不使用@ComponentScan指明對象掃描範圍,默認只掃描當前啓動類所在的包裏的對象,
由於啓動類不能直接放在main/java文件夾下,必需要建一個包把它放進去,這是就須要使用@ComponentScan指明要掃描的包。
如:
javademo-tyh-common模塊的包名:javademo.tyh.common
javademo-tyh-job模塊的包名:javademo.tyh.job
這樣默認就不會把common模塊中標記@Component的組件裝配到SpringBoot中,由於它默認只掃描javademo.tyh.job包下的組件,
因此這時就須要在main()啓動類中使用@ComponentScan註解來指明要掃描那些包,但只掃描該註解指定的包,當前mian()方法所在的包就不會被掃描了
因此要寫它的上級包「javademo.tyh」
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

/*
SpringBoot在寫啓動類的時候若是不使用@ComponentScan指明對象掃描範圍,默認只掃描當前啓動類所在的包裏的對象,
由於啓動類不能直接放在main/java文件夾下,必需要建一個包把它放進去,這是就須要使用@ComponentScan指明要掃描的包。
如:
javademo-tyh-common模塊的包名:javademo.tyh.common
javademo-tyh-job模塊的包名:javademo.tyh.job
這樣默認就不會把common模塊中標記@Component的組件裝配到SpringBoot中,由於它默認只掃描javademo.tyh.job包下的組件,
因此這時就須要在main()啓動類中使用@ComponentScan註解來指明要掃描那些包,但只掃描該註解指定的包,當前mian()方法所在的包就不會被掃描了,
因此要寫它的上級包「javademo.tyh」
*/ @ComponentScan("javademo.tyh") @SpringBootApplication public class AppJob { public static void main( String[] args ) { SpringApplication.run(AppJob.class); } }

controller控制器

import javademo.tyh.common.CacheProvider;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.Cookie;

@Controller
@RequestMapping("/test")
public class TestController {

    @ResponseBody
    @RequestMapping("index")
    public String index(){

        String str = "";

        str += CacheProvider.set("tyh", "aaaaaaaaaaaaaaaaaa");
        str += "|";
        str += CacheProvider.get("tyh");
        str += "|";
        str += CacheProvider.del("tyh");

        str += "|||";

        Cookie cookie = new Cookie("aaa", "bbb");
        str += CacheProvider.set("cookie", cookie);
        str += "|";
        str += CacheProvider.get("cookie", Cookie.class);
        str += "|";
        str += CacheProvider.del("cookie");

        return str.toString();
    }
}

 

好了,啓動程序,打開http://localhost:15000/cacheManage/test 能夠看到以下結果,就證實已經集成完成了

相關文章
相關標籤/搜索