1.定义注解

package com.g2.order.server.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; /**
* redis缓存注解
* 仅支持方法
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisCachetAttribute {
/**
* @return 缓存的key值
* 对应的Method的返回值必须 实现 Serializable 接口
*
*/
String key(); /**
* 到期秒数
*
* @return 到期秒数
*/
int expireSeconds() default 20;
}

2.定义切面

package com.g2.order.server.aspect;

import com.g2.order.server.annotation.RedisCachetAttribute;
import com.g2.order.server.utils.ObjectUtils; import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component; import java.lang.reflect.Method; import redis.clients.jedis.JedisCluster; //开启AspectJ 自动代理模式,如果不填proxyTargetClass=true,默认为false,
@EnableAspectJAutoProxy(proxyTargetClass = true)
@Component
@Order(-1)
@Aspect
public class RedisCacheAspect {
/**
* 日志
*/
private static Logger logger = LoggerFactory.getLogger(RedisCacheAspect.class); /**
* SPEL表达式解析器
*/
private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser(); /**
* 获取方法参数名称发现器
*/
private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer(); /**
* Redis集群
*/
@Autowired
private JedisCluster jedisCluster; /**
* 切面切入点
*/
@Pointcut("@annotation(com.g2.order.server.annotation.RedisCachetAttribute)")
public void mergeDuplicationRequest() { } /**
* 环绕切面
*/
@Around("mergeDuplicationRequest()")
public Object handleControllerMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//获取controller对应的方法.
MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
//获取方法
Method method = methodSignature.getMethod(); //获取注解
RedisCachetAttribute annotation = method.getAnnotation(RedisCachetAttribute.class);
//获取缓存key的表达式,并根据上下文等数据计算表达式
String cacheKey = parseKey(annotation.key(), proceedingJoinPoint);
int seconds = annotation.expireSeconds();
//先尝试从redis里获取数据(字节)
byte[] redisKey = cacheKey.getBytes();
byte[] redisValue = jedisCluster.get(redisKey); if (redisValue != null && redisValue.length > 0) {
//redis有数据,直接返回
return ObjectUtils.toObject(redisValue);
} //redis没有数据,则调用原方法,获取结果值
Object result = proceedingJoinPoint.proceed();
//将返回值序列化为Byte[],保存到redis
redisValue = ObjectUtils.toByteArray(result);
jedisCluster.setex(redisKey, seconds, redisValue); return result;
} /**
* 计算spel表达式
*
* @param expression 表达式
* @param context 上下文
* @return String的缓存key
*/
private static String parseKey(String expression, JoinPoint context) {
//获取切入点的方法信息
MethodSignature methodSignature = (MethodSignature) context.getSignature();
Method method = methodSignature.getMethod(); // 获取传入参数值
Object[] args = context.getArgs();
if (args == null || args.length == 0) {
// 无参传入,直接计算表达式(无需参数上下文)
return EXPRESSION_PARSER.parseExpression(expression).getValue(String.class);
} // 获取参数名
String[] parameterNames = PARAMETER_NAME_DISCOVERER.getParameterNames(method);
if (parameterNames.length > args.length) {
//由于java不允许有匿名参数,所以如果参数名多于参数值,则必为非法
logger.error("参数值的长度少于参数名长度, 方法:{}, 参数名长度: {},参数值长度:{}", method, parameterNames.length, args.length);
throw new IllegalArgumentException("参数传入不足");
} // 将参数名与参数值放入参数上下文
EvaluationContext evaluationContext = new StandardEvaluationContext();
for (int i = 0; i < parameterNames.length; i++) {
evaluationContext.setVariable(parameterNames[i], args[i]);
} // 计算表达式(根据参数上下文)
return EXPRESSION_PARSER.parseExpression(expression).getValue(evaluationContext, String.class);
}
}

3.引用代码

package com.g2.order.server.business;

import com.google.common.collect.ImmutableMap;
import com.g2.order.server.annotation.RedisCachetAttribute;
import com.g2.order.server.business.vo.JsonResult;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
import java.util.Map; /**
* SettingBusiness.
*/
@Component
public class SettingBusiness { @RedisCachetAttribute(key = "T(com.g2.order.server.vo.CommonConst)" +
".SETTING_JSON_KEY" +
" + #code"
, expireSeconds = 30
)
public JsonResult getSetting(String code) {
return new JsonResult("234");
} @RedisCachetAttribute(key = "T(com.g2.order.server.vo.CommonConst)" +
".SETTING_LIST_KEY" +
" + #code"
, expireSeconds = 30
)
public List<JsonResult> getSettingList(String code) {
return Arrays.<JsonResult>asList(new JsonResult("234"), new JsonResult("345"));
} @RedisCachetAttribute(key = "T(com.g2.order.server.vo.CommonConst)" +
".SETTING_MAP_KEY" +
" + #code"
, expireSeconds = 30
)
public Map<String, JsonResult> getSettingMap(String code) {
return ImmutableMap.of(code, new JsonResult("234"),"abc",new JsonResult("abc234"));
}
}
package com.g2.order.server.business.vo;

