spring中如何通过自定义注解加对接口进行加缓存?
近期为了快速升职加薪,不断的学习架构之路
以前在使用Redis缓存时,经常是在代码中通过调用RedisTemplate进行操作缓存的,哪里需要加缓存,就通过这种方式来使用,当时也没多想,觉得用得还挺顺手的,甚至其他同事在使用Redis时,基本上的使用方式和我是一样的,没啥区别
但是最近学习了架构之后,总感觉之前的做法不太好,每次加缓存,都要得去修改代码,增加缓存的代码,而修改代码很容易引起很多风险
为什么有风险?因为至少每次都需要有以下两个步骤
1.先去获取缓存,在判断缓存是否存在,如果已经存在,则直接返回
2.缓存不存在,就去读数据库,将数据写入缓存
这只是一个简单的场景,当然,实际上可能不仅仅这两步,可能还要涉及到读库时要加锁…
总之,一句话,这种方式去加锁,存在比较打的风险
如何避免加缓存带来的风险?
其实,有了架构思想后,就简单很多了,以不变应万变,这也是高级架构师必须具备的思想
怎么以不变应万变?
经过了上述分析,我们知道加缓存必须经过上述说到的至少2个步骤,我们这里就按照两个步骤进行思考架构
既然加缓存,必须要写代码,但是怎么写,才能减少风险?
那就是自定义注解类,在需要加缓存的接口上,加上一个注解,这样在调用接口的过程中,如果缓存没有数据,自动的去查库,然后设置缓存,如果缓存有数据,就直接返回。然后在基于AOP和spring框架,即可实现,代码如下
自定义注解
/**
* 自定义cache注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CoustomCache {
/**
* key的规则,可以使用springEL表达式,可以使用方法执行的一些参数
*/
String key();
}
AOP实现:
@Component
@Aspect
public class CoustomCacheAspect {
@Autowired
private RedisTemplate redisTemplate;
@Pointcut("@annotation(custom.annotations.CoustomCache)")
public void cachePointcut() {
}
// 定义相应的事件
@Around("cachePointcut()")
public Object doCache(ProceedingJoinPoint joinPoint) {
Object value = null;
try {
// 0-1、 当前方法上注解的内容
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = joinPoint.getTarget().getClass().getMethod(signature.getName(), signature.getMethod().getParameterTypes());
CoustomCache cacheAnnotation = method.getAnnotation(CoustomCache.class);
String keyEl = cacheAnnotation.key();
// 0-2、 前提条件:拿到作为key的依据 - 解析springEL表达式
// 创建解析器
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(keyEl);
EvaluationContext context = new StandardEvaluationContext(); // 参数
// 添加参数
Object[] args = joinPoint.getArgs();
DefaultParameterNameDiscoverer discover = new DefaultParameterNameDiscoverer();
String[] parameterNames = discover.getParameterNames(method);
for (int i = 0; i < parameterNames.length; i++) {
context.setVariable(parameterNames[i], args[i].toString());
}
// 解析
String key = expression.getValue(context).toString();
// 1、 判定缓存中是否存在
value = redisTemplate.opsForValue().get(key);
if (value != null) {
System.out.println("从缓存中读取到值:" + value);
return value;
}
// 2、不存在则执行方法
value = joinPoint.proceed();
// 3、 同步存储value到缓存。
redisTemplate.opsForValue().set(key, value);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return value;
}
}
package custom;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import custom.annotations.CoustomCache;
import custom.pojo.User;
@Service
@Profile("custom")
public class CustomAnnoDemoService {
@CoustomCache(key = "#userId")
public User findUserById(String userId) throws Exception {
User user = null;
user = new User(userId, "张三");
System.out.println("从数据库中读取到值:" + user);
return user;
}
}
测试用例:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
@ActiveProfiles("custom") // 设置profile
public class CustomCacheTests {
@Autowired
CustomAnnoDemoService customDemoService;
// get
@Test
public void springCacheTest() throws Exception {
User user = customDemoService.findUserById("wahaha");
System.out.println(user);
}
}
这里只是为了说明架构思路和实现自定义注解加缓存的案例,源码就不提供了