spring boot 整合 redis

1 引入依赖


   org.springframework.boot
   spring-boot-starter-data-redis
   1.5.6.RELEASE

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> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate,String> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);
        //key序列化方式;(不然会出现乱码;),但是如果方法上有Long等非String类型的话,会报类型转换错误;
        //所以在没有自己定义key生成策略的时候,以下这个代码建议不要这么写,可以不配置或者自己实现ObjectRedisSerializer
        //或者JdkSerializationRedisSerializer序列化方式;
        RedisSerializer 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 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 scanfields(Class clazz){
        List 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文档

   org.springframework.boot
   spring-boot-starter-web
   1.5.6.RELEASE

测试用的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;
    }
}

测试用的controller
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)
    }
}

原本这边放了目录结构和测试的图片,结果发上来一看图片飞走了,还是大家自己动手试试吧~~




你可能感兴趣的:(redis,spring,boot,redis)