1. AOP实现Redis缓存
1.1 如何理解AOP
名称: 面向切面编程
作用: 降低系统中代码的耦合性,并且在不改变原有代码的条件下对原有的方法进行功能的扩展.
公式: AOP = 切入点表达式 + 通知方法
1.2 通知类型
1.前置通知 目标方法执行之前执行
2.后置通知 目标方法执行之后执行
3.异常通知 目标方法执行过程中抛出异常时执行
4.最终通知 无论什么时候都要执行的通知
特点: 上述的四大通知类型 不能干预目标方法是否执行.一般用来做程序运行状态的记录.监控
5.环绕通知 在目标方法执行前后都要执行的通知方法 该方法可以控制目标方法是否运行.joinPoint.proceed(); 功能作为强大的.
1.3 切入点表达式
理解: 切入点表达式就是一个程序是否进入通知的一个判断(IF)
作用: 当程序运行过程中 ,**满足了切入点表达式时才会去执行通知方法,**实现业务的扩展.
种类(写法):
-
bean(bean的名称 bean的ID) 只能拦截具体的某个bean对象 只能匹配一个对象
lg: bean(“itemServiceImpl”) -
within(包名.类名) within(“com.jt.service.*”) 可以匹配多个对象
粗粒度的匹配原则 按类匹配 -
execution(返回值类型 包名.类名.方法名(参数列表)) 最为强大的用法
lg : execution(* com.jt.service….(…))
返回值类型任意 com.jt.service包下的所有的类的所有的方法都会被拦截. -
@annotation(包名.注解名称) 按照注解匹配.
1.4 AOP入门案例
package com.jt.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import java.util.Arrays;
@Aspect //我是一个AOP切面类
@Component //将类交给spring容器管理
public class CacheAOP {
//公式 = 切入点表达式 + 通知方法
/**
* 关于切入点表达式的使用说明
* 粗粒度:
* 1.bean(bean的Id) 一个类
* 2.within(包名.类名) 多个类
* 细粒度
*/
//@Pointcut("bean(itemCatServiceImpl)")
//@Pointcut("within(com.jt.service..*)") //匹配多级目录
@Pointcut("execution(* com.jt.service..*.*(..))") //方法参数级别
public void pointCut(){
//定义切入点表达式 只为了占位
}
//区别: pointCut() 表示切入点表达式的引用 适用于多个通知 共用切入点的情况
// @Before("bean(itemCatServiceImpl)") 适用于单个通知.不需要复用的
// 定义前置通知,与切入点表达式进行绑定. 注意绑定的是方法
/**
* 需求:获取目标对象的相关信息.
* 1.获取目标方法的路径 包名.类名.方法名
* 2.获取目标方法的类型 class
* 3.获取传递的参数
* 4.记录当前的执行时间
*/
@Before("pointCut()")
//@Before("bean(itemCatServiceImpl)")
public void before(JoinPoint joinPoint){
String className = joinPoint.getSignature().getDeclaringTypeName();
String methodName = joinPoint.getSignature().getName();
Class targetClass = joinPoint.getTarget().getClass();
Object[] args = joinPoint.getArgs();
Long runTime = System.currentTimeMillis();
System.out.println("方法路径:" +className+"."+methodName);
System.out.println("目标对象类型:" + targetClass);
System.out.println("参数:" + Arrays.toString(args));
System.out.println("执行时间:" + runTime+"毫秒");
}
/* @AfterReturning("pointCut()")
public void afterReturn(){
System.out.println("我是后置通知");
}
@After("pointCut()")
public void after(){
System.out.println("我是最终通知");
}*/
/**
* 环绕通知说明
* 注意事项:
* 1.环绕通知中必须添加参数ProceedingJoinPoint
* 2.ProceedingJoinPoint只能环绕通知使用
* 3.ProceedingJoinPoint如果当做参数 则必须位于参数的第一位
*/
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint){
System.out.println("环绕通知开始!!!");
Object result = null;
try {
result = joinPoint.proceed(); //执行下一个通知或者目标方法
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("环绕通知结束");
return result;
}
}
2 关于AOP实现Redis缓存
2.1 自定义缓存注解
问题: 如何控制 哪些方法需要使用缓存? cacheFind()
解决方案: 采用自定义注解的形式 进行定义,如果 方法执行需要使用缓存,则标识注解即可.
关于注解的说明:
1.注解名称 : cacheFind
2.属性参数 :
2.1 key: 应该由用户自己手动添加 一般添加业务名称 之后动态拼接形成唯一的key
2.2 seconds: 用户可以指定数据的超时的时间
@Target(ElementType.METHOD) //注解对方法有效
@Retention(RetentionPolicy.RUNTIME) //运行期有效
public @interface CacheFind {
public String preKey(); //用户标识key的前缀.
public int seconds() default 0; //如果用户不写表示不需要超时. 如果写了以用户为准.
}
2.2 编辑CacheAOP
package com.jt.aop;
import com.jt.anno.CacheFind;
import com.jt.config.JedisConfig;
import com.jt.util.ObjectMapperUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import java.lang.reflect.Method;
import java.util.Arrays;
@Aspect //我是一个AOP切面类
@Component //将类交给spring容器管理
public class CacheAOP {
@Autowired
private Jedis jedis;
/**
* 切面 = 切入点 + 通知方法
* 注解相关 + 环绕通知 控制目标方法是否执行
*
* 难点:
* 1.如何获取注解对象
* 2.动态生成key prekey + 用户参数数组
* 3.如何获取方法的返回值类型
*/
@Around("@annotation(cacheFind)")
public Object around(ProceedingJoinPoint joinPoint,CacheFind cacheFind){
Object result = null;
try {
//1.拼接redis存储数据的key
Object[] args = joinPoint.getArgs();
String key = cacheFind.preKey() +"::" + Arrays.toString(args);
//2. 查询redis 之后判断是否有数据
if(jedis.exists(key)){
//redis中有记录,无需执行目标方法
String json = jedis.get(key);
//动态获取方法的返回值类型 向上造型 向下造型
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Class returnType = methodSignature.getReturnType();
result = ObjectMapperUtil.toObj(json,returnType);
System.out.println("AOP查询redis缓存");
}else{
//表示数据不存在,需要查询数据库
result = joinPoint.proceed(); //执行目标方法及通知
//将查询的结果保存到redis中去
String json = ObjectMapperUtil.toJSON(result);
//判断数据是否需要超时时间
if(cacheFind.seconds()>0){
jedis.setex(key,cacheFind.seconds(),json);
}else {
jedis.set(key, json);
}
System.out.println("aop执行目标方法查询数据库");
}
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return result;
}
}