阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

IoC原理

24次阅读
没有评论

共计 3736 个字符,预计需要花费 10 分钟才能阅读完成。

Spring 提供的容器又称为 IoC 容器,什么是 IoC?

IoC 全称 Inversion of Control,直译为控制反转。那么何谓 IoC?在理解 IoC 之前,我们先看看通常的 Java 组件是如何协作的。

我们假定一个在线书店,通过 BookService 获取书籍:

public class BookService {private HikariConfig config = new HikariConfig();
    private DataSource dataSource = new HikariDataSource(config);

    public Book getBook(long bookId) {try (Connection conn = dataSource.getConnection()) {
            ...
            return book;
        }
    }
}

为了从数据库查询书籍,BookService持有一个DataSource。为了实例化一个HikariDataSource,又不得不实例化一个HikariConfig

现在,我们继续编写 UserService 获取用户:

public class UserService {private HikariConfig config = new HikariConfig();
    private DataSource dataSource = new HikariDataSource(config);

    public User getUser(long userId) {try (Connection conn = dataSource.getConnection()) {
            ...
            return user;
        }
    }
}

因为 UserService 也需要访问数据库,因此,我们不得不也实例化一个HikariDataSource

在处理用户购买的 CartServlet 中,我们需要实例化 UserServiceBookService

public class CartServlet extends HttpServlet {private BookService bookService = new BookService();
    private UserService userService = new UserService();

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {long currentUserId = getFromCookie(req);
        User currentUser = userService.getUser(currentUserId);
        Book book = bookService.getBook(req.getParameter("bookId"));
        cartService.addToCart(currentUser, book);
        ...
    }
}

类似的,在购买历史 HistoryServlet 中,也需要实例化 UserServiceBookService

public class HistoryServlet extends HttpServlet {private BookService bookService = new BookService();
    private UserService userService = new UserService();}

上述每个组件都采用了一种简单的通过 new 创建实例并持有的方式。仔细观察,会发现以下缺点:

  1. 实例化一个组件其实很难,例如,BookServiceUserService 要创建HikariDataSource,实际上需要读取配置,才能先实例化HikariConfig,再实例化HikariDataSource
  2. 没有必要让 BookServiceUserService分别创建 DataSource 实例,完全可以共享同一个 DataSource,但谁负责创建DataSource,谁负责获取其他组件已经创建的DataSource,不好处理。类似的,CartServletHistoryServlet也应当共享 BookService 实例和 UserService 实例,但也不好处理。
  3. 很多组件需要销毁以便释放资源,例如DataSource,但如果该组件被多个组件共享,如何确保它的使用方都已经全部被销毁?
  4. 随着更多的组件被引入,例如,书籍评论,需要共享的组件写起来会更困难,这些组件的依赖关系会越来越复杂。
  5. 测试某个组件,例如BookService,是复杂的,因为必须要在真实的数据库环境下执行。

从上面的例子可以看出,如果一个系统有大量的组件,其生命周期和相互之间的依赖关系如果由组件自身来维护,不但大大增加了系统的复杂度,而且会导致组件之间极为紧密的耦合,继而给测试和维护带来了极大的困难。

因此,核心问题是:

  1. 谁负责创建组件?
  2. 谁负责根据依赖关系组装组件?
  3. 销毁时,如何按依赖顺序正确销毁?

解决这一问题的核心方案就是 IoC。

传统的应用程序中,控制权在程序本身,程序的控制流程完全由开发者控制,例如:

CartServlet创建了 BookService,在创建BookService 的过程中,又创建了 DataSource 组件。这种模式的缺点是,一个组件如果要使用另一个组件,必须先知道如何正确地创建它。

在 IoC 模式下,控制权发生了反转,即从应用程序转移到了 IoC 容器,所有组件不再由应用程序自己创建和配置,而是由 IoC 容器负责,这样,应用程序只需要直接使用已经创建好并且配置好的组件。为了能让组件在 IoC 容器中被“装配”出来,需要某种“注入”机制,例如,BookService自己并不会创建 DataSource,而是等待外部通过setDataSource() 方法来注入一个DataSource

public class BookService {private DataSource dataSource;

    public void setDataSource(DataSource dataSource) {this.dataSource = dataSource;
    }
}

不直接 new 一个DataSource,而是注入一个DataSource,这个小小的改动虽然简单,却带来了一系列好处:

  1. BookService不再关心如何创建DataSource,因此,不必编写读取数据库配置之类的代码;
  2. DataSource实例被注入到BookService,同样也可以注入到UserService,因此,共享一个组件非常简单;
  3. 测试 BookService 更容易,因为注入的是DataSource,可以使用内存数据库,而不是真实的 MySQL 配置。

因此,IoC 又称为依赖注入(DI:Dependency Injection),它解决了一个最主要的问题:将组件的创建 + 配置与组件的使用相分离,并且,由 IoC 容器负责管理组件的生命周期。

因为 IoC 容器要负责实例化所有的组件,因此,有必要告诉容器如何创建组件,以及各组件的依赖关系。一种最简单的配置是通过 XML 文件来实现,例如:

<beans>
    <bean id="dataSource" class="HikariDataSource" />
    <bean id="bookService" class="BookService">
        <property name="dataSource" ref="dataSource" />
    </bean>
    <bean id="userService" class="UserService">
        <property name="dataSource" ref="dataSource" />
    </bean>
</beans>

上述 XML 配置文件指示 IoC 容器创建 3 个 JavaBean 组件,并把 id 为 dataSource 的组件通过属性 dataSource(即调用setDataSource() 方法)注入到另外两个组件中。

在 Spring 的 IoC 容器中,我们把所有组件统称为 JavaBean,即配置一个组件就是配置一个 Bean。

依赖注入方式

我们从上面的代码可以看到,依赖注入可以通过 set() 方法实现。但依赖注入也可以通过构造方法实现。

很多 Java 类都具有带参数的构造方法,如果我们把 BookService 改造为通过构造方法注入,那么实现代码如下:

public class BookService {private DataSource dataSource;

    public BookService(DataSource dataSource) {this.dataSource = dataSource;
    }
}

Spring 的 IoC 容器同时支持属性注入和构造方法注入,并允许混合使用。

无侵入容器

在设计上,Spring 的 IoC 容器是一个高度可扩展的无侵入容器。所谓无侵入,是指应用程序的组件无需实现 Spring 的特定接口,或者说,组件根本不知道自己在 Spring 的容器中运行。这种无侵入的设计有以下好处:

  1. 应用程序组件既可以在 Spring 的 IoC 容器中运行,也可以自己编写代码自行组装配置;
  2. 测试的时候并不依赖 Spring 容器,可单独进行测试,大大提高了开发效率。

正文完
星哥说事-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2024-08-05发表,共计3736字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中