1.AOP简介
AOP(Aspect Orient Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程(OOP)的一种补充和完善。实际项目中我们通常将面向对象理解为一个静态过程(例如一个系统有多少个模块,一个模块有哪些对象,对象有哪些属性),面向切面理解为一个动态过程(在对象运行时动态织入一些扩展功能或控制对象执行)。如图:
AOP 与 OOP 字面意思相近,但其实两者完全是面向不同领域的设计思想。实际项目中我们通常将面向对象理解为一个静态过程(例如一个系统有多少个模块,一个模块有哪些对象,对象有哪些属性),面向切面的运行期代理方式,理解为一个动态过程,可以在对象运行时动态织入一些扩展功能或控制对象执行。
2.AOP的实现原理
其中,为目标类型(XxxServiceImpl)创建其代理对象方式有两种(先了解):
第一种方式:借助JDK官方API为目标对象类型创建其兄弟类型对象,但是目标对象类型需要实现相应接口.我这个案例中controller没有实现接口所以代理对象的创建不是用JDK.
第二种方式:借助CGLIB库为目标对象类型创建其子类类型对象,但是目标对象类型不能使用final修饰.
3.AOP相关术语解析
切面(aspect): 横切面对象,一般为一个具体类对象。
切入点(pointcut):定义了切入扩展业务逻辑的位置(哪些方法运行时切入扩展业务),一般会通过表达式进行相关定义,一个切面中可以定义多个切入点。
通知(Advice): 内部封装扩展业务逻辑的具体方法对象,一个切面中可以有多个通知(在切面的某个特定位置上执行的动作(扩展功能)。
连接点(joinpoint):程序执行过程中,封装了某个正在执行的目标方法信息的对象,可以通过此对象获取具体的目标方法信息,甚至去调用目标方法。连接点与切入点定义如图所示:
说明:我们可以简单的将机场的一个安检口理解为连接点,多个安检口为切入点,安全检查过程看成是 通知。总之,概念很晦涩难懂,多做例子,做完就会清晰。先可以按白话去理解。
4.AOP入门案例实践
4.1pom文件中添加依赖
说明:AOP的切入点的选择可以是任意的--AOP--controller--AOP--service--AOP--dao等,我这里选择在controller和service之间.
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
我们选择用启动类来启动demo所以要添加springBoot的starter依赖,然后添加aop的依赖
4.2创建demo的启动类
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
4.3创建controller
@RestController
@RequestMapping("/demo/")
public class DemoController {
@RequiredLog//由此注解描述的方法就是切入点方法
@GetMapping
public String doSayHello(){
System.out.println("====doSayHello====");
return "hello spring";
}
}
4.4创建注解类型,应用于切入点表达式的定义
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequiredLog {
String value() default "";
}
注解@Retention可以用来修饰注解,是注解的注解,称为元注解。
Retention注解有一个属性value,是RetentionPolicy类型的,Enum RetentionPolicy是一个枚举类型,
这个枚举决定了Retention注解应该如何去保持,也可理解为Rentention 搭配 RententionPolicy使用。RetentionPolicy有3个值:CLASS RUNTIME SOURCE
按生命周期来划分可分为3类:
1、RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;
2、RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;
3、RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
这3个生命周期分别对应于:Java源文件(.java文件) ---> .class文件 ---> 内存中的字节码。
@Target:注解的作用目标
@Target(ElementType.TYPE)——接口、类、枚举、注解
@Target(ElementType.FIELD)——字段、枚举的常量
@Target(ElementType.METHOD)——方法
@Target(ElementType.PARAMETER)——方法参数
@Target(ElementType.CONSTRUCTOR) ——构造函数
@Target(ElementType.LOCAL_VARIABLE)——局部变量
@Target(ElementType.ANNOTATION_TYPE)——注解
@Target(ElementType.PACKAGE)——包
4.5创建切面对象,用于做日志业务增强
/**
* @Aspect 注解描述的类我们称之为切面对象,此对象负责定义切入点和通知
* 1)切入点(在哪些方法执行时我们进行功能扩展-锦)
* 2)通知(所有的扩展逻辑都会写到通知方法中-花)
*/
@Aspect
@Component
public class LogAspect {
/**
* 通过@Pointcut定义切入点,但是切入点的定义需要有一个表达式,表达式的写法有多种,
* 比较常用 有注解方式的表达式(@annotation(你自己定义的注解)).当使用自己写的注解
* 对方法进行描述时,这个方法就是切入点方法,在这个方法上要锦上添花
*/
@Pointcut("@annotation(com.jt.aop.RequiredLog)")
public void doLog(){}//这个方法没有意义,主要用于承载切入点
/**
* Around 通知,在此通知内部可以定义我们扩展业务逻辑,还可以调用目标执行链
* @param joinPoint 连接点(通知方法与目标方法的连接点对象)
* ProceedingJoinPoint
* @return
* @throws Throwable
*/
@Around("doLog()")
public Object doAround(ProceedingJoinPoint joinPoint)
throws Throwable {//通过ProceedingJoinPoint可以找到doSayHello方法
System.out.println("Before:"+System.currentTimeMillis());
Object result = joinPoint.proceed();//执行链(其他切面,目标对象方法)
System.out.println("After:"+System.currentTimeMillis());
return result;
}
}
4.6测试结果
4.7通知业务方法的原理