spring-data-redis 自定义注解扩展实现时效设置

来源:互联网 发布:模拟倒车软件 编辑:程序博客网 时间:2024/04/28 08:01

前言

spring目前在@Cacheable和@CacheEvict等注解上不支持缓存时效设置,只允许通过配置文件设置全局时效。这样就很不方便设定时间。比如系统参数和业务数据的时效是不一样的,这给程序开发造成很大的困扰。不得已,本人实现了类似Sping的三个注解。以下是具体实现。

工程下载>>>

实现

1、注解类的实现

package com.example.spring.boot.redis.annotation;import java.lang.annotation.*;/** * Author: 王俊超 * Date: 2017-06-10 05:56 * All Rights Reserved !!! */@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Inheritedpublic @interface RedisCacheEvict {    /**     * 缓存名称     *     * @return     */    String cacheName();    /**     * 缓存key     *     * @return     */    String key();}
package com.example.spring.boot.redis.annotation;import java.lang.annotation.*;import java.util.concurrent.TimeUnit;/** * Author: 王俊超 * Date: 2017-06-10 05:56 * All Rights Reserved !!! */@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Inheritedpublic @interface RedisCacheGet {    /**     * 缓存名称     *     * @return     */    String cacheName();    /**     * 缓存key     *     * @return     */    String key();    /**     * 缓存过期时间     *     * @return     */    int expire() default 0;    /**     * 缓存的时间单位     *     * @return     */    TimeUnit timeUnit();}
package com.example.spring.boot.redis.annotation;import java.lang.annotation.*;import java.util.concurrent.TimeUnit;/** * Author: 王俊超 * Date: 2017-06-10 05:56 * All Rights Reserved !!! */@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Inheritedpublic @interface RedisCachePut {    /**     * 缓存名称     *     * @return     */    String cacheName();    /**     * 缓存key     *     * @return     */    String key();    /**     * 缓存过期时间     *     * @return     */    int expire() default 0;    /**     * 缓存的时间单位     *     * @return     */    TimeUnit timeUnit();}

2、切面代码实现

