1. Definitions comment
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; / ** * annotations Redis cache * only methods * / @Target (ElementType.METHOD) @Retention (RetentionPolicy.RUNTIME) public @ interface RedisCachetAttribute { / ** * @return cache key value * corresponding to the return value must method implements Serializable * * / String Key (); / ** * expire in seconds * *@return expiration seconds * / int expireSeconds () default 20 is ; }
2. Definitions section
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; importredis.clients.jedis.JedisCluster; // Open AspectJ automatic proxy mode, if not fill proxyTargetClass = true, defaults to false, @EnableAspectJAutoProxy (= the proxyTargetClass to true ) @Component the @Order ( -1 ) @Aspect public class RedisCacheAspect { / * * * log * / Private static Logger Logger = LoggerFactory.getLogger (RedisCacheAspect. class ); / ** * the SPEL expression parser * / Private static Final ExpressionParser is EXPRESSION_PARSER = new new SpelExpressionParser (); / ** * Get parameter name discovery method device */ 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()") publicHandleControllerMethod Object (ProceedingJoinPoint ProceedingJoinPoint) throws the Throwable { // Get the corresponding method of the controller. MethodSignature MethodSignature = (MethodSignature) proceedingJoinPoint.getSignature (); // Get method Method, Method = methodSignature.getMethod (); // Get annotation RedisCachetAttribute annotation = method .getAnnotation (RedisCachetAttribute. class ); // Get cache key expression, and the like based on the context calculation expression data String = cacheKey parseKey (annotation.key (), ProceedingJoinPoint); int seconds the = annotation.expireSeconds (); // first tries to get the data (bytes) in the redis byte [] = redisKey cacheKey.getBytes (); byte [] = redisValue jedisCluster.get (redisKey); IF (! RedisValue = null && redisValue.length> 0 ) { // Redis data directly back return ObjectUtils.toObject (redisValue); } // Redis no data, the original method is called, the result value acquiring Object result = proceedingJoinPoint.proceed (); // the return value sequence into Byte [ ], to save Redis redisValue = ObjectUtils.toByteArray (Result); jedisCluster.setex (redisKey, seconds the, redisValue); return Result; } / ** * Calculation spel expression * * @param expression The expression * @param context context * @return String Cache Key * / Private static String parseKey (expression The String, the JoinPoint context) { // Get the information entry point method MethodSignature = MethodSignature (MethodSignature) context.getSignature (); Method, Method = methodSignature.getMethod (); // Get the value of the parameter passed Object [] args = context.getArgs (); IF (args == null || = args.length = 0) { // no arguments passed directly to the calculation expression (no context parameters) return EXPRESSION_PARSER.parseExpression (expression The) .getValue (String. Class ); } // Get Parameter Name String [] = parameterNames PARAMETER_NAME_DISCOVERER.getParameterNames (Method) ; IF (parameterNames.length> args.length) { // Since java does not allow anonymous parameters, the more so if the parameter name parameter value, certainly illegal logger.error ( "length parameter name parameter value is less than the length, method: {}, the parameter name length: {}, the value of the length parameter: {} " , method, parameterNames.length, args.length); the throw new new an IllegalArgumentException (" argument to inadequate " ); } // parameter name and context parameter values into the parameter 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. the reference code
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. Test as follows. (Acquired authentication ordinary POJO, List, Map of the return structure)
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; } }
The ancillary code
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. Remarks
It is only a demo on, If you want to produce, you may need to do the following improvements
1. The section of code to write dead JedisCluster, where you want to modify into a single interface support / Sentinel / cluster, etc.
2. does not support millisecond storage (because jedisCluster does not support ...)
3. When there is no cache acquisition value should be based on distributed lock key to add, or likely to cause the same process multiple times