目录
一、jedis对5种java数据类型的存储方式
二、关于redis的一点介绍
三、相关代码
四、总结
一个缓存信息包含三个,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 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 urlList = jedis.hmget("url", "google", "taobao", "baidu");
//取hash所有key值
Map userInfoMap = jedis.hgetAll("userInfo");
}
@Test
public void test_list() {
jedis.lpush("charList", "abc");//lpush,在list首部添加元素
jedis.rpush("charList", "def");//rpush,在listw尾部添加元素
//截取list
List 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 sets = jedis.smembers("setMem");
}
@Test
public void test_sort_set() {
jedis.zadd("sortSetMem", 1, "s1");
jedis.zadd("sortSetMem", 2, "s1");
Set sets = jedis.zrange("sortSetMem", 0, 1);
Set revesortSet = jedis.zrevrange("sortSetMem", 0, 1);//反向取
}
}
具体见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 implements Storage {
//序列化默认为jdk序列化,也可以选择为hessian2/kryo
private Serializer
5、RedisStorageFactory
创建redisStoage,redis集群信息可以使用类JedisShardInfo。
/**
* 构建jedis连接
* 单个连接 JedisPool
* 集群连接 ShardedJedisPool
*/
public class RedisStorageFactory {
public RedisStorage extends BinaryJedisCommands> newStorage(JedisPoolConfig poolConfig, List shardInfos) {
List 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 getHostAndPortByShardInfos(List shardInfos) {
List 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 implements Cache {
private String name;
private String tag;
private int duration;
private TimeUnit timeUnit;
private RedisStorage redisStorage;
public RedisCache() {
}
public RedisCache(RedisStorage redisStorage, String name, int duration, TimeUnit timeUnit) {
this.redisStorage = redisStorage;
this.name = name;
this.duration = duration;
this.timeUnit = timeUnit;
}
public RedisCache(RedisStorage 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 get(Object key, Class 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 shards = new ArrayList();
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 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 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进行管理)。