1, Foreword
Redis is one of the basic components that must be used in distributed microservices. At present, it is basically used in most domestic projects, and caching is one of its main functions. Frequent use of set() method to add annotations in projects will cause code duplication and bloated. For those who do not have enough development experience, even improper addition of caching will directly affect the normal business process, This will lead to accidents. Therefore, mature companies will automatically add redis cache through annotations by encapsulating basic components. Starting from the principle, this paper will lead you to personally implement custom annotations and complete the development of redis cache. If you learn it, you can show it in front of your colleagues.
2, Parameter description of custom annotation
@Target:
The purpose of annotations, that is, where annotations can be used, usually has
@Target(ElementType.TYPE) -- interface, class, enumeration and annotation
@Target(ElementType.FIELD) -- constant of field and enumeration
@Target(ElementType.METHOD) -- Method
@Target(ElementType.PARAMETER) -- Method Parameter
@Target(ElementType.CONSTRUCTOR) -- constructor
@Target(ElementType.LOCAL_VARIABLE) -- local variable
@Target(ElementType.ANNOTATION_TYPE) -- Annotation
@Target(ElementType.PACKAGE) - package
@Retention position of annotation
It is used to define the life cycle of annotations, and RetentionPolicy needs to be specified when used. RetentionPolicy has three policies, namely:
SOURCE - the annotation is only kept in the SOURCE file. When the Java file is compiled into a class file, the annotation is discarded.
class - annotations are retained in the class file, but are discarded when the jvm loads the class file, which is the default life cycle.
RUNTIME - the annotation is not only saved in the class file, but still exists after the jvm loads the class file.
@Documented
Annotations are only used for identification. They have no practical effect. Just understand them.
If @ Documented annotation is used, the @ Documented annotation will be displayed when generating javadoc.
3, Custom redis annotation
JhRedisCache -- add the annotation of rediscache
/** * @author ljx * @Description: Add redis cache annotation * @date 2020/6/9 4:11 afternoon */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface JhRedisCache { /* * redis The key in the cache supports spel expressions */ String key() default ""; /* * Cache time. The default cache time is 60 * 60 * 24 a day */ long expire() default 86400L; /* * If the annotation is added to the method that returns the list, you need to specify the class type in the list through this field */ Class type() default Object.class; }
JhRedisCacheEvict -- delete the annotation of redis cache
/** * @author ljx * @Description: Delete the redis cache annotation * @date 2020/6/10 3:38 afternoon */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface JhRedisCacheEvict { String key() default ""; }
4, Custom AOP cut
/** * @author ljx * @Description: Annotation section * @date 2020/6/9 11:12 morning */ @Component @Aspect public class RedisCacheAspect { private static final String SPEL = "#"; private static final String KEY_SEPARATOR = "_"; private static final int TWO = 2; private static final Logger logger = LoggerFactory.getLogger(RedisCacheAspect.class); private RedisClient redisClient; private AppInfo appInfo; /** * @Description: Cut into this point where JhRedisCache annotation is used to query whether the cache exists in redis. If it already exists, it will be returned directly. Otherwise, query the database * @param pjp Pointcut information * @return java.lang.Object Method return value * @Author: ljx * @Date: 2020/6/9 4:20 afternoon */ @Around("@annotation(edu.jiahui.redis.starter.annotation.JhRedisCache)") private Object handleCache(final ProceedingJoinPoint pjp) throws Throwable { // Get the method object of the cut in // This m is a proxy object and does not contain annotations Method m = ((MethodSignature) pjp.getSignature()).getMethod(); // this() returns the proxy object, target() returns the target object, and the method object obtained by the target object reflection contains the annotation Method methodWithAnnotations = pjp.getTarget().getClass().getDeclaredMethod(pjp.getSignature().getName(), m.getParameterTypes()); // Gets the annotation object from the target method object JhRedisCache cacheAnnotation = methodWithAnnotations.getDeclaredAnnotation(JhRedisCache.class); // Parse key String keyExpr = cacheAnnotation.key(); Object[] as = pjp.getArgs(); String key = getRedisKeyBySpel(keyExpr,methodWithAnnotations, as); // Get cache from redis String cache = null; try { cache = redisClient.get(key); } catch (Exception e) { logger.error("{}query redis Cache exception:{}",keyExpr,e.getMessage()); } if (StringUtils.isBlank(cache)) { // If it does not exist, get it from the database Object result = pjp.proceed(); // After being obtained from the database, it is stored in redis. If the expiration time is specified, it is set try { long expireTime = cacheAnnotation.expire(); if (expireTime > 0) { redisClient.set(key,JSON.toJSONString(result), expireTime, TimeUnit.SECONDS); }else{ redisClient.set(key, JSON.toJSONString(result)); } } catch (Exception e) { logger.warn("{}{}cache redis abnormal:{}",keyExpr,e.getMessage(),result); } return result; } // Get the annotation on the proxy method Class modelType = cacheAnnotation.type(); // Get the return value type of the proxied method Class returnType = ((MethodSignature) pjp.getSignature()).getReturnType(); // Returns the json obtained from the cache by deserialization return deserialize(cache, returnType, modelType); } @Around("@annotation(edu.jiahui.redis.starter.annotation.JhRedisCacheEvict)") private Object handleCacheEvict(ProceedingJoinPoint pjp) throws Throwable { // Get the method object of the cut in // This m is a proxy object and does not contain annotations Method m = ((MethodSignature) pjp.getSignature()).getMethod(); // this() returns the proxy object, target() returns the target object, and the method object obtained by the target object reflection contains the annotation Method methodWithAnnotations = pjp.getTarget().getClass().getDeclaredMethod(pjp.getSignature().getName(), m.getParameterTypes()); // Gets the annotation object from the target method object JhRedisCacheEvict cacheEvictAnnotation = methodWithAnnotations.getDeclaredAnnotation(JhRedisCacheEvict.class); // Parse key String keyExpr = cacheEvictAnnotation.key(); Object[] as = pjp.getArgs(); String key = getRedisKeyBySpel(keyExpr,methodWithAnnotations, as); // Delete the user information in the database before deleting the cache Object result = pjp.proceed(); redisClient.delete(key); return result; } public RedisCacheAspect() { // Different projects may implement different redisClient objects when initializing them. This is implemented in combination with redis in their own projects appInfo= SpringContext.getBean(AppInfo.class); String appName = appInfo.getAppName(); this.redisClient = SpringContext.getBean(appName, RedisClient.class); } /** * @Description: Parse the key in the annotation and support the parsing of spel expressions * @param spelExpress spel expression in annotation * @param method Method object * @param params Method parameters * @return java.lang.String * @Author: ljx * @Date: 2020/6/10 3:16 afternoon */ private String getRedisKeyBySpel(String spelExpress, Method method, Object[] params) { String redisKey = appInfo.getAppName()+KEY_SEPARATOR+method.getName(); // If it is blank, the default service name is_ Method name if (StringUtils.isBlank(spelExpress)){ return redisKey; } // If it is not a spel expression, the key passed in by the user is used directly if(!spelExpress.contains(SPEL)){ return spelExpress; } // If it is a spel expression, but the parameter is empty, the default service name is_ Method name if(params==null){ return redisKey; } ExpressionParser parser = new SpelExpressionParser(); EvaluationContext context = new StandardEvaluationContext(); // Set the first parameter of the variable used in the spel expression context.setVariable("entity", params[0]); // Set the second parameter if(params.length>1&¶ms[1]!=null){ context.setVariable("entityTwo", params[1]); } // Set the third parameter if(params.length>TWO&¶ms[TWO]!=null){ context.setVariable("entityTrd", params[2]); } // Parsing spel expressions Expression expression = parser.parseExpression(spelExpress, new TemplateParserContext()); final Object value = expression.getValue(context); return redisKey + KEY_SEPARATOR+"_"+Objects.toString(value,""); } /** * @Description: FastJSON Deserialize get object * @param json String obtained from redis cache * @param clazz The class type of the return value of the annotated method * @param modelType Convert to class type in list * @return java.lang.Object * @Author: ljx * @Date: 2020/6/11 3:50 afternoon */ private Object deserialize(String json, Class clazz, Class modelType) { return clazz.isAssignableFrom(List.class) ? JSON.parseArray(json, modelType) : JSON.parseObject(json, clazz); } }
5, Use case
Simple use:
@JhRedisCache(key = "#{#entity}") public TeachCenter selectTeacherCenter(Integer teachCenterId) { return teachCenterMapper.selectByPrimaryKey(teachCenterId); }
Complex use:
@JhRedisCache(key = "#{#entity.getProvinceId()}", type = TeachCenter.class) public List<TeachCenter> selectTeachCenterList(TeachCenterCommonRequest teacherCenterCommonRequest) { //Obtain the region id; Integer provinceId = teacherCenterCommonRequest.getProvinceId(); //paging PageHelper.startPage(teacherCenterCommonRequest.getPageNum(), teacherCenterCommonRequest.getPageSize()); //Get all teaching centers List<TeachCenter> teachCenterList = teachCenterMapper.selectByProvinceId(provinceId); return teachCenterList; }
My personal test is effective and has been used on a large scale in the company's projects. Because it depends on the redis configuration, I will not lead you to test here. Interested partners can test in the project. If you have any problems, please communicate at any time.