在电商网站中,用户的查询的需求量远远高于更新的数据量,所以为了提高服务器响应的能力,需要添加缓存服务器。
Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。它支持字符串、哈希表、列表、集合、有序集合,位图,hyperloglogs等数据类型。内置复制、Lua脚本、LRU收回、事务以及不同级别磁盘持久化功能,同时通过Redis Sentinel提供高可用,通过Redis Cluster提供自动分区。
#配置redis集群
redis.nodes=192.168.72.130:7000,192.168.72.130:7001,192.168.72.130:7002,192.168.72.130:7003,192.168.72.130:7004,192.168.72.130:7005
//redis配置类,相当于xml
@Configuration
@PropertySource("classpath:/properties/redis.properties")//通过加载配置文件为属性赋值
public class RedisConfig {
//spring整合redis集群
@Value("${redis.nodes}")
private String nodes;
@Bean
public JedisCluster jedisCluster() {
Set<HostAndPort> nodeSet=new HashSet<>();
String[] nodeArray=nodes.split(",");
for (String node : nodeArray) {
String host=node.split(":")[0];
int port=Integer.parseInt(node.split(":")[1]);
nodeSet.add(new HostAndPort(host, port));
}
return new JedisCluster(nodeSet);
}
}
package com.jt.util;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* 对象与JSON相互转换工具API
* @author Administrator
*/
public class ObjectMapperUtil {
private static final ObjectMapper MAPPER=new ObjectMapper();
/**
* 将对象转换成JSON
*/
public static String toJSON(Object object) {
String json=null;
try {
json=MAPPER.writeValueAsString(object);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
}
return json;
}
/**
* 将JSON转换成对象
*/
public static <T>T toObject(String json,Class<T> targetClass) {
T object=null;
try {
object=MAPPER.readValue(json, targetClass);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
}
return object;
}
}
package com.jt.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**AOP缓存注解*/
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Cache_Find {
String key() default "";//存到缓存中的key,用户可以不写,如果为空串,表示自动生成key
int seconds() default 0;//缓存超时时间,0表示用户设置该数据不需要超时时间,如果不等于0则说明用户自己定义了超时时间
}
package com.jt.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.alibaba.druid.util.StringUtils;
import com.jt.anno.Cache_Find;
import com.jt.util.ObjectMapperUtil;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.ShardedJedis;
@Component//交给spring管理
@Aspect//标识切面类 切面=切入点+通知
public class CacheAspect {
@Autowired(required = false)//表示调用时才注入,相当于延时加载@Lazy
//private Jedis jedis;//redis单片
//private ShardedJedis jedis;redis分片
//private Jedis jedis;//redis哨兵
private JedisCluster jedis;//redis集群
/**
* 环绕通知
* 1.返回值 必须为Object类型,表示执行完成业务之后返回给用户的数据
* 2.参数 a.必须位于第1位 b.参数类型必须为ProceedingJoinPoint(因为要用环绕通知控制目标方法执行,其他通知不能控制)
* 3.关于注解取值规则 springAOP中提供了可以直接获取注解的方法,但是要求参数的名称必须一致,否则映射错误
*/
//@Around("@annotation(com.jt.anno.Cache_Find)")//这样写下面就要通过反射获取注解的名称和参数key、seconds等
@Around("@annotation(cacheFind)")
public Object around(ProceedingJoinPoint joinPoint,Cache_Find cacheFind) {
Object resultData=null;//结果数据,对象
String key=getKey(joinPoint,cacheFind);
String resultJSON=jedis.get(key);//从缓存中取数据,JSON串
if (StringUtils.isEmpty(resultJSON)) {
//表示缓存中没有数据则需要执行目标方法,从数据库查询数据
try {
resultData=joinPoint.proceed();//从数据库查询出来的数据,对象
String value=ObjectMapperUtil.toJSON(resultData);//把数据库中查询出来的数据转换成JSON,要保存到缓存中
//判断注解中是否要求数据永久保存,再保存到缓存中
if (cacheFind.seconds()>0) {
jedis.setex(key, cacheFind.seconds(), value);
}else {
jedis.set(key, value);
}
System.out.println("AOP查询数据库成功!");
} catch (Throwable e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}else {
//获取目标方法的返回值类型
Class returnType=getReturnType(joinPoint);
//表示缓存中有数据,将resultJSON转换成对象并传给resultData
resultData=ObjectMapperUtil.toObject(resultJSON, returnType);
System.out.println("AOP查询缓存成功!");
}
return resultData;
}
/**
* 获取目标方法的返回值类型
* @param joinPoint
* @return
*/
private Class getReturnType(ProceedingJoinPoint joinPoint) {
MethodSignature signature=(MethodSignature)joinPoint.getSignature();
return signature.getReturnType();
}
/**
* key生成策略:
* 1.如果用户在注解中指定了key,则使用用户指定的
* 2.如果用户没指定,在使用自动生成的key 类名+方法名+目标方法第一个参数(parentId)
* @param joinPoint
* @param cacheFind
* @return
*/
private String getKey(ProceedingJoinPoint joinPoint, Cache_Find cacheFind) {
String key=cacheFind.key();//默认值 ""
if (StringUtils.isEmpty(key)) {
//如果为空就自动生成
String className=joinPoint.getSignature().getDeclaringTypeName();//类名
String methodName=joinPoint.getSignature().getName();//方法名
String arg1=String.valueOf(joinPoint.getArgs()[0]);//目标方法第一个参数
//com.jt.controller.list::0 拼接格式
return className+"."+methodName+"::"+arg1;
} else {
return key;
}
}
}
@RestController
@RequestMapping("/item/cat")
public class ItemCatController {
@Autowired
private ItemCatService itemCatService;
//查询分类进行tree展示
@RequestMapping("/list")
@Cache_Find//配置redis缓存
public List<EasyUITree> findItemCatList(@RequestParam(defaultValue = "0",name = "id")Long parentId) {
return itemCatService.findEasyUITreeList(parentId);//从数据库查询
}
}