package com.demo.utils; import org.springframework.util.StringUtils; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; /** * Description: 缓存工具类 * 1.部分方法未验证,如有问题请自行修改 * 2.其他方法请自行添加 * * @Author: zhx & moon hongxu_1234@163.com * @Date: 2022-04-07 20:54 * @version: V1.0.0 */ public class Cache { /** * 屏蔽工具类的无参构造 避免工具类被实例化 */ private Cache(){} /** * 缓存留存期 30min 1H 24H */ public static final long CACHE_HOLD_TIME_30M = 30 * 60 * 1000L; public static final long CACHE_HOLD_TIME_1H = 2 * CACHE_HOLD_TIME_30M; public static final long CACHE_HOLD_TIME_24H = 24 * CACHE_HOLD_TIME_1H; public static final long CACHE_HOLD_TIME_FOREVER = -1L; /** * 缓存容量、最少使用容量 */ private static final int CACHE_MAX_CAP = 1000; private static final int CLEAN_LRU_CAP = 800; /** * 缓存当前大小 */ private static AtomicInteger CACHE_CURRENT_SIZE = new AtomicInteger(0); /** * 缓存对象 */ private static final Map<String,Node> CACHE_MAP = new ConcurrentHashMap<>(CACHE_MAX_CAP); /** * 最少使用记录 */ private static final List<String> LRU_LIST = new LinkedList<>(); /** * 自动清理标志位 */ private static volatile boolean CLEAN_RUN_FLAG = false; /** * 默认30MIN * @param key * @param val */ public static void put(String key,Object val){ put(key,val,CACHE_HOLD_TIME_30M); } /** * 添加永久缓存 * @param key * @param val */ public static void putForever(String key,Object val){ put(key,val,CACHE_HOLD_TIME_FOREVER); } /** * 添加缓存 * @param key * @param val * @param ttlTime */ public static void put(String key,Object val,long ttlTime){ if (!StringUtils.hasLength(key) || null == val){ return; } checkSize(); updateCacheLru(key); CACHE_MAP.put(key,new Node(val,ttlTime)); } /** * 获取缓存信息 * @param key * @param clazz * @param <T> * @return */ public static <T> T get(String key,Class<T> clazz){ if (!StringUtils.hasLength(key) || !CACHE_MAP.containsKey(key)){ return null; } updateCacheLru(key); return (T) CACHE_MAP.get(key).getVal(); } /** * 更新最近使用位置 * @param key */ private static void updateCacheLru(String key){ synchronized (LRU_LIST){ LRU_LIST.remove(key); LRU_LIST.add(0,key); } } /** * 删除,成功则容量-1 * @param key */ private static boolean remove(String key){ Node node = CACHE_MAP.remove(key); if (null!=node){ CACHE_CURRENT_SIZE.getAndDecrement(); return true; } return false; } /** * 检查是否超过容量,先清理过期,在清理最少使用 */ private static void checkSize(){ if (CACHE_CURRENT_SIZE.intValue() > CACHE_MAX_CAP){ deleteTimeOut(); } if (CACHE_CURRENT_SIZE.intValue() > CLEAN_LRU_CAP){ deleteLru(); } } /** * 删除最久未使用,尾部删除 * 永久缓存不会被清除 */ private static void deleteLru(){ synchronized (LRU_LIST){ while (LRU_LIST.size() > CLEAN_LRU_CAP){ int lastIndex = LRU_LIST.size() - 1; String key = LRU_LIST.get(lastIndex); if (!CACHE_MAP.get(key).isForever() && remove(key)){ LRU_LIST.remove(lastIndex); } } } } /** * 删除过期 */ private static void deleteTimeOut(){ List<String> del = new LinkedList<>(); for (Map.Entry<String,Node> entry:CACHE_MAP.entrySet()){ if (entry.getValue().isExpired()){ del.add(entry.getKey()); } } for (String k:del){ remove(k); } } /** * 缓存是否已存在,过期则删除返回False * @param key * @return */ public static boolean contains(String key){ if (CACHE_MAP.containsKey(key)){ if (!CACHE_MAP.get(key).isExpired()){ return true; } if (remove(key)){ return false; } return true; } return false; } /** * 清空缓存 */ public static void clear(){ CACHE_MAP.clear(); CACHE_CURRENT_SIZE.set(0); LRU_LIST.clear(); } /** * 重置自动清理标志 * @param flag */ public static void setCleanRunFlag(boolean flag){ CLEAN_RUN_FLAG = flag; } /** * 自动清理过期缓存 */ private static void startAutoClean(){ if (!CLEAN_RUN_FLAG){ setCleanRunFlag(true); ScheduledExecutorService scheduledExecutor = new ScheduledThreadPoolExecutor(1); scheduledExecutor.scheduleAtFixedRate(()->{ try { Cache.setCleanRunFlag(true); while (CLEAN_RUN_FLAG){ Cache.deleteTimeOut(); } } catch (Exception e) { e.printStackTrace(); } },10,Cache.CACHE_HOLD_TIME_1H, TimeUnit.SECONDS); } } /** * 缓存对象类 */ public static class Node{ /** * 缓存值 */ private Object val; /** * 过期时间 */ private long ttlTime; public Node(Object val,long ttlTime){ this.val = val; if (ttlTime<0){ this.ttlTime = ttlTime; }else{ this.ttlTime = System.currentTimeMillis() + ttlTime; } } public Object getVal(){ return this.val; } public boolean isExpired(){ if (this.ttlTime<0){ return false; } return System.currentTimeMillis() > this.ttlTime; } public boolean isForever(){ if (this.ttlTime<0){ return true; } return false; } } }
Cache component | Type |
---|---|
Distributed cache | |
Distributed Cache | |
Distributed Cache | |
Distributed Cache | |
local cache | |
local cache |
This specification has designed two top-level The interfaces Cache and CacheManager are cache and cache management. By implementing CacheManager and introducing cache components, you can easily set the cache through annotations in the SpringBoot project.
//org.springframework.boot.autoconfigure.cache.CacheConfigurations static { Map<CacheType, String> mappings = new EnumMap<>(CacheType.class); mappings.put(CacheType.GENERIC, GenericCacheConfiguration.class.getName()); mappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration.class.getName()); mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class.getName()); mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class.getName()); mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class.getName()); mappings.put(CacheType.REDIS, RedisCacheConfiguration.class.getName()); mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class.getName()); mappings.put(CacheType.CACHE2K, Cache2kCacheConfiguration.class.getName()); mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class.getName()); mappings.put(CacheType.NONE, NoOpCacheConfiguration.class.getName()); MAPPINGS = Collections.unmodifiableMap(mappings); }2. Cache annotations
Function | |
---|---|
The function of enabling annotation caching is usually added to the project startup class | |
If there is a cache, then Return cache information; if it does not exist, get the value and add it to the cache | |
Add cache, which can be used to update methods (force the method return value to be added to the specified Key ) | |
Delete cache | |
Package operation, combine the above The annotations are packaged together and work | |
General configuration annotations. If you want to set a cache for an object, you can mark this annotation on the class to set the cache name. Primary key generator, etc. |
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>LenovoTest</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>19</maven.compiler.source> <maven.compiler.target>19</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring.version>3.0.0</spring.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>${spring.version}</version> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>3.1.2</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.24</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>2.0.14.graal</version> </dependency> </dependencies> </project>
server: port: 8088 spring: cache: type: caffeine custom-caffeine: specs: ## 用户信息写入10S后过期 userInfo: maximumSize=10,expireAfterWrite=10s ## 登陆信息写入5S后过期 accessInfo: maximumSize=10,expireAfterWrite=5s
package com.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; /** * @author * @date * @since 1.8 */ @EnableCaching @SpringBootApplication public class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class,args); } }
package com.demo.comfig; import com.github.benmanes.caffeine.cache.Caffeine; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cache.CacheManager; import org.springframework.cache.caffeine.CaffeineCacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import java.util.HashMap; import java.util.Iterator; import java.util.Map; /** * @author * @date * @since 1.8 */ @Configuration public class CustomCaffeineConfig { /** * 加载 Caffeine 配置 * @return */ @Bean(name = "caffeineProperties") @ConfigurationProperties(prefix = "custom-caffeine.specs") public Map<String,String> caffeineProperties(){ return new HashMap(16); } /** * 自定义 CacheManager * @param properties * @return */ @Bean @Primary public CacheManager caffeineManager(@Qualifier("caffeineProperties") Map<String,String> properties){ CaffeineCacheManager manager = new CaffeineCacheManager(); Map.Entry<String,String> entry; Iterator<Map.Entry<String,String>> iterator = properties.entrySet().iterator(); while (iterator.hasNext()){ entry = iterator.next(); manager.registerCustomCache(entry.getKey(), Caffeine.from(entry.getValue()).build()); } return manager; } }5. Test classDefine a User object
package com.demo.entity; import lombok.Data; /** * @author zhanghx19 * @date 2023-01-28 15:53 * @since 1.8 */ @Data public class UserInfo { private String name; private String account; private long age; }Define a Controller class For testing
package com.demo.controller; import com.demo.entity.UserInfo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * @author * @date 2023-01-28 11:36 * @since 1.8 */ @RestController @RequestMapping("/test") public class TestController { /** * 注入缓存管理器,所有注解操作也可以直接操作管理器实现 */ @Autowired CacheManager cacheManager; /** * CachePut 强制刷新缓存 * @param id * @param val * @return */ @GetMapping("/update") @CachePut(cacheNames = "test" ,key = "#id") public String update(String id,String val){ //TODO Query Data By @{id} return val; } /** * Cacheable 查看缓存,存在则直接返回;否则查询数据,添加缓存并返回 * @param id * @param val * @return */ @GetMapping("/query") @Cacheable(cacheNames = "test" ,key = "#id" ) public String query(String id,String val){ //TODO Query Data By @{id} return val; } /** * 删除注解内指定的 缓存名下的 Key * @param id */ @GetMapping("/deleteTest") @CacheEvict(cacheNames = "test",key = "#id") public void deleteTest(String id){ } /** * 通过 cacheManager 删除缓存 * @param cacheNames * @param id */ @GetMapping("/deleteByNameAndKet") public void deleteByNameAndKet(String cacheNames,String id){ Cache cache = cacheManager.getCache(cacheNames); cache.evict(id); } /** * CachePut 强制缓存用户信息 且10秒后过期 * @param id * @param val * @return */ @GetMapping("/updateUser") @CachePut(cacheNames = "userInfo" ,key = "#id") public String updateUser(String id,String val){ return val; } /** * Cacheable 10秒内缓存不更新,丢失后可刷新为当前值 * @param id * @param val * @return */ @GetMapping("/queryUser") @Cacheable(cacheNames = "userInfo" ,key = "#id") public String queryUser(String id,String val){ return val; } /** * 缓存对象 * @param id * @param val * @return */ @GetMapping("/queryUserById") @Cacheable(cacheNames = "userInfo" ,key = "#id") public UserInfo getUserInfo(String id,String val){ UserInfo info = new UserInfo(); info.setAccount(id); info.setName(val); info.setAge(System.currentTimeMillis()); return info; } }6. Test recordStart the project and add mandatory cache Use Cacheable to try to refresh the cache (return already exists Value) Delete cache Use Cacheable again to try to refresh the cache (you can refresh it after clearing it above) Automatic expiration test, add user information through CachePut Try to refresh the cache with Cacheable, it will take effect after 10S After 10 seconds Cache object information
The above is the detailed content of How to use the integrated caching tool class CacheManager under SpringBoot. For more information, please follow other related articles on the PHP Chinese website!