导入依赖
<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>31.1-jre</version>
</dependency>
使用
项目中使用到了缓存,定义一个切面,拦截类或方法上存在@SysDataCache注解请求,对于这些方法的返回值进行缓存。项目中主要还是使用在缓存常量,一些不易改变的值
定义注解
@Documented
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SysDataCache {}
定义切面和初始化缓存容器并使用缓存
@Aspect //定义一个切面
@Configuration
public class SysDataCacheAspect {private static Logger logger = LogManager.getLogger(SysDataCacheAspect.class);private static final Map<String,Boolean> cacheFileNames = new ConcurrentHashMap<String, Boolean>();private static LoadingCache<String,Object> cache = null;static {// CacheLoader 初始化CacheLoader<String, Object> cacheLoader = new CacheLoader<String, Object>() {@Override// load方法的作用是在通过get方法从LoadingCache获取不到值时去加载该值并放入缓存。public Object load(String key) throws Exception {return null;}};cache = CacheBuilder.newBuilder()// 设置容量大小.maximumSize(80000)//默认一天后过期.expireAfterWrite(10, TimeUnit.DAYS).removalListener(new RemovalListener<String, Object>() {@Overridepublic void onRemoval(RemovalNotification<String, Object> notification) {if(notification.getValue()!=null && notification.getValue() instanceof CacheFile) {CacheFile cacheFile = (CacheFile)notification.getValue();removeCacheFile(cacheFile.fileName);}}})// 加载器配置.build(cacheLoader);}private String normalizedArgsStr(Object[] args){if(args==null || args.length==0) {return "";}Object[] normalizedArgs = new Object[args.length];if(args!=null) {for(int i=0;i<args.length;i++) {Object arg = args[i];if(arg instanceof AccessTokenUser) {AccessTokenUser user = (AccessTokenUser)arg;normalizedArgs[i]= user.getUserId();}else {normalizedArgs[i]=arg;}}}return JsonConverter.toJsonStr(normalizedArgs);}@Around("execution(* (@com.xysd.bizbase.annotation.SysDataCache *).*(..)) || execution(@com.xysd.bizbase.annotation.SysDataCache * *(..))")public Object process(ProceedingJoinPoint point) throws Throwable {String className = point.getSignature().getDeclaringTypeName();String methodName = point.getSignature().getName();Object[] args = point.getArgs();String key = className+"_$_"+methodName+"_$_"+(normalizedArgsStr(args));Object cached = cache.getIfPresent(key);if(methodName.endsWith("_dontCache")){return point.proceed(args);}if(cached!=null) {if(cached instanceof CacheFile) {CacheFile cachedFile = (CacheFile)cached;Object cachedData = readCachedData(cachedFile);if(cachedData==null) {//读取缓存失败return point.proceed(args);}else {return cachedData;}}else {return cached;}}else {cached = point.proceed(args);if(cached instanceof ApiResultDTO){if(((ApiResultDTO<?>) cached).getData() == null) return cached;}if(cached!=null) {try {CacheFile cachedFile = cacheToDiskIfNecessary(cached);if(cachedFile!=null) {cache.put(key, cachedFile);}else {cache.put(key, cached);}}catch(Exception e) {logger.error("缓存失败,失败信息{}",e.getMessage());e.printStackTrace();}}return cached;}}private Object readCachedData(CacheFile cachedFile) {String fileName = cachedFile.getFileName();String absolutePath = getAbsoluteCacheFilePath(fileName);File f = new File(absolutePath);InputStream in = null;ObjectInputStream oin = null;try {in = new FileInputStream(f);oin = new ObjectInputStream(in);Object cachedData = oin.readObject();return cachedData;}catch(Exception e) {logger.error("读取缓存序列化文件失败,失败信息:{}",e.getMessage());e.printStackTrace();return null;}finally {Utils.clean(in,oin);}}/*** 当value序列化后占用字节大于50K时写入磁盘进行缓存* @param value* @return*/private CacheFile cacheToDiskIfNecessary(Object value) {int cachThreadshold = 50*1024;ByteArrayOutputStream bos = null ; ObjectOutputStream oos = null;try {bos = new ByteArrayOutputStream();oos = new ObjectOutputStream(bos);oos.writeObject(value);oos.flush();byte[] byteArray = bos.toByteArray();if(byteArray!=null && byteArray.length>cachThreadshold) {return buildCacheFile(byteArray);}else {return null;}}catch(Exception e) {throw new RuntimeException(e);}finally {Utils.clean(bos,oos);}}private CacheFile buildCacheFile(byte[] byteArray) {String fileName = "syscachefile_"+Utils.getUUID("");String absolutePath = getAbsoluteCacheFilePath(fileName);File f = new File(absolutePath);OutputStream out = null;try {if(!f.getParentFile().exists()) {f.getParentFile().mkdirs();}out = new FileOutputStream(f);out.write(byteArray);out.flush();cacheFileNames.put(fileName, true);return new CacheFile(fileName);}catch(Exception e) {throw new RuntimeException(e);}finally {Utils.clean(out);}}private static String getAbsoluteCacheFilePath(String fileName) {String sysCacheBaseDir = Utils.getTmpDirRoot()+"/sysDataCache";return sysCacheBaseDir+"/"+fileName;}public static void removeCacheFile(String fileName) {if(StringUtils.isNoneBlank(fileName)) {cacheFileNames.remove(fileName);String absolutePath = getAbsoluteCacheFilePath(fileName);File f = new File(absolutePath);try {if(f.exists() && f.isFile()) {f.delete();}}catch(Exception e) {//删除失败不做任何处理e.printStackTrace();}}}/*** 清空缓存*/public static final void clearCache() {for(String fileName:cacheFileNames.keySet()) {removeCacheFile(fileName);}cacheFileNames.clear();cache.invalidateAll();}public static class CacheFile implements Serializable {private static final long serialVersionUID = -6926387004863371705L;private String fileName;public CacheFile(String fileName) {super();this.fileName = fileName;}public String getFileName() {return fileName;}}}
项目中缓存使用
@Service
@Transactional
@SuppressWarnings("unchecked")
public class SysDatasServiceImpl implements _ISysDatasService {private final static ConstantItem dataKey_dict_politicalStatus = new ConstantItem("politicalStatus", "政治身份");@Override@SysDataCache@Transactional(readOnly = true)public Map<String, String> getSysDataKeysInfo(AccessTokenUser user) {result.put((String) dataKey_dict_politicalStatus.getId(), dataKey_dict_politicalStatus.getName());}@Override@SysDataCache@Transactional(readOnly = true)public Map<String, Object> getSysDatasByDataKeys(AccessTokenUser user, Map<String, Object> dataKeyAndParams) {if (dataKey_dict_politicalStatus.getId().equals(entry.getKey())) {//政治身份Map<String, Object> dictParams = (Map<String, Object>) entry.getValue();data.put(entry.getKey(), getDictValue(entry.getKey(), dictParams));continue;}}//从字典中获取数据private List<SimpTreeNode> getDictValue(String dictCodes, Map<String, Object> params) {if("all".equals(dictCodes)){List<SysDataSimpleDTO> rootDicts = systemGatewayService.findAllRootDicts(1);dictCodes = Optional.ofNullable(rootDicts).orElse(new ArrayList<>()).stream().map(r->r.getId().replace("-","")).collect(Collectors.joining(","));}else if (params != null && params.get("dictCodes") != null) {dictCodes = (String) params.get("dictCodes");params.remove("dictCodes");}List<SimpTreeNode> codeList = systemGatewayService.findDictSimTreeNodeByDictCodes(dictCodes, params);return codeList;}
}