1.定義註解java
package com.g2.order.server.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * redis緩存註解 * 僅支持方法 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RedisCachetAttribute { /** * @return 緩存的key值 * 對應的Method的返回值必須 實現 Serializable 接口 * */ String key(); /** * 到期秒數 * * @return 到期秒數 */ int expireSeconds() default 20; }
2.定義切面web
package com.g2.order.server.aspect; import com.g2.order.server.annotation.RedisCachetAttribute; import com.g2.order.server.utils.ObjectUtils; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.annotation.Order; import org.springframework.expression.EvaluationContext; 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 java.lang.reflect.Method; import redis.clients.jedis.JedisCluster; //開啓AspectJ 自動代理模式,若是不填proxyTargetClass=true,默認爲false, @EnableAspectJAutoProxy(proxyTargetClass = true) @Component @Order(-1) @Aspect public class RedisCacheAspect { /** * 日誌 */ private static Logger logger = LoggerFactory.getLogger(RedisCacheAspect.class); /** * SPEL表達式解析器 */ private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser(); /** * 獲取方法參數名稱發現器 */ private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer(); /** * Redis集羣 */ @Autowired private JedisCluster jedisCluster; /** * 切面切入點 */ @Pointcut("@annotation(com.g2.order.server.annotation.RedisCachetAttribute)") public void mergeDuplicationRequest() { } /** * 環繞切面 */ @Around("mergeDuplicationRequest()") public Object handleControllerMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { //獲取controller對應的方法. MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature(); //獲取方法 Method method = methodSignature.getMethod(); //獲取註解 RedisCachetAttribute annotation = method.getAnnotation(RedisCachetAttribute.class); //獲取緩存key的表達式,並根據上下文等數據計算表達式 String cacheKey = parseKey(annotation.key(), proceedingJoinPoint); int seconds = annotation.expireSeconds(); //先嚐試從redis裏獲取數據(字節) byte[] redisKey = cacheKey.getBytes(); byte[] redisValue = jedisCluster.get(redisKey); if (redisValue != null && redisValue.length > 0) { //redis有數據,直接返回 return ObjectUtils.toObject(redisValue); } //redis沒有數據,則調用原方法,獲取結果值 Object result = proceedingJoinPoint.proceed(); //將返回值序列化爲Byte[],保存到redis redisValue = ObjectUtils.toByteArray(result); jedisCluster.setex(redisKey, seconds, redisValue); return result; } /** * 計算spel表達式 * * @param expression 表達式 * @param context 上下文 * @return String的緩存key */ private static String parseKey(String expression, JoinPoint context) { //獲取切入點的方法信息 MethodSignature methodSignature = (MethodSignature) context.getSignature(); Method method = methodSignature.getMethod(); // 獲取傳入參數值 Object[] args = context.getArgs(); if (args == null || args.length == 0) { // 無參傳入,直接計算表達式(無需參數上下文) return EXPRESSION_PARSER.parseExpression(expression).getValue(String.class); } // 獲取參數名 String[] parameterNames = PARAMETER_NAME_DISCOVERER.getParameterNames(method); if (parameterNames.length > args.length) { //因爲java不容許有匿名參數,因此若是參數名多於參數值,則必爲非法 logger.error("參數值的長度少於參數名長度, 方法:{}, 參數名長度: {},參數值長度:{}", method, parameterNames.length, args.length); throw new IllegalArgumentException("參數傳入不足"); } // 將參數名與參數值放入參數上下文 EvaluationContext evaluationContext = new StandardEvaluationContext(); for (int i = 0; i < parameterNames.length; i++) { evaluationContext.setVariable(parameterNames[i], args[i]); } // 計算表達式(根據參數上下文) return EXPRESSION_PARSER.parseExpression(expression).getValue(evaluationContext, String.class); } }
3.引用代碼redis
package com.g2.order.server.business; import com.google.common.collect.ImmutableMap; import com.g2.order.server.annotation.RedisCachetAttribute; import com.g2.order.server.business.vo.JsonResult; import org.springframework.stereotype.Component; import java.util.Arrays; import java.util.List; import java.util.Map; /** * SettingBusiness. */ @Component public class SettingBusiness { @RedisCachetAttribute(key = "T(com.g2.order.server.vo.CommonConst)" + ".SETTING_JSON_KEY" + " + #code" , expireSeconds = 30 ) public JsonResult getSetting(String code) { return new JsonResult("234"); } @RedisCachetAttribute(key = "T(com.g2.order.server.vo.CommonConst)" + ".SETTING_LIST_KEY" + " + #code" , expireSeconds = 30 ) public List<JsonResult> getSettingList(String code) { return Arrays.<JsonResult>asList(new JsonResult("234"), new JsonResult("345")); } @RedisCachetAttribute(key = "T(com.g2.order.server.vo.CommonConst)" + ".SETTING_MAP_KEY" + " + #code" , expireSeconds = 30 ) public Map<String, JsonResult> getSettingMap(String code) { return ImmutableMap.of(code, new JsonResult("234"),"abc",new JsonResult("abc234")); } }
package com.g2.order.server.business.vo; import java.io.Serializable; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * Model */ @AllArgsConstructor @NoArgsConstructor @Data public class JsonResult implements Serializable { private Object data; }
4.測試以下.(驗證獲取普通POJO,List,Map的返回結構)spring
package com.g2.order.server.controller; import com.g2.order.server.business.SettingBusiness; import com.g2.order.server.business.vo.JsonResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import java.util.List; import java.util.Map; import io.swagger.annotations.Api; @Api(value = "H5Controller", description = "H5接口") @RestController @RequestMapping("/h5") public class H5Controller { private static Logger logger = LoggerFactory.getLogger(H5Controller.class); @Autowired private SettingBusiness settingBusiness; @ResponseBody //@MergeDuplicationRequestAttribute(redisLockKeyTemplate = "A:%s", redisLockKeyObjectFileds = {"code"}) @RequestMapping(value = "/{code}.jsonp", method = RequestMethod.GET) public Object testJsonp1(@PathVariable("code") String code) { // return JsonMapper.INSTANCE.toJsonP("callabc111", new JsonResult("234")); JsonResult result = settingBusiness.getSetting(code); logger.info("獲取結果,參數:{},值:{}", code, result); return result; } @ResponseBody //@MergeDuplicationRequestAttribute(redisLockKeyTemplate = "A:%s", redisLockKeyObjectFileds = {"code"}) @RequestMapping(value = "/{code}.LIST", method = RequestMethod.GET) public Object testJsonp2(@PathVariable("code") String code) { // return JsonMapper.INSTANCE.toJsonP("callabc111", new JsonResult("234")); List<JsonResult> result = settingBusiness.getSettingList(code); logger.info("獲取結果,參數:{},值:{}", code, result); return result; } @ResponseBody //@MergeDuplicationRequestAttribute(redisLockKeyTemplate = "A:%s", redisLockKeyObjectFileds = {"code"}) @RequestMapping(value = "/{code}.MAP", method = RequestMethod.GET) public Object testJsonp3(@PathVariable("code") String code) { // return JsonMapper.INSTANCE.toJsonP("callabc111", new JsonResult("234")); Map<String,JsonResult> result = settingBusiness.getSettingMap(code); logger.info("獲取結果,參數:{},值:{}", code, result); return result; } }
5.輔助代碼express
package com.g2.order.server.utils; import com.google.common.collect.Lists; import java.beans.IntrospectionException; import java.beans.PropertyDescriptor; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; /** * Object幫助類 */ public class ObjectUtils { /** * 對象轉Byte數組 */ public static byte[] toByteArray(Object obj) throws IOException { byte[] bytes = null; try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos)) { oos.writeObject(obj); oos.flush(); bytes = bos.toByteArray(); } return bytes; } /** * Byte數組轉對象 */ public static Object toObject(byte[] bytes) throws IOException, ClassNotFoundException { Object obj = null; try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes); ObjectInputStream ois = new ObjectInputStream(bis)) { obj = ois.readObject(); } return obj; } }
package com.g2.order.server.vo; /** * CommonConst */ public class CommonConst { public static final String PREFIX = "g2:"; public static final String SETTING_PREFIX = PREFIX + "setting:"; /** * JSON_KEY */ public static final String SETTING_JSON_KEY = SETTING_PREFIX + "json:"; /** * LIST_KEY */ public static final String SETTING_LIST_KEY = SETTING_PREFIX + "list:"; /** * MAP_KEY */ public static final String SETTING_MAP_KEY = SETTING_PREFIX + "map:"; }
6.備註json
這只是一個實現上的demo,若是要用到生產,可能還須要作如下改進數組
1.切面代碼裏寫死了JedisCluster,這裏要修改爲一個接口 來支持單機/哨兵/集羣 等緩存
2.不支持毫秒級的存儲(由於jedisCluster不支持...)app
3.當沒有獲取緩存值時,應當根據key來加分佈式鎖,不然容易形成一樣的處理屢次執行分佈式