给网站做,wordpress分站点,化妆品公司网站建设方案,soho外贸建站Why#xff1f;
为什么要对方法的返回值进行缓存呢#xff1f;
简单来说是为了提升后端程序的性能和提高前端程序的访问速度。减小对db和后端应用程序的压力。
一般而言#xff0c;缓存的内容都是不经常变化的#xff0c;或者轻微变化对于前端应用程序是可以容忍的。
否…Why
为什么要对方法的返回值进行缓存呢
简单来说是为了提升后端程序的性能和提高前端程序的访问速度。减小对db和后端应用程序的压力。
一般而言缓存的内容都是不经常变化的或者轻微变化对于前端应用程序是可以容忍的。
否则不建议加入缓存因为增加缓存会使程序复杂度增加还会出现一些其他的问题比如缓存同步数据一致性更甚者可能出现经典的缓存穿透、缓存击穿、缓存雪崩问题。
HowDo
如何缓存方法的返回值应该会有很多的办法本文简单描述两个比较常见并且比较容易实现的办法
自定义注解SpringCache
annotation
整体思路
第一步定义一个自定义注解在需要缓存的方法上面添加此注解当调用该方法的时候方法返回值将被缓存起来下次再调用的时候将不会进入该方法。其中需要指定一个缓存键用来区分不同的调用建议为类名方法名参数名
第二步编写该注解的切面根据缓存键查询缓存池若池中已经存在则直接返回不执行方法若不存在将执行方法并在方法执行完毕写入缓冲池中。方法如果抛异常了将不会创建缓存
第三步缓存池首先需要尽量保证缓存池是线程安全的当然了没有绝对的线程安全。其次为了不发生缓存臃肿的问题可以提供缓存释放的能力。另外缓存池应该设计为可替代比如可以丝滑得在使用程序内存和使用redis直接调整。
MethodCache
创建一个名为MethodCache 的自定义注解 package com.ramble.methodcache.annotation;
import java.lang.annotation.*;Documented
Retention(RetentionPolicy.RUNTIME)
Target({ElementType.METHOD})
public interface MethodCache {}
MethodCacheAspect
编写MethodCache注解的切面实现 package com.ramble.methodcache.annotation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;Slf4j
Aspect
Component
public class MethodCacheAspect {private static final MapString, Object CACHE_MAP new ConcurrentHashMap();Around(value annotation(methodCache))public Object around(ProceedingJoinPoint jp, MethodCache methodCache) throws Throwable {String className jp.getSignature().getDeclaringType().getSimpleName();String methodName jp.getSignature().getName();String args String.join(,, Arrays.toString(jp.getArgs()));String key className : methodName : args;// key 示例DemoController:findUser:[FindUserParam(id1, namec7)]log.debug(缓存的key{}, key);Object cache getCache(key);if (null ! cache) {log.debug(走缓存);return cache;} else {log.debug(不走缓存);Object value jp.proceed();setCache(key, value);return value;}}private Object getCache(String key) {return CACHE_MAP.get(key);}private void setCache(String key, Object value) {CACHE_MAP.put(key, value);}
}
Around对被MethodCache注解修饰的方法启用环绕通知ProceedingJoinPoint通过此对象获取方法所在类、方法名和参数用来组装缓存keyCACHE_MAP缓存池生产环境建议使用redis等可以分布式存储的容器直接放程序内存不利于后期业务扩张后多实例部署
controller package com.ramble.methodcache.controller;
import com.ramble.methodcache.annotation.MethodCache;
import com.ramble.methodcache.controller.param.CreateUserParam;
import com.ramble.methodcache.controller.param.FindUserParam;
import com.ramble.methodcache.service.DemoService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;Tag(name demo - api)
Slf4j
RequiredArgsConstructor
RestController
RequestMapping(/demo)
public class DemoController {private final DemoService demoService;MethodCacheGetMapping(/{id})public String getUser(PathVariable(id) String id) {return demoService.getUser(id);}Operation(summary 查询用户)MethodCachePostMapping(/list)public String findUser(RequestBody FindUserParam param) {return demoService.findUser(param);}
}
通过反复调用被MethodCache注解修饰的方法会发现若缓存池有数据将不会进入方法体。
SpringCache
其实SpringCache的实现思路和上述方法基本一致SpringCache提供了更优雅的编程方式更丝滑的缓存池切换和管理更强大的功能和统一规范。
EnableCaching
使用 EnableCaching 开启SpringCache功能无需引入额外的pom。
默认情况下缓存池将由 ConcurrentMapCacheManager 这个对象管理也就是默认是程序内存中缓存。其中用于存放缓存数据的是一个 ConcurrentHashMap源码如下 public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {private final ConcurrentMapString, Cache cacheMap new ConcurrentHashMap(16);......}
此外可选的缓存池管理对象还有 EhCacheCacheManager JCacheCacheManager RedisCacheManager ......
Cacheable package com.ramble.methodcache.controller;
import com.ramble.methodcache.controller.param.FindUserParam;
import com.ramble.methodcache.service.DemoService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.*;Tag(name user - api)
Slf4j
RequiredArgsConstructor
RestController
RequestMapping(/user)
public class UserController {private final DemoService demoService;Cacheable(value userCache)GetMapping(/{id})public String getUser(PathVariable(id) String id) {return demoService.getUser(id);}Operation(summary 查询用户)Cacheable(value userCache)PostMapping(/list)public String findUser(RequestBody FindUserParam param) {return demoService.findUser(param);}
}
使用Cacheable注解修饰需要缓存返回值的方法value必填不然运行时报异常。类似一个分组将不同的数据或者方法当然也可以其他维度主要看业务需要放到一堆便于管理可以修饰接口方法但是不建议IDEA会报一个提示Spring doesnt recommend to annotate interface methods with Cache* annotation
常用属性
value缓存名称cacheNames缓存名称。value 和cacheNames都被AliasFor注解修饰他们互为别名key缓存数据时候的key默认使用方法参数的值可以使用SpEL生产keykeyGeneratorkey生产器。和key二选一cacheManager缓存管理器cacheResolver和caheManager二选一互为别名condition创建缓存的条件可用SpEL表达式如#id0表示当入参id大于0时候才缓存方法返回值unless不创建缓存的条件如#resultnull表示方法返回值为null的时候不缓存
CachePut
用来更新缓存。被CachePut注解修饰的方法在被调用的时候不会校验缓存池中是否已经存在缓存会直接发起调用然后将返回值放入缓存池中。
CacheEvict
用来删除缓存会根据key来删除缓存中的数据。并且不会将本方法返回值缓存起来。
常用属性
value/cacheeName缓存名称或者说缓存分组key缓存数据的键allEntries是否根据缓存名称清空所有缓存默认为false。当此值为true的时候将根据cacheName清空缓存池中的数据然后将新的返回值放入缓存beforeInvocation是否在方法执行之前就清空缓存默认为false
Caching
此注解用于在一个方法或者类上面同时指定多个SpringCache相关注解。这个也是SpringCache的强大之处可以自定义各种缓存创建、更新、删除的逻辑应对复杂的业务场景。
属性
cacheable指定Cacheable注解put指定CachePut注解evict指定CacheEvict注解
源码 Target({ElementType.TYPE, ElementType.METHOD})
Retention(RetentionPolicy.RUNTIME)
Inherited
Documented
public interface Caching {Cacheable[] cacheable() default {};CachePut[] put() default {};CacheEvict[] evict() default {};
}
相当于就是注解里面套注解用来完成复杂和多变的场景这个设计相当的哇塞。
CacheConfig
放在类上面那么类中所有方法都会被缓存
SpringCacheEnv
SpringCache内置了一些环境变量可用于各个注解的属性中。 methodName被修饰方法的方法名 method被修饰方法的Method对象 target被修饰方法所属的类对象的实例 targetClass被修饰方法所属类对象 args方法入参是一个 object[] 数组 caches这个对象其实就是ConcurrentMapCacheManager中的cacheMap这个cacheMap呢就是一开头提到的ConcurrentHashMap即缓存池。caches的使用场景尚不明了。 argumentName方法的入参 result方法执行的返回值
使用示例 Cacheable(value userCache, condition #result!null,unless #resultnull)
public String showEnv() { return 打印各个环境变量;}
表示仅当方法返回值不为null的时候才缓存结果这里通过result env 获取返回值。
另外condition 和 unless 为互补关系上述condition #result!null和unless #resultnull其实是一个意思。 Cacheable(value userCache, key #name)
public String showEnv(String id, String name) {return 打印各个环境变量;
}
表示使用方法入参作为该条缓存数据的key若传入的name为gg则实际缓存的数据为gg-打印各个环境变量
另外如果name为空会报异常因为缓存key不允许为null Cacheable(value userCache,key #root.args)
public String showEnv(String id, String name) {return 打印各个环境变量;
}
表示使用方法的入参作为缓存的key若传递的参数为id100namegg则实际缓存的数据为Object[]-打印各个环境变量Object[]数组中包含两个值。
既然是数组可以通过下标进行访问root.args[1] 表示获取第二个参数本例中即 取 name 的值 gg则实际缓存的数据为gg-打印各个环境变量。 Cacheable(value userCache,key #root.targetClass)
public String showEnv(String id, String name) {return 打印各个环境变量;
}
表示使用被修饰的方法所属的类作为缓存key实际缓存的数据为Class-打印各个环境变量key为class对象不是全限定名全限定名是一个字符串这里是class对象。
可是不是很懂这样设计的应用场景是什么...... Cacheable(value userCache,key #root.target)
public String showEnv(String id, String name) {return 打印各个环境变量;
}
表示使用被修饰方法所属类的实例作为key实际缓存的数据为UserController-打印各个环境变量。
被修饰的方法就是在UserController中调试的时候甚至可以获取到此实例注入的其它容器对象如userService等。
可是不是很懂这样设计的应用场景是什么...... Cacheable(value userCache,key #root.method)
public String showEnv(String id, String name) {return 打印各个环境变量;
}
表示使用Method对象作为缓存的key是Method对象不是字符串。
可是不是很懂这样设计的应用场景是什么...... Cacheable(value userCache,key #root.methodName)
public String showEnv(String id, String name) {return 打印各个环境变量;
}
表示使用方法名作为缓存的key就是一个字符串。
如何获取缓存的数据
ConcurrentMapCacheManager的cacheMap是一个私有变量所以没有办法可以打印缓存池中的数据不过可以通过调试的方式进入对象内部查看。如下 Tag(name user - api)
Slf4j
RequiredArgsConstructor
RestController
RequestMapping(/user)
public class UserController {private final ConcurrentMapCacheManager cacheManager;/*** 只有调试才课可以查看缓存池中的数据*/GetMapping(/cache)public void showCacheData() {//需要debug进入CollectionString cacheNames cacheManager.getCacheNames();}}
总结
虽然提供了很多的环境变量但是大多都无法找到对应的使用场景其实在实际开发中最常见的就是key的生产一般而言使用类名方法名参数值足矣。