Java开发到底为什么要用 IoC 和 AOP

作为一名 Java 开发,对 Spring 框架是再熟悉不过的了。Spring 支持的控制反转(Inversion of Control,缩写为IoC)和面向切面编程(Aspect-oriented programming,缩写为AOP)早已成为我们的开发习惯,仿佛 Java 开发天生就该如此。人总是会忽略习以为常的事物,所有人都熟练使用 IoC 和 AOP,却鲜有人说得清楚到底为什么要用 IoC 和 AOP。

技术肯定是为了解决某个问题而诞生,要弄清楚为什么使用 IoC 和 AOP,就得先弄清楚不用它们会碰到什么问题。

一、IoC

我们现在假设回到了没有 IoC 的时代,用传统的 Servlet 进行开发。

1. 传统开发模式的弊端

三层架构是经典的开发模式,我们一般将视图控制、业务逻辑和数据库操作分别抽离出来单独形成一个类,这样各个职责就非常清晰且易于复用和维护。大致代码如下:

@WebServlet("/user")
public class UserServlet extends HttpServlet {
  // 用于执行业务逻辑的对象
  private UserService userService = new UserServiceImpl();

  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    // ...省略其他代码

    // 执行业务逻辑
    userService.doService();

    // ...返回页面视图
  }
}
public class UserServiceImpl implements UserService{
  // 用于操作数据库的对象
  private UserDao userDao = new UserDaoImpl();

  @Override
  public void doService() {
    // ...省略业务逻辑代码

    // 执行数据库操作
    userDao.doUpdate();

    // ...省略业务逻辑代码
  }
}
public class UserDaoImpl implements UserDao{
  @Override
  public void doUpdate() {
    // ...省略JDBC代码
  }
}

上层依赖下层的抽象,代码就分为了三层:

业界普遍按这种分层方式组织代码,其核心思想是职责分离。层次越低复用程度越高,比如一个 DAO 对象往往会被多个 Service 对象使用,一个 Service 对象往往也会被多个 Controller 对象使用:

条理分明,井然有序。这些被复用的对象就像一个个的组件,供多方使用。

虽然这个倒三角看上去非常漂亮,然而我们目前的代码有一个比较大的问题,那就是我们只做到了逻辑复用,并没有做到资源复用。

上层调用下一层时,必然会持有下一层的对象引用,即成员变量。目前我们每一个成员变量都会实例化一个对象,如下图所示:

每一个链路都创建了同样的对象,造成了极大的资源浪费。本应多个 Controller 复用同一个 Service,多个 Service 复用同一个 DAO。现在变成了一个 Controller创建多个重复的 Service,多个 Service 又创建了多个重复的 DAO,从倒三角变成了正三角。

许多组件只需要实例化一个对象就够了,创建多个没有任何意义。针对对象重复创建的问题,我们自然而然想到了单例模式。只要编写类时都将其写为单例,这样就避免了资源浪费。但是,引入设计模式必然会带来复杂性,况且还是每一个类都为单例,每一个类都会有相似的代码,其弊端不言自明。

有人可能会说,那我不在意“这点”资源浪费了,我服务器内存大无所谓,我只求开发便捷痛快不想写额外的代码。

确实,三层架构达到逻辑复用已经非常方便了,还奢求其他的干什么呢。但就算不管资源问题,目前代码还有一个致命缺陷,那就是变化的代价太大。

假设有 10 个 Controller 依赖了 UserService,最开始实例化的是 UserServiceImpl,后面需要换一个实现类 OtherUserServiceImpl,我就得逐个修改那 10 个 Controller,非常麻烦。更换实现类的需求可能不会太多,没多大说服力。那咱们看另一个情况。

之前咱们演示的组件创建过程非常简单,new 一下就完了,可很多时候创建一个组件没那么容易。比如 DAO 对象要依赖一个这样的数据源组件:

public class UserDaoImpl implements UserDao{
  private MyDataSource dataSource;

  public UserDaoImpl() {
    // 构造数据源
    dataSource = new MyDataSource("jdbc:mysql://localhost:3306/test", "root", "password");
    // 进行一些其他配置
    dataSource.setInitiaSize(10);
    dataSource.setMaxActive(100);
    // ...省略更多配置项
  }
}

该数据源组件要想真正生效需要对其进行许多配置,这个创建和配置过程是非常麻烦的。而且配置可能会随着业务需求的变化经常更改,这时候你就需要修改每一个依赖该组件的地方,牵一发而动全身。这还只是演示了一个数据源的创建配置过程,真实开发中可有太多组件和太多配置需要编码了,其麻烦程度堪称恐怖。

当然,这些问题都可以引入设计模式来解决,不过这样一来又绕回去了:设计模式本身也会带来复杂性。这就像一种死循环:传统开发模式编码复杂,要想解决这种复杂却得陷入另一种复杂。难道没有办法解决了吗?当然不是的,在讲优秀解决方案前,我们先来梳理一下目前出现的问题:

  • 创建了许多重复对象,造成大量资源浪费;
  • 更换实现类需要改动多个地方;
  • 创建和配置组件工作繁杂,给组件调用方带来极大不便。

透过现象看本质,这些问题的出现都是同一个原因:组件的调用方参与了组件的创建和配置工作。

其实调用方只需关注组件如何调用,至于这个组件如何创建和配置又与调用方有什么关系呢?就好比我去餐馆只需点菜,饭菜并不需要我亲自去做,餐馆自然会做好给我送过来。如果我们编码时,有一个「东西」能帮助我们创建和配置好那些组件,我们只负责调用该多好。这个「东西」就是容器。

容器这一概念我们已接触过,Tomcat 就是 Servlet 的容器,它帮我们创建并配置好 Servlet,我们只需编写业务逻辑即可。试想一下,如果 Servlet 要我们自己创建,HttpRequest、HttpResponse 对象也需要我们自己配置,那代码量得有多恐怖。

Tomcat 是 Servlet 容器,只负责管理 Servlet。我们平常使用的组件则需要另一种容器来管理,这种容器我们称之为 IoC 容器。

2. 控制反转和依赖注入

控制反转,是指对象的创建和配置的控制权从调用方转移给容器。好比在家自己做菜,菜的味道全部由自己控制;去餐馆吃饭,菜的味道则是交由餐馆控制。IoC 容器就担任了餐馆的角色。

有了 IoC 容器,我们可以将对象交由容器管理,交由容器管理后的对象称之为 Bean。调用方不再负责组件的创建,要使用组件时直接获取 Bean 即可:

@Component
public class UserServiceImpl implements UserService{
  @Autowired // 获取 Bean
  private UserDao userDao;
}

调用方只需按照约定声明依赖项,所需要的 Bean 就自动配置完毕了,就好像在调用方外部注入了一个依赖项给其使用,所以这种方式称之为 依赖注入(Dependency Injection,缩写为 DI)。控制反转和依赖注入是一体两面,都是同一种开发模式的表现形式。

IoC 轻而易举地解决了我们刚刚总结的问题:

对象交由容器管理后,默认是单例的,这就解决了资源浪费问题。

若要更换实现类,只需更改 Bean 的声明配置,即可达到无感知更换:

public class UserServiceImpl implements UserService{
  ...
}

// 将该实现类声明为 Bean
@Component
public class OtherUserServiceImpl implements UserService{
  ...
}

现在组件的使用和组件的创建与配置完全分离开来。调用方只需调用组件而无需关心其他工作,这极大提高了我们的开发效率,也让整个应用充满了灵活性、扩展性。

这样看来,我们如此中意 IoC 不是没有道理的。

二、AOP

我们再来假设没有 AOP 会怎样。

1. 面向对象的局限性

面向对象编程(Object-oriented programming,缩写:OOP)的三大特性:封装、继承、多态,我们早已用得炉火纯青。OOP 的好处已无需赘言,相信大家都有体会。这里咱们来看一下 OOP 的局限性。

当有重复代码出现时,可以就将其封装出来然后复用。我们通过分层、分包、分类来规划不同的逻辑和职责,就像之前讲解的三层架构。但这里的复用的都是核心业务逻辑,并不能复用一些辅助逻辑,比如:日志记录、性能统计、安全校验、事务管理,等等。这些边缘逻辑往往贯穿你整个核心业务,传统 OOP 很难将其封装:

public class UserServiceImpl implements UserService {
  @Override
  public void doService() {
    System.out.println("---安全校验---");
    System.out.println("---性能统计 Start---");
    System.out.println("---日志打印 Start---");
    System.out.println("---事务管理 Start---");

    System.out.println("业务逻辑");

    System.out.println("---事务管理 End---");
    System.out.println("---日志打印 End---");
    System.out.println("---性能统计 End---");
  }
}

为了方便演示,这里只用了打印语句,就算如此这代码看着也很难受,而且这些逻辑是所有业务方法都要加上,想想都恐怖。

OOP 是至上而下的编程方式,犹如一个树状图,A调用B、B调用C,或者A继承B、B继承C。这种方式对于业务逻辑来说是合适的,通过调用或继承以复用。而辅助逻辑就像一把闸刀横向贯穿所有方法,

如图

这一条条横线仿佛切开了 OOP 的树状结构,犹如一个大蛋糕被切开多层,每一层都会执行相同的辅助逻辑,所以大家将这些辅助逻辑称为层面或者切面。

代理模式用来增加或增强原有功能再适合不过了,但切面逻辑的难点不是不修改原有业务,而是对所有业务生效。对一个业务类增强就得新建一个代理类,对所有业务增强,每个类都要新建代理类,这无疑是一场灾难。而且这里只是演示了一个日志打印的切面逻辑,如果我再加一个性能统计切面,就得新建一个切面代理类来代理日志打印的代理类,一旦切面多起来这个代理类嵌套就会非常深。

面向切面编程(Aspect-oriented programming,缩写为 AOP)正是为了解决这一问题而诞生的技术。

2. 面向切面编程

AOP 不是 OOP 的对立面,它是对 OOP 的一种补充。OOP 是纵向的,AOP 是横向的,两者相结合方能构建出良好的程序结构。AOP 技术,让我们能够不修改原有代码,便能让切面逻辑在所有业务逻辑中生效。

我们只需声明一个切面,写上切面逻辑:

@Aspect // 声明一个切面
@Component
public class MyAspect {
  // 原业务方法执行前
  @Before("execution(public void com.rudecrab.test.service.*.doService())")
  public void methodBefore() {
    System.out.println("===AspectJ 方法执行前===");
  }

  // 原业务方法执行后
  @AfterReturning("execution(* com.rudecrab.test.service..doService(..))")
  public void methodAddAfterReturning() {
    System.out.println("===AspectJ 方法执行后===");
  }
}

无论你有一个业务方法,还是一万个业务方法,对我们开发者来说只需编写一次切面逻辑,就能让所有业务方法生效,极大提高了我们的开发效率。

三、总结

IoC 解决了以下问题:

  • 创建了许多重复对象,造成大量资源浪费;
  • 更换实现类需要改动多个地方;
  • 创建和配置组件工作繁杂,给组件调用方带来极大不便。

AOP 解决了以下问题:

切面逻辑编写繁琐,有多少个业务方法就需要编写多少次。

以上就是Java开发到底为什么要用 IoC 和 AOP的详细内容,更多关于Java IoC和AOP的资料请关注我们其它相关文章!

(0)

相关推荐

  • Java使用IOC控制反转的三种设计模式详解

    对于许多开发人员来说,控制反演(IoC)都是一个模糊的概念,因为他们在现实世界中很少或没有被应用过.在最好的情况下,控制反演(IoC)可以加单的认为是等效于依赖注入(DI).实际上,只有在翻转控制与依赖注入双方都只是反映翻转依赖管理控制的时候,才认为两者是等效的.虽然,依赖注入实际上是IoC的一种众所周知的形式.但是,事实上IoC却是一个相对更为广泛的软件设计范例,可以通过多种模式来进行实现.在本文中,我们将介绍依赖注入,观察者模式和模板方法模式如何实现控制反转的. 正如许多其他设计模式,是从各

  • 深入理解java的spring-ioc的使用

    spring-ioc的使用 IOC容器在很多框架里都在使用,而在spring里它被应用的最大广泛,在框架层面上,很多功能都使用了ioc技术,下面我们看一下ioc的使用方法. 把服务注册到ioc容器 使用属性注入反射对应类型的实例 多态情况下,使用名称反射类型的实例 把服务注册到ioc容器 @Bean注册组件 使用@Bean注解进行类型的注册,默认你的ioc容器里类型为bean的返回值,名称为bean所有的方法名,与你的包名称没有直接关系,如果你的接口有多种实现,在注册时可以使用@Bean("li

  • 浅析Java的Spring框架中IOC容器容器的应用

    Spring容器是Spring框架的核心.容器将创建对象,它们连接在一起,配置它们,并从创建到销毁管理他们的整个生命周期.在Spring容器使用依赖注入(DI)来管理组成应用程序的组件.这些对象被称为Spring Beans. 容器获得其上的哪些对象进行实例化,配置和组装通过阅读提供的配置元数据的说明.配置元数据可以通过XML,Java注释或Java代码来表示.下面的图是Spring如何工作的高层次图. Spring IoC容器是利用Java的POJO类和配置元数据的产生完全配置和可执行的系统或

  • 用java的spring实现一个简单的IOC容器示例代码

    要想深入的理解IOC的技术原理,没有什么能比的上我们自己实现它.这次我们一起实现一个简单IOC容器.让大家更容易理解Spring IOC的基本原理. 这里会涉及到一些java反射的知识,如果有不了解的,可以自己去找些资料看看. 注意 在上一篇文章,我说,启动IOC容器时,Spring会将xml文件里面配置的bean扫描并实例化,其实这种说法不太准确,所以我在这里更正一下,xml文件里面配置的非单利模式的bean,会在第一次调用的时候被初始化,而不是启动容器的时候初始化.但是我们这次要做的例子是容

  • 使用Java注解模拟spring ioc容器过程解析

    使用注解,简单模拟spring ioc容器.通过注解给对象属性注入值. 项目结构 annotation 包,用于存放自定义注解 Component 注解表示该类为组件类,并需要声明名字 package priv.haidnor.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy;

  • 基于Java反射技术实现简单IOC容器

    前言 首先思考一个问题,如果你正在做一个复杂的系统,一个系统模块内有几百个功能业务类,这些类需要使用同一些对象来进行工作.那么,你会怎样去管理这些通用且一样的对象呢? 学习过Spring的朋友会知道,Spring框架为此提供了一种非常先进的思想,即IOC(控制反转).Spring可以理解为一个工厂,负责对象的创建和对象间关系的维护.IoC即控制反转,简单说就是之前需要使用new的方式创建对象,而Spring框架会从XML文件中根据配置的信息来创建对象,然后放进它自己的容器之中.在程序要使用到该对

  • 深入理解Java的Spring框架中的IOC容器

    Spring IOC的原型 spring框架的基础核心和起点毫无疑问就是IOC,IOC作为spring容器提供的核心技术,成功完成了依赖的反转:从主类的对依赖的主动管理反转为了spring容器对依赖的全局控制. 这样做的好处是什么呢? 当然就是所谓的"解耦"了,可以使得程序的各模块之间的关系更为独立,只需要spring控制这些模块之间的依赖关系并在容器启动和初始化的过程中将依据这些依赖关系创建.管理和维护这些模块就好,如果需要改变模块间的依赖关系的话,甚至都不需要改变程序代码,只需要将

  • 轻松理解Java面试和开发中的IoC(控制反转)

    IoC的概念介绍 控制反转(IOC)模式(又称DI:Dependency Injection)就是Inversion of Control,控制反转.在Java开发中,IoC意 味着将你设计好的类交给系统去控制,而不是在你的类内部控制.这称为控制反转. 控制反转(Inversion of Control,英文缩写为IoC)是框架的重要特征,做到控制反转需要一个容器来实现,就是我们所说的IoC容器,最常见的IoC容器是Spring. 控制反转从字面意思看来不是很好理解,其实就是将创建管理对象的工作

  • Spring学习笔记1之IOC详解尽量使用注解以及java代码

    在实战中学习Spring,本系列的最终目的是完成一个实现用户注册登录功能的项目. 预想的基本流程如下: 1.用户网站注册,填写用户名.密码.email.手机号信息,后台存入数据库后返回ok.(学习IOC,mybatis,SpringMVC的基础知识,表单数据验证,文件上传等) 2.服务器异步发送邮件给注册用户.(学习消息队列) 3.用户登录.(学习缓存.Spring Security) 4.其他. 边学习边总结,不定时更新.项目环境为Intellij + Spring4. 一.准备工作. 1.m

  • Java开发到底为什么要用 IoC 和 AOP

    作为一名 Java 开发,对 Spring 框架是再熟悉不过的了.Spring 支持的控制反转(Inversion of Control,缩写为IoC)和面向切面编程(Aspect-oriented programming,缩写为AOP)早已成为我们的开发习惯,仿佛 Java 开发天生就该如此.人总是会忽略习以为常的事物,所有人都熟练使用 IoC 和 AOP,却鲜有人说得清楚到底为什么要用 IoC 和 AOP. 技术肯定是为了解决某个问题而诞生,要弄清楚为什么使用 IoC 和 AOP,就得先弄清

  • Java开发编程到底是用idea好还是eclipse好

    IDEA 全称 IntelliJ IDEA 是java编程语言开发的集成环境.IntelliJ在业界被公认为最好的java开发工具,尤其在智能代码助手.代码自动提示.重构.JavaEE支持.各类版本工具(git.svn等).JUnit.CVS整合.代码分析. 创新的GUI设计等方面的功能可以说是超常的. IDEA是JetBrains公司的产品,这家公司总部位于捷克共和国的首都布拉格,开发人员以严谨著称的东欧程序员为主.它的旗舰版本还支持HTML,CSS,PHP,MySQL,Python等. 只可

  • Java 自定义Spring框架与Spring IoC相关接口分析

    在本讲,我们来对Spring IoC功能相关的接口逐一进行分析,分析这些接口的原因就是为了我们自己定义Spring IoC功能提前做好准备. Spring IoC相关接口分析 BeanFactory接口解析 对于BeanFactory接口,我之前只是稍微提到过,并且将就着用了一下它.这里,我将会对BeanFactory接口进行一个具体讲解. Spring中bean的创建是典型的工厂模式,这一系列的bean工厂,即IoC容器,为开发者管理对象之间的依赖关系提供了很多便利和基础服务,在Spring中

  • java开发接口吞吐量提升10多倍技巧

    目录 背景 分析过程 定位“慢”原因 继续定位“慢”的原因 定位CPU使用率高的原因 总结 TODO 背景 公司的一个ToB系统,因为客户使用的也不多,没啥并发要求,就一直没有经过压测.这两天来了一个“大客户”,对并发量提出了要求:核心接口与几个重点使用场景单节点吞吐量要满足最低500/s的要求. 当时一想,500/s吞吐量还不简单.Tomcat按照100个线程,那就是单线程1S内处理5个请求,200ms处理一个请求即可.这个没有问题,平时接口响应时间大部分都100ms左右,还不是分分钟满足的事

  • Java开发人员需知的十大戒律

    本文讲述了Java开发人员需知的十大戒律.分享给大家供大家参考,具体如下: 作为一个Java开发人员提高自己代码的质量,可维护性,是个恒久不变的话题,网上看到这篇文章,拿来自勉. 对Java开发者来说,有许多的标准和最佳实践.本文列举了每一个开发人员必须遵从的十大基本法则:如果有了可以遵从的规则而不遵从,那么将导致的是十分悲惨的结局. 1. 在你的代码里加入注释 每个人都知道这点,但不知何故忘记了遵守.算一算有多少次你"忘记"了添加注释?这是事实:注释对程序在功能上没有实质的贡献.但是

  • 为什么Java开发需要配置环境变量

    之前学习 Java 的时候,感觉最难做的一件事情就是配置 jdk 的环境.那叫一个困难啊,Path, JAVA_HOME, CLASSPATH 印象深刻的很-(但是现在 JDK11 不用再配置 classpath 了,jre 和 jdk 合并了) 就在去年暑假,要配 OpenCV 的环境,要调的东西还是比较多的,对环境配置的概念又加深了. 现在懂的多了,配过的环境也多了,配过的平台也不算少.现在就想分享一下 关于我对配环境这件事情的感受. 那就以 Windows 来说说环境配置的问题,Linux

  • 推荐两款java开发实用工具 hutool 和 lombok

    一.hutool工具 摘抄一段hutool工具的简介: Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,是项目中"util"包友好的替代,它节省了开发人员对项目中公用类和公用工具方法的封装时间,使开发专注于业务. hutool-aop JDK动态代理封装,提供非IOC下的切面支持 hutool-bloomFilter 布隆过滤,提供一些Hash算法的布隆过滤 hutool-cache 简单缓存实现 hutool-core 核心,包括Bean操作.

  • 详解Java从工厂方法模式到 IOC/DI思想

    前言 简单工厂的本质是选择实现,说白了是由一个专门的类去负责生产我们所需要的对象,从而将对象的创建从代码中剥离出来,实现松耦合.我们来看一个例子: 我们要创建一个文件导出工具 public interface FileOper{ public Boolean exceptFile(String data); } public class XMLFileOp implment FileOper{ public Boolean exceptFile(String data){ System.out.

  • Java开发岗位面试被问到反射怎么办

    目录 到底什么是反射呢??? 2. 类的生命周期 3. Java反射框架主要提供以下功能: 反射的基本用法 1. 获得Class对象 2. 判断是否为某个类的实类 3.创建实例 4. 获取构造器信息 5. 获取方法 6. 获取类的成员变量(字段)信息 7. 利用反射创建数组 反射的注意事项 反射的主要用途 总结 到底什么是反射呢??? 反射的核心就是JVM在运行时才动态加载类或调用方法,访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁. 每一个类都会产生一个对应的Class对象,也

  • Java Spring框架简介与Spring IOC详解

    目录 Spring简介和配置 1.Spring概述 1.1 spring 是什么 1.2 Spring发展历程 1.3 Spring的优势 (理解) \1. 方便解耦,简化开发 \2. AOP 编程的支持 \3. 声明式事务的支持 \4. 方便程序的测试 \5. 方便集成各种优秀框架 \6. 降低 JavaEE API 的使用难度 \7. Java 源码是经典学习范例 1.4 Spring的体系结构(了解) 2.Spring IoC快速入门 2.1 IoC的概念和作用 2.2 Spring Io

随机推荐