手机靓号网站建设,谷歌云服务器永久免费,鹤壁网站建设鹤壁,江门 网站设计1. 简介
为了体现我们的实力#xff0c;首先我们要有造轮子的能力。这意味着我们不仅要熟练掌握现有的技术栈和框架#xff0c;还要具备深厚的技术功底。通过自主设计和实现关键组件#xff0c;如高性能缓存系统#xff0c;我们能够深入理解技术背后的原理#xff0c;掌握…1. 简介
为了体现我们的实力首先我们要有造轮子的能力。这意味着我们不仅要熟练掌握现有的技术栈和框架还要具备深厚的技术功底。通过自主设计和实现关键组件如高性能缓存系统我们能够深入理解技术背后的原理掌握核心技术的精髓从而在面对复杂问题时能够提出独到见解和解决方案。
本篇文章将带大家实现一个简化版但功能类似Spring Cache的缓存组件。虽然不会像Spring Cache那样全面和复杂但我们将通过动手实践掌握缓存机制的核心原理。
2. 实战案例
2.1 环境准备
既然是基于redis实现那么我们通过引入以下依赖来简化环境配置
dependency
groupIdorg.springframework.boot/groupId
artifactIdspring-boot-starter-data-redis/artifactId/dependency通过data-redis我们需要做的就是配置redis服务器信息即可。
spring:redis:timeout: 10000connectTimeout: 20000host: 127.0.0.1password: xxxooolettuce:pool:maxActive: 8maxIdle: 100minIdle: 10maxWait: -1通过以上的配置在项目中我们就可以直接通过SpringBoot自动配置的StringRedisTemplate来实现各种操作。
2.2 自定义注解
这里我们仿照spring-cache定义如下两个注解Cacheable 触发缓存, CacheEvict 删除缓存。
**Cacheable**设置缓存或读取缓存如果缓存中存在直接返回如果不存在则将方法返回值设置到缓存中。
**CacheEvict**将当前指定key从缓存中删除。
触发缓存:
Target({ElementType.TYPE, ElementType.METHOD})
Retention(RetentionPolicy.RUNTIME)
public interface Cacheable {// 缓存KeyString key() default ;// 缓存名称类似分组String name() default ;
}删除缓存 Target({ElementType.TYPE, ElementType.METHOD})
Retention(RetentionPolicy.RUNTIME)
public interface CacheEvict {// 缓存KeyString key() default ;// 缓存名称类似分组String name() default ;
}以上2个注解基本相同分别完成读写清除缓存操作。
注为了灵活我们需要让上面注解中的key支持SpEL表达式。
2.3 定义切面
该切面用来处理带有上面注解的类或方法如方法上有**Cacheable**注解那么先从缓存中读取内容如果缓存中不存在则将当前方法返回值写入缓存中。
Component
Aspect
public class CacheAspect {private static final Logger logger LoggerFactory.getLogger(CacheAspect.class) ;private final StringRedisTemplate stringRedisTemplate ;public CacheAspect(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate stringRedisTemplate ;}private DefaultParameterNameDiscoverer discoverer new DefaultParameterNameDiscoverer() ;Pointcut(annotation(com.pack.redis.cache.Cacheable))private void cacheable() {}Pointcut(annotation(com.pack.redis.cache.CacheEvict))private void cacheevict() {}Around(cacheable() || cacheevict()) public Object proceed(ProceedingJoinPoint pjp) throws Throwable {Object ret null ;Method method ((MethodSignature) pjp.getSignature()).getMethod() ;Object[] args pjp.getArgs() ;// SPEL 表达式解析SpelExpressionParser parser new SpelExpressionParser() ;StandardEvaluationContext context new StandardEvaluationContext() ;// 获取参数名称String[] parameterNames discoverer.getParameterNames(method) ;for (int i 0, len parameterNames.length; i len; i) {context.setVariable(parameterNames[i], args[i]) ;}// 类上的注解不做处理了只处理方法上Cacheable cacheable method.getAnnotation(Cacheable.class) ;if (cacheable ! null) {// 假设都配置了name和keyString name cacheable.name() ;String expression cacheable.key() ;Object value parser.parseExpression(expression).getValue(context) ;String cacheKey name : value;boolean hasKey this.stringRedisTemplate.hasKey(cacheKey) ;if (!hasKey) {ret pjp.proceed() ;this.stringRedisTemplate.opsForValue().set(cacheKey, new ObjectMapper().writeValueAsString(ret)) ;logger.info(写缓存【{}】,数据{}, cacheKey, ret) ;return ret ; }logger.info(从缓存【{}】获取, cacheKey);String result this.stringRedisTemplate.opsForValue().get(cacheKey) ;return new ObjectMapper().readValue(result, method.getReturnType()) ;}CacheEvict cacheevict method.getAnnotation(CacheEvict.class) ;if (cacheevict ! null) {String name cacheevict.name() ;String expression cacheevict.key() ;Object value parser.parseExpression(expression).getValue(context) ;String cacheKey name : value ;this.stringRedisTemplate.delete(cacheKey) ;}return pjp.proceed() ;}}上面的环绕通知通过或(||)表达式的方式拦截2个注解实现比较的简单主要是结合了SpEL表达式。上面的代码我们假设是你key进行了配置如果没有配置我们可以通过如下方式去生成key
private String getKey(Class? targetType, Method method) {StringBuilder builder new StringBuilder();builder.append(targetType.getSimpleName());builder.append(#).append(method.getName()).append(();Class?[] types method.getParameterTypes() ;for (Class? clazz : types) {builder.append(clazz.getSimpleName()).append(,) ;}if (method.getParameterTypes().length 0) {builder.deleteCharAt(builder.length() - 1);}return (builder.append()).toString()).replaceAll([^a-zA-Z0-9], ) ;
}如果你没有配置key可以通过当前执行的类及方法生成唯一的key。
2.4 测试
这里我是通过JPA实现CRUD操作
Service
public class UserService {private final UserRepository userRepository ;public UserService(UserRepository userRepository) {this.userRepository userRepository ;}Cacheable(name user, key #id)public User queryById(Long id) {return this.userRepository.findById(id).orElse(null) ;}CacheEvict(name user, key #user.id)public void clearUserById(User user) {this.userRepository.deleteById(user.getId()) ;}
}这里的clearUserById方法是有意将方法参数设置为User对象就是为了方便演示SpEL表达式的使用。
测试接口
GetMapping(/{id})
public User getUser(PathVariable(id) Long id) {return this.userService.queryById(id) ;
}DeleteMapping(/{id})
public void removeUser(PathVariable(id) Long id) {User user new User() ;user.setId(id) ;this.userService.clearUserById(user) ;
}
到此一个简单的缓存组件就实现了。这里待完善及优化的还是非常多的比如更新缓存并发量大时缓存那里是不是应该加锁不然肯定会有很多的线程都执行查询再存入缓存我们这里是不是还可以借助本地缓存做多级缓存的优化呢可以参考下面这篇文章。
[[Redis结合Caffeine实现二级缓存提高应用程序性能]]