springboot中AOP+redis封装缓存
目录
一、目的
二、配置
--1、pom.xml
--2、redis配置
--3、响应类
--4、AOP切面的配置
三、如何使用自已写的缓存程序
一、目的
在controller层的方法上加一个注释就可以利用aop切面去做代码的扩展,在代码的扩展中利用redis做为缓存中间件。关于注解就用自定义的@RedisCached
二、配置
1、pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、redis配置
基础配置
@Configuration
public class RedisConfig {
/**
* @Description: 创建一个模板类,将redis连接工厂设置到模板类中{ @link RedisTemplate}
* @Author: maozi
* @Date: 2018/6/5 11:49
* @see:
**/
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory factory){
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(factory);
return template;
}
}
再封装一层
@Repository
public class RedisDao {
/**
* @Description: 字符类的模板{ @link }
* @Author: maozi
* @Date: 2018/6/5 11:52
* @see:
**/
@Autowired
private StringRedisTemplate stringTemplate;
/**
* @Description: 对象类的模板 { @link }
* @Author: maozi
* @Date: 2018/6/5 11:53
* @see:
**/
@Autowired
private RedisTemplate<String, Object> template;
public void setStringKey(String key, String value,int expire) {
if(stringTemplate.hasKey(key)){
stringTemplate.delete(key);
}
ValueOperations<String, String> ops = stringTemplate.opsForValue();
ops.set(key,value,expire, TimeUnit.MINUTES);
}
public String getStringValue(String key) {
ValueOperations<String, String> ops = this.stringTemplate.opsForValue();
return ops.get(key);
}
public Object getValue(String key) {
return template.opsForValue().get(key);
}
public void setKey(String key,Object value,long minutes){
if(template.hasKey(key)){
template.delete(key);
}
template.opsForValue().set(key,value,minutes, TimeUnit.MINUTES);
}
public boolean existByKey(String key){
return template.hasKey(key);
}
public boolean existByStringKey(String key){
return stringTemplate.hasKey(key);
}
public long getExpireTime(String key){
return template.getExpire(key);
}
public Boolean setExpireToReturnYes(String key,long timeout){
if (!template.hasKey(key)){
return false;
}
return template.expire(key,timeout,TimeUnit.MINUTES);
}
public Boolean setStringExpireToReturnYes(String key,long timeout){
if(!stringTemplate.hasKey(key)){
return false;
}
return stringTemplate.expire(key,timeout,TimeUnit.MINUTES);
}
public void cleanCacheByString(String key){
stringTemplate.delete(key);
}
public void cleanCache(String key){
template.delete(key);
}
}
注意:这里的模板有分字符的和对象的,如果value是字符的,那建议有字符模板。
application.properties
# redis
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=
spring.redis.database=1
spring.redis.pool.max-active=8
spring.redis.pool.max-wait=-1
spring.redis.pool.max-idle=500
spring.redis.pool.min-idle=0
spring.redis.timeout=0
3、响应类
@ApiModel(description = "请求返回结果")
public class ResponseResult {
private String errorCode;
private String errorMsg;
private Object objectResult;
public String getErrorCode() {
return errorCode;
}
public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
}
public String getErrorMsg() {
return errorMsg;
}
public void setErrorMsg(String errorMsg) {
this.errorMsg = errorMsg;
}
public Object getObjectResult() {
return objectResult;
}
public void setObjectResult(Object objectResult) {
this.objectResult = objectResult;
}
@Override
public String toString(){
return JSON.toJSONString(this);
}
}
4、AOP切面的配置
自定义注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RedisCached {
public int expire() default 5;//过期时间,默认5分钟
}
切面类
/**
* @Description: redis缓存的AOP
* @Author: maozi
* @Date: 2018/9/3 10:07
* @see: RedisCached
**/
@Aspect
@Component
public class RedisAspect {
private final Logger logger = LoggerFactory.getLogger(RedisAspect.class);
@Autowired
RedisDao redisDao;
@Pointcut("execution(public com.zhengjia.entity.ResponseResult com.zhengjia.web.*.*(..)) && @annotation(com.zhengjia.common.Annotation.RedisCached)")
public void redisAdvice(){}
@Around("redisAdvice()")
public Object Interceptor(ProceedingJoinPoint pjp){
Object result = null;
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
HttpServletRequest request = sra.getRequest();
String port = String.valueOf(request.getServerPort());
String uri = request.getRequestURI();
String methodType = request.getMethod();
String queryString = request.getQueryString();
//反射拿方法信息
Method method=getMethod(pjp);
//获得annotation的信息
RedisCached annotation = method.getAnnotation(RedisCached.class);
int expire = annotation.expire();
//检查请求类型
if(!methodType.equalsIgnoreCase("GET")){
throw new RuntimeException("只允许get请求做缓存");
}
//key的唯一性由url加上参数保证
String keyName = "";
if(method.getAnnotation(IgnoreToken.class) == null){ //如果没有注解的话,keyName规则上加上一sourceFrom参数
String token = request.getHeader("Authorization");
if(token.isEmpty()) throw new RuntimeException("请求头Authorization中没有token信息");
Map userMap = (Map)redisDao.getValue(token);
keyName = (String)userMap.get("sourceFrom") + port + uri + "?" + queryString;
}else {
keyName = port + uri + "?" + queryString;
}
ResponseResult responseResult = new ResponseResult();
try {
if (!redisDao.existByKey(keyName)){//没存在缓存,去查数据库
result = pjp.proceed();
responseResult = (ResponseResult)result;
if(responseResult.getObjectResult() != null){ //防止缓存空值
redisDao.setKey(keyName,responseResult.getObjectResult(),expire);
}
}else{//存在缓存中,在缓存中取数据库
responseResult.setObjectResult(redisDao.getValue(keyName));
responseResult.setErrorCode(Constants.RESPONSE_CODE_SUCCESS);
responseResult.setErrorMsg(Constants.RESPONSE_MSG_OK);
result = responseResult;
logger.info("redis缓存中查询数据,key值为" + keyName);
}
} catch (Throwable e) {
e.printStackTrace();
responseResult.setErrorCode(Constants.RESPONSE_CODE_ERROR);
responseResult.setErrorMsg("redisAspect报错!");
logger.info("redisAspect报错!");
}
return result;
}
/**
* 获取被拦截方法对象
*
* MethodSignature.getMethod() 获取的是顶层接口或者父类的方法对象
* 而缓存的注解在实现类的方法上
* 所以应该使用反射获取当前对象的方法对象
*/
public Method getMethod(ProceedingJoinPoint pjp){
//获取参数的类型
Object [] args=pjp.getArgs();
Class [] argTypes=new Class[pjp.getArgs().length];
if(args.length == 1 && args[0] == null){
args = new Object[0];
}
for(int i=0;i<args.length;i++){
argTypes[i]=args[i].getClass();
}
Method method=null;
try {
//有参的情况下
if(args.length > 0){
method = pjp.getTarget().getClass().getMethod(pjp.getSignature().getName(), argTypes);
}else { //无参的情况下
Class clazz = Class.forName(pjp.getSignature().getDeclaringTypeName());
Method[] i = clazz.getMethods();
for(Method data : clazz.getMethods()){
if(data.getName().equalsIgnoreCase(pjp.getSignature().getName())){
method = data;
break;
}
}
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return method;
}
}
注意:
1、缓存的key的唯一性是用url来保证,而且这里只允许Get请求,如果想扩展,那就自行修改代码,另外key的唯一性也可以用其他方式来确定。
2、这里缓存ResponseResult
中的objectResult的值,所以要用ResponseResult做返回,另外是因为,返回值在切面中做了限制。也就是说不用ResponseResult做返回值返回,那这个缓存是不起作用的。
3、expire是提供出去,动态设置缓存过期的时间
三、如何使用自已写的缓存程序
/**
* 商圈-购物中心关联度
*/
@GetMapping(value = "/correlation-degree/{name}")
@RedisCached(expire = 6)
public ResponseResult shopMallCorrelationDegree(@PathVariable String name) {
ResponseResult responseResult = new ResponseResult();
List<Map<String, Object>> resultList = bussinessCircleService.getShopMallCorrelationDegree(name);
responseResult.setErrorCode(Constants.RESPONSE_CODE_SUCCESS);
responseResult.setErrorMsg(Constants.RESPONSE_MSG_OK);
responseResult.setObjectResult(resultList);
return responseResult;
}