package com.example.spring.boot.redis.aspect;import com.example.spring.boot.redis.annotation.RedisCacheEvict;import com.example.spring.boot.redis.annotation.RedisCacheGet;import com.example.spring.boot.redis.annotation.RedisCachePut;import com.example.spring.boot.redis.common.RedisClient;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.Signature;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.reflect.MethodSignature;import org.springframework.core.LocalVariableTableParameterNameDiscoverer;import org.springframework.core.ParameterNameDiscoverer;import org.springframework.util.StringUtils;import java.lang.reflect.Method;/** * 缓存处理时间切面 * * Author: 王俊超 * Date: 2017-06-10 06:17 * All Rights Reserved !!! */@Aspectpublic class RedisCacheAspect {    private final static char DOT = '.';    private final static char SHARP = '#';    private RedisClient redisClient;    public RedisClient getRedisClient() {        return redisClient;    }    public void setRedisClient(RedisClient redisClient) {        this.redisClient = redisClient;    }    /**     * 对象入缓存     * @param pjp     * @param cachePut     * @return     * @throws Throwable     */    @Around("@annotation(cachePut)")    public Object cachePut(ProceedingJoinPoint pjp, RedisCachePut cachePut) throws Throwable {        Object keyObject = getCacheKey(pjp, cachePut.key());        Object result = pjp.proceed();        // 不为空才保存数据        if (result != null && keyObject != null){            redisClient.set(cachePut.cacheName(), keyObject, result, cachePut.expire());        }        return result;    }    /**     * 优先从缓存中取对象     *     * @param pjp     * @param cacheGet     * @return     * @throws Throwable     */    @Around("@annotation(cacheGet)")    public Object cacheGet(ProceedingJoinPoint pjp, RedisCacheGet cacheGet) throws Throwable {        Object keyObject = getCacheKey(pjp, cacheGet.key());        Object result = redisClient.get(cacheGet.cacheName(), keyObject);        // 如果从缓存中没有取到数据,就从调用方法获取数据        if (result == null) {            result = pjp.proceed();            // 方法的返回值不是void类型,就要将结果入缓存            Class clz = getAdvicedMethod(pjp).getReturnType();            if (clz != Void.class) {                redisClient.set(cacheGet.cacheName(), keyObject, result, cacheGet.expire());            }        }        return result;    }    /**     * 删除缓存     *     * @param pjp     * @param cacheEvict     * @return     * @throws Throwable     */    @Around("@annotation(cacheEvict)")    public Object cacheEvict(ProceedingJoinPoint pjp, RedisCacheEvict cacheEvict) throws Throwable {        Object keyObject = getCacheKey(pjp, cacheEvict.key());        redisClient.del(cacheEvict.cacheName(), keyObject);        return pjp.proceed();    }    /**     * 获取缓存的key对象     *     * @param pjp     * @param key     * @return     * @throws Exception     */    private Object getCacheKey(ProceedingJoinPoint pjp, String key) throws Exception {        // 以#开头        if (key.length() > 0 && key.charAt(0) == SHARP) {            // 去掉#            key = key.substring(1);            // 将key分割成属性和参数名,第一个“.”之前是参数名,之后是属性名称            int dotIdx = key.indexOf(DOT);            String argName = key;            if (dotIdx > 0) {                argName = key.substring(0, dotIdx);                key = key.substring(dotIdx + 1); // 剩下的属性            }            // 取参数值            Object argVal = getArg(pjp, argName);            // 获取参数的属性值            Object objectKey = argVal;            if (dotIdx > 0) {                objectKey = getObjectKey(argVal, key);            }            return objectKey;        } else { // 不是以#开头的就以其值作为参数key            return key;        }    }    /**     * 获取参数对象     *     * @param pjp 连接点     * @param parameterName 参数名称     * @return     */    private Object getArg(ProceedingJoinPoint pjp, String parameterName) throws NoSuchMethodException {        Method method = getAdvicedMethod(pjp);        ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();        String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);        if (parameterNames != null) {            int idx = 0;            for (String name : parameterNames) {                if (name.equals(parameterName)) {                    return pjp.getArgs()[idx];                }                idx++;            }        }        throw new IllegalArgumentException("no such parameter name: [" + parameterName + "] in method: " + method);    }    /**     * 获取拦截的方法     *     * @param pjp 连接点     * @return     * @throws NoSuchMethodException     */    private Method getAdvicedMethod(ProceedingJoinPoint pjp) throws NoSuchMethodException {        Signature sig = pjp.getSignature();        MethodSignature msig = null;        if (!(sig instanceof MethodSignature)) {            throw new IllegalArgumentException("annotation can only use to method.");        }        msig = (MethodSignature) sig;        Object target = pjp.getTarget();        return target.getClass().getMethod(msig.getName(), msig.getParameterTypes());    }    /**     * 获取从object上获取key所对应的属性对象     * 例如:key: country.province.city.town     * 就相当于调用:object.getCountry().getProvince().getCity.getTown()     *     * @param object     * @param key     * @return     * @throws Exception     */    private Object getObjectKey(Object object, String key) throws Exception {        // 如果key已经是空了就直接返回        if (StringUtils.isEmpty(key)) {            return object;        }        int dotIdx = key.indexOf(DOT);        // 形如key=aa.bb**的情况        if (dotIdx > 0) {            // 取第一个属性值            String propertyName = key.substring(0, dotIdx);            // 取剩下的key            key = key.substring(dotIdx + 1);            Object property = getProperty(object, getterMethod(propertyName));            return getObjectKey(property, key);        } else { // key=aa            return getProperty(object, getterMethod(key));        }    }    /**     * 获取name的getter方法名称     *     * @param name     * @return     */    private String getterMethod(String name) {        return "get" + Character.toUpperCase(name.charAt(0)) + name.substring(1);    }    /**     * 调用obj对象上的getterMethodName     *     * @param obj     * @param getterMethodName     * @return     * @throws Exception     */    private Object getProperty(Object obj, String getterMethodName) throws Exception {        return obj.getClass().getMethod(getterMethodName).invoke(obj);    }}

3、工具类实现

package com.example.spring.boot.redis.common;import com.esotericsoftware.kryo.Kryo;import com.esotericsoftware.kryo.io.Input;import com.esotericsoftware.kryo.io.Output;import org.springframework.data.redis.serializer.RedisSerializer;import org.springframework.data.redis.serializer.SerializationException;/** * 序列化和反序列化工具 * * Author: 王俊超 * Date: 2017-05-31 21:43 * All Rights Reserved !!! */public class KryoRedisSerializer<T> implements RedisSerializer<T> {    private Kryo kryo = new Kryo();    @Override    public byte[] serialize(T t) throws SerializationException {        byte[] buffer = new byte[2048];        Output output = new Output(buffer);        kryo.writeClassAndObject(output, t);        byte[] result = output.toBytes();        output.close();        return result;    }    @Override    public T deserialize(byte[] bytes) throws SerializationException {        Input input = new Input(bytes);        @SuppressWarnings("unchecked")        T t = (T) kryo.readClassAndObject(input);        input.close();        return t;    }}
package com.example.spring.boot.redis.common;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;import java.util.Set;/** * Redis客户端实现工具 * * Author: 王俊超 * Date: 2017-06-04 19:57 * All Rights Reserved !!! */public class RedisClientImpl implements RedisClient {    private RedisTemplate<Object, Object> redisTemplate;    private RedisSerializer<Object> keySerializer;    private RedisSerializer<Object> valSerializer;    public RedisTemplate<Object, Object> getRedisTemplate() {        return redisTemplate;    }    public void setRedisTemplate(RedisTemplate<Object, Object> redisTemplate) {        this.redisTemplate = redisTemplate;    }    public RedisSerializer<Object> getKeySerializer() {        return keySerializer;    }    public void setKeySerializer(RedisSerializer<Object> keySerializer) {        this.keySerializer = keySerializer;    }    public RedisSerializer<Object> getValSerializer() {        return valSerializer;    }    public void setValSerializer(RedisSerializer<Object> valSerializer) {        this.valSerializer = valSerializer;    }    /////////////////////////////////////    /**     * 获取最终的key     *     * @param cacheName     * @param key     * @return     */    private byte[] getRealKey(Object cacheName, Object key) {        byte[] b1 = keySerializer.serialize(cacheName);        byte[] b2 = keySerializer.serialize(key);        byte[] result = new byte[b1.length + b2.length];        System.arraycopy(b1, 0, result, 0, b1.length);        System.arraycopy(b2, 0, result, b1.length, b2.length);        return result;    }    /**     * 获取真实key     * @param cacheName     * @param key     * @return     */    private byte[] getRealKey(byte[] cacheName, Object key) {        byte[] b2 = keySerializer.serialize(key);        byte[] result = new byte[cacheName.length + b2.length];        System.arraycopy(cacheName, 0, result, 0, cacheName.length);        System.arraycopy(b2, 0, result, cacheName.length, b2.length);        return result;    }    @Override    public long del(Object cacheName, Object... keys) {        return redisTemplate.execute(new RedisCallback<Long>() {            @Override            public Long doInRedis(RedisConnection connection) throws DataAccessException {                byte[] b1 = keySerializer.serialize(cacheName);                long result = 0;                for (Object o : keys) {                    result += connection.del(getRealKey(b1, o));                }                return result;            }        });    }    @Override    public void set(byte[] key, byte[] value, long liveTime) {        redisTemplate.execute(new RedisCallback<Boolean>() {            @Override            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {                connection.set(key, value);                if (liveTime > 0) {                    connection.expire(key, liveTime);                }                return true;            }        });    }    @Override    public void set(Object cacheName, Object key, Object value, long liveTime) {        this.set(getRealKey(cacheName, key), valSerializer.serialize(value), liveTime);    }    @Override    public void set(Object cacheName, Object key, Object value) {        this.set(cacheName, key, value, 0L);    }    @Override    public void set(byte[] key, byte[] value) {        this.set(key, value, 0L);    }    /**     * 获取redis value (String)     *     * @param key     * @return     */    @Override    public <T> T get(byte[] key) {        return redisTemplate.execute(new RedisCallback<T>() {            @Override            public T doInRedis(RedisConnection connection) throws DataAccessException {                byte[] result = connection.get(key);                Object obj = null;                if (result != null) {                    obj = valSerializer.deserialize(result);                }                return (T) obj;            }        });    }    @Override    public <T> T get(Object cacheName, Object key) {        return this.get(getRealKey(cacheName, key));    }    @Override    public Set keys(byte[] pattern) {        return redisTemplate.execute(new RedisCallback<Set>() {            @Override            public Set doInRedis(RedisConnection connection) throws DataAccessException {                return connection.keys(pattern);            }        });    }    @Override    public Set keys(String pattern) {        return this.keys(keySerializer.serialize(pattern));    }    @Override    public boolean exists(byte[] key) {        return redisTemplate.execute(new RedisCallback<Boolean>() {            @Override            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {                return connection.exists(key);            }        });    }    @Override    public boolean exists(Object cacheName, Object key) {        return this.exists(getRealKey(cacheName, key));    }    @Override    public boolean flushDb() {        return redisTemplate.execute(new RedisCallback<Boolean>() {            @Override            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {                connection.flushDb();                return true;            }        });    }    @Override    public long dbSize() {        return redisTemplate.execute(new RedisCallback<Long>() {            public Long doInRedis(RedisConnection connection) throws DataAccessException {                return connection.dbSize();            }        });    }    @Override    public String ping() {        return redisTemplate.execute(new RedisCallback<String>() {            public String doInRedis(RedisConnection connection) throws DataAccessException {                return connection.ping();            }        });    }}

4、应用配置

package com.example.spring.boot.redis;import com.example.spring.boot.redis.aspect.RedisCacheAspect;import com.example.spring.boot.redis.common.KryoRedisSerializer;import com.example.spring.boot.redis.common.RedisClient;import com.example.spring.boot.redis.common.RedisClientImpl;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Primary;import org.springframework.data.redis.connection.RedisClusterConfiguration;import org.springframework.data.redis.connection.RedisConnectionFactory;import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;import org.springframework.data.redis.core.RedisTemplate;import java.net.UnknownHostException;import java.util.Arrays;import java.util.List;/** * Author: 王俊超 * Date: 2017-05-07 10:02 * All Rights Reserved !!! */@Configurationpublic class AppConfig {    /**     * Redis连接工厂     *     * @return     */    @Primary    @Bean("redisConnectionFactory")    public RedisConnectionFactory redisConnectionFactory() {        // Redis集群地址        List<String> clusterNodes = Arrays.asList("192.168.241.150:7110",                "192.168.241.150:7111", "192.168.241.150:7112", "192.168.241.150:7113",                "192.168.241.150:7114", "192.168.241.150:7115", "192.168.241.150:7116",                "192.168.241.150:7117", "192.168.241.150:7118", "192.168.241.150:7119"        );        // 获取Redis集群配置信息        RedisClusterConfiguration rcf = new RedisClusterConfiguration(clusterNodes);        return new JedisConnectionFactory(rcf);    }    /**     * 创建redis模板     *     * @param factory     * @return     * @throws UnknownHostException     */    @Primary    @Bean("redisTemplate")    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory)            throws UnknownHostException {        RedisTemplate<Object, Object> template = new RedisTemplate<>();        template.setConnectionFactory(factory);        // 可根据需要设置//        // redis value使用的序列化器//        template.setValueSerializer(new KryoRedisSerializer<>());//        template.setHashKeySerializer(new KryoRedisSerializer<>());//        // redis key使用的序列化器//        template.setKeySerializer(new KryoRedisSerializer<>());//        template.setHashValueSerializer(new KryoRedisSerializer<>());        template.afterPropertiesSet();        return template;    }    /**     * 返回redis客户端     *     * @param redisTemplate     * @return     */    @Bean    public RedisClient redisClient(RedisTemplate<Object, Object> redisTemplate) {        RedisClientImpl redisClient = new RedisClientImpl();        redisClient.setRedisTemplate(redisTemplate);        KryoRedisSerializer<Object> serializer = new KryoRedisSerializer<>();        redisClient.setKeySerializer(serializer);        redisClient.setValSerializer(serializer);        return redisClient;    }    /**     * redis缓存的切面     * @param redisClient     * @return     */    @Bean    public RedisCacheAspect redisCacheAspect(RedisClient redisClient) {        RedisCacheAspect aspect = new RedisCacheAspect();        aspect.setRedisClient(redisClient);        return aspect;    }}

5、应用的启动

package com.example.spring.boot.redis;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.EnableAutoConfiguration;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cache.annotation.EnableCaching;import org.springframework.context.ConfigurableApplicationContext;import org.springframework.transaction.annotation.EnableTransactionManagement;/** * Author: 王俊超 * Date: 2017-05-07 10:04 * All Rights Reserved !!! */@SpringBootApplication@EnableTransactionManagement@EnableCaching@EnableAutoConfiguration//@EnableAspectJAutoProxypublic class AppRunner {    public static void main(String[] args) {        ConfigurableApplicationContext ctx = SpringApplication.run(AppRunner.class, args);    }}

6、配置文件

server.context-path=/redis/cache# 开启AOPspring.aop.auto=true

测试

1、测试数据

package com.example.spring.boot.redis.entity;/** * 县 * Author: 王俊超 * Date: 2017-06-12 20:06 * All Rights Reserved !!! */public class City {    private long id;    private String name;    private Province province;    public City() {    }    public City(long id, String name, Province province) {        this.id = id;        this.name = name;        this.province = province;    }    public long getId() {        return id;    }    public void setId(long id) {        this.id = id;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public Province getProvince() {        return province;    }    public void setProvince(Province province) {        this.province = province;    }}
package com.example.spring.boot.redis.entity;/** * 国家 * Author: 王俊超 * Date: 2017-06-12 20:06 * All Rights Reserved !!! */public class Country {    private long id;    private String name;    public Country() {    }    public Country(long id, String name) {        this.id = id;        this.name = name;    }    public long getId() {        return id;    }    public void setId(long id) {        this.id = id;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }}
package com.example.spring.boot.redis.entity;/** * 省 * Author: 王俊超 * Date: 2017-06-12 20:06 * All Rights Reserved !!! */public class Province {    private long id;    private String name;    private Country country;    public Province() {    }    public Province(long id, String name, Country country) {        this.id = id;        this.name = name;        this.country = country;    }    public long getId() {        return id;    }    public void setId(long id) {        this.id = id;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public Country getCountry() {        return country;    }    public void setCountry(Country country) {        this.country = country;    }}
package com.example.spring.boot.redis.entity;/** * 镇 * Author: 王俊超 * Date: 2017-06-12 20:06 * All Rights Reserved !!! */public class Town {    private long id;    private String name;    private City city;    public Town() {    }    public Town(long id, String name, City city) {        this.id = id;        this.name = name;        this.city = city;    }    public long getId() {        return id;    }    public void setId(int id) {        this.id = id;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public City getCity() {        return city;    }    public void setCity(City city) {        this.city = city;    }}
package com.example.spring.boot.redis;import com.example.spring.boot.redis.annotation.RedisCacheEvict;import com.example.spring.boot.redis.annotation.RedisCacheGet;import com.example.spring.boot.redis.annotation.RedisCachePut;import com.example.spring.boot.redis.entity.City;import com.example.spring.boot.redis.entity.Country;import com.example.spring.boot.redis.entity.Province;import com.example.spring.boot.redis.entity.Town;import org.springframework.stereotype.Component;import java.util.concurrent.TimeUnit;/** * Author: 王俊超 * Date: 2017-06-10 06:26 * All Rights Reserved !!! */@Componentpublic class TestData {    public  final static Country COUNTRY = new Country(111111, "CHINA");    public  final static Province PROVINCE = new Province(222222, "GuangZhou", COUNTRY);    public  final static City CITY = new City(333333, "ShenZhen", PROVINCE);    public  final static Town TOWN = new Town(444444, "Where", CITY);    public final static String LOCATION = "location";    @RedisCachePut(cacheName = LOCATION, key = "#town.city.province.country.id",            expire = 60, timeUnit = TimeUnit.SECONDS)    public Country createCountry(Town town) {        return town.getCity().getProvince().getCountry();    }    @RedisCacheGet(cacheName = LOCATION, key = "#id", expire = 60, timeUnit = TimeUnit.SECONDS)    public Country getCountry(long id) {        return COUNTRY;    }    @RedisCacheEvict(cacheName = LOCATION, key = "#id")    public void deleteCountry(long id) {        // 清除缓存    }}

2、测试用例

import com.example.spring.boot.redis.AppRunner;import com.example.spring.boot.redis.TestData;import com.example.spring.boot.redis.common.KryoRedisSerializer;import com.example.spring.boot.redis.common.RedisClient;import com.example.spring.boot.redis.entity.Country;import org.junit.Assert;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;/** * Author: 王俊超 * Date: 2017-06-12 21:10 * All Rights Reserved !!! */@RunWith(SpringRunner.class)@SpringBootTest( classes = AppRunner.class)public class TestRunner {    @Autowired    RedisClient redisClient;    @Autowired    TestData testData;    /**     * 测试序列化,反序列化     */    @Test    public void testSerialize() {        Country c1 = testData.createCountry(TestData.TOWN);        KryoRedisSerializer<Object> serializer = new KryoRedisSerializer<>();        byte[] b1 = serializer.serialize(c1);        Country c2 = (Country) serializer.deserialize(b1);        Assert.assertNotNull(c2);        Assert.assertEquals(c1.getId(), c2.getId());        Assert.assertEquals(c1.getName(), c2.getName());    }    @Test    public void testCachePut() {        // 先清理缓存        redisClient.del(TestData.LOCATION, TestData.COUNTRY.getId());        Country c = redisClient.get(TestData.LOCATION, TestData.COUNTRY.getId());        Assert.assertNull(c);        // 创建一个国家        Country c1 = testData.createCountry(TestData.TOWN);        // 直接从缓存中取数据,说明数据已经入缓存        Country c2 = redisClient.get(TestData.LOCATION, TestData.COUNTRY.getId());        Assert.assertNotNull(c2);        Assert.assertEquals(c1.getId(), c2.getId());        Assert.assertEquals(c1.getName(), c2.getName());    }    @Test    public void testCacheGet() {        // 先清理缓存        redisClient.del(TestData.LOCATION, TestData.COUNTRY.getId());        Country c = redisClient.get(TestData.LOCATION, TestData.COUNTRY.getId());        Assert.assertNull(c);        // 取数据        Country c1 = testData.getCountry(TestData.COUNTRY.getId());        // 从缓存中取        Country c2 = redisClient.get(TestData.LOCATION, TestData.COUNTRY.getId());        Assert.assertNotNull(c2);        Assert.assertEquals(c1.getId(), c2.getId());        Assert.assertEquals(c1.getName(), c2.getName());    }    @Test    public void testCacheEvict() {        // 创建一个国家        Country c = testData.createCountry(TestData.TOWN);        redisClient.del(TestData.LOCATION, TestData.COUNTRY.getId());        c = redisClient.get(TestData.LOCATION, TestData.COUNTRY.getId());        Assert.assertNull(c);    }}
原创粉丝点击