目录
1. 定义: AOP(Aspect Orient Programming)
2. aop+redis实现缓存=> springboot整合的@EnableCaching
2.1 查询商品列表 查询后存入redis 下次看缓存中是否存在
1. 定义: AOP(Aspect Orient Programming)
是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程(OOP)的一种补充和完善。实际项目中我们通常将面向对象理解为一个静态过程(例如一个系统有多少个模块,一个模块有哪些对象,对象有哪些属性),面向切面理解为一个动态过程(在对象运行时动态织入一些扩展功能或控制对象执行)
AOP面向切面的运行期代理方式,理解为一个动态过程,可以在对象运行时动态织入一些扩展功能或控制对象执行。
2. 实现原理: 代理(CGLIB代理和JDK代理)
AOP可以在系统启动时为目标类型创建子类或兄弟类型对象,这样的对象我们通常会称之为动态代理对象.
其中,为目标类型(XxxServiceImpl)创建其代理对象方式有两种(先了解):
第一种方式:借助JDK官方API为目标对象类型创建其兄弟类型对象,但是目标对象类型需要实现相应接口.
第二种方式:借助CGLIB库为目标对象类型创建其子类类型对象,但是目标对象类型不能使用final修饰.
3. 相关术语:
切面(aspect): 横切面对象,一般为一个具体类对象。
切入点(pointcut):定义了切入扩展业务逻辑的位置(哪些方法运行时切入扩展业务),一般会通过表达式进行相关定义,一个切面中可以定义多个切入点。
通知(Advice): 内部封装扩展业务逻辑的具体方法对象,一个切面中可以有多个通知(在切面的某个特定位置上执行的动作(扩展功能)。
连接点(joinpoint):程序执行过程中,封装了某个正在执行的目标方法信息的对象,可以通过此对象获取具体的目标方法信息,甚至去调用目标方法。连接点与切入点定义如图所示:
4. 实战 (执行一个方法后查看执行时间)
随便写一个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)
* 有一百个接口 难道我要写一百次这个方法吗 当然是不能的
* 根据封装思想 把公共的地方抽出去
* <p>
* 这个时候 继承就可以实现了 但是java是单继承
* 而这个业务 是重要 但是不能影响其他业务, 也就是锦上添花的业务(暂时这么理解)
* <p>
* 使用新技术 aop 面向切面编程
* 定义: 切面编程 切面是一个类 切入点是一个方法 切面类里面有一个切入点方法 切入点方法里面有业务逻辑
* <p>
* 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 返回一个字符串)
5. 自定义注解
创建注解类型,应用于切入点表达式的定义
详细解释在代码中 基本每行我都写注释了
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 "";
}
6. 导入依赖 Aop
spring-boot-starter-aop 跟springboot统一版本一致
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
7. 创建切面对象 (创建切面类)
里面的反射可以先不看, 但是只要使用注解内的对象类型 就需要反射
(例如次数 过期秒数 熔断策略等)
详细解释在代码中 基本每行我都写注释了
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;
}
}
}
8. 按照最上面4方法里的controller 进行测试
---------> test01
http://localhost:9000/test0101/test01
-----------> test02
http://localhost:9000/test0101/test02
===> 结束了 最最简单的aop入门 around方法
**** 思维拓展: ****
两个拓展思维
1. 写一个接口限制(单体项目的熔断策略)
需求: (60秒内 一个ip 可以访问10次 , 超过返回error 提示: 稍后重试!!!)aop配合redis
2. aop+redis实现缓存=> springboot整合的@EnableCaching
2.1 查询商品列表 查询后存入redis 下次看缓存中是否存在
-> 存在显示
-> 不存在插库然后存redis 循环
2.2 列表更新修改 缓存也需要修改 如何实现!!!
pzy 20220814