模拟springcache 注解实现 缓存操作
背景就是 每次都需要查询判断 不为空 返回 缓存里面数据,其实有点重复代码
提供两种方式
直接使用springcache
-
自定义注解 + aop 实现
2.1 定义两个注解
2.2 实现切面 获取注解里面的值进行对应的逻辑判断
Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) @Documented public @interface CacheKey { /** * 缓存键名称注解,默认值为类名称 * * @return */ String key() default "cacheKey:"; /** * 缓存过期时间,默认3分钟 * * @return */ int expireTime() default 3*60; /** * 缓存过期时间单位,默认为分钟 * * @return */ TimeUnit timeUnit() default TimeUnit.SECONDS; /** * 缓存事件 默认查询 * @return */ CacheEvent cacheEvent() default CacheEvent.QUERY; } /** * 参数注解,用于指定要获取的参数字段。 */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) @Documented public @interface ParameterCacheKey { /** * 参数字段 * * @return */ String fieldName() default ""; } Aspect @Service @Slf4j public class QueryCacheAspect { @Autowired private RedisTemplate redisTemplate; /** * 定义拦截规则:拦截所有@QueryCache注解的方法。 */ @Pointcut("@annotation(com.cnest.cacheredis.common.annotation.CacheKey)") public void queryCachePointcut() { } /** * 拦截器具体实现 * * @param joinPoint * @return * @throws Throwable */ @Around("queryCachePointcut()") public Object interceptor(ProceedingJoinPoint joinPoint) throws Throwable { long beginTime = System.currentTimeMillis(); MethodSignature signature = (MethodSignature) joinPoint.getSignature(); //获取被拦截的方法 Method method = signature.getMethod(); //获取方法注解 CacheKey cacheKey = method.getAnnotation(CacheKey.class); CacheEvent cacheEvent = cacheKey.cacheEvent(); String key = cacheKey.key(); StringBuilder realKey = new StringBuilder(cacheKey.key()); // 循环所有的参数 Object[] args = joinPoint.getArgs(); for (int i = 0; i < args.length; i++) { MethodParameter methodParam = new SynthesizingMethodParameter(method, i); //获取参数注解 Annotation[] parameterAnnotations = methodParam.getParameterAnnotations(); // 循环参数上所有的注解 for (Annotation paramAnn : parameterAnnotations) { if (paramAnn instanceof ParameterCacheKey) { ParameterCacheKey parameterCacheKey = (ParameterCacheKey) paramAnn; //取到ParameterCacheKey的标识参数的值 Object fieldValue = AopMethodUtil.getParamValue(args[i], parameterCacheKey.fieldName());; realKey.append(":").append(parameterCacheKey.fieldName()).append(":").append(fieldValue); break; } } } //获取不到key值,抛异常 if (StringUtils.isBlank(realKey.toString())) throw new RuntimeException("****缓存key值不存在****"); // 如果没有 ParameterCacheKey 注解 默认md5(类名+方法+参数) if ((Objects.equals(realKey.toString(), key))) { realKey.append(":").append(getKey(signature, joinPoint.getArgs())); } log.debug("获取到缓存key值 {} ", realKey); boolean hasKey = redisTemplate.hasKey(realKey.toString()); if (hasKey) { // 根据对应操作 if (Objects.equals(cacheEvent, CacheEvent.QUERY)) { // 缓存中获取到数据,直接返回。 Object object = redisTemplate.opsForValue().get(realKey.toString()); log.debug("缓存命中耗时:{}", (System.currentTimeMillis() - beginTime)); return object; } if (Objects.equals(cacheEvent, CacheEvent.DELETE)) { // 删除缓存 redisTemplate.delete(realKey.toString()); log.debug("缓存删除key {} ", realKey); } } Object object = joinPoint.proceed(); //设置缓存 if (Objects.equals(cacheEvent, CacheEvent.QUERY)) { redisTemplate.opsForValue().set(realKey.toString(), object, cacheKey.expireTime(), cacheKey.timeUnit()); } log.debug("结束耗时:{}", (System.currentTimeMillis() - beginTime)); return object; } /** * 默认md5(类名+方法+参数) * * @param methodSignature * @param args * @return */ private String getKey(MethodSignature methodSignature, Object[] args) { StringBuilder key = new StringBuilder(methodSignature.getDeclaringTypeName()); key.append(methodSignature.getMethod().getName()); Object[] paramValues = args; String[] paramNames = (methodSignature).getParameterNames(); for (int i = 0; i < paramNames.length; i++) { key.append(paramValues[i]); } return MD5.create().digestHex(key.toString()); } } @Slf4j public class AopMethodUtil { /** * 基础数据类型 */ private static List<String> types = Arrays.asList("java.lang.Integer", "java.lang.Double", "java.lang.Float", "java.lang.Long", "java.lang.Short", "java.lang.Byte", "java.lang.Boolean", "java.lang.Char", "java.lang.String","java.math.BigDecimal", "int", "double", "long", "short", "byte", "boolean", "char", "float"); /** * 获取参数值 * * @param arg * @param fieldName 参数字段名 * @return */ public static Object getParamValue(Object arg, String fieldName) { Object value = ""; // 获取对象类型 if (Objects.nonNull(arg)) { String typeName = arg.getClass().getTypeName(); //1 判断是否是基础类型 if (types.contains(typeName)) { value = arg; } else { //2 通过反射获取实体类属性 value = getFieldsValue(arg, fieldName); } } return value; } /** * 解析实体类,获取实体类中的指定属性値 * * @param object 参数实体对象 * @param fieldName 字段 * @return */ public static Object getFieldsValue(Object object, String fieldName) { //通过反射获取所有的字段,getFileds()获取public的修饰的字段 //getDeclaredFields获取private protected public修饰的字段 Field[] fields = object.getClass().getDeclaredFields(); String typeName = object.getClass().getTypeName(); if (types.contains(typeName)) { return object; } Object value = ""; for (Field field : fields) { //在反射时能访问私有变量 field.setAccessible(true); try { //如果实体类里面继续包含实体类,就没法获取。 //我们可以通递归的方式去处理实体类包含实体类的问题。 if (types.contains(field.getType().getName())) { if (field.getName().equals(fieldName)) { value = field.get(object) == null ? "" : field.get(object).toString(); return value; } } } catch (IllegalArgumentException | IllegalAccessException e) { log.error("error {}",e); } } return value; } /** * 解析实体类,获取实体类中的属性 * * @param object * @return */ public static Object getFieldsValue(Object object) { //通过反射获取所有的字段,getFileds()获取public的修饰的字段 //getDeclaredFields获取private protected public修饰的字段 Field[] fields = object.getClass().getDeclaredFields(); String typeName = object.getClass().getTypeName(); if (types.contains(typeName)) { return object; } StringBuilder sb = new StringBuilder(); sb.append("{"); for (Field f : fields) { //在反射时能访问私有变量 f.setAccessible(true); try { //这边会有问题,如果实体类里面继续包含实体类,这边就没法获取。 //其实,我们可以通递归的方式去处理实体类包含实体类的问题。 if (types.contains(f.getType().getName())) { sb.append(f.getName()).append(" : ").append(f.get(object)).append(", "); } } catch (IllegalArgumentException | IllegalAccessException e) { log.error("error {}",e); } } sb.append("}"); return sb.toString(); } }
2.3 也可以支持 不过我没有用而已 就是 解析表达式SPEL
2.4 demo 是不是很简单
/** * id删除 */ @ApiOperation(value = "id删除",notes = "通用结果返回对象") @PostMapping("user/delete") @CacheKey(cacheEvent = CacheEvent.DELETE) public CommonResult<Boolean> delete(@ParameterCacheKey(fieldName = "id") User user){ Boolean success = userService.removeById(user.getId()); return CommonResultResponse.ok(success); } /** * id查询 */ @ApiOperation(value = "id查询",notes = "通用结果返回对象") @GetMapping("user/findById") @CacheKey public CommonResult<User> findById(@ParameterCacheKey(fieldName = "id") Integer id, HttpServletResponse response, HttpServletRequest request, MultipartFile file){ User user = userService.getById(id); return CommonResultResponse.ok(user); }
优点 可以省略缓存判断操作 单一职责的方法
缺点 带有多重缓存业务逻辑操作 还得自己灵活编写