import java.io.Serializable;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor; /**
* Model
*/ @AllArgsConstructor
@NoArgsConstructor
@Data
public class JsonResult implements Serializable {
private Object data;
}

4.测试如下.(验证获取普通POJO,List,Map的返回结构)

package com.g2.order.server.controller;

import com.g2.order.server.business.SettingBusiness;
import com.g2.order.server.business.vo.JsonResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
import io.swagger.annotations.Api; @Api(value = "H5Controller", description = "H5接口")
@RestController
@RequestMapping("/h5")
public class H5Controller {
private static Logger logger = LoggerFactory.getLogger(H5Controller.class); @Autowired
private SettingBusiness settingBusiness; @ResponseBody
//@MergeDuplicationRequestAttribute(redisLockKeyTemplate = "A:%s", redisLockKeyObjectFileds = {"code"})
@RequestMapping(value = "/{code}.jsonp", method = RequestMethod.GET)
public Object testJsonp1(@PathVariable("code") String code) {
// return JsonMapper.INSTANCE.toJsonP("callabc111", new JsonResult("234"));
JsonResult result = settingBusiness.getSetting(code);
logger.info("获取结果,参数:{},值:{}", code, result);
return result;
} @ResponseBody
//@MergeDuplicationRequestAttribute(redisLockKeyTemplate = "A:%s", redisLockKeyObjectFileds = {"code"})
@RequestMapping(value = "/{code}.LIST", method = RequestMethod.GET)
public Object testJsonp2(@PathVariable("code") String code) {
// return JsonMapper.INSTANCE.toJsonP("callabc111", new JsonResult("234"));
List<JsonResult> result = settingBusiness.getSettingList(code);
logger.info("获取结果,参数:{},值:{}", code, result);
return result;
} @ResponseBody
//@MergeDuplicationRequestAttribute(redisLockKeyTemplate = "A:%s", redisLockKeyObjectFileds = {"code"})
@RequestMapping(value = "/{code}.MAP", method = RequestMethod.GET)
public Object testJsonp3(@PathVariable("code") String code) {
// return JsonMapper.INSTANCE.toJsonP("callabc111", new JsonResult("234"));
Map<String,JsonResult> result = settingBusiness.getSettingMap(code);
logger.info("获取结果,参数:{},值:{}", code, result);
return result;
}
}

5.辅助代码

package com.g2.order.server.utils;

import com.google.common.collect.Lists;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors; /**
* Object帮助类
*/
public class ObjectUtils { /**
* 对象转Byte数组
*/
public static byte[] toByteArray(Object obj) throws IOException {
byte[] bytes = null;
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos)) {
oos.writeObject(obj);
oos.flush();
bytes = bos.toByteArray();
}
return bytes;
} /**
* Byte数组转对象
*/
public static Object toObject(byte[] bytes) throws IOException, ClassNotFoundException {
Object obj = null;
try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis)) { obj = ois.readObject();
}
return obj;
}
}
package com.g2.order.server.vo;

/**
* CommonConst
*/
public class CommonConst { public static final String PREFIX = "g2:";
public static final String SETTING_PREFIX = PREFIX + "setting:";
/**
* JSON_KEY
*/
public static final String SETTING_JSON_KEY = SETTING_PREFIX + "json:"; /**
* LIST_KEY
*/
public static final String SETTING_LIST_KEY = SETTING_PREFIX + "list:"; /**
* MAP_KEY
*/
public static final String SETTING_MAP_KEY = SETTING_PREFIX + "map:";
}

6.备注

这只是一个实现上的demo,如果要用到生产,可能还需要做以下改进

1.切面代码里写死了JedisCluster,这里要修改成一个接口 来支持单机/哨兵/集群 等

2.不支持毫秒级的存储(因为jedisCluster不支持...)

3.当没有获取缓存值时,应当根据key来加分布式锁,否则容易造成同样的处理多次执行

redis缓存切面实现(支持缓存key的spel表达式)的更多相关文章

  1. 使用AOP 实现Redis缓存注解,支持SPEL

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

  2. Redis整合Spring结合使用缓存实例(三)

    一.Redis介绍 什么是Redis? redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set( ...

  3. Redis整合Spring结合使用缓存实例

    林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 摘要:本文介绍了如何在Spring中配置redis,并通过Spring中AOP的思想,将缓存的 ...

  4. Redis整合Spring结合使用缓存实例(转)

    林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 摘要:本文介绍了如何在Spring中配置redis,并通过Spring中AOP的思想,将缓存的 ...

  5. Redis学习总结(3)——Redis整合Spring结合使用缓存实例

    摘要:本文介绍了如何在Spring中配置redis,并通过Spring中AOP的思想,将缓存的方法切入到有需要进入缓存的类或方法前面. 一.Redis介绍 什么是Redis? redis是一个key- ...

  6. 使用Redis做MyBatis的二级缓存

    使用Redis做MyBatis的二级缓存 通常为了减轻数据库的压力,我们会引入缓存.在Dao查询数据库之前,先去缓存中找是否有要找的数据,如果有则用缓存中的数据即可,就不用查询数据库了. 如果没有才去 ...

  7. 利用Azure Redis Cache构建百万量级缓存读写

    Redis是一个非常流行的基于内存的,低延迟,高吞吐量的key/value数据存储,被广泛用于数据库缓存,session的管理,热数据高速访问,甚至作为数据库方式提高应用程序可扩展性,吞吐量,和实施处 ...

  8. SpringCache与redis集成,优雅的缓存解决方案

    缓存可以说是加速服务响应速度的一种非常有效并且简单的方式.在缓存领域,有很多知名的框架,如EhCache .Guava.HazelCast等.Redis作为key-value型数据库,由于他的这一特性 ...

  9. redis结合自定义注解实现基于方法的注解缓存,及托底缓存的实现

    本次分享如何使用redis结合自定义注解实现基于方法的注解缓存,及托底缓存的实现思路    现在的互联网公司大多数都是以Redis作为缓存,使用缓存的优点就不赘述了,写这篇文章的目的就是想帮助同学们如 ...

随机推荐

  1. 苹果IOS 12将使您的iPhone更安全,并有更强大的黑客保护

    一年一度的IOS刷新正在进行中,苹果已经预览了它,beta测试者已经安装了它,当iPhone在9月份到货时我们其他人应该获得iOS12.虽然软件3-D表情符号和屏幕时间限制等功能在软件到货时可能会受到 ...

  2. js arguments参数

    在调用函数时,浏览器每次都会传递进两个隐含的参数:      1.函数的上下文对象 this      2.封装实参的对象 arguments         - arguments是一个类数组对象, ...

  3. OC + RAC(一) RACSignal 基本使用

    -(void)_test1{ //测试RAC流程 发送next类型事件 以completed结束时: //至于有无 sendCompleted 的区别主要是用在需要知道信号状态 NSLog(@&quo ...

  4. 2019 上海网络赛 F Rhyme scheme (字典树DP)

    题目:https://nanti.jisuanke.com/t/41414 题意:求长度为n的第k个bell number  ,  就是第i位的选取范围在 1-(i-1)位的最大值 +1,第一位固定为 ...

  5. Power Strings POJ - 2406

    Power Strings POJ - 2406 时限: 3000MS   内存: 65536KB   64位IO格式: %I64d & %I64u 提交 状态 已开启划词翻译 问题描述 Gi ...

  6. Nexus Repository OSS 3安装配置使用

    Nexus Repository OSS 3是一个开源的仓库管理系统,提供了更加丰富的功能,而且安装.配置.使用起来也更加简单方便.OSS 3版本主要支持的仓库(Repository)包括如下: bo ...

  7. flask中获取request的参数的方法

    request请求总体分为两类: 1.get请求 访问时会在地址栏直接显示参数不安全,且参数大小比较小. 2.post请求 参数不显示在地址栏,一般用户注册.登录都通过post请求完成. flask获 ...

  8. windowns环境下mysql 安装教程

    windowns环境下mysql 安装教程 一:这里以绿色版安装为例(解压就可以使用) 下载地址: 下载页面:https://dev.mysql.com/downloads/mysql/  2:点击 ...

  9. .NETFramework:System.Net.WebClient.cs

    ylbtech-.NETFramework:System.Net.WebClient.cs 提供用于将数据发送到和接收来自通过 URI 确认的资源数据的常用方法 1.返回顶部 1. #region 程 ...

  10. 20150909—Win10系统80端口被System占用

    安装了Windows10系统后,首先是查看80端口是不是被占用, 运行netstat -aon | findstr :80 ,发现pid是4的进程占用着80端口,这还是一个系统进程,kill不掉. 所 ...