详解Spring AOP
前言👀~
上一章我们介绍了SpringBoot统一功能相关的知识点,今天来讲解Spring框架另外一个核心AOP,细!!!
什么是AOP?
什么是面向切面编程呢?
什么是面向特定方法编程呢?
什么是Spring AOP?
AOP具体实现
Spring AOP 详解
AOP概念
1.切点(Pointcut)
2.连接点(Join Point)
3.通知(Advice)
4.切面(Aspect)
AOP通知类型
切点 @PointCut
切面优先级 @Order
切点表达式
execution表达式
@annotation
Spring AOP 原理
代理模式
1.静态代理
2.动态代理
1. JDK动态代理
2. CGLIB动态代理(第三方)
Spring AOP 剖析
如果各位对文章的内容感兴趣的话,请点点小赞,关注一手不迷路,讲解的内容我会搭配我的理解用我自己的话去解释如果有什么问题的话,欢迎各位评论纠正 🤞🤞🤞
个人主页:N_0050-CSDN博客
相关专栏:java SE_N_0050的博客-CSDN博客 java数据结构_N_0050的博客-CSDN博客 软件测试_N_0050的博客-CSDN博客 MySQL_N_0050的博客-CSDN博客 java EE_N_0050的博客-CSDN博客
什么是AOP?
Aspect Oriented Programming(面向切面编程),OOP是面向对象编程(开发的时候把一个个都抽象成对象来开发),两者的维度不同,处理的事情不同
什么是面向切面编程呢?
切⾯就是指某⼀类特定问题, 所以AOP也可以理解为⾯向特定⽅法编程。面向切面编程就是针对某一类特定问题统一进行编程
例子:比如people类有自己的属性和行为,但是有小一部分人生病要去医院看病,看病这个业务逻辑就不属于哪一个类,因为people泛指所有人,所有人不会都看病。AOP就是把医院看病这一个业务逻辑功能抽取出来,然后动态把这个功能切入到需要的方法(或行为)中,需要的才切入,这样便于减少系统的重复代码,降低模块间的耦合度
什么是面向特定方法编程呢?
⽐如上个章节的"登录校验",就是⼀类特定问题。前面提到的登录校验拦截器就是对"登录校验"这类问题统一的处理。所以,拦截器也是AOP的⼀种应用。AOP是⼀种思想,拦截器是AOP思想的⼀种实现。Spring框架实现了这种思想,提供了拦截器技术的相关接口。简单来说: AOP是⼀种思想,就是对某⼀类事情的集中处理,或说统一处理
小结:AOP和IoC一样也是一种思想。都有具体的实现,例如IoC的具体实现就是存和取也就是五大注解和方法注解以及依赖注入。例如AOP的具体实现前面讲的拦截器和统一数据返回格式以及统一异常处理就是AOP的具体实现
什么是Spring AOP?
AOP是⼀种思想,它的实现⽅法有很多,有Spring AOP,也有AspectJ、CGLIB等。Spring AOP是其中的⼀种实现⽅式。Spring对AOP进行了实现,并且提供了一些API就是Spring AOP
前面学会了统⼀功能之后,是不是就学会了Spring AOP呢, 当然不是。拦截器作⽤的维度是URL。@ControllerAdvice 应⽤场景主要是全局异常处理,数据绑定, 数据预处理。前面的学的拦截器和数据返回格式和统一异常是Spring针对常见的场景提供统一的功能,对于一些更定制化的信息,Spring也给我们提供了一些API让我们自己写。AOP作用的维度更加细致(可以根据包、类、⽅法名、参数等进⾏拦截),能够实现更加复杂的业务逻辑
引出问题:
现在有⼀些业务的执行效率⽐较低,耗时较长,我们需要对接口进行优化。第⼀步就需要定位出执行耗时⽐较长的业务⽅法,再针对该业务⽅法来进行优化
如何定位呢? 我们就需要统计当前项⽬中每⼀个业务方法的执行耗时。就是统一每一个方法的执行时间是多少
如何统计呢? 可以在业务方法运行前和运行后,记录下方法的开始时间和结束时间,两者之差就是这个方法的耗时。就是在记录执行方法前和后的时间,两者一减就是这个方法执行的时间。但是呢如果接口有很多我们每一个都要这样去统计那太麻烦了。所以我们需要利用AOP的思想去解决
AOP具体实现
AOP就可以做到在不改动这些原始方法的基础上,针对特定的方法进行功能的增强。AOP的作用:在程序运⾏期间在不修改源代码的基础上对已有方法进行增强(无侵⼊性 解耦)
利用AOP的思想解决上述的问题,代码如下,这里面的一些注解下面会进行解释。首先引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
@Slf4j
//标记为切面类
@Aspect
@Component
public class TimeAspect {
//表示AOP在 哪个环节起作用 在哪个方法起作用
@Around("execution(* com.example.bookdemo.controller.*.*(..))")
public Object timeCost(ProceedingJoinPoint joinPoint) throws Throwable {
//目标方法执行前的逻辑
long start = System.currentTimeMillis();
log.info("方法执行前");
//joinPoint表示目标方法 调用这个方法就是执行目标方法
Object result = joinPoint.proceed();
//目标方法执行后的逻辑
long end = System.currentTimeMillis();
log.info("方法执行后");
log.info(joinPoint + "消耗时间" + (end - start) + "ms");
return result;
}
}
输出结果,注意这里可以对所以的接口都进行测试,这里就简单对两个接口进行测试
@Aspect:这个注解标识这是⼀个切⾯类
@Around:这个注解是环绕通知,在⽬标⽅法的前后都会被执行。后⾯的表达式表示对哪些⽅法进行增强。里面填写的内容就是AOP在哪个环节起作用对哪些方法起作用
ProceedingJoinPoint:目标方法
通过上⾯的程序,我们也可以感受到AOP面向切面编程的⼀些优势:
• 1.代码无侵入:不修改原始的业务⽅法, 就可以对原始的业务⽅法进⾏了功能的增强或者是功能的改变
• 2.减少了重复代码
• 3.提⾼开发效率
• 4.维护⽅便
Spring AOP 详解
AOP概念
1.切点(Pointcut)
切点的作用就是提供⼀组规则,告诉程序对哪些方法来进行功能增强。切点就是⼀组规则,通过表达式来描述。就是上面@Around注解后面的表达式称为切点,也可以叫切点表达式。用于定义在哪些连接点执行通知
2.连接点(Join Point)
满足切点表达式规则的方法,就是连接点,也就是可以被AOP控制的⽅法。可以简单理解目标方法就是连接点,表达式中的controller.*.*,第一个*表示这个包下的所有controller,第二个*就是里面的所有方法,第二个*就可以看作是连接点
切点和连接点的关系:连接点是满足切点表达式的元素,切点可以看做是保存了众多连接点的⼀个集合。连接点是由切点来描述的,这样理解就是这些方法都在这个切点里面,前面说了切点可以看做是保存了众多连接点的⼀个集合
3.通知(Advice)
就是具体要做的⼯作,就是具体的逻辑,也就是共性功能。比如上面的timeCost方法中的代码就统一对controller中所有的方法统计执行时间,在AOP中我们把重复的代码逻辑抽取出来单独定义,这部分代码就是通知的内容
4.切面(Aspect)
切⾯(Aspect) = 切点(Pointcut) + 通知(Advice),可以把一个切面类中的一个个包含通知和切点的方法看作是一个个切面。大白话就好比老师通知班级里的所有学生下午要考试,连接点就是所有学生,下午考试就是一个通知,老师通知就是切点,三者加起来就是一个切面。切面所在的类, 我们⼀般称为切面类
AOP通知类型
@Around:环绕通知,此注解标注的通知方法在⽬标方法前, 后都被执行。就是可以在目标方法执行前后添加一些逻辑也是会被执行的,是在这个切面,后面说的都是切面
注意:
• @Around环绕通知需要调用 ProceedingJoinPoint.proceed() 来让原始方法执行,其他通知不需要考虑⽬标⽅法执⾏
• @Around必须要返回结果的,环绕通知方法的返回值,必须指定为Object,来接收原始⽅法的返回值,否则原始⽅法执行完毕,是获取不到返回值的
@Before:前置通知,此注解标注的通知方法在⽬标方法前被执行,就是在目标方法执行前添加一些逻辑会被执行
@After:后置通知,此注解标注的通知方法在⽬标方法后被执行,无论是否有异常都会执行
@AfterReturning:返回后通知,此注解标注的通知方法在⽬标方法后被执行,有异常不会执行
@AfterThrowing:异常后通知,此注解标注的通知方法发生异常后执行
下面进行演示,先演示接口响应正常,观察一下这些通知的执行顺序
@Slf4j
@Aspect
@Component
public class AspectDemo {
@Before("execution(* com.example.aopdemo2.demos.controller.*.*(..))")
public void doBefore() {
log.info("执行AspectDemo Before...");
}
@After("execution(* com.example.aopdemo2.demos.controller.*.*(..))")
public void doAfter() {
log.info("执行AspectDemo After...");
}
@AfterReturning("execution(* com.example.aopdemo2.demos.controller.*.*(..))")
public void doAfterReturning() {
log.info("执行AspectDemo AfterReturning...");
}
@AfterThrowing("execution(* com.example.aopdemo2.demos.controller.*.*(..))")
public void doAfterThrowing() {
log.info("执行AspectDemo AfterThrowing...");
}
@Around("execution(* com.example.aopdemo2.demos.controller.*.*(..))")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("执行AspectDemo Around之前...");
Object result = joinPoint.proceed();
log.info("执行AspectDemo Around之后...");
return result;
}
}
输出结果,先执行Around再执行before,再执行AfterReturning,先执行After再执行Around
接着演示接口响应异常
@Slf4j
@Aspect
@Component
public class AspectDemo {
@Before("execution(* com.example.aopdemo2.demos.controller.*.*(..))")
public void doBefore() {
log.info("执行AspectDemo Before...");
}
@After("execution(* com.example.aopdemo2.demos.controller.*.*(..))")
public void doAfter() {
log.info("执行AspectDemo After...");
}
@AfterReturning("execution(* com.example.aopdemo2.demos.controller.*.*(..))")
public void doAfterReturning() {
log.info("执行AspectDemo AfterReturning...");
}
@AfterThrowing("execution(* com.example.aopdemo2.demos.controller.*.*(..))")
public void doAfterThrowing() {
log.info("执行AspectDemo AfterThrowing...");
}
@Around("execution(* com.example.aopdemo2.demos.controller.*.*(..))")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("执行AspectDemo Around之前...");
Object result = joinPoint.proceed();
log.info("执行AspectDemo Around之后...");
return result;
}
}
输出结果,当发生异常,AfterReturning不执行,Around后也不执行。因为AfterReturning是返回后通知,都来不及返回所以后面的Around后也不执行
切点 @PointCut
上面代码存在⼀个问题,就是存在大量重复的切点表达式 execution(*com.example.demo.controller.*.*(..)) ,Spring提供了 @PointCut 注解, 把公共的切点表达式提取出来, 需要用到时引⽤该切⼊点表达式即可
代码如下
@Slf4j
@Aspect
@Component
public class AspectDemo {
@Pointcut("execution(* com.example.aopdemo2.demos.controller.*.*(..))")
private void pt() {
}
@Before("pt()")
public void doBefore() {
log.info("执行AspectDemo Before...");
}
@After("pt()")
public void doAfter() {
log.info("执行AspectDemo After...");
}
@AfterReturning("pt()")
public void doAfterReturning() {
log.info("执行AspectDemo AfterReturning...");
}
@AfterThrowing("pt()")
public void doAfterThrowing() {
log.info("执行AspectDemo AfterThrowing...");
}
@Around("pt()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("执行AspectDemo Around之前...");
Object result = joinPoint.proceed();
log.info("执行AspectDemo Around之后...");
return result;
}
}
注意:如果其他切⾯类需要使用这个切点,把切点的声明改为public即可,使用时,类的全限定名称+切点名称,全限定名称就是包名+类名
切面优先级 @Order
⼀个项⽬中,定义了多个切⾯类时, 并且这些切面类的多个切⼊点都匹配到了同⼀个目标方法。当目标方法运行的时候, 这些切⾯类中的通知方法都会执行,那么这⼏个通知方法的执行顺序是什么样的呢?结果已经在上面进行演示了
如下图输出的结果,可以发现优先级高的,最先执行before,最后执行after
使用@Order注解来定义切面的优先级,数字越小,优先级越高。先执行优先级较⾼的切⾯, 再执行优先级较低的切⾯, 最终执行⽬标⽅法。下面进行演示,创建三个类搭配@Order注解
@Slf4j
@Aspect
@Component
@Order(3)
public class AspectDemo1 {
@Before("com.example.aopdemo2.demos.aspect.AspectDemo.pt()")
public void doBefore() {
log.info("执行AspectDemo1 Before...");
}
@After("com.example.aopdemo2.demos.aspect.AspectDemo.pt()")
public void doAfter() {
log.info("执行AspectDemo1 After...");
}
}
@Slf4j
@Aspect
@Component
@Order(1)
public class AspectDemo2 {
@Before("com.example.aopdemo2.demos.aspect.AspectDemo.pt()")
public void doBefore() {
log.info("执行AspectDemo2 Before...");
}
@After("com.example.aopdemo2.demos.aspect.AspectDemo.pt()")
public void doAfter() {
log.info("执行AspectDemo2 After...");
}
}
@Slf4j
@Aspect
@Component
@Order(2)
public class AspectDemo3 {
@Before("com.example.aopdemo2.demos.aspect.AspectDemo.pt()")
public void doBefore() {
log.info("执行AspectDemo3 Before...");
}
@After("com.example.aopdemo2.demos.aspect.AspectDemo.pt()")
public void doAfter() {
log.info("执行AspectDemo3 After...");
}
}
输出结果,可以发现我把AspectDemo2优先定义最高,先输出Before,after最后才输出
切点表达式
切点表达式常见有两种表达⽅式:
• 1. execution():根据⽅法的签名来匹配
• 2. @annotation() :根据注解匹配
execution表达式
execution() 是最常用的切点表达式, ⽤来匹配⽅法。其中访问修饰符和异常可以省略,并且execution表达式更适用有规则的
前面我们一直使用的就是execution表达式
切点表达式支持通配符表达:
@annotation
execution表达式更适⽤有规则的,如果我们要匹配多个无规则的方法呢。我们可以借助⾃定义注解的方式以及另⼀种切点表达式 @annotation 来描述这⼀类的切点
步骤:
• 1. 编写自定义注解,创建一个注解类,和创建Class⽂件⼀样的流程, 选择Annotation就可以了
• 2. 使⽤ @annotation 表达式来描述切点
• 3. 在连接点的⽅法上添加⾃定义注解
• 自定义注解,就像平常创建类一样,选择Annotation即可
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAspect {
}
• 下面进行演示,先在切面类中通知注解后的表达式换成@annotation,如下面的代码
@Slf4j
@Aspect
@Component
public class AspectDemo1 {
@Before("@annotation(com.example.aopdemo2.demos.aspect.MyAspect)")
public void doBefore() {
log.info("执行AspectDemo1 Before...");
}
@After("@annotation(com.example.aopdemo2.demos.aspect.MyAspect)")
public void doAfter() {
log.info("执行AspectDemo1 After...");
}
}
然后我把自定义注解加指定的方法上
@RequestMapping("/hello")
@RestController
public class HelloController {
@MyAspect
@RequestMapping("/h1")
public String h1() {
return "h1";
}
@RequestMapping("/h2")
public String h2() {
return "h2";
}
}
接下来我对接口进行测试,看看输出结果,先对h1接口进行测试
再对h2接口进行测试
可以发现啥也没输出,因为这个接口没有加上自定义注解
• 还可以这样写,带有@RequestMapping注解的都会执行@After注解
@Slf4j
@Aspect
@Component
public class AspectDemo1 {
@Before("@annotation(com.example.aopdemo2.demos.aspect.MyAspect)")
public void doBefore() {
log.info("执行AspectDemo1 Before...");
}
@After("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
public void doAfter() {
log.info("执行AspectDemo1 After...");
}
}
@RequestMapping("/hello")
@RestController
public class HelloController {
@MyAspect
@RequestMapping("/h1")
public String h1() {
return "h1";
}
@RequestMapping("/h2")
public String h2() {
return "h2";
}
}
我们观察h2接口,看看输出结果,如我们所料带有@RequestMapping注解会执行@After注解
Spring AOP的实现方式:
• 1. 基于注解 @Aspect,结合上面的代码看
• 2. 基于⾃定义注解,结合上面的代码看
• 3. 基于Spring API (通过xml配置的⽅式, ⾃从SpringBoot ⼴泛使⽤之后, 这种⽅法⼏乎看不到了, 课下⾃⼰了解下即可)
• 4. 基于代理来实现(更加久远的⼀种实现⽅式, 写法笨重, 不建议使⽤)
Spring AOP 原理
• Spring AOP 是基于动态代理来实现AOP的
代理模式
为其他对象提供⼀种代理以控制对这个对象的访问。它的作用就是通过提供⼀个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进⾏调用,而是通过代理类间接调用。代理可以看作是调用目标的一个包装,通常用来在调用真实的目标之前进行一些逻辑处理,消除一些重复的代码
例子:就像我们现在租房子的时候大部分通过中介然后和房东沟通然后租到房子。还可以理解为广告商找艺人代言,需要经过经纪人一样,由经纪人和艺人沟通
• 使用代理前
• 使用代理后
代理模式的主要角色:
• 1. Subject:业务接口类,可以是抽象类或者接口(不⼀定有)。上面例子中提前定义了房东做的事情,交给中介代理,也是中介要做的事情
• 2. RealSubject:业务实现类,具体的业务执行,也就是被代理对象。例子中的房东
• 3. Proxy:代理类,RealSubject的代理。例子中的中介
适配器模式和代理模式以及代理模式和装饰器的区别:
首先要明确都叫设计模式了,我们要从它们的思想和解决办法去入手,对于适配器模式它解决的问题呢是不兼容相关的问题,而代理模式呢是通过代理对象来完成目标对象所需要完成的任务并且可以在其基础上在进一步的进行扩展,也可以说是在不改变目标对象的源代码情况下对功能的进一步增强
装饰器模式:目的是在不改变原始类接口、不对原始类复杂化的情况下,对原始类进行本身功能增强,并且支持嵌套多装饰器多功能增强
代理模式:目的是在不改变原始类接口的情况下,为原始类提供代理类,对其进行访问控制,实现额外功能,它和装饰器的区别在于不是增强原始类本身对应的功能
1.静态代理
静态代理指的是我们预先编码好一个代理类,在程序运行前,代理类的 .class⽂件就已经存在了。而动态代理指的是运行时生成代理类
例子:像上面租房子的时候,房东先把要交代的事先和中介说好,在用户来租房之前,中介已经提前做好相应的准备了,类似单例模式中的饿汉模式一样我们已经创建好实例,就等其他类通过调用方法来获取到这个对象的实例,注意是类似
• 模拟实现静态代理,先搞个接口
public interface HouseSubject {
void rentHouse();
}
接着是目标对象,也就是例子中的房东
public class RealSubject implements HouseSubject {
@Override
public void rentHouse() {
System.out.println("我是房东,我要出租房子");
}
}
接着就是代理对象,也就是例子中的中介
public class HouseProxy implements HouseSubject {
private RealSubject realSubject;
public HouseProxy(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public void rentHouse() {
System.out.println("开始代理...");
realSubject.rentHouse();
System.out.println("结束代理...");
}
}
接着创建实例
public class Main {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
HouseProxy proxy = new HouseProxy(realSubject);
proxy.rentHouse();
}
}
输出结果
缺点:虽然静态代理也完成了对⽬标对象的代理,但是由于代码都写死了,对⽬标对象的每个⽅法的增强都是手动完成的,非常不灵活
2.动态代理
在程序运行时,运用反射机制动态创建而成。相⽐于静态代理来说,动态代理更加灵活,不需要针对每个⽬标对象都单独创建⼀个代理对象,⽽是把这个创建代理对象的⼯作推迟到程序运行时由JVM来实现。也就是说动态代理在程序运行时, 根据需要动态创建⽣成
例子:类似单例模式中的懒汉模式,等需要的时候调用方法再创建实例,注意是类似。动态代理中的代理对象可以动态生成,就像每个房东找中介代理可以不局限于这一个中介,想出租的时候随便找一个都可以
注意:动态代理底层利用了反射机制,反射包下Proxy类!!!
Java也对动态代理进行了实现,并给我们提供了⼀些API,常见的实现方式有两种:
1. JDK动态代理
步骤:首先自定义一个类实现InvocationHandler接口(反射包中的),接着还要搞一个目标对象,我们在自定义类的构造方法中可以传入一个目标对象,这个目标对象就是例子中的房东,里面有要执行的方法,然后还有就是重写InvocationHandler接口的Invoke方法。invoke ⽅法中我们会调用⽬标⽅法(被代理类的⽅法)并⾃定义⼀些处理逻辑。具体看下面代码的实现
Invoke方法:其中proxy参数代表代理对象就是中介,method方法就是目标对象(房东)要出租或出售房子这些事情代理对象(中介)来负责实现,args就是method中所对应⽅法的参数
• JDK动态代理实现
public class JDKInvocation implements InvocationHandler {
//目标对象 被代理对象
private Object target;
public JDKInvocation(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK开始代理...");
//通过反射机制调用目标对象的方法
Object result = method.invoke(target, args);
System.out.println("JDK结束代理...");
return result;
}
}
public class Main {
public static void main(String[] args) {
//目标对象
RealSubject target = new RealSubject();
//创建⼀个代理类:通过被代理类、被代理类实现的接⼝、⽅法调用处理器来创建
HouseSubject JDKProxy = (HouseSubject) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
new Class[]{HouseSubject.class},
new JDKInvocation(target));
JDKProxy.rentHouse();
}
}
输出结果
• 这样子做,只需要在业务接口/类和目标对象里修改即可,不需要在代理对象中修改
public interface HouseSubject {
void rentHouse();
void saleHouse();
}
public class RealSubject implements HouseSubject {
@Override
public void rentHouse() {
System.out.println("我是房东,我要出租房子");
}
@Override
public void saleHouse() {
System.out.println("我是房东,我要出售房子");
}
}
输出结果
newProxyInstance方法:这个⽅法主要用来⽣成⼀个代理对象
这个方法⼀共有 3 个参数:
Loader:类加载器,用于加载代理对象
interfaces:被代理类实现的⼀些接口(这个参数的定义,也决定了JDK动态代理只能代理实现了接口的⼀些类)
h:实现了 InvocationHandler 接口的对象
• newProxyInstance方法源码中里面就已经写明了参数是interfaces
注意:JDK只能代理接口不能代理类,就是代理对象不能直接调用目标对象的方法
2. CGLIB动态代理(第三方)
JDK 动态代理有⼀个最致命的问题是其只能代理实现了接口的类,CGLIB动态代理既可以代理接口又可以代理类
CGLIB 动态代理类实现步骤:
• 1.首先添加依赖,定义⼀个类(被代理类)
• 2. ⾃定义 MethodInterceptor 并重写 intercept ⽅法, intercept ⽤于增强⽬标⽅法,和 JDK 动态代理中的 invoke ⽅法类似
• 3. 通过 Enhancer 类的 create()创建代理类
• CGLIB动态代理实现,先演示代理接口,先引入依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
public class CGLIBInterceptor implements MethodInterceptor {
//目标对象 被代理对象
private Object target;
public CGLIBInterceptor(Object target) {
this.target = target;
}
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("CGLIB开始代理...");
Object result = methodProxy.invoke(target, args);
System.out.println("CGLIB结束代理...");
return result;
}
}
public class Main {
public static void main(String[] args) {
//目标对象
RealSubject target = new RealSubject();
//create方法用来⽣成⼀个代理对象
HouseSubject CGLIBProxy = (HouseSubject) Enhancer.create(target.getClass(), new CGLIBInterceptor(target));
CGLIBProxy.rentHouse();
}
}
输出结果
• 演示代理类,前面代码一样,只需要把创建代理对象的时候修改一下即可
public class Main {
public static void main(String[] args) {
//目标对象
RealSubject target = new RealSubject();
RealSubject CGLIBProxy = (RealSubject) Enhancer.create(target.getClass(), new CGLIBInterceptor(target));
CGLIBProxy.rentHouse();
}
}
输出结果
注意:两者动态代理写法都是固定的,以及搞清楚代理的目的,是为了对目标对象进行功能的增强,类似房东出租房子不提供水电维修,但是中介提供,这就是功能增强的体现
Spring AOP 剖析
Spring对于AOP的实现,基本上都是靠 AnnotationAwareAspectJAutoProxyCreator 去完成⽣成代理对象的逻辑在⽗类 AbstractAutoProxyCreator 中
1.Spring AOP是怎么实现的?
• Spring AOP是基于动态代理实现的
2.动态代理是怎么实现的?
• Spring 动态代理主要基于 JDK 及 CGLIB 两种⽅式实现的
3.Spring使用的是哪个?
• JDK和CGLIB两个都用
4.什么时候用JDK,什么时候用CGLIB?
SpringBoot 2.x以后默认使用CGLIB 代理不管接口还是类,在这之前默认是jdk代理,但是也要看是不是接口,是接口的话是JDK代理,类的话还是CGLIB代理。我们可以通过配置项 spring.aop.proxy-target-class=false 来进行修改,设置默认为jdk代理。如果设置为true则全都使用CGLIB代理。
• 设置代理模式
5.JDK和CGLIB 有什么区别?
• 最大的区别就是JDK只能代理接口,而CGLIB能代理类和接口
以上便是Spring AOP的知识点,AOP作为Spring两大核心功能之一也是很重要的另外一个IoC之前已经讲解过了,我们下一章再见💕