Spring的IOC与AOP特性

Spring的IOC特性

什么是控制反转

图1. 控制反转

见图1,软件中的对象就如同图中的齿轮,协同工作,互相耦合,若是一个零件不能正常工作,则会导致整个系统的奔溃,这就是强耦合系统。为了解决对象间耦合度过高的问题,软件专家Michael Mattson提出了IoC理论。

控制反转(Inversion of Control)是一种面向对象编程中的一种设计原则,用来解决计算机代码之间的耦合度。其基本思想是:借助于"第三方"实现具有依赖关系的对象之间的耦合。

图2. IoC

见图2,由于引进了IoC容器,使得A、B、C、D这四个对象没有了耦合关系,对象的控制权全部由IoC容器负责。

我们再次来对比一下:

  1. 软件系统在没有引入IoC容器之前,如图1,对象A依赖与对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建还是使用对象B,控制权都在自己手上。
  2. 软件系统在引入IoC容器之后,就完全不同了。如图2,由于IoC容器的加入,对象A与对象B之间失去了直接联系,所以,当对象A运行到需要B的对象,IoC容器会主动创建一个对象B注入到对象A需要的地方。

通过对比,可以看出对象A依赖对象B的过程,由主动行为变成了被动行为,控制权颠倒了过来,这就是"控制反转"的由来。

什么是依赖注入

当A对象需要调用B对象方法时,这种情况在Spring中称为依赖,即A对象依赖B对象,Spring把互相调用的关系称为依赖关系。

在传统模式下当需要调用其他对象的方法时,一般有以下两种方式:

  • 原始做法:调用者主动创建被依赖对象,然后再调用被依赖对象的方法。
  • 简单工厂模式:调用者先找到被依赖对象的工厂,然后主动通过工厂去获取被依赖对象,最后再调用被依赖对象的方法。

对于第一种方式,由于调用者需要通过形如"new 被依赖对象构造器();"的代码来创建对象,这种方式会导致调用者与被依赖对象实现类的硬编码耦合,不利于项目升级维护。

对于第二种方式,要把握一下三点:

  • 调用者面向被依赖对象的接口编程
  • 将被依赖对象的创建交给工厂完成
  • 调用者通过工厂来获得被依赖组件

这样,调用者只需与被依赖对象的接口耦合,这样就避免了类层次的硬编码耦合。缺点是,调用组件需要主动通过工厂去获取被依赖对象,这就会带来调用组件与被依赖对象的耦合。

当使用Spring容器后,程序无须使用new调用构造器去创建对象,所有的Java对象都可交给Spring容器去创建;当调用者需要被依赖对象的方法时,调用者无须主动获取被依赖对象,只需要等待Spring容器注入即可。

控制反转与依赖注入的关系

  • 控制反转是一种思想
  • 依赖注入是一种设计模式

IoC框架使用依赖注入作为实现控制反转的方式,但是控制反转还有其他的实现方式,例如说ServiceLocator,所以不能将控制反转和依赖注入等同。

参考资料


Spring的AOP特性

以下内容来自于博客Spring AOP 实现原理与 CGLIB 应用

AOP(Aspect Orient Programming),作为面向对象编程的一种补充,广泛应用于处理一些具有横切性质的系统级服务,如事务管理、安全检查、缓存、对象池管理等。AOP实现的关键就在于AOP框架自动创建的AOP代理,AOP代理则可分为静态代理和动态代理两大类,其中静态代理是指AOP框架提供的命令进行编译,从而在编译阶段就可生成AOP代理类,因此也被称为编译时增强;而动态代理则是在运行时借助于JDK动态代理、CGLIB等在内存中“临时”生成AOP动态代理类,因此也被称为运行时增强。

AOP的存在价值

在传统的OOP编程里以对象为核心,整个软件系统由一系列相互依赖的对象组成,而这些对象将被抽象成一个个类,并允许使用类继承来管理类与类之间一般到特殊的关系。随着软件规模的增大,应用的逐渐升级,慢慢出现了一些OOP很难解决的问题。

我们可以通过分析、抽象出一系列具有一定属性与行为的对象,并通过这些对象的协作来形成一个完整的软件功能。由于对象可以继承,因此我们可以把具有相同功能或相同特性的属性抽象到一个层次分明的类结构体系中。随着软件规范的不断扩大,专业化分工越来越系列,以及OOP应用实践的不断增多,随之也暴露出了一些OOP无法很好解决的问题。

现在假设系统中有3段完全相似的代码,这些代码通常会采用“复制”、“粘贴”方式来完成,通过这种“复制”、“粘贴”方式开发出来的软件如图3所示。

图3. 多个地方包含相同代码的软件

看到如图3所示的示意图,可以看到了这种设计的不足之处。当有一天,图1中的深色代码段需要修改,那是不是要打开3个地方的代码进行修改?如果不是3个地方包含这段代码,而是100个地方,甚至是1000个地方包含这段代码段,那会是什么后果?

为了解决这个问题,我们通常会采用将如图3所示的深色代码部分定义成一个方法,然后在3个代码段中分别调用该方法即可。在这种方式下,软件系统的结构如图4所示。

图4. 通过方法调用实现系统功能

对于如图4所示的软件系统,如果需要修改深色部分的代码,只要修改一个地方即可,不管整个系统中有多少方法调用了该方法,程序无须修改这些地方,只需要修改被调用的方法即可——通过这种方式,大大降低了软件后期维护的复杂度。

对于如图4所示的方法1、方法2、方法3依然需要显式调用深色方法,这样做能够解决大部分应用场景。但对于一些更特殊的情况:应用需要方法1、方法2、方法3彻底与深色方法分离——方法1、方法2、方法3无须直接调用深色方法,该如何解决?

因为软件系统需求变更是非常频繁的事情,系统前期设计方法1、方法2、方法3时只实现了核心业务功能,过了一段时间,我们需要为方法1、方法2、方法3都增加事务控制;又过了一段时间,客户提出方法1、方法2、方法3需要进行用户合法性验证。只有合法的用户才能执行这些方法。因此,我们希望有一种特殊的方法:我们只要定义该方法,无须在方法1、方法2、方法3中显式调用它,系统会“自动”执行该特殊方法。

实现上述需求的技术就是AOP。AOP专门用于处理系统中分布于各个模块(不同方法)中交叉关注点的问题,在JavaEE应用中,常常通过AOP来处理一些具有横切性质的系统级服务,如事务管理、安全检查、缓存、对象池管理等,AOP已经成为一种非常常用的解决方案。

Spring AOP原理剖析

Spring AOP框架对AOP代理类的处理原则是:如果目标对象的实现类实现了接口,Spring AOP将会采用JDK动态代理来生成AOP代理类;如果目标对象的实现类没有实现接口,Spring AOP将会采用CGLIB来生成AOP代理类——不过这个选择过程对开发者完全透明、开发者无须关心。

AOP代理其实是由AOP框架动态生成的一个对象,该对象可作为目标对象使用。AOP代理包含了目标对象的全部方法,但AOP代理中的方法与目标对象的方法存在差异:AOP方法在特定切入点添加了增强处理,并回调了目标对象的方法。

AOP代理所包含的方法与目标对象的方法示意图如图5所示。

图5. AOP代理的方法与目标对象的方法

Spring的AOP代理有Spring的IoC容器负责生成、管理,其依赖关系也有IoC容器负责管理。因此,AOP代理可以直接使用容器中的其他Bean实例作为目标,这种关系可由IoC容器的依赖注入提供。

纵观AOP编程,其中需要程序员参与的只有3个部分:

  • 定义普通业务组件
  • 定义切入点,一个切入点可能横切多个业务组件
  • 定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作

上面3个部分的第一个部分是最平常不过的事情,无须额外说明。那么进行AOP编程的关键就是定义切入点和定义增强处理。一但定义了合适的切入点和增强处理,AOP框架将会自动生成AOP代理,而AOP代理的方法大致有如下公式:

代理对象的方法 = 增强处理 + 被代理对象的方法

Spring AOP的实现原理:AOP框架负责动态生成AOP代理类,这个代理类的方法则由Advice和回调方法对象的方法所组成。

对于前面提到的图4所示的软件调用结构:当方法1、方法2、方法3…都需要去调用某个具有“横切”性质的方法时,传统的做法是程序员去手动修改方法1、方法2、方法3…通过代码来调用这个具有“横切”性质的方法,但这种做法的可扩展性不好,因为每次都要修改代码。

于是AOP框架出现,AOP框架则可以“动态的”生成一个新的代理类,而这个代理类所包含的方法1、方法2、方法3的代码,程序员只要定义切入点即可——AOP框架所生成的AOP代理类中包含了新的方法1、方法2、方法3,而AOP框架会根据切入点来决定是否要在方法1、方法2、方法3中回调具有“横切”性质的方法。

简而言之:AOP原理的奥妙就在于动态地生成了代理类,这个代理类实现了图2的调用——这种调用无须程序员修改代码。

参考资料

  • 《轻量级JavaEE企业应用实战》(第4版)李刚 编著
  • 《疯狂Java讲义》李刚 编著
Author: Toyan
Link: https://toyan.top/ioc-and-aop/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
支付宝打赏
微信打赏