自定義aop實現Cacheable註解(零拷貝),CacheItemGet,CacheMapGet,CacheMapPut

推薦一個公衆號

號主爲一線大廠架構師,CSDN博客專家,博客訪問量突破一千萬。主要分享Java、golang架構,源碼,分佈式,高併發等技術,用大廠程序員的視角來探討技術進階、面試指南、職業規劃等。15W技術人的選擇!

 

開發背景:

針對Cacheable的痛點,緩存的數據在客戶端中查看,如果遇到錯誤很難排查錯誤。

Cacheable不方便使用

指向針對Map類型做處理,並且Redis中也想存儲成Map集合。

針對Redis的Set集合也可以開發類似的組件

1、定義註解

 

1)、獲取map中某一項值的註解

package com.biubiu.annotation;

import java.lang.annotation.*;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheItemGet {
    String key();
    String hKey();
}

 2)、獲取map集合的註解

package com.biubiu.annotation;

import java.lang.annotation.*;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheMapGet {
    String key();
    long expire() default 12 * 60 * 60L;
    boolean parse() default false;
}

3)、更新map的註解 

package com.biubiu.annotation;

import java.lang.annotation.*;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheMapPut {

    String key();
    long expire() default 12 * 60 * 60L;
    boolean parse() default false;
}

 

2、redisTemplate

 

package com.biubiu;



@Configuration
public class Config extends WebMvcConfigurationSupport {


    @Bean(name = "hashRedisTemplate")
    public RedisTemplate<String, Object> hashRedisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(lettuceConnectionFactory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key採用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也採用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式採用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式採用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }

   
}

 

3、aop切面

package com.biubiu.aop;

import com.biubiu.annotation.CacheItemGet;
import com.biubiu.util.CommonUtil;
import com.biubiu.util.MD5;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;

@Aspect
@Component
public class CacheItemGetAspect {

    @Qualifier("hashRedisTemplate")
    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Around("@annotation(com.biubiu.annotation.CacheItemGet)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = joinPoint.getTarget().getClass().getMethod(signature.getName(), signature.getMethod().getParameterTypes());
        CacheItemGet cacheItemGet = method.getAnnotation(CacheItemGet.class);
        String key = cacheItemGet.key();
        String hKeyEl = cacheItemGet.hKey();
        //創建解析器
        ExpressionParser parser = new SpelExpressionParser();
        Expression hKeyExpression = parser.parseExpression(hKeyEl);
        //設置解析上下文有哪些佔位符。
        EvaluationContext context = new StandardEvaluationContext();
        //獲取方法參數
        Object[] args = joinPoint.getArgs();
        String[] parameterNames = new DefaultParameterNameDiscoverer().getParameterNames(method);
        for(int i = 0; i < parameterNames.length; i++) {
            context.setVariable(parameterNames[i], args[i]);
        }
        //解析得到 item的 key
        String hKeyValue = hKeyExpression.getValue(context).toString();
        String hKey = MD5.getMD5Str(hKeyValue);
        HashOperations<String, String, Object> ops = redisTemplate.opsForHash();
        Object value = ops.get(key, hKey);
        if(value != null) {
            return value.toString();
        }
        return joinPoint.proceed();
    }

}

 

package com.biubiu.aop;

import com.biubiu.annotation.CacheMapGet;
import com.biubiu.annotation.CacheMapPut;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
public class CacheMapGetAspect {
    @Qualifier("hashRedisTemplate")
    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Around("@annotation(com.biubiu.annotation.CacheMapGet)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = joinPoint.getTarget().getClass().getMethod(signature.getName(), signature.getMethod().getParameterTypes());
        CacheMapGet cacheMap = method.getAnnotation(CacheMapGet.class);
        String key = cacheMap.key();
        //強制刷緩存
        HashOperations<String, String, Object> ops = redisTemplate.opsForHash();
        Object val;
        Map<String, Object> value = ops.entries(key);
        if(value.size() != 0) {
            return value;
        }
        //加鎖
        synchronized (this) {
            value = ops.entries(key);
            if(value.size() != 0) {
                return value;
            }
            //執行目標方法
            val = joinPoint.proceed();
            ops.delete(key);
            //把值設置回去
            ops.putAll(key, (Map<String, Object>) val);
            redisTemplate.expire(key, cacheMap.expire(), TimeUnit.SECONDS);
            return val;
        }
    }

}
package com.biubiu.aop;

import com.biubiu.annotation.CacheMapPut;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
public class CacheMapPutAspect {

    @Qualifier("hashRedisTemplate")
    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Around("@annotation(com.biubiu.annotation.CacheMapPut)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = joinPoint.getTarget().getClass().getMethod(signature.getName(), signature.getMethod().getParameterTypes());
        CacheMapPut cacheMap = method.getAnnotation(CacheMapPut.class);
        String key = cacheMap.key();
        //強制刷緩存
        HashOperations<String, String, Object> ops = redisTemplate.opsForHash();
        Object val;
        //加鎖
        synchronized (this) {
            //執行目標方法
            val = joinPoint.proceed();
            redisTemplate.delete(key);
            //把值設置回去
            ops.putAll(key, (Map<String, Object>) val);
            redisTemplate.expire(key, cacheMap.expire(), TimeUnit.SECONDS);
            return val;
        }
    }

}

 

4、使用方法

    
    @CacheItemGet(key = "biubiu-field-hash", hKey = "#tableName+#colName")
    public String getValue(String tableName, String colName) {
        
        //省略...
    }
    @CacheMapGet(key = "biubiu-field-hash", expire = 6 * 60 * 60L)
    public Map<String, String> getFieldTypeMap() {
        
        //省略...
    }
    @CacheMapPut(key = "biubiu-field-hash", expire = 6 * 60 * 60L)
    public Map<String, String> putFieldTypeMap() {
        //省略...
    }

 

5、MD5工具類

package com.biubiu.util;

import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;


/**
 * @Author yule.zhang
 * @CreateTime 2019-6-16下午05:28:11
 * @Version 1.0
 * @Explanation 用MD5對數據進行加密
 */
public class MD5 {

	static final Logger log = LogManager.getLogger(MD5.class);

	MessageDigest md5;

	static final char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };

	public MD5() {
		try {
			// 獲得MD5摘要算法的 MessageDigest 對象
			md5 = MessageDigest.getInstance("MD5");
		} catch (NoSuchAlgorithmException e) {
			log.error("創建MD5對象出錯, ", e);
			throw new IllegalArgumentException("創建md5對象時出錯");
		}
	}

	public synchronized String getMD5(String s) {
		return this.getMD5(s.getBytes()).toLowerCase();
	}

	public synchronized String getMD5(byte[] btInput) {
		try {
			// 使用指定的字節更新摘要
			md5.update(btInput);
			// 獲得密文
			byte[] md = md5.digest();
			// 把密文轉換成十六進制的字符串形式
			int j = md.length;
			char str[] = new char[j * 2];
			int k = 0;
			for (int i = 0; i < j; i++) {
				byte byte0 = md[i];
				str[k++] = hexDigits[byte0 >>> 4 & 0xf];
				str[k++] = hexDigits[byte0 & 0xf];
			}
			return new String(str).toLowerCase();
		} catch (Exception e) {
			log.error("生成MD5碼時出錯,", e);
			throw new IllegalArgumentException("生成MD5出錯");
		}
	}
	
	 /**
	  * 獲取32位的MD5加密
	  * @param sourceStr
	  * @return
	  */
	public static String getMD5Str(String sourceStr) {
	        String result = "";
	        try {
	            MessageDigest md = MessageDigest.getInstance("MD5");
	            md.update(sourceStr.getBytes(Charset.forName("utf-8")));
	            byte b[] = md.digest();
	            int i;
	            StringBuffer buf = new StringBuffer("");
	            for (int offset = 0; offset < b.length; offset++) {
	                i = b[offset];
	                if (i < 0)
	                    i += 256;
	                if (i < 16)
	                    buf.append("0");
	                buf.append(Integer.toHexString(i));
	            }
	            result = buf.toString();
	        } catch (NoSuchAlgorithmException e) {
	            System.out.println(e);
	        }
	        return result;
   }

}

 

 

 

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