Java如何设计内存框架呢?
下文笔者讲述java设计内存框架的方法分享,如下所示
即可看出从缓存中获取数据
如何使用缓存呢?
缓存的常用操作查询时: 先读取缓存 当缓存中没有数据,则触发真正的数据获取 当缓存中有数据,则直接返回缓存中的数据; 新增数据时,将数据写入缓存; 删除数据时,删除对应的缓存数据 且自定义每个KEY的缓存有效期
Spring Cache 提供缓存注解
@Cacheable 此注解对方法配置 可根据方法的请求参数对其进行缓存 @CacheEvict 清空缓存 @CachePut 保证方法被调用 结果被缓存与@Cacheable区别在于是否每次都调用方法 常用于更新
缓存架构的示例
pom.xml <?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.0</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.java265</groupId> <artifactId>custom-cache</artifactId> <version>0.0.1-SNAPSHOT</version> <name>custom-cache</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>Runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> </dependency> <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>2.6.2</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
Cacheable注解
package com.java265.aop.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @ClassName Cacheable * @Description 用于缓存读取 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Cacheable { String cacheName() default ""; // 缓存名称 String cacheKey(); // 缓存key int expire() default 3600; // 有效时间(单位,秒),默认1个小时 int reflash() default -1; // 缓存主动刷新时间(单位,秒) } package com.java265.aop.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @ClassName CacheEvict * @Description 缓存清除 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface CacheEvict { String cacheName() default ""; //缓存名称 String cacheKey(); //缓存key boolean allEntries() default false; //是否清空cacheName的全部数据 } //CachePut 注解 package com.java265.aop.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @ClassName CachePut * @Description 缓存写入 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface CachePut { String cacheName() default ""; //缓存名称 String cacheKey(); //缓存key int expire() default 3600; //有效期时间(单位:秒),默认1个小时 } //缓存配置文件 package com.java265.aop.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @ClassName CachePut * @Description 缓存写入 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface CachePut { String cacheName() default ""; //缓存名称 String cacheKey(); //缓存key int expire() default 3600; //有效期时间(单位:秒),默认1个小时 } //默认缓存键生成器 package com.java265.aop; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.expression.EvaluationContext; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import java.lang.reflect.Method; /** * @Description: key生成策略,缓存名+缓存KEY(支持Spring EL表达式) */ @Component public class DefaultKeyGenerator { private static Logger logger = LoggerFactory.getLogger(DefaultKeyGenerator.class); // 用于SpEL表达式解析 private SpelExpressionParser parser = new SpelExpressionParser(); // 用于获取方法参数定义名字 private DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer(); /** * @Description cache key生成 * @Param cacheKey:key值必传,cacheNames:缓存名称,不传取方法路径 **/ public String generateKey(ProceedingJoinPoint pjp, String cacheName, String cacheKey) throws NoSuchMethodException { if (StringUtils.isEmpty(cacheKey)) { throw new NullPointerException("CacheKey can not be null..."); } Signature signature = pjp.getSignature(); if (cacheName == null) { cacheName = new String(signature.getDeclaringTypeName() + "." + signature.getName()); } EvaluationContext evaluationContext = new StandardEvaluationContext(); if (!(signature instanceof MethodSignature)) { throw new IllegalArgumentException("This annotation can only be used for methods..."); } MethodSignature methodSignature = (MethodSignature) signature; //method参数列表 Method method = pjp.getTarget().getClass().getMethod(methodSignature.getName(), methodSignature.getMethod().getParameterTypes()); String[] parameterNames = nameDiscoverer.getParameterNames(method); Object[] args = pjp.getArgs(); for (int i = 0; i < parameterNames.length; i++) { evaluationContext.setVariable(parameterNames[i], args[i]); } //解析cacheKey String result = "CacheName_" + cacheName + "_CacheKey_" + parser.parseExpression(cacheKey).getValue(evaluationContext, String.class); //暂时只使用String类型 logger.info("=============>>> generateKeys : {}", result); return result; } } //AOP package com.java265.aop; import com.java265.customcache.aop.annotation.CacheEvict; import com.java265.customcache.aop.annotation.CachePut; import com.java265.customcache.aop.annotation.Cacheable; import com.github.benmanes.caffeine.cache.Cache; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.SerializationUtils; /** * @Description: 自定义缓存注解AOP实现 */ @Aspect @Component public class CacheableAspect { private static Logger logger = LoggerFactory.getLogger(CacheableAspect.class); @Autowired private Cache caffeineCache; @Autowired private DefaultKeyGenerator defaultKeyGenerator; /** * 读取缓存数据 * 定义增强,pointcut连接点使用@annotaion(xxx)进行定义 * @param pjp * @param cache * @return * @throws Throwable */ @Around(value = "@annotation(cache)") // cache 与 下面参数名around对应 public Object cache(final ProceedingJoinPoint pjp, Cacheable cache) throws Throwable { try { String key = defaultKeyGenerator.generateKey(pjp, cache.cacheName(), cache.cacheKey()); Object valueData = null; // 获取缓存中的值 Object value = caffeineCache.getIfPresent(key); if (value != null) { //如果缓存有值,需要判断刷新缓存设置和当前缓存的失效时间 if (cache.reflash() > 0) { //查询当前缓存失效时间是否在主动刷新规则范围内 // caffeine 中刷新的话可以使用同步加载的方式调用reflash() } return value; } //缓存中没有值,执行实际数据查询方法 if (valueData == null) { valueData = pjp.proceed(); //写入缓存 } if (cache.expire() > 0) { caffeineCache.put(key, valueData); } else { //否则设置缓存时间 ,序列化存储 caffeineCache.put(key, valueData); } return valueData; } catch (Exception e) { logger.error("读取caffeine缓存失败,异常信息:" + e.getMessage()); return pjp.proceed(); } } /** * 新增缓存 */ @Around(value = "@annotation(cache)") public Object cachePut(final ProceedingJoinPoint pjp, CachePut cache) throws Throwable { try { String key = defaultKeyGenerator.generateKey(pjp, cache.cacheName(), cache.cacheKey()); Object valueData = pjp.proceed(); // 写入缓存 // 由于 caffeine 是内存缓存,对每个可以设置超时支持并不够好。除非每个 key 都构造一个 cache 对象;可以使用 redis 代替 if (cache.expire() > 0) { caffeineCache.put(key, SerializationUtils.serialize(pjp.getArgs()[0])); } else { caffeineCache.put(key, SerializationUtils.serialize(pjp.getArgs()[0])); } return valueData; } catch (Exception e) { logger.error("写入caffeine缓存失败,异常信息:" + e.getMessage()); return pjp.proceed(); } } /** * 删除缓存 */ @Around(value = "@annotation(cache)") public Object cacheEvict(final ProceedingJoinPoint pjp, CacheEvict cache) throws Throwable { try { String cacheName = cache.cacheName(); boolean allEntries = cache.allEntries(); if (allEntries) { if (cacheName == null) { Signature signature = pjp.getSignature(); cacheName = new String(signature.getDeclaringTypeName() + "." + signature.getName()); } caffeineCache.invalidate("CacheName_" + cacheName); } else { String key = defaultKeyGenerator.generateKey(pjp, cache.cacheName(), cache.cacheKey()); caffeineCache.invalidate(key); } } catch (Exception e) { logger.error("删除caffeine缓存失败,异常信息:" + e.getMessage()); } return pjp.proceed(); } } //模拟实现类 package com.java265.entity; import lombok.Data; import java.io.Serializable; @Data public class Country implements Serializable { /** * 主键 */ private Integer id; /** * 名称 */ private String countryname; /** * 代码 */ private String countrycode; } //service相关 package com.java265.service; import com.java265.entity.Country; import java.util.list; public interface ICountryService { List<Country> listAll(); int save(Country country); int update(Country country); int deleteById(int id); Country getById(int id); } package com.java265.customcache.service.impl; import com.java265.customcache.aop.annotation.CacheEvict; import com.java265.customcache.aop.annotation.CachePut; import com.java265.customcache.aop.annotation.Cacheable; import com.java265.customcache.entity.Country; import com.java265.customcache.mapper.CountryMapper; import com.java265.customcache.service.ICountryService; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.List; /** * @Author: Neco * @Description: * @Date: create in 2022/6/14 13:07 */ @Service public class CountryServiceImpl implements ICountryService { @Resource private CountryMapper countryMapper; @Override public List<Country> listAll() { return countryMapper.listAll(); } @Override @CachePut(cacheName = "country_", cacheKey = "#country.id", expire = 3600) public int save(Country country) { return countryMapper.save(country); } @Override @CacheEvict(cacheName = "country_", cacheKey = "#id") public int update(Country country) { return countryMapper.update(country); } @Override @CacheEvict(cacheName = "country_", cacheKey = "#id") public int deleteById(int id) { return countryMapper.deleteById(id); } @Override @Cacheable(cacheName = "country_", cacheKey = "#id", expire = 3600) public Country getById(int id) { return countryMapper.getById(id); } } 9. Mapper package com.java265.customcache.mapper; import com.java265.customcache.entity.Country; import org.apache.ibatis.annotations.*; import java.util.List; @Mapper public interface CountryMapper { @Select("select * from country") List<Country> listAll(); @Insert("insert into country (id, countryname, countrycode) values(#{country.id}, #{country.countryname}, #{country.countrycode})") int save(@Param("country") Country country); @Update("update country set countryname = #{country.countryname} and countrycode = #{country.countrycode} where id=#{country.id}") int update(@Param("country") Country country); @Delete("delete from country where id=#{country.id}") int deleteById(int id); @Select("select * from country where id = #{id}") Country getById(int id); } //Controller package com.java265.customcache.controller; import com.java265.customcache.entity.Country; import com.java265.customcache.entity.vo.Result; import com.java265.customcache.service.ICountryService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/cache") public class CacheController { @Autowired private ICountryService countryService; @GetMapping("/country/list") public Result list() { List<Country> list = countryService.listAll(); return Result.OK(list); } @GetMapping("/country/{id}") public Result getById(@PathVariable Integer id) { Country country = countryService.getById(id); return Result.OK(country); } @DeleteMapping("/country/{id}") public Result deleteById(@PathVariable Integer id) { countryService.deleteById(id); return Result.OK("删除成功!"); } @PostMapping("/country/save") public Result save(@RequestBody Country country) { countryService.save(country); return Result.OK("保存成功!"); } @PutMapping("/country/update") public Result update(@RequestBody Country country) { countryService.update(country); return Result.OK("更新成功!"); } }多次访问 http://localhost:8080/cache/country/1
即可看出从缓存中获取数据
版权声明
本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。