当用户点击商品分类按钮时,应该实现缓存查询.
思路:
1).准备key=“ITEM_CAT::”+parentID
2).准备value="JSON"串
3).首先查询redis缓存
有: 直接获取缓存数据返回给用户.
没有: 直接查询数据库,之后将返回值结果保存到redis中,方便下次使用.
/**
* 业务:查询商品分类信息,返回VO对象
* url地址: /item/cat/list
* 参数: id:一级分类id值
* 返回值: EasyUITree对象
* json格式:
* [{"id":"2","text":"王者荣耀","state":"closed"},{"id":"3","text":"王者荣耀","state":"closed"}]`
* sql语句:
* 一级商品分类信息 parent_id=0 SELECT * FROM tb_item_cat WHERE parent_id=0
*/
@RequestMapping("/list")
public List<EasyUITree> findItemCatByParentId
(@RequestParam(value = "id",defaultValue = "0") Long parentId){
//初始化时应该设定默认值.
//1.查询一级商品分类信息
//Long parentId = id==null?0L:id;
//return itemCatService.findItemCatByParentId(parentId);
//通过缓存的方式获取数据.
return itemCatService.findItemCatByCache(parentId);
}
/**
* 通过缓存的方式查询数据库.
* 1).定义key
* 2).根据key查询redis.
*/
@SuppressWarnings("unchecked")
@Override
public List<EasyUITree> findItemCatByCache(Long parentId) {
//1.定义key
String key = "ITEM_CAT_LIST::"+parentId;
List<EasyUITree> treeList = new ArrayList<EasyUITree>();
Long startTime = System.currentTimeMillis();
//2.判断redis中是否有值
if(jedis.exists(key)) {
//不是第一次查询,则获取缓存数据之后直接返回
String json = jedis.get(key);
Long endTime = System.currentTimeMillis();
treeList =
ObjectMapperUtil.toObject(json, treeList.getClass());
System.out.println("redis查询缓存的时间为:"+(endTime-startTime)+"毫秒");
}else {
//redis中没有这个key,表示用户第一次查询.
treeList = findItemCatByParentId(parentId);
Long endTime = System.currentTimeMillis();
//需要将list集合转化为json
String json = ObjectMapperUtil.toJSON(treeList);
//将数据保存到redis中
jedis.set(key, json);
System.out.println("查询数据库的时间为:"+(endTime-startTime)+"毫秒");
}
return treeList;
}
说明:
1).由于将redis的操作写到service层中,必须导致业务的耦合性高
2).如果采用上述的方式完成缓存,则改缓存不通用,并且代码冗余.效率低.
1). bean(bean的ID) 按照指定的bean名称拦截用户的请求,之后执行通知方法. 只能匹配单个bean对象
2).within(包名.类名) 可以按照类通配的方式去拦截用户的请求. 控制粒度较粗.
3).execution(返回值类型 包名.类名.方法名(参数列表)) 方法参数级别 控制粒度较细
4).@annotation(包名.注解名称) 按照注解的方式去拦截用户请求.
1.前置通知: 主要在 目标方法执行之前执行
2.后置通知: 在目标方法执行之后执行
3.异常通知: 在目标方法执行的过程中报了异常之后执行.
4.最终通知: 无论什么时候都要执行的通知方法.
上述的通知方法,无法控制目标方法是否执行.所以一般"只做记录不做改变"
5.环绕通知: 一般采用环绕通知 实现对业务的控制.
//1.将对象交给容器管理
@Component
//2.定义aop切面
@Aspect
public class CacheAOP {
//公式: 切面 = 切入点表达式 + 通知方法.
/**
* 业务需求: 要求拦截ItemCatServiceImpl类中的业务
* @Pointcut 切入点表达式 可以理解为就是一个if判断,只有满足条件,才能执行通知方法.
*/
//@Pointcut("bean(itemCatServiceImpl)") //按类匹配,控制的粒度较粗 单个bean
//@Pointcut("within(com.jt.service..*)") //按类匹配,控制的粒度较粗 多个bean
@Pointcut("execution(* com.jt.service..*.*(..))") //细粒度的匹配方式
public void pointCut() {
}
//joinPoint 方法执行切恰好被切入点表达式匹配,该方法的执行就称之为连接点.
@Before("pointCut()")
public void before(JoinPoint joinPoint) {
System.out.println("我是前置通知!!!!");
String typeName =
joinPoint.getSignature().getDeclaringTypeName();
String methodName = joinPoint.getSignature().getName();
Object[] objs = joinPoint.getArgs();
Object target = joinPoint.getTarget();
System.out.println("方法执行的全路径为:"+typeName+"."+methodName);
System.out.println("获取方法参数:"+objs);
System.out.println("获取目标对象:"+target);
}
//添加环绕通知 可以控制目标方法执行 要求添加参数
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) {
System.out.println("我是环绕通知开始");
try {
//Object result = joinPoint.proceed();
System.out.println("我是环绕通知结束");
return null;
} catch (Throwable e) {
e.printStackTrace();
throw new RuntimeException(e);
} //指定目标方法
}
}
@Target(ElementType.METHOD) //标识注解 对谁生效
@Retention(RetentionPolicy.RUNTIME) //注解使用的有效期
public @interface CacheFind {
public String key(); //标识存入redis的key的前缀
public int seconds() default 0; //标识保存的时间 单位是秒
}
package com.jt.aop;
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.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.jt.anno.CacheFind;
import com.jt.util.ObjectMapperUtil;
import redis.clients.jedis.Jedis;
//1.将对象交给容器管理
@Component
//2.定义aop切面
@Aspect
public class CacheAOP {
@Autowired(required = false)
private Jedis jedis;
/**
* 实现思路: 拦截被@CacheFind标识的方法 之后利用aop进行缓存的控制
* 通知方法: 环绕通知
* 实现步骤:
* 1.准备查询redis的key ITEM_CAT_LIST::第一个参数
* 2.@annotation(cacheFind) 动态获取注解的语法.
* 拦截指定注解类型的注解并且将注解对象当做参数进行传递.
*/
@SuppressWarnings("unchecked")
@Around("@annotation(cacheFind)")
public Object around(ProceedingJoinPoint joinPoint,CacheFind cacheFind) {
//1.获取用户注解中的key ITEM_CAT_LIST::0
String key = cacheFind.key();
//2.动态获取第一个参数当做key
String firstArg = joinPoint.getArgs()[0].toString();
key += "::"+firstArg;
Object result = null;
//3.根据key查询redis.
if(jedis.exists(key)) {
//根据redis获取数据信息
String json = jedis.get(key);
//如何获取返回值类型
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
result = ObjectMapperUtil.toObject(json, methodSignature.getReturnType());
System.out.println("aop查询redis缓存");
}else {
//如果key不存在,则证明是第一次查询. 应该查询数据库
try {
result = joinPoint.proceed(); //目标方法返回值
System.out.println("AOP查询数据库获取返回值结果");
//将数据保存到redis中
String json = ObjectMapperUtil.toJSON(result);
int seconds = cacheFind.seconds();
if(seconds>0)
jedis.setex(key, seconds, json);
else
jedis.set(key, json);
} catch (Throwable e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
return result;
}
//公式: 切面 = 切入点表达式 + 通知方法.
/**
* 业务需求: 要求拦截ItemCatServiceImpl类中的业务
* @Pointcut 切入点表达式 可以理解为就是一个if判断,只有满足条件,才能执行通知方法.
*/
/**
//@Pointcut("bean(itemCatServiceImpl)") //按类匹配,控制的粒度较粗 单个bean
//@Pointcut("within(com.jt.service..*)") //按类匹配,控制的粒度较粗 多个bean
@Pointcut("execution(* com.jt.service..*.*(..))") //细粒度的匹配方式
public void pointCut() {
}
//joinPoint 方法执行切恰好被切入点表达式匹配,该方法的执行就称之为连接点.
@Before("pointCut()")
public void before(JoinPoint joinPoint) {
System.out.println("我是前置通知!!!!");
String typeName =
joinPoint.getSignature().getDeclaringTypeName();
String methodName = joinPoint.getSignature().getName();
Object[] objs = joinPoint.getArgs();
Object target = joinPoint.getTarget();
System.out.println("方法执行的全路径为:"+typeName+"."+methodName);
System.out.println("获取方法参数:"+objs);
System.out.println("获取目标对象:"+target);
}
//添加环绕通知 可以控制目标方法执行 要求添加参数
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) {
System.out.println("我是环绕通知开始");
try {
//Object result = joinPoint.proceed();
System.out.println("我是环绕通知结束");
return null;
} catch (Throwable e) {
e.printStackTrace();
throw new RuntimeException(e);
} //指定目标方法
}
**/
}
在itemCatServiceImpl中添加注解。实现商品列表查询时的缓存处理。
独立完成关于redis缓存实现
拦截所有的业务层的方式 service 要求打印 sys目标方法类名/方法名称/参数相关信息.并且要求记录各个方法的执行时间 环绕通知
实现异常的记录 但凡service层报错,需要打印目标方法类名/方法名/异常信息
异常通知
记录业务层service层调用的返回值信息。 输出 类名/方法名/参数信息/返回值信息