1).业务代码与Redis缓存服务,紧紧的耦合在一起. 不方便后期维护.
2).如果采用下列代码的形式添加缓存机制,则不方便.每次使用缓存时,都需要按照缓存的机制重新执行业务代码. 显得特别的繁琐.
3).利用AOP的形式实现该操作.!!
/**
* 数据的来源: 数据库中
* 数据库中的数据类型: ItemCat对象信息 POJO
* 需要的类型: EasyUITree对象信息. VO对象
* 思路: 将ItemCat转化为EasyUITree对象.
* sql: parent_id=0 SELECT * FROM tb_item_cat WHERE parent_id=0
*/
@Override
public List<EasyUITree> findItemCatByParentId(Long parentId) {
//1.根据parentId 查询数据库信息 根据父级查询子级信息.
QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("parent_id", parentId);
List<ItemCat> itemCatList = itemCatMapper.selectList(queryWrapper);
//2.将数据库记录转化为VO数据.
List<EasyUITree> treeList = new ArrayList<EasyUITree>();
for (ItemCat itemCat : itemCatList) {
Long id = itemCat.getId();
String text = itemCat.getName();
//如果是父级标题,则默认关闭 closed,否则开启. open
String state = itemCat.getIsParent()?"closed":"open";
EasyUITree uiTree = new EasyUITree(id, text, state);
treeList.add(uiTree);
}
return treeList;
}
/**
* 思路:
* 1.定义查询redis的key, key要求唯一的
* 2.第一次查询先查询redis.
* 没有数据: 表示缓存中没有数据, 查询数据库,之后将数据保存到redis中
* 有数据: 证明缓存中有值, 直接返回给用户即可.
*/
@SuppressWarnings("unchecked")
@Override
public List<EasyUITree> findItemCatCache(Long parentId) {
//定义key值
String key = "ITEM_CAT_LIST::" + parentId;
List<EasyUITree> treeList = new ArrayList<EasyUITree>();
//判断redis缓存中是否有值
if (jedis.exists(key)) {
//表明有值时,直接将数据返回到页面
String json = jedis.get(key);
//将json转化为对象,返回到页面上
treeList = ObjectMapperUtil.toObject(json, treeList.getClass());
System.out.println("redis缓存查询");
}else {
//表明没有值,先查询数据库
treeList = findItemCatByParentId(parentId);
//将对象转化为json
String json = ObjectMapperUtil.toJson(treeList);
//将json保存到redis缓存中
jedis.set(key, json);
System.out.println("查询数据库");
}
return treeList;
}
AOP: 对原有的方法进行扩展,可以将重复的事情,但是右不得不做的事情,放到AOP中执行.可以减少代码的耦合性.降 低维护的成本
公式:AOP = 切入点表达式 + 通知方法
Spring中通过切入点表达式定义具体切入点,其常用AOP切入点表达式定义及说明:
bean表达式(重点)
bean表达式一般应用于类级别,实现粗粒度的切入点定义,案例分析:
bean(“userServiceImpl”)指定一个userServiceImpl类中所有方法。
bean("*ServiceImpl")指定所有后缀为ServiceImpl的类中所有方法。
说明:bean表达式内部的对象是由spring容器管理的一个bean对象,表达式内部的名字应该是spring容器中某个bean的name。
within表达式(了解)
within表达式应用于类级别,实现粗粒度的切入点表达式定义,案例分析:
within(“aop.service.UserServiceImpl”)指定当前包中这个类内部的所有方法。
within(“aop.service.") 指定当前目录下的所有类的所有方法。
within("aop.service…”) 指定当前目录以及子目录中类的所有方法。
execution表达式(了解)
execution表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析:
语法:execution(返回值类型 包名.类名.方法名(参数列表))。
execution(void aop.service.UserServiceImpl.addUser())匹配addUser方法。
execution(void aop.service.PersonServiceImpl.addUser(String)) 方法参数必须为String的addUser方法。
execution(* aop.service….(…)) 万能配置。
@annotation表达式(重点)
@annotaion表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析
@annotation(anno.RequiredLog) 匹配有此注解描述的方法。
@annotation(anno.RequiredCache) 匹配有此注解描述的方法。
其中:RequiredLog为我们自己定义的注解,当我们使用@RequiredLog注解修饰业务层方法时,系统底层会在执行此方法时进行扩展操作。
在基于Spring AOP编程的过程中,基于AspectJ框架标准,spring中定义了五种类型的通知(通知描述的是一种扩展业务),它们分别是:
@Before
@AfterReturning
@AfterThrowing
@After
@Around.重点掌握(优先级最高)
AOP 相关术语分析
切面(aspect): 横切面对象,一般为一个具体类对象(可以借助@Aspect声明)。
通知(Advice):在切面的某个特定连接点上执行的动作(扩展功能),例如around,before,after等。
连接点(joinpoint):程序执行过程中某个特定的点,一般指被拦截到的的方法。
切入点(pointcut):对多个连接点(Joinpoint)一种定义,一般可以理解为多个连接点的集合。
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.springframework.stereotype.Component;
//1.将对象交给spring容器管理
@Component
//2.定义一个切面
@Aspect
public class CacheAop {
//切入点表达值
@Pointcut("bean(itemCatServiceImpl)") //按类匹配
public void pointCut() {
}
//通知方法
/**
* 记录程序的执行状态 获取哪个类,哪个方法执行的
*/
@Before("pointCut()")
public void before( JoinPoint joinPoint) {
System.out.println("我是前置通知");
//获取方法的所在的包名和类名
String typeName = joinPoint.getSignature().getDeclaringTypeName();
String methodName = joinPoint.getSignature().getName();//获取方法的方法名
Object[] args = joinPoint.getArgs();//获取方法的参数
Object target = joinPoint.getTarget();//获取目标对象
System.out.println("方法执行的全路径:" + typeName + "." + methodName);
System.out.println("方法的参数:" + args);
System.out.println("目标对象:" + target);
}
//添加环绕通知 可以控制目标方法执行
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) {
System.out.println("我是环绕通知的开始");
try {
Object result = joinPoint.proceed();
System.out.println("我是环绕通知的结束");
return result;
} catch (Throwable e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Retention(RUNTIME) //表示运行期有效 注解使用的声明周期
@Target(METHOD)//表示注解在方法上有效
public @interface CacheFind {
public String key(); //表示存入redis的key的前缀
public int seconds() default 0; //表示保存的时间单位是秒
}
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.jt.annotation.CacheFind;
import com.jt.util.ObjectMapperUtil;
import redis.clients.jedis.Jedis;
@Component
@Aspect
public class CacheRedis {
@Autowired
private Jedis jedis;
/**
* 实现思路: 拦截@CacheFind标识的方法之后利用AOP进行缓存的控制
* 通知方法:环绕通知
* 实现步骤:
* 1.准备redis查询的key ITEM_CAT_PARENTID
* 2.@annotation(cacheFind)动态获取注解的语法,拦截指定注解类型的注解并且将注解对象
* 当做参数进行传递
*/
@SuppressWarnings("unchecked")
@Around("@annotation(cacheFind)")
public Object around(ProceedingJoinPoint joinPoint,CacheFind cacheFind) {
//1.获取用户注解中的key
String key = cacheFind.key();
//2.动态获取第一个参数当做key
String args1 = joinPoint.getArgs()[0].toString();
key += args1;
//3.根据key查询redis
Object result = null;
if (jedis.exists(key)) {
//如果缓存中有数据
//获取redis中的数据
String json = jedis.get(key);
//动态获取目标方法的返回值类型
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Class returnType = methodSignature.getReturnType();
//将json字符串转化为对象
result = ObjectMapperUtil.toObject(json, returnType);
System.out.println("查询AOP缓存");
}else {
try {
//如果key不存在,则需要执行目标方法查询数据库
result = joinPoint.proceed();
//将查询到的数据转化为json字符串
String json = ObjectMapperUtil.toJson(result);
System.out.println("AOP执行数据库查询");
//将json保存到redis中
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();
}
}
return result;
}
}