简介
本文则通过自定义注解的方式,来完成一个hash与POJO之间的转换。目标是为了简化代码结构。
类似的功能,Spring Data Redis是有的。
定义POJO在redis中的数据结构
这里随便定了几个,本文只实现了hash。
public enum RedisStorageStructure {
SET, // 基本元素
HASH, // 哈希
LIST, // 列表
}
定义注解
@Retention(RUNTIME)
@Target(TYPE)
@Inherited
public @interface RedisStorageInjector {
RedisStorageStructure value() default RedisStorageStructure.HASH;// 默认为hash存储
}
定义存入redis的key值
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.METHOD})
public @interface RedisStorageKey {
}
定义存入redis的非key值
@Retention(RUNTIME)
@Target(ElementType.FIELD)
public @interface RedisStorageElement {
String value();
}
存储抽象工具类
通过本类
public abstract class RedisStorageUtil {
protected RedisTemplate<String, String> redisTemplate;
protected RedisStorageUtil(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public static RedisStorageWrtieUtil writeOps(RedisTemplate<String, String> redisTemplate) {
return new RedisStorageWrtieUtil(redisTemplate);
}
public static RedisStorageReadUtil readOps(RedisTemplate<String, String> redisTemplate) {
return new RedisStorageReadUtil(redisTemplate);
}
}
写工具类
public class RedisStorageWrtieUtil extends RedisStorageUtil {
public RedisStorageWrtieUtil(RedisTemplate<String, String> redisTemplate) {
super(redisTemplate);
}
public void write(Object object) throws IllegalArgumentException, IllegalAccessException, IllegalStateException, InvocationTargetException {
// 判断o是加了那种类型的注解,hash有hash的处理方式。
if (object.getClass().isAnnotationPresent(RedisStorageInjector.class)) {
RedisStorageInjector gg = object.getClass().getAnnotation(RedisStorageInjector.class);
RedisStorageStructure way = gg.value();
// TODO 后续扩展其他
switch (way) {
case HASH:
handleHash(object);
break;
default:
throw new IllegalStateException("该类不支持以" + way + "存储进redis");
}
} else {
throw new IllegalStateException("该类不支持存储进redis,请添加 @RedisStorageInjector");
}
}
private void handleHash(Object object) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
String key = null;
Map<String, String> mapStorageStr = new HashMap<>();
//
for (Field field : object.getClass().getDeclaredFields()) {
if (field.isAnnotationPresent(RedisStorageKey.class)) { // 拿key
field.setAccessible(true);
key = (String) field.get(object);
} else if (field.isAnnotationPresent(RedisStorageElement.class)) {
// 拿到加了注解的域
RedisStorageElement g = field.getAnnotation(RedisStorageElement.class);
String value = g.value();// 拿到存进去的域名
if (value != null && value.length() > 0) {
// TODO 目前只实现String类型
Object nextObject = field.get(object);
if (nextObject == null) {// 默认值
nextObject = "";
}
if (nextObject instanceof String) {
mapStorageStr.put(value, nextObject.toString());
} else {
throw new IllegalStateException(field.getName() + "该值不支持非String存储进redis");
}
} else {
throw new IllegalStateException(field.getName() + "值缺少映射关系,无法存储进redis");
}
}
}
if (key == null) {
for (Method m : object.getClass().getDeclaredMethods()) {
if (m.isAnnotationPresent(RedisStorageKey.class)) {
m.setAccessible(true);
key = m.invoke(object).toString();
}
}
}
if (key != null) {
redisTemplate.opsForHash().putAll(key, mapStorageStr);
} else {
throw new IllegalStateException(object.getClass().getName() + "缺少key,无法存储进redis");
}
}
}
读工具类
public class RedisStorageReadUtil extends RedisStorageUtil {
public RedisStorageReadUtil(RedisTemplate<String, String> redisTemplate) {
super(redisTemplate);
}
public Object read(Object object) throws IllegalArgumentException, IllegalAccessException, IllegalStateException, InvocationTargetException {
if (object.getClass().isAnnotationPresent(RedisStorageInjector.class)) {
RedisStorageInjector gg = object.getClass().getAnnotation(RedisStorageInjector.class);
RedisStorageStructure way = gg.value();
// TODO 扩展处理方式
switch (way) {
case HASH:
handleHash(object);
break;
default:
throw new IllegalStateException("该类不支持以" + way + "读取");
}
} else {
throw new IllegalStateException("该类不支持读取redis");
}
return object;
}
private String getKey(Object object) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
// 在field上找
for (Field field : object.getClass().getDeclaredFields()) {// 可以设计为直接获取名为key的域
if (field.isAnnotationPresent(RedisStorageKey.class)) { // 拿key
field.setAccessible(true);
return field.get(object).toString();
}
}
// 在method上找
for (Method m : object.getClass().getDeclaredMethods()) {
if (m.isAnnotationPresent(RedisStorageKey.class)) {
m.setAccessible(true);
return m.invoke(object).toString();
}
}
return null;
}
private void handleHash(Object object, String key) throws IllegalArgumentException, IllegalAccessException {
if (object == null || key == null) {
throw new IllegalStateException("无法存取");
}
// 读取redis
Map<Object, Object> map =redisTemplate.opsForHash().entries(key);
// map转类
for (Field field:object.getClass().getDeclaredFields()) {
if (field.isAnnotationPresent(RedisStorageElement.class)) {
// 拿到加了注解的域
RedisStorageElement g = field.getAnnotation(RedisStorageElement.class);
String value = g.value();// 拿到存进去的域名
if (value != null && value.length() > 0) {
String data = (String) map.get(value);
field.set(object, data);
}
} else if (field.isAnnotationPresent(RedisStorageKey.class)) {
field.setAccessible(true);
field.set(object, key);// 设置key
}
}
}
private void handleHash(Object object) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
// 拿key,然后读取
String key = getKey(object);
handleHash(object, key);
}
}
使用示例
redis.properties
redis.host=127.0.0.1
redis.port=9479
redis.pass=hello
redis.timeout=15000
redis.usePool=true
redis.maxIdle=6
redis.minEvictableIdleTimeMillis=300000
redis.numTestsPerEvictionRun=3
redis.timeBetweenEvictionRunsMillis=60000
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath:redis.properties" />
<context:component-scan base-package="gg.zsw.redis">
</context:component-scan>
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="100" />
<!-- <property name="maxActive" value="600" /> -->
<!-- <property name="maxWait" value="10000" /> -->
<!-- 最大等待时间 -->
<property name="maxWaitMillis" value="20000" />
<property name="testOnBorrow" value="true" />
</bean>
<!--<bean id="jdkSerializer"-->
<!--class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" /> -->
<bean id="jdkSerializer"
class="org.springframework.data.redis.serializer.StringRedisSerializer" />
<bean id="jedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="${redis.host}"></property>
<property name="port" value="${redis.port}"></property>
<property name="password" value="${redis.pass}"></property>
<property name="usePool" value="${redis.usePool}"></property>
<property name="poolConfig" ref="jedisPoolConfig"></property>
</bean>
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory"></property>
<property name="keySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
<property name="valueSerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
<property name="hashKeySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
<property name="hashValueSerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
</bean>
</beans>
User类
@RedisStorageInjector(RedisStorageStructure.HASH)
public class User {
// @RedisStorageKey
public String key;
/**
* 基本结构,只有存储没有绑定
*/
@RedisStorageElement("name")
public String name;
@RedisStorageElement("age")
public String age;
public String haha;// 这属性没加注解,不存
@RedisStorageKey
private String k(){
return key;
}
}
测试类
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@ContextConfiguration(locations = {"classpath*:applicationContext.xml"})
public class Main extends AbstractJUnit4SpringContextTests {
@Autowired
protected RedisTemplate<String, String> redisTemplate;
@Test
public void test001write() {
RedisStorageWrtieUtil r = RedisStorageUtil.writeOps(redisTemplate);
User user = new User();
user.key = "person";
user.name = "ada";
user.age = "18";
try {
r.write(user);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void test002read() {
RedisStorageReadUtil r = RedisStorageUtil.readOps(redisTemplate);
User user = new User();
user.key = "person";
try {
r.read(user);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(user.key + "||name:" + user.name + "|age:" + user.age);
}
}
运行效果
person||name:ada|age:18
总结
前面输入很多代码,但测试中,仅仅是一个User类,以及RedisStorageWrtieUtil和RedisStorageReadUtil的操作,实际是大大方便了使用,只需要设定好User类中的关系即可。
注解的使用,可以提升可读性,使用也更加方便,可减少重复代码,相当好用。
本文旨在形成一套用注解来解决实际问题的思路。