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
一、先在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中序列化类有如下几个:缓存
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; } }
这里只是一个调用方,使用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 能够看到以下结果,就证实已经集成完成了