自定義註解實現redis與對象相互轉換

簡介

本文則通過自定義註解的方式,來完成一個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類中的關係即可。

註解的使用,可以提升可讀性,使用也更加方便,可減少重複代碼,相當好用。
本文旨在形成一套用註解來解決實際問題的思路。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章