spring与redis整合

目录

 

一、jedis对5种java数据类型的存储方式

二、关于redis的一点介绍

三、相关代码

四、总结


一、jedis对5种java数据类型的存储方式

一个缓存信息包含三个,name缓存名称,key缓存的key值,value缓存的value值。jedis常用操作如下:

public class JedisTest {
    private static final Jedis jedis = new Jedis("127.0.0.1", 6379);

    @Test
    public void test_string() {
        jedis.set("name", "zhangsan");
    }

    @Test
    public void test_hash() {
        //存放的类似map类型的
        jedis.hset("url", "google", "www.google.com");
        jedis.hset("url", "taobao", "www.taobao.com");
        jedis.hset("url", "baidu", "www.baidu.com");

        Map<String, String> userInfo = new HashMap<>();
        userInfo.put("name", "zhangsan");
        userInfo.put("age", "35");
        userInfo.put("sex", "man");
        jedis.hmset("userInfo", userInfo);
        String name = jedis.hget("userInfo", "name");
        //取多个返回值
        List<String> urlList = jedis.hmget("url", "google", "taobao", "baidu");
        //取hash所有key值
        Map<String, String> userInfoMap = jedis.hgetAll("userInfo");
    }

    @Test
    public void test_list() {
        jedis.lpush("charList", "abc");//lpush,在list首部添加元素
        jedis.rpush("charList", "def");//rpush,在listw尾部添加元素
        //截取list
        List<String> charList = jedis.lrange("charList", 0, 1);
        jedis.lpop("charList");//在list首部删除元素
        jedis.rpop("charList");//在list尾部删除元素
    }

    @Test
    public void test_set() {
        jedis.sadd("setMem", "s1");
        jedis.sadd("setMem", "s2");
        Set<String> sets = jedis.smembers("setMem");
    }

    @Test
    public void test_sort_set() {
        jedis.zadd("sortSetMem", 1, "s1");
        jedis.zadd("sortSetMem", 2, "s1");
        Set<String> sets = jedis.zrange("sortSetMem", 0, 1);
        Set<String> revesortSet = jedis.zrevrange("sortSetMem", 0, 1);//反向取
    }
}

二、关于redis的一点介绍

具体见redis简介

三、相关代码

1、CacheDefinition

在@Cacheable中没有属性描述缓存有效时间,以及当有个多个相关缓存(比如产品的缓存)的关联性属性,所以提供两个属性,duration和tag。tag属性的作用是将多个缓存关联,可以一起删除这些缓存(利用redis的hash数据类型)。

public class CacheDefinition {
    private String name;//缓存名称

    private int duration;//缓存有效时间

    private String tag;//缓存标签

    public String getName() {
        return name;
    }

    public CacheDefinition setName(String name) {
        this.name = name;
        return this;
    }

    public int getDuration() {
        return duration;
    }

    public CacheDefinition setDuration(int duration) {
        this.duration = duration;
        return this;
    }

    public String getTag() {
        return tag;
    }

    public CacheDefinition setTag(String tag) {
        this.tag = tag;
        return this;
    }
}

2、CacheMapping

定义该注解,用于表示缓存时间和标签。

 

/**
 * 定义一个缓存,支持类级别和方法级别,如果同时存在,类级别覆盖方法级别配置
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface CacheMapping {

    // 缓存时间(秒)
    public int duration() default 60;

    // 缓存标签,用于清理缓存
    public String tag() default "";
}

3、Storage

定义该接口,提供一些常用的缓存方法。

public interface Storage {

    Object get(Object key);

    void put(Object key, Object value, int seconds);//seconds,过期时间,单位秒

    void evict(Object key);

    void replace(Object key, Object value, int seconds);

    void destroy();

    boolean available();
}

4、RedisStorage

定义了关于Cache的一些信息,比如连接信息(redisStorage)、缓存名称、tag、有效时间等信息;
该类实现了上面的Storage接口,提供了基本的缓存增删改查操作。这里注意可以key、value等值都进行序列化了,方便存储和传输。然后对于redis的hash类型,采用hget/hset方法,第一个参数为缓存名称,第二个参数为缓存key;
另外Jedis只提供了保存string类型的方法,所以我们可以使用可以保存字节数组的BinaryJedisCommands方法。

public class RedisStorage<ResourceType extends BinaryJedisCommands> implements Storage {

    //序列化默认为jdk序列化,也可以选择为hessian2/kryo
    private Serializer<Object> serializer = new DefaultSerializer();
    private Deserializer<Object> deserializer = new DefaultDeserializer();
    private Logger log = Logger.getLogger(RedisStorage.class);

    private final Pool<ResourceType> pool;
    private final List<HostAndPort> hostAndPorts;

    public RedisStorage(Pool<ResourceType> pool, List<HostAndPort> hostAndPorts) {
        this.pool = checkNotNull(pool);
        this.hostAndPorts = checkNotNull(hostAndPorts);
    }

    //下面方法为Storage接口的方法实现
    @Override
    public Object get(Object key) {
        ResourceType resource = getResource();
        byte[] bytes = resource.get(key.toString().getBytes());
        if (bytes == null || bytes.length == 0) {
            return null;
        }
        DeserializingConverter dc = new DeserializingConverter(deserializer);
        return dc.convert(bytes);//反序列化成对象
    }

    @Override
    public void put(Object key, Object value, int seconds) {
        ResourceType resource = getResource();
        SerializingConverter sc = new SerializingConverter(serializer);
        byte[] bytes = sc.convert(value);
        resource.setex(key.toString().getBytes(), seconds, bytes);
    }

    @Override
    public void evict(Object key) {
        ResourceType resource = getResource();
        if (resource.exists(key.toString().getBytes())) {
            resource.del(key.toString().getBytes());
        }
    }

    @Override
    public void replace(Object key, Object value, int seconds) {
        put(key, value, seconds);
    }

    @Override
    public void destroy() {
        if (!pool.isClosed()) {
            pool.close();
        }
    }

    //返回增加的条数(如果key存在update,update返回0)
    public Long hset(final Object key, final Object field, final Object value, int seconds) {
        ResourceType resource = getResource();
        SerializingConverter sc = new SerializingConverter(serializer);
        byte[] bytes = sc.convert(value);
        long result = resource.hset(key.toString().getBytes(), field.toString().getBytes(), bytes);
        resource.expire(key.toString().getBytes(), seconds);
        return result;
    }

    public Set<Object> hkeys(String tag) {
        ResourceType resource = getResource();
        Set<byte[]> bytes = resource.hkeys(tag.getBytes());
        Set<Object> stringSet = new HashSet<>();
        for (byte[] bytes1 : bytes) {
            stringSet.add(new String(bytes1));
        }
        return stringSet;
    }

    public Object hget(Object tag, Object key) {
        ResourceType resource = getResource();
        byte[] bytes = resource.hget(tag.toString().getBytes(), key.toString().getBytes());
        if (bytes == null || bytes.length == 0) {
            return null;
        }
        DeserializingConverter dc = new DeserializingConverter(deserializer);
        return dc.convert(bytes);//反序列化成对象
    }

    //标签类缓存,利用hash数据类型(类似map),可以保存某一类的缓存
    //hash类型: key(field,value)
    public void putCacheToTag(Object tag, Object key, Object value, int duration) {
        if (StringUtils.isEmpty(tag)) {
            return;
        }
        Long resultCode = this.hset(tag, key, value, duration);
        if (null == resultCode) {
            throw new IllegalArgumentException("Fail to put cache to tag");
        }
    }

    //根据标签删除相关缓存
    //如果可以通过key删除,该方法可以不要
    public void evictTag(String tag) {
        if (StringUtils.isEmpty(tag)) {
            return;
        }
        this.evict(tag);
        Set<Object> keys = this.hkeys(tag);
        log.info("Removed cache tag " + tag);
        for (Object k : keys) {
            log.info("Removed cache,tag is " + tag + ",key is " + k.toString());
        }
    }


    //检测是否可用
    @Override
    public boolean available() {
        ResourceType resource = getResource();
        byte[] bytes = resource.echo("test".getBytes());
        if (bytes == null || bytes.length == 0) {
            return false;
        }
        return Arrays.equals(bytes, "test".getBytes());
    }

    private static <T> T checkNotNull(T reference) {
        if (reference == null) {
            throw new NullPointerException();
        }
        return reference;
    }

    public Pool<ResourceType> getPool() {
        return pool;
    }

    private ResourceType getResource() {
        return pool.getResource();
    }
}

5、RedisStorageFactory

创建redisStoage,redis集群信息可以使用类JedisShardInfo。

/**
 * 构建jedis连接
 * 单个连接 JedisPool
 * 集群连接 ShardedJedisPool
 */
public class RedisStorageFactory {
    public RedisStorage<? extends BinaryJedisCommands> newStorage(JedisPoolConfig poolConfig, List<JedisShardInfo> shardInfos) {
        List<HostAndPort> hostAndPortList = getHostAndPortByShardInfos(shardInfos);
        if (shardInfos.size() == 1) {
            return new RedisStorage<>(new JedisPool(poolConfig, shardInfos.get(0).getHost(), shardInfos.get(0).getPort(), shardInfos.get(0).getSoTimeout(), shardInfos.get(0).getPassword()), hostAndPortList);
        }
        ShardedJedisPool shardedJedisPool = new ShardedJedisPool(poolConfig, shardInfos);
        return new RedisStorage<>(shardedJedisPool, hostAndPortList);
    }

    private List<HostAndPort> getHostAndPortByShardInfos(List<JedisShardInfo> shardInfos) {
        List<HostAndPort> hostAndPorts = new ArrayList<>();
        for (JedisShardInfo shardInfo : shardInfos) {
            hostAndPorts.add(new HostAndPort(shardInfo.getHost(), shardInfo.getPort()));
        }
        return hostAndPorts;
    }

}

6、RedisCache

spring Cache接口的实现类,里面的实现方法可以调用storage的方法。

public class RedisCache<ResourceType extends BinaryJedisCommands> implements Cache {

    private String name;
    private String tag;
    private int duration;
    private TimeUnit timeUnit;
    private RedisStorage<ResourceType> redisStorage;

    public RedisCache() {
    }

    public RedisCache(RedisStorage<ResourceType> redisStorage, String name, int duration, TimeUnit timeUnit) {
        this.redisStorage = redisStorage;
        this.name = name;
        this.duration = duration;
        this.timeUnit = timeUnit;
    }

    public RedisCache(RedisStorage<ResourceType> redisStorage, String name, String tag, int duration, TimeUnit timeUnit) {
        this.redisStorage = redisStorage;
        this.name = name;
        this.duration = duration;
        this.timeUnit = timeUnit;
        this.tag = tag;
    }


    @Override
    public String getName() {
        return name;
    }

    @Override
    public Object getNativeCache() {
        return redisStorage.getPool();
    }

    @Override
    public ValueWrapper get(Object key) {
        Object actObject = null;
        if (StringUtils.isEmpty(tag)) {
            actObject = redisStorage.get(key);
        } else {
            actObject = redisStorage.hget(tag, key);
        }
        return actObject == null ? null : new SimpleValueWrapper(actObject);
    }


    @Override
    public <T> T get(Object key, Class<T> aClass) {
        ValueWrapper valueWrapper = this.get(key);
        if (valueWrapper == null) {
            return null;
        }
        return aClass.cast(valueWrapper.get());
    }

    @Override
    public void put(Object key, Object value) {
        if (StringUtils.isEmpty(tag)) {
            redisStorage.put(key, value, (int) timeUnit.toSeconds(duration));
            return;
        }
        redisStorage.putCacheToTag(tag, key, value, duration);
    }

    @Override
    public void evict(Object key) {
        redisStorage.evict(key);//如果该方法不行,试试evictTag,key为tag
    }

    @Override
    public void clear() {
        redisStorage.destroy();
    }
}

7、RedisServersManager

该类主要是对redis的服务进行管理。

public class RedisServersManager {

    private Logger log = Logger.getLogger(RedisServersManager.class);
    //jedis连接分片信息
    private List<JedisShardInfo> shards = new ArrayList<JedisShardInfo>();

    protected volatile RedisStorage<? extends BinaryJedisCommands> redisStorage;
    protected final static Object REDIS_STORAGE_LOCK = new Object();
    protected RedisStorageFactory factory = new RedisStorageFactory();
    protected JedisPoolConfig config = new JedisPoolConfig();

    /**
     * 根据servers生成jedis分片连接池
     */
    public void setServers(String servers) {
        String[] serversArrays = servers.trim().split(",");
        for (String server : serversArrays) {
            JedisShardInfo jedisShardInfo = shardInfo(server);
            this.shards.add(jedisShardInfo);
            log.info("Add a node to redis shard info,node info:" + jedisShardInfo);
        }
    }

    private JedisShardInfo shardInfo(String server) {
        String[] texts = server.split("\\^");
        JedisShardInfo shard = new JedisShardInfo(texts[0], Integer.parseInt(texts[1]), 1000);
        if (texts.length == 3) {
            shard.setPassword(texts[2]);
        }
        return shard;
    }

    public RedisStorage<? extends BinaryJedisCommands> newStorage() {
        if (null == redisStorage) {
            synchronized (REDIS_STORAGE_LOCK) {
                if (null == redisStorage) {
                    redisStorage = factory.newStorage(config, shards);
                    log.info("Successfully created a redis storage.");
                }
            }
        }
        return redisStorage;
    }

}

8、SpringDataCacheManager

实现CacheManager接口,对cache进行管理。通过ApplicationContextAware接口获取到applicationContext,通过InitializingBean接口在初始化该类的时候把所有有缓存注解信息的放入到集合中。

public class SpringDataCacheManager extends RedisServersManager implements ApplicationContextAware, InitializingBean, CacheManager {

    private ApplicationContext applicationContext;
    private final Map<String, CacheDefinition> caches = new ConcurrentReferenceHashMap<>();
    private int defaultDuration = 60;//默认缓存60秒
    private String servers = "127.0.0.1^7100^password,127.0.0.1^6379^password";

    //InitializingBean的实现方法
    @Override
    public void afterPropertiesSet() throws Exception {
        setServers(servers);
        parseFromContext(applicationContext);
    }

    private void parseFromContext(final ApplicationContext context) {
        String[] beanNames = context.getBeanNamesForType(Object.class);
        for (final String beanName : beanNames) {
            final Class<?> classType = context.getType(beanName);
            //查询该类是否有service和repository注解
            Service service = findAnnotation(classType, Service.class);
            Repository repository = findAnnotation(classType, Repository.class);
            if (service == null && repository == null) {
                continue;
            }
            ReflectionUtils.doWithMethods(classType, new ReflectionUtils.MethodCallback() {
                public void doWith(Method method) {
                    ReflectionUtils.makeAccessible(method);
                    CacheMapping cacheMapping = findCacheMapping(method, classType);//先找类级别,没有再找当前方法级别
                    Cacheable cacheable = findAnnotation(method, Cacheable.class);
                    String[] cacheNames = getCacheNamesWithCacheable(cacheable);
                    for (String cacheName : cacheNames) {
                        CacheDefinition cacheDefinition = new CacheDefinition()
                                .setName(cacheName)
                                .setDuration(cacheMapping.duration())
                                .setTag(cacheMapping.tag());
                        caches.put(cacheName, cacheDefinition);
                    }
                }
            }, new ReflectionUtils.MethodFilter() {
                public boolean matches(Method method) {
                    return !method.isSynthetic() && findAnnotation(method, Cacheable.class) != null;
                }
            });
            if (context.getParent() != null) {
                parseFromContext(context.getParent());
            }
        }
    }

    private CacheMapping findCacheMapping(Method method, Class<?> handlerType) {
        CacheMapping cacheMapping = findAnnotation(handlerType, CacheMapping.class);
        if (cacheMapping == null) {//如果类注解没有,再从方法注解查询
            cacheMapping = findAnnotation(method, CacheMapping.class);
        }
        if (cacheMapping == null) {
            throw new IllegalStateException("No cache mapping (@CacheMapping) could be detected on '" +
                    method.toString() + "'. Make sure to set the value parameter on the annotation or " +
                    "declare a @CacheMapping at the class-level with the default cache cacheMapping to use.");
        }
        return cacheMapping;
    }

    private String[] getCacheNamesWithCacheable(Cacheable cacheable) {
        return cacheable.value();
    }

    //CacheManager的实现方法,该方法每次调用cache的get方法之前会调用一次
    @Override
    public Cache getCache(String name) {
        CacheDefinition cacheDefinition = caches.get(name);
        if (null != cacheDefinition) {
            return new RedisCache<>(newStorage(), name, cacheDefinition.getTag(), cacheDefinition.getDuration(), TimeUnit.SECONDS);
        }
        return new RedisCache<>(newStorage(), name, defaultDuration, TimeUnit.SECONDS);
    }

    @Override
    public Collection<String> getCacheNames() {
        return caches.keySet();
    }

    //ApplicationContextAware
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

9、Service类

@Service
public class PersonService2 {

    @Cacheable(value = "getPersonByName", key = "'PersonService.getPersonByName'+#name")
    @CacheMapping(duration = 20, tag = "PERSON")
    public Person getPersonByName(String name) {
        // 方法内部实现不考虑缓存逻辑,直接实现业务
        System.out.println("调用了Service方法");
        return getFromDB(name);
    }

    private Person getFromDB(String name) {
        System.out.println("从数据库查询了");
        return new Person();
    }
}

10、测试类

public class PersonCacheTest {

    private PersonService2 personService2;

    @Before
    public void setUp() {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans/application_redis_cache.xml");
        personService2 = context.getBean("personService2", PersonService2.class);
    }

    @Test
    public void testGetPersonByName() {
        System.out.println("第一次查询………………");
        personService2.getPersonByName("张三");
        System.out.println("第二次查询………………");
        personService2.getPersonByName("李四");
        System.out.println("第三次查询………………");
        personService2.getPersonByName("张三");
    }
}

输出

第一次查询………………
调用了Service方法
从数据库查询了
第二次查询………………
调用了Service方法
从数据库查询了
第三次查询………………

第三次没有从数据库查询了。

 

四、总结

1、BinaryJedisCommands接口可以存储字节数组,存储的key/value建议先转化成字节数组然后再序列化;

2、单服务jedis使用到连接池接口有JedisPoolConfig/JedisPool,集群的对应有JedisPoolConfig/JedisShardInfo/ShardedJedisPool;

3、实现spring Cache需要实现Cache接口和CacheManage接口(cache接口提供增删改查,cacheManage接口对cache进行管理)。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值