咱们到底为何要用 IoC 和 AOP

IoCAOP.png

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

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

IoC

咱们如今假设回到了没有 IoC 的时代,用传统的 Servlet 进行开发。sql

传统开发模式的弊端

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

@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代码
    }
}

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

2-1.png

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

2-2.png

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

虽然这个倒三角看上去很是漂亮,然而咱们目前的代码有一个比较大的问题,那就是咱们只作到了逻辑复用,并无作到资源复用服务器

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

2-3.png

每个链路都建立了一样的对象,形成了极大的资源浪费。本应多个 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 容器

控制反转和依赖注入

控制反转,是指对象的建立和配置的控制权从调用方转移给容器。比如在家本身作菜,菜的味道所有由本身控制;去餐馆吃饭,菜的味道则是交由餐馆控制。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 会怎样。

面向对象的局限性

面向对象编程(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。这种方式对于业务逻辑来讲是合适的,经过调用或继承以复用。而辅助逻辑就像一把闸刀横向贯穿全部方法,如图2-4所示:

2-4.png

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

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

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

面向切面编程

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 解决了如下问题:

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

本文到这里就结束了,我是「RudeCrab」,一只粗鲁的螃蟹,咱们下篇文章再见。

关注「RudeCrab」微信公众号,和螃蟹一块儿横行霸道。

相关文章
相关标签/搜索