【Spring】Spring AOP原理

简介: 【Spring】Spring AOP原理

2.png

前言

前面我们学习了关于 Spring AOP 的使用,那么今天这篇文章,我们将深入理解 Spring AOP 的原理,也就是 Spring 是如何实现 AOP 的。


Spring AOP 是基于动态代理来实现 AOP 的,那么什么是代理呢?这里的代理其实是一种模式——代理模式。

代理模式

代理模式是一种设计模式,它为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。


代理模式的主要作用是扩展目标对象的功能,例如在目标对象的某个方法执行前后可以增加一些自定义的操作。此外,代理模式还可以结合享元模式以减少存储器用量,例如创建一个复杂对象及多个代理者,每个代理者会引用到原本的复杂对象,作用在代理者的运算会转送到原本对象。一旦所有的代理者都不存在时,复杂对象会被移除。


这是使用代理之前:

使用代理之后:


我们生活中最常见的可以体现代理模式的就是房屋中介了,当我们想要租房子的时候,如果我们想要直接找到房东的话,会很难且麻烦,因为我们不知道哪一间房子正在出租,如果一个一个问的话就需要很多时间,所以这个时候我们的选择往往就是寻找房屋中介,告诉中介的诉求了之后,房屋中介就会根据我们的需求去寻找合适的房主,这样就极大的方便了我们租房的过程。


代理模式中的主要角色:


Subject:业务接口类,可以是抽象类或者接口(不一定有)

RealSubject:业务实现类。具体的业务执行,也就是被代理对象,上面的例子中房东就是体现

Proxy:代理类。RealSubject的代理,通过中介体现

代理模式可以分为静态代理和动态代理。静态代理中,我们对目标对象的每个方法的增强都是手动完成的,非常不灵活且麻烦。而动态代理则是通过在运行时创建一个子类实例来实现的,可以更加灵活地实现代理。


静态代理:有程序员创建代理类或特定工具自动生成源代码再对其进行编译,在程序运行前代理类的 .class 文件就已经存在了。

动态代理:在程序运行时,运用反射机制动态创建而成

在实际开发中,代理模式可以帮助降低主要业务与辅助业务的耦合度,同时提高辅助业务代码的重用度。

静态代理


静态代理是指代理类在程序运行前就已经定义好,其与目标类的关系在程序运行前就已经确立。在静态代理中,代理类和目标类都实现相同的接口,代理类通过调用目标类的方法来实现接口中定义的业务逻辑,同时可以在调用前后增加额外的操作。


根据租房子的案例来讲就是:在用户租房子之前 ,我中介和房东就已经协商好了,中介就知道你房东要出租房子还是出售房子,然后当客户有相同的需求的话,我中介就可以直接联系客户和房东进行对接。通过代码展示就是这样的:


定义接口(房东要做的事,也就是中介要做的主要的事):

public interface HouseSubject {
    void rentHouse();
}

实现接口(房东出租房子):

public class RealHouseSubject implements HouseSubject {
    @Override
    public void rentHouse() {
        System.out.println("我是房东,我要出租房子...");
    }
}

代理(中介帮房东出租房子):

public class HouseProxy implements HouseSubject {
    //将被代理对象声明为成员变量
    private HouseSubject subject;
    public HouseProxy(RealHouseSubject realHouseSubject) {
        this.subject = realHouseSubject;
    }
    @Override
    public void rentHouse() {
        System.out.println("我是中介,开始代理");
        subject.rentHouse();
        System.out.println("我是中介,结束代理");
    }
}

客户租房子:

public class Main {
    public static void main(String[] args) {
        RealHouseSubject subject = new RealHouseSubject();
        HouseProxy proxy = new HouseProxy(subject);
        proxy.rentHouse();
    }
}

通过这个过程,客户就通过中介成功找到了房东租房子,但是呢?由于这是静态代理,如果我们的房东之前已经和中介商量好了要出租房子,但是这时房东又想出租+出售房子,那么这时候房东就需要和中介再进行沟通,通过我们代码的体现就是这样:


接口:

public interface HouseSubject {
    void rentHouse();
    void saleHouse();
}

被代理对象:

public class RealHouseSubject implements HouseSubject {
    @Override
    public void rentHouse() {
        System.out.println("我是房东,我要出租房子...");
    }
    @Override
    public void saleHouse() {
        System.out.println("我是房东,我要出售房子");
    }
}

代理对象:

public class HouseProxy implements HouseSubject {
    //将被代理对象声明为成员变量
    private HouseSubject subject;
    public HouseProxy(RealHouseSubject realHouseSubject) {
        this.subject = realHouseSubject;
    }
    @Override
    public void rentHouse() {
        System.out.println("我是中介,开始代理");
        subject.rentHouse();
        System.out.println("我是中介,结束代理");
    }
    @Override
    public void saleHouse() {
        System.out.println("我是中介,开始代理");
        subject.saleHouse();
        System.out.println("我是中介,结束代理");
    }
}

客户需要买房子:

public class Main {
    public static void main(String[] args) {
        RealHouseSubject subject = new RealHouseSubject();
        HouseProxy proxy = new HouseProxy(subject);
        proxy.saleHouse();
    }
}

由此可以看出,当我们需要修改业务的时候,既需要修改接口,也需要修改RealSubject 类,还需要修改代理类,那么是否有一种方法,可以使用一种代理类来实现变动业务的代理呢?是可以的,这时就需要用到我们的代理类了。


动态代理

始对象,并在访问前后执行额外的操作。动态代理通常用于在不修改原始对象的情况下增强对象的功能,例如实现横切关注点(cross-cutting concerns)如日志记录、性能监控、事务管理等。


在动态代理中,代理类在程序运行期间创建代理对象,而不是在代码中事先定义好。代理对象中的方法是对目标对象方法的增强方法,可以在方法执行前后插入额外的逻辑。动态代理能够根据需要在运行时动态生成代理对象,因此可以更加灵活地扩展对象的功能。


常见的动态代理有JDK代理和CGLib代理,分别可以针对实现了接口的类代理以及没有实现接口的类代理。

JDK动态代理

JDK动态代理实现的关键步骤如下:


定义接口:首先定义一个接口,该接口将由目标对象和代理对象实现。

创建目标对象:创建目标对象的实例,该实例将被代理对象代理。

实现InvocationHandler接口:创建一个实现了java.lang.reflect.InvocationHandler接口的类,该类将定义代理对象的方法4调用逻辑。在该类中,需要重写invoke()方法,以便在目标对象的方法调用前后执行额外的操作。

创建代理对象:使用java.lang.reflect.Proxy类和InvocationHandler对象创建代理对象。具体来说,通过Proxy类的静态方法newProxyInstance()创建一个代理实例,该实例将调用InvocationHandler对象的invoke()方法来处理方法调用。

使用代理对象:使用代理对象来调用目标对象的方法,在方法调用前后会执行额外的逻辑。

(❁´◡`❁)定义接口

public interface HouseSubject {
    void rentHouse();
    void saleHouse();
}

(❁´◡`❁)创建目标对象

public class RealHouseSubject implements HouseSubject {
    @Override
    public void rentHouse() {
        System.out.println("我是房东,我要出租房子...");
    }
    @Override
    public void saleHouse() {
        System.out.println("我是房东,我要出售房子");
    }
}

(❁´◡`❁)实现 InvocationHandler 接口

public class JDKInvocationHandler implements InvocationHandler {
    //目标对象/被代理对象
    private Object target;
    public JDKInvocationHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("我是中介,开始代理...");
        //通过反射调⽤被代理类的⽅法
        Object retVal = method.invoke(target,args);
        System.out.println("我是中介,结束代理...");
        return retVal;
    }
}

(❁´◡`❁)创建代理对象并使用

public class Main {
    public static void main(String[] args) {
        HouseSubject target = new RealHouseSubject();
        //创建⼀个代理类:通过被代理类、被代理实现的接⼝、⽅法调⽤处理器来创建
        HouseSubject proxy = (HouseSubject) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                new Class[]{HouseSubject.class},
                new JDKInvocationHandler(target)
        );
        proxy.saleHouse();
    }
}

InvocationHandler 接⼝是Java动态代理的关键接⼝之⼀,它定义了⼀个单⼀⽅法 invoke() ,⽤于处理被代理对象的⽅法调⽤:

public interface InvocationHandler {
  /**
  * 参数说明
  * proxy:代理对象
  * method:代理对象需要实现的⽅法,即其中需要重写的⽅法
  * args:method所对应⽅法的参数
  */
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

通过实现 InvocationHandler 接⼝,可以对被代理对象的⽅法进⾏功能增强

Proxy 类中使⽤频率最⾼的⽅法是: newProxyInstance() ,这个⽅法主要⽤来⽣成⼀个代理对象

public static Object newProxyInstance(ClassLoader loader,
  Class<?>[] interfaces,
  InvocationHandler h) throws IllegalArgumentException
  {}
  • Loder:类加载器,用于加载代理对象
  • interfaces:被代理类实现的⼀些接⼝(这个参数的定义,也决定了JDK动态代理只能代理实现了接⼝的⼀些类)
  • h:实现了 InvocationHandler 接口的对象

CGLib 动态代理

CGLib是另一个流行的Java动态代理框架,它扩展了Java的反射机制,并提供了更高级的功能。CGLib可以用来创建具有完全功能的代理类,并且能够代理没有接口的类。


CGLib动态代理实现的关键步骤如下:


下载CGLib库:首先需要下载CGLib库,并将其添加到项目的类路径中。

创建目标对象:创建一个目标对象的实例,该实例将被代理对象代理。

创建Enhancer对象:创建一个org.easymock.classextension.Enhancer对象,用于创建代理对象。Enhancer类是CGLib的核心类,用于生成代理类和实例化代理对象。

设置代理类和回调函数:通过调用Enhancer对象的setSuperclass()方法设置代理类的父类为目标类,然后通过setCallback()方法设置回调函数。回调函数是一个实现了目标类方法的拦截器对象,用于拦截目标方法调用并执行额外的逻辑。

创建代理对象:通过调用Enhancer对象的create()方法创建代理对象。

使用代理对象:使用代理对象来调用目标对象的方法,在方法调用前后会执行额外的逻辑。

与JDK动态代理相比,CGLib提供了更多的灵活性和功能。它支持代理没有接口的类,并且能够动态地修改和扩展类的行为。CGLib还支持方法级别的拦截和过滤,可以更加精细地控制目标方法的调用。因此,CGLib在许多场景中得到了广泛应用,例如AOP编程、测试框架、远程调用等。

(❁´◡`❁)添加依赖

<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.3.0</version>
</dependency>

(❁´◡`❁)⾃定义MethodInterceptor(⽅法拦截器,并且实现MethodInterceptor接⼝)

import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CGLibInterceptor implements MethodInterceptor {
    private Object target;
    public CGLibInterceptor(Object target) {
        this.target = target;
    }
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("我是中介,开始代理...");
        Object retVal = methodProxy.invoke(target,objects);
        System.out.println("我是中介,结束代理...");
        return retVal;
    }
}

(❁´◡`❁)创建类并使用

public class Main {
    public static void main(String[] args) {
        HouseSubject target = new RealHouseSubject();
        HouseSubject proxy = (HouseSubject) Enhancer.create(target.getClass(),new CGLibInterceptor(target));
        proxy.saleHouse();
    }
}

总结

总结一下:

1. Spring AOP 是如何实现的?

Spring AOP(Aspect-Oriented Programming)通过动态代理实现了面向切面编程。具体来说,Spring在创建某个bean的时候,实际上创建的是这个bean的一个代理对象。后续对bean中方法的调用,实际上调用的是代理类重写的代理方法。

2. 动态代理是如何实现的?


参考上文

3. Spring使用的是 JDK 动态代理和 CGLib 代理中的哪一个?


两个都用了

4. 什么时候使用 JDK 代理,什么时候使用 CGLib 代理?


在Spring中,JDK动态代理和CGLib动态代理的选择取决于目标对象的类型。


当目标对象实现了接口时,Spring会使用JDK动态代理。这是因为JDK动态代理通过实现java.lang.reflect.InvocationHandler接口来创建代理对象,并且使用目标对象的接口来定义代理对象的行为。这种情况下,Spring会在运行时动态地创建一个实现了目标对象接口的代理类,该代理类将拦截对目标对象的调用并执行额外的逻辑。

当目标对象没有实现接口时,Spring会使用CGLib动态代理。CGLib是一个代码生成库,可以在运行时动态地创建一个目标对象的子类,并重写目标类的方法以实现额外的逻辑。这种情况下,Spring会在运行时动态地创建一个目标对象的子类,该子类将拦截对目标对象的调用并执行额外的逻辑。

5. JDK 代理和 CGLib 代理有什么区别?


实现方式:JDK代理是基于接口实现的,因此必须先定义接口;而CGLib代理则直接基于被代理类实现,不需要定义接口。

代理机制:JDK动态代理机制是委托机制,通过委托handler调用原始实现类方法;而CGLib则使用继承机制,被代理类和代理类是继承关系,因此代理类对象可以赋值给被代理类类型的变量。

对final方法的处理:由于CGLib的原理是动态生成被代理类的子类,因此无法对声明为final的方法进行代理。

相关文章
|
2月前
|
缓存 Java 开发者
【Spring】原理:Bean的作用域与生命周期
本文将围绕 Spring Bean 的作用域与生命周期展开深度剖析,系统梳理作用域的类型与应用场景、生命周期的关键阶段与扩展点,并结合实际案例揭示其底层实现原理,为开发者提供从理论到实践的完整指导。
|
2月前
|
人工智能 Java 开发者
【Spring】原理解析:Spring Boot 自动配置
Spring Boot通过“约定优于配置”的设计理念,自动检测项目依赖并根据这些依赖自动装配相应的Bean,从而解放开发者从繁琐的配置工作中解脱出来,专注于业务逻辑实现。
|
2月前
|
XML 安全 Java
使用 Spring 的 @Aspect 和 @Pointcut 注解简化面向方面的编程 (AOP)
面向方面编程(AOP)通过分离横切关注点,如日志、安全和事务,提升代码模块化与可维护性。Spring 提供了对 AOP 的强大支持,核心注解 `@Aspect` 和 `@Pointcut` 使得定义切面与切入点变得简洁直观。`@Aspect` 标记切面类,集中处理通用逻辑;`@Pointcut` 则通过表达式定义通知的应用位置,提高代码可读性与复用性。二者结合,使开发者能清晰划分业务逻辑与辅助功能,简化维护并提升系统灵活性。Spring AOP 借助代理机制实现运行时织入,与 Spring 容器无缝集成,支持依赖注入与声明式配置,是构建清晰、高内聚应用的理想选择。
413 0
|
1月前
|
XML Java 数据格式
《深入理解Spring》:AOP面向切面编程深度解析
Spring AOP通过代理模式实现面向切面编程,将日志、事务等横切关注点与业务逻辑分离。支持注解、XML和编程式配置,提供五种通知类型及丰富切点表达式,助力构建高内聚、低耦合的可维护系统。
|
1月前
|
XML Java 测试技术
《深入理解Spring》:IoC容器核心原理与实战
Spring IoC通过控制反转与依赖注入实现对象间的解耦,由容器统一管理Bean的生命周期与依赖关系。支持XML、注解和Java配置三种方式,结合作用域、条件化配置与循环依赖处理等机制,提升应用的可维护性与可测试性,是现代Java开发的核心基石。
|
1月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
366 2
|
6月前
|
监控 安全 Java
Spring AOP实现原理
本内容主要介绍了Spring AOP的核心概念、实现机制及代理生成流程。涵盖切面(Aspect)、连接点(Join Point)、通知(Advice)、切点(Pointcut)等关键概念,解析了JDK动态代理与CGLIB代理的原理及对比,并深入探讨了通知执行链路和责任链模式的应用。同时,详细分析了AspectJ注解驱动的AOP解析过程,包括切面识别、切点表达式匹配及通知适配为Advice的机制,帮助理解Spring AOP的工作原理与实现细节。
1073 13
|
3月前
|
Java 关系型数据库 数据库
深度剖析【Spring】事务:万字详解,彻底掌握传播机制与事务原理
在Java开发中,Spring框架通过事务管理机制,帮我们轻松实现了这种“承诺”。它不仅封装了底层复杂的事务控制逻辑(比如手动开启、提交、回滚事务),还提供了灵活的配置方式,让开发者能专注于业务逻辑,而不用纠结于事务细节。
|
3月前
|
人工智能 监控 安全
如何快速上手【Spring AOP】?核心应用实战(上篇)
哈喽大家好吖~欢迎来到Spring AOP系列教程的上篇 - 应用篇。在本篇,我们将专注于Spring AOP的实际应用,通过具体的代码示例和场景分析,帮助大家掌握AOP的使用方法和技巧。而在后续的下篇中,我们将深入探讨Spring AOP的实现原理和底层机制。 AOP(Aspect-Oriented Programming,面向切面编程)是Spring框架中的核心特性之一,它能够帮助我们解决横切关注点(如日志记录、性能统计、安全控制、事务管理等)的问题,提高代码的模块化程度和复用性。