目录
1. 定义: AOP(Aspect Orient Programming)
2. aop+redis实现缓存=> springboot整合的@EnableCaching
2.1 查询商品列表 查询后存入redis 下次看缓存中是否存在
是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程(OOP)的一种补充和完善。实际项目中我们通常将面向对象理解为一个静态过程(例如一个系统有多少个模块,一个模块有哪些对象,对象有哪些属性),面向切面理解为一个动态过程(在对象运行时动态织入一些扩展功能或控制对象执行)
AOP面向切面的运行期代理方式,理解为一个动态过程,可以在对象运行时动态织入一些扩展功能或控制对象执行。
AOP可以在系统启动时为目标类型创建子类或兄弟类型对象,这样的对象我们通常会称之为动态代理对象.
其中,为目标类型(XxxServiceImpl)创建其代理对象方式有两种(先了解):
第一种方式:借助JDK官方API为目标对象类型创建其兄弟类型对象,但是目标对象类型需要实现相应接口.
第二种方式:借助CGLIB库为目标对象类型创建其子类类型对象,但是目标对象类型不能使用final修饰.
- 切面(aspect): 横切面对象,一般为一个具体类对象。
- 切入点(pointcut):定义了切入扩展业务逻辑的位置(哪些方法运行时切入扩展业务),一般会通过表达式进行相关定义,一个切面中可以定义多个切入点。
- 通知(Advice): 内部封装扩展业务逻辑的具体方法对象,一个切面中可以有多个通知(在切面的某个特定位置上执行的动作(扩展功能)。
- 连接点(joinpoint):程序执行过程中,封装了某个正在执行的目标方法信息的对象,可以通过此对象获取具体的目标方法信息,甚至去调用目标方法。连接点与切入点定义如图所示:
随便写一个controller 写两个不同的方法 区分是否使用aop
详细解释在代码中 基本每行我都写注释了
- package cn.pingzhuyan.testAop.controller;
-
- import cn.pingzhuyan.testAop.annotation.TimeCount;
- import cn.pingzhuyan.testAop.aspect.TimeCountLogAspect;
- import lombok.SneakyThrows;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- //import org.springframework.cloud.context.config.annotation.RefreshScope;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RestController;
-
- /**
- * 简易aop 模拟
- * 每个接口 我现在都需要 请求时间(ms)
- * 有一百个接口 难道我要写一百次这个方法吗 当然是不能的
- * 根据封装思想 把公共的地方抽出去
- *
- * 这个时候 继承就可以实现了 但是java是单继承
- * 而这个业务 是重要 但是不能影响其他业务, 也就是锦上添花的业务(暂时这么理解)
- *
- * 使用新技术 aop 面向切面编程
- * 定义: 切面编程 切面是一个类 切入点是一个方法 切面类里面有一个切入点方法 切入点方法里面有业务逻辑
- *
- * around() 最常用(优先级最高) 讲的也是这种 //@Order(2) 注解代表执行优先级 可以自定义一下
- * before()
- * after()
- * afterReturning()
- * afterThrowing()
- * afterFinally()
- *
- * @Author pzy
- * @Version 0.1.0
- * @CreateTime 2022/08/14
- */
-
- //@RefreshScope
- @RestController
- @RequestMapping("/test0101")
- public class AopTestController {
- private static final Logger log = LoggerFactory.getLogger(TimeCountLogAspect.class);
-
- /**
- * 不加aop (java代码实现)
- *
- * @param
- * @return
- */
- // @SneakyThrows
- @GetMapping("/test01")
- public String test01() throws InterruptedException {
- long startTime = System.currentTimeMillis();
- // log.debug("不加aop的测试->{}","true");
- System.out.println("开始执行~~~");
- //模拟耗时操作
- Thread.sleep(3000);
-
- long endTime = System.currentTimeMillis();
- System.out.println("不加aop的测试--->" + (endTime - startTime) + "ms");
-
- System.out.println("程序结束~~~");
- return "不加aop的测试!!!";
- }
-
- /**
- * 加aop(注解实现)
- *
- * @param
- * @return
- */
- @SneakyThrows
- @TimeCount(value = "pzy随意写的 看到这个值就行", operation = "加aop的测试 看到我是在哪写的")
- @GetMapping("/test02")
- public String test02() {
-
- Thread.sleep(3000);
-
- return "加aop的测试!!!";
- }
-
-
- }
注意: 里面爆红的地方 (先注释掉, 写最简淡的controller 返回一个字符串)
创建注解类型,应用于切入点表达式的定义
详细解释在代码中 基本每行我都写注释了
- package cn.pingzhuyan.testAop.annotation;
-
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
-
- /**
- * @Author pzy
- * @Version 0.1.0
- * @CreateTime 2022/08/14
- * 自定义注解 时间输出(简单)
- */
- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.METHOD)
- public @interface TimeCount {
- /**
- * 操作信息
- * @return
- */
- String operation();
-
- /**
- * value 默认值 可以不写
- * @return
- */
- String value() default "";
-
- }
spring-boot-starter-aop 跟springboot统一版本一致
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-aopartifactId>
- dependency>
里面的反射可以先不看, 但是只要使用注解内的对象类型 就需要反射
(例如次数 过期秒数 熔断策略等)
详细解释在代码中 基本每行我都写注释了
- package cn.pingzhuyan.testAop.aspect;
-
- import cn.pingzhuyan.testAop.annotation.TimeCount;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.Around;
- import org.aspectj.lang.annotation.Aspect;
- import org.aspectj.lang.annotation.Pointcut;
- import org.aspectj.lang.reflect.MethodSignature;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.springframework.stereotype.Component;
-
- import java.lang.reflect.Method;
-
- /**
- * @Author pzy
- * @Description: TODO
- * @Version 0.1.0
- * @CreateTime 2022/08/14
- */
- @Aspect //切面类 就是声明我是一个切面
- @Component //交给 spring bean容器管理 看看我之前总结的注解 没了我博客都有
- public class TimeCountLogAspect {
-
- //这个等效于 @Slf4j -> 写了这个 不用写 @Slf4j 知道即可 设计模式: 简单工厂模式
- private static final Logger log = LoggerFactory.getLogger(TimeCountLogAspect.class);
-
- /**
- * @Pointcut注解用于定义切入点
- * @annotation(注解)为切入点表达式,后续由此注解描述的方法为切入 点方法
- */
- @Pointcut("@annotation(cn.pingzhuyan.testAop.annotation.TimeCount)")
- public void doLog() {
- }//此方法只负责承载切入点的定义
-
- /**
- * @param jp 连接点对象,此对象封装了要执行的目标方法信息.
- * 可以通过连接点对象调用目标方法.
- * @return 目标方法的执行结果
- * @throws Throwable
- * @Around注解描述的方法,可以在切入点执行之前和之后进行业务拓展,
- */
- @Around("doLog()")
- public Object doAround(ProceedingJoinPoint jp) throws Throwable {
- log.error("========= 区分主动pzy(警示非异常) ========");
-
- long t1 = System.currentTimeMillis();
- System.out.println("aop程序开始~~~" + t1);
-
- /*反射获取目标方法的参数 可以不会 之前讲过反射和暴力反射 需要操作类或者方法时使用*/
- //获取目标方法的类对象
- Class> targetCls = jp.getTarget().getClass();
- //获取目标方法的反射对象
- MethodSignature ms = (MethodSignature) jp.getSignature();
- //获取目标方法
- Method targetMethod = targetCls.getMethod(ms.getName(), ms.getParameterTypes());
- //获取目标方法的注解
- TimeCount annotation = targetMethod.getAnnotation(TimeCount.class);
-
- String operation = annotation.operation();
- String value = annotation.value();
- try {
- //执行目标方法(切点方法中的某个方法)
- Object result = jp.proceed();
- long t2 = System.currentTimeMillis();
- log.info("opertime:{}", t2 - t1);
-
- System.out.println("正确执行 -> " + operation + ":" + value + ":" + (t2 - t1));
-
-
- System.out.println("aop程序执行结束~~~" + t2);
- return result;//目标业务方法的执行结果
- } catch (Throwable e) {
- e.printStackTrace();
- long t2 = System.currentTimeMillis();
- // log.info("exception:{}", e.getMessage());
- System.out.println("错误执行 -> " + operation + ":" + value + ":" + (t2 - t1));
- throw e;
- }
- }
- }
http://localhost:9000/test0101/test01

http://localhost:9000/test0101/test02

需求: (60秒内 一个ip 可以访问10次 , 超过返回error 提示: 稍后重试!!!)aop配合redis
-> 存在显示
-> 不存在插库然后存redis 循环
pzy 20220814