公司项目对Redis使用比较多,因为之前没有做AOP,所以缓存逻辑和业务逻辑交织在一起,维护比较艰难
所以最近实现了针对于Redis的@Cacheable,把缓存的对象依照类别分别存放到redis的Hash中,对于key也实现了SPEL支持。

1.applicationContext.xml,配置JedisPool

<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="50" />
<property name="maxIdle" value="10" />
<property name="maxWaitMillis" value="1000" />
<property name="testOnBorrow" value="true" />
</bean> <bean id="jedisPool" class="redis.clients.jedis.JedisPool">
<constructor-arg index="0" ref="jedisPoolConfig" />
<constructor-arg index="1" value="127.0.0.1" />
<constructor-arg index="2" value="6379" />
</bean>

2.Redis的封装类,使用FastJSON进行JSON和Object的转化,这里只用到了hset,hget,hdel,其他省略了

@Component
public class RedisCacheBean {
@Resource
JedisPool jedisPool; /**
* 把对象放入Hash中
*/
public void hset(String key,String field,Object o){
Jedis jedis =jedisPool.getResource();
jedis.hset(key,field, JsonUtil.toJSONString(o));
jedisPool.returnResource(jedis);
}
/**
* 从Hash中获取对象
*/
public String hget(String key,String field){
Jedis jedis =jedisPool.getResource();
String text=jedis.hget(key,field);
jedisPool.returnResource(jedis);
return text;
}
/**
* 从Hash中获取对象,转换成制定类型
*/
public <T> T hget(String key,String field,Class<T> clazz){
String text=hget(key, field);
T result=JsonUtil.parseObject(text, clazz);
return result;
}
/**
* 从Hash中删除对象
*/
public void hdel(String key,String ... field){
Jedis jedis =jedisPool.getResource();
Object result=jedis.hdel(key,field);
jedisPool.returnResource(jedis);
} }

3.创建注解,其实大部分数据都是以hash形式存储的(使的key易于管理),所以,注解中定义了fieldKey,用作Hash的field。

/**
* 缓存注解
* @author liudajiang
*
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Cacheable {
String key();
String fieldKey() ;
int expireTime() default 3600;
}

4.定义切面,定义PointCut 表达式为注解

@Component
@Aspect
public class CacheAspect {
@Resource RedisCacheBean redis; /**
* 定义缓存逻辑
*/
@Around("@annotation(org.myshop.cache.annotation.Cacheable)")
public Object cache(ProceedingJoinPoint pjp ) {
Object result=null;
Boolean cacheEnable=SystemConfig.getInstance().getCacheEnabled();
//判断是否开启缓存
if(!cacheEnable){
try {
result= pjp.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
return result;
} Method method=getMethod(pjp);
Cacheable cacheable=method.getAnnotation(org.myshop.cache.annotation.Cacheable.class); String fieldKey =parseKey(cacheable.fieldKey(),method,pjp.getArgs()); //获取方法的返回类型,让缓存可以返回正确的类型
Class returnType=((MethodSignature)pjp.getSignature()).getReturnType(); //使用redis 的hash进行存取,易于管理
result= redis.hget(cacheable.key(), fieldKey,returnType); if(result==null){
try {
result=pjp.proceed();
Assert.notNull(fieldKey);
redis.hset(cacheable.key(),fieldKey, result);
} catch (Throwable e) {
e.printStackTrace();
}
}
return result;
} /** * 定义清除缓存逻辑 */
@Around(value="@annotation(org.myshop.cache.annotation.CacheEvict)")
public Object evict(ProceedingJoinPoint pjp ){
//和cache类似,使用Jedis.hdel()删除缓存即可...
} /**
* 获取被拦截方法对象
*
* MethodSignature.getMethod() 获取的是顶层接口或者父类的方法对象
* 而缓存的注解在实现类的方法上
* 所以应该使用反射获取当前对象的方法对象
*/
public Method getMethod(ProceedingJoinPoint pjp){
//获取参数的类型
Object [] args=pjp.getArgs();
Class [] argTypes=new Class[pjp.getArgs().length];
for(int i=0;i<args.length;i++){
argTypes[i]=args[i].getClass();
}
Method method=null;
try {
method=pjp.getTarget().getClass().getMethod(pjp.getSignature().getName(),argTypes);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
return method; }
/**
* 获取缓存的key
* key 定义在注解上,支持SPEL表达式
* @param pjp
* @return
*/
private String parseKey(String key,Method method,Object [] args){ //获取被拦截方法参数名列表(使用Spring支持类库)
LocalVariableTableParameterNameDiscoverer u =
new LocalVariableTableParameterNameDiscoverer();
String [] paraNameArr=u.getParameterNames(method); //使用SPEL进行key的解析
ExpressionParser parser = new SpelExpressionParser();
//SPEL上下文
StandardEvaluationContext context = new StandardEvaluationContext();
//把方法参数放入SPEL上下文中
for(int i=0;i<paraNameArr.length;i++){
context.setVariable(paraNameArr[i], args[i]);
}
return parser.parseExpression(key).getValue(context,String.class);
}
}

5.使用

    @Transactional
@Cacheable(key="getAdminByName",fieldKey="#name")
public Admin getByName(String name) {
return adminDao.getByUsername(name);
}
@Transactional
@CacheEvict(key="getAdminByName",fieldKey="#admin.username")
public void update(Admin admin){
adminDao.update(admin);
}

效果:

使用AOP 实现Redis缓存注解,支持SPEL的更多相关文章

  1. ssm+redis 如何更简洁的利用自定义注解+AOP实现redis缓存

    基于 ssm + maven + redis 使用自定义注解 利用aop基于AspectJ方式 实现redis缓存 如何能更简洁的利用aop实现redis缓存,话不多说,上demo 需求: 数据查询时 ...

  2. spring aop搭建redis缓存

    SpringAOP与Redis搭建缓存 近期项目查询数据库太慢,持久层也没有开启二级缓存,现希望采用Redis作为缓存.为了不改写原来代码,在此采用AOP+Redis实现. 目前由于项目需要,只需要做 ...

  3. 【原】spring redis 缓存注解使用

    由于最近新上的项目很多模块没有做数据缓存,大量的请求都会到数据库去查询,为了减轻数据库的压力以及提高网站响应速度,所以在这里采用了spring 提供的注解+redis实现对数据的缓存,主要针对非热点数 ...

  4. 分布式限流组件-基于Redis的注解支持的Ratelimiter

    原文:https://juejin.im/entry/5bd491c85188255ac2629bef?utm_source=coffeephp.com 在分布式领域,我们难免会遇到并发量突增,对后端 ...

  5. redis缓存切面实现(支持缓存key的spel表达式)

    1.定义注解 package com.g2.order.server.annotation; import java.lang.annotation.ElementType; import java. ...

  6. SpringBoot AOP控制Redis自动缓存和更新

    导入redis的jar包 <!-- redis --> <dependency> <groupId>org.springframework.boot</gro ...

  7. Spring 缓存注解 SpEL 表达式解析

    缓存注解上 key.condition.unless 等 SpEL 表达式的解析 SpEl 支持的计算变量: 1)#ai.#pi.#命名参数[i 表示参数下标,从 0 开始] 2)#result:Ca ...

  8. SpringBoot集成Redis分布式锁以及Redis缓存

    https://blog.csdn.net/qq_26525215/article/details/79182687 集成Redis 首先在pom.xml中加入需要的redis依赖和缓存依赖 < ...

  9. Spring boot AOP 实现Redis 存储

    package com.carloan.common.web.annotation; import java.lang.annotation.*; /** * 自定义redis缓存注解.只要在serv ...

随机推荐

  1. APK签名原理

    网上已有多篇分析签名的类似文章,但是都有一个共同的问题,就是概念混乱,混乱的一塌糊涂. 在了解APK签名原理之前,首先澄清几个概念: 消息摘要 -Message Digest 简称摘要,请看英文翻译, ...

  2. 【HDOJ】1241 Oil Deposits

    经典的BFS. #include <stdio.h> #include <string.h> #define MAXNUM 105 #define MAXROW 105 #de ...

  3. Oracle系列之索引

    涉及到表的处理请参看原表结构与数据  Oracle建表插数据等等 Oracle索引.权限 介绍 为什么添加了索引后,会加快查询速度呢? 索引是用于加速数据存取的数据对象.合理的使用索引可以大大降低i/ ...

  4. 结构体dfield_t

    /* SQL data field struct */ typedef struct dfield_struct dfield_t; /** Structure for an SQL data fie ...

  5. sql 不同server間寫入數據

    select * from sys.servers sp_dropserver @server =N'' sp_dropserver '' ,'droplogins' EXEC master.dbo. ...

  6. 【转】解决wine中文乱码的问题

    原文网址:http://blog.chinaunix.net/uid-24993439-id-2979620.html 新装的wine中文全是乱码,需要修改一下几个配置文件,找到一篇比较详细的配置说明 ...

  7. 面试准备--Spring(AOP)

    AOP:面向切面编程,在执行某个指令时,需要添加某个预编译的指令. 下面这个例子是来自网上的: 1.OOP回顾 在介绍AOP之前先来回顾一下大家都比较熟悉的OOP(Object Oriented Pr ...

  8. C程序设计 习题之1-20 detab

    码农一定是最需要动手实操的行业之一.有一句话叫,好记性不如烂笔头,牵强附会引申到这里,变成看书百遍,不如码字运行一遍.是不是有那么一点味道?哈哈! 这几天看的<C程序设计>,看完每个章节还 ...

  9. 基础 HTML之目录问题(相对路径和绝对路径区别)

    一.相对路径和绝对路径 相对路径:以引用文件之网页所在位置为参考基础,而建立出的目录路径.因此,当保存于不同目录的网页引用同一个文件时,所使用的路径将不相同,故称之为相对. 绝对路径:以Web站点根目 ...

  10. POJ1155 - TELE(树形DP)

    题目大意 电视台要直播一场比赛,电视网络刚好形成了一棵树,其中有M个为客户端,其他的为中转站,其中中转站与中转站以及中转站与客户端之间连接都需要一定费用,每个客户i愿意支付pay[i]元钱,问电视台在 ...