1 引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>1.5.6.RELEASE</version> </dependency>
2 添加配置
配置默认是写在application.properties或application.yml中,也可以自己新建一个资源文件,但是在RedisCacheConfig用需要注解出配置具体的文件
######################################################## ###REDIS (RedisProperties) redis基本配置 ######################################################## # database name spring.redis.database=0 # Redis服务器IP spring.redis.host=10.176.65.137 # Redis密码 spring.redis.password=redis # Redis端口号 spring.redis.port=6379 # 连接超时时间 单位 ms(毫秒) spring.redis.timeout=3000 ######################################################## ###REDIS (RedisProperties) redis线程池设置 ######################################################## # 控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8。 spring.redis.pool.max-idle=20 # 控制一个pool最少有多少个状态为idle(空闲的)的jedis实例,默认值也是0。 spring.redis.pool.min-idle=10 # 如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。 spring.redis.pool.max-active=60 # 等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException spring.redis.pool.max-wait=3000 #################################################################################### ###REDIS (RedisProperties) redis哨兵设置,和上面的host和port不要同时配置 #################################################################################### # Redis服务器master的名字 #spring.redis.sentinel.master=master8026 # redis-sentinel的配置地址和端口(注意:不是redis的地址和端口) #spring.redis.sentinel.nodes=10.189.80.25:26379,10.189.80.26:26379,10.189.80.26:26378 ######################################################## ###REDIS (RedisProperties) redis 自定义参数 ######################################################## #默认生命周期30天 spring.redis.defaultExpiration = 2592000 #服务器上下文路径 spring.redis.contextPath = contextPath
3 redis配置代码实现
import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cache.Cache; import org.springframework.cache.annotation.*; import org.springframework.cache.interceptor.CacheErrorHandler; import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.*; /** * @author qianjianfeng * @version 1.0.0 * @since 1.0.0 */ @Configuration @ConfigurationProperties(prefix = "spring.redis") @PropertySource(value = "classpath:/META-INF/redis.properties") @EnableCaching public class RedisCacheConfig extends CachingConfigurerSupport { private Logger logger = LoggerFactory.getLogger(this.getClass()); private Long defaultExpiration; private String contextPath; public Long getDefaultExpiration() { return defaultExpiration; } public void setDefaultExpiration(Long defaultExpiration) { this.defaultExpiration = defaultExpiration; } public String getContextPath() { return contextPath; } public void setContextPath(String contextPath) { this.contextPath = contextPath; } /** * 自定义key. * key --> 项目名 + 缓存空间值 + 所有参数的值 * 即使@Cacheable中的value属性一样,key也会不一样。 */ @Bean @Override public KeyGenerator keyGenerator() { return new KeyGenerator(){ @Override public String generate(Object o, Method method, Object... objects) { // This will generate a unique key of the class name, the method name //and all method parameters appended. StringBuilder sb = new StringBuilder(); sb.append(contextPath).append("/:"); Cacheable cacheable = method.getAnnotation(Cacheable.class); CachePut cachePut = method.getAnnotation(CachePut.class); CacheEvict cacheEvict = method.getAnnotation(CacheEvict.class); if (cacheable != null) { sb.append(Arrays.toString(cacheable.value())).append(":"); }else if (cachePut != null) { sb.append(Arrays.toString(cachePut.value())).append(":"); }else if (cacheEvict != null) { sb.append(Arrays.toString(cacheEvict.value())).append(":"); } Map valueMap = new HashMap(); for (Object obj : objects) { try { getStringValueMap(obj,valueMap); } catch (IllegalAccessException e) { logger.info("生成key的时候,[{}]转换map异常,生成的key丢弃了该值。",obj.getClass(),e); } } sb.append(valueMap.toString()); System.err.println(sb.toString()); return sb.toString(); } }; } /** * redis模板操作类,类似于jdbcTemplate的一个类; * 虽然CacheManager也能获取到Cache对象,但是操作起来没有那么灵活; * 这里在扩展下:RedisTemplate这个类不见得很好操作,我们可以在进行扩展一个我们 * 自己的缓存类,比如:RedisStorage类; * @param factory : 通过Spring进行注入,参数在application.properties进行配置; * @return */ @Bean public RedisTemplate<String,String> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String,String> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(factory); //key序列化方式;(不然会出现乱码;),但是如果方法上有Long等非String类型的话,会报类型转换错误; //所以在没有自己定义key生成策略的时候,以下这个代码建议不要这么写,可以不配置或者自己实现ObjectRedisSerializer //或者JdkSerializationRedisSerializer序列化方式; RedisSerializer<String> redisSerializer = new StringRedisSerializer();//Long类型不可以会出现异常信息; redisTemplate.setKeySerializer(redisSerializer); redisTemplate.setHashKeySerializer(redisSerializer); 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); redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); redisTemplate.afterPropertiesSet(); return redisTemplate; } /** * 缓存管理器 * @param redisTemplate * @return */ @Bean public RedisCacheManager cacheManager(RedisTemplate<?,?> redisTemplate) { RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate); cacheManager.setDefaultExpiration(defaultExpiration);//默认生命周期30天 return cacheManager; } /** * redis数据操作异常处理 * 这里的处理:在日志中打印出错误信息,但是放行 * 保证redis服务器出现连接等问题的时候不影响程序的正常运行,使得能够出问题时不用缓存 * @return */ @Bean @Override public CacheErrorHandler errorHandler() { CacheErrorHandler cacheErrorHandler = new CacheErrorHandler() { @Override public void handleCacheGetError(RuntimeException e, Cache cache, Object key) { logger.error("redis异常:key=[{}]",key,e); } @Override public void handleCachePutError(RuntimeException e, Cache cache, Object key, Object value) { logger.error("redis异常:key=[{}]",key,e); } @Override public void handleCacheEvictError(RuntimeException e, Cache cache, Object key) { logger.error("redis异常:key=[{}]",key,e); } @Override public void handleCacheClearError(RuntimeException e, Cache cache) { logger.error("redis异常:",e); } }; return cacheErrorHandler; } /** * 取出对象及其父类定义的字段名和值存入map中 * @param obj * @param valueMap * @throws IllegalAccessException */ private void getStringValueMap(Object obj, Map valueMap) throws IllegalAccessException { List<Field> fields = scanfields(obj.getClass()); for (Field field : fields){ Boolean accessFlag = field.isAccessible(); field.setAccessible(true); Object o = field.get(obj); if (o != null){ if (o instanceof Integer || o instanceof Long || o instanceof Float || o instanceof Double || o instanceof String || o instanceof Collections || o instanceof Map || o instanceof Byte){ valueMap.put(field.getName(), o.toString()); }else { getStringValueMap(o,valueMap); } } field.setAccessible(accessFlag); } } /** * 对一个类扫描取出它和它父类定义的字段 * @param clazz * @return */ private List<Field> scanfields(Class clazz){ List<Field> fields = new ArrayList<>(); if (clazz == Object.class) { return fields; } Field[] fieldArray = clazz.getDeclaredFields(); String fieldName; for (int i = 0; i < fieldArray.length; i++){ fieldName = fieldArray[i].getName(); if (!("$staticClassInfo".equals(fieldName) || "__$stMC".equals(fieldName) || "metaClass".equals(fieldName) || "$staticClassInfo$".equals(fieldName) || "$callSiteArray".equals(fieldName))){ fields.add(fieldArray[i]); } } fields.addAll(scanfields(clazz.getSuperclass())); return fields; } }
4 测试
目前为止,所有的配置都已经写完了,接下来就是测试了。既然我们整合的是springboot,那接下来再依赖一个启动器,当然他的parent依赖也要加上,具体看springboot文档
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>1.5.6.RELEASE</version> </dependency>
测试用的service,需要缓存操作的加上Cacheable、CacheEvict或CachePut,必须要赋予value值
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; @Service public class TestService { @Cacheable(value = "hello") public Person hello(Person person){ System.err.println("2"); return person; } @Cacheable(value = "hello2") public Person hello2(Person person){ System.err.println("2"); return person; } @CacheEvict(value = "hello") public Person bye(Person person){ System.err.println("2"); return person; } @CacheEvict(value = "hello2") public Person bye2(Person person){ System.err.println("2"); return person; } @Cacheable(value = "door") public Door door(Door door){ System.err.println("2"); return door; } }
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class TestController { @Autowired private TestService testService; @RequestMapping("/hello/{age}") public Person hello(@PathVariable int age){ Person person = new Person("link",age,new Head(2,1)); return testService.hello(person); } @RequestMapping("/hello2/{age}") public Person hello2(@PathVariable int age){ Person person = new Person("link",age,new Head(2,1)); return testService.hello2(person); } @RequestMapping("/bye/{age}") public Person bye(@PathVariable int age){ Person person = new Person("link",age,new Head(2,1)); return testService.bye(person); } @RequestMapping("/bye2/{age}") public Person bye2(@PathVariable int age){ Person person = new Person("link",age,new Head(2,1)); return testService.bye2(person); } @RequestMapping("/door/{id}") public Door door(@PathVariable int id){ Door door = new Door(id); return testService.door(door); } }
spring boot的启动文件
import org.springframework.boot.SpringApplication import org.springframework.boot.autoconfigure.SpringBootApplication @SpringBootApplication public class Application { static void main(String[] args) { SpringApplication.run(Application.class,args) } }
原本这边放了目录结构和测试的图片,结果发上来一看图片飞走了,还是大家自己动手试试吧~~