依赖注入DI和面向切面编程AOP是Spring框架的核心概念,几乎后续使用到Spring框架的地方都有这两个概念的影子。今天我们将研究 Spring IOC容器机制与依赖注入的概念,并且结合实际的例子演示说明 。我们知道 Spring Framework 的核心概念是“依赖注入”(Dependency Injection)和“面向切面编程”(Aspect Oriented Programming)。 我之前再阿里云开发者学院直播课程中也讲过多次Java 依赖注入以及我们如何使用 Spring 框架在项目中实现DI依赖注入和AOP面向切面编程。
1、IOC与依赖注入
控制反转(Inversion of Control)是软件工程中的一项原则,目前也属于一种常见的设计模式,简称IOC。它将对对象或程序部分的控制职责转移到容器或框架中,比如创建MySQL数据库连接对象Connection的功能代码,单独封装为独立的工具方法,便于职责分离,功能内聚,方便统一使用和维护。
我们最常在OOP面向对象编程的上下文中使用它。后面再开发Spring Boot网站后台API接口、Spring Cloud微服务等等经常会使用到依赖注入,对象的创建不再有调用方创建,而是有单独的工厂Factory实现。
与我们的自定义代码调用库的传统编程相比,IoC 使框架能够控制程序的流程并调用我们的自定义代码。为了实现这一点,框架使用内置附加行为的抽象。如果我们想添加自己的行为,我们需要扩展框架的类或插入我们自己的类。
这种架构的优点是:
- 将对象的创建、管理、执行与实现代码分离。
- 工厂模式,抽象创建对象,解耦,容易扩展。
- 维护更方便。
- 通过隔离组件或模拟其依赖关系并允许组件通过契约接口进行通信,从而更容易测试程序。
控制反转(Inversion of Control)很重要的一种实现方式就是“依赖注入”(Dependency Injection),把对象注入到需要的地方。解决对象创建、管理、注入等工作的工具类库我们成为IOC容器。
我们可以通过各种机制来实现控制反转IOC,例如:策略设计模式、服务定位器模式、工厂模式和依赖注入(DI)。
为了更方便里实现Java编程中的控制反转(Inversion of Control),出现了很多优秀的工具框架,Spring 就有对应的实现。
2、Spring IOC 容器
Spring框架中解决IOC问题的类库统称为:Spring IoC Container 或者Spring IoC 容器。
Spring IoC Container 是 Spring 框架的核心部分,用于管理Java应用程序 bean的生命周期。 它在创建 bean 时注入依赖项,并在执行期间管理 bean 生命周期。
Spring IoC 的基本任务就是:
- 实例化
- 配置
- 组装Bean
Spring IOC 容器从 Spring 配置文件中获取配置相关信息。 这可以是 XML 或 Java配置 文件。
IOC 容器使用依赖注入 (DI) 来管理组成Java应用程序的组件。
Spring 提供了两种类型的 IOC 容器:
- BeanFactory ,Bean工厂
- ApplicationContext ,应用程序上下文
3、BeanFactory 和 ApplicationContext 的区别
Spring框架提供了两个重要的接口org.springframework.beans.factory.BeanFactory 和 org.springframework.context.ApplicationContext ,这两个接口充当 IoC 容器,也有具体的实现类型。
3.1 BeanFactory 容器
BeanFactory 是一个 IoC 容器,负责维护 bean 及其依赖项。 它基本上是一个提供基本功能的接口。
3.2 ApplicationContext 容器
ApplicationContext 接口建立在 BeanFactory 接口之上。
ApplicationContext 是 BeanFactory 的子接口,提供更多类似企业的功能。 它为 Web 应用程序添加了应用程序层特定的上下文,例如 WebApplicationContext。
它比 BeanFactory 添加了一些额外的功能,例如与 Spring 的 AOP 的简单集成、消息资源处理(对于 I18N)、事件传播、Web 应用程序的应用层特定上下文(例如 WebApplicationContext)。
所以使用 ApplicationContext 比使用 BeanFactory 更好。
创建完Java项目或者Spring 项目,可以再POM里加入对Spring的依赖。
<dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>${spring.version}</version></dependency>
Maven会获取对应的依赖项目,我们可以通过开发工具来查看依赖包,以及BeanFactory 在的包。
IOC容器位于Spring-beans包中。
4、Spring IOC依赖注入实战
我们可以通过三种方式来配置Spring IoC 容器。
- 基于 XML,比较原始
- 基于注解,目前使用的最多,比如@Autowired
- 基于 Java,比较原始
4.1使用BeanFactory依赖注入
BeanFactory是基本的容器接口,Spring框架中包括许多BeanFactory的子接口: ApplicationContext, AutowireCapableBeanFactory, ConfigurableApplicationContext, ConfigurableBeanFactory, ConfigurableListableBeanFactory, ConfigurableWebApplicationContext, HierarchicalBeanFactory, ListableBeanFactory, WebApplicationContext。
以下是BeanFactory的子类型:
AbstractApplicationContext, AbstractAutowireCapableBeanFactory, AbstractBeanFactory, AbstractRefreshableApplicationContext, AbstractRefreshableConfigApplicationContext, AbstractRefreshableWebApplicationContext, AbstractXmlApplicationContext, AnnotationConfigApplicationContext, AnnotationConfigWebApplicationContext, ClassPathXmlApplicationContext, DefaultListableBeanFactory, FileSystemXmlApplicationContext, GenericApplicationContext, GenericGroovyApplicationContext, GenericWebApplicationContext, GenericXmlApplicationContext, GroovyWebApplicationContext, ResourceAdapterApplicationContext, SimpleJndiBeanFactory, StaticApplicationContext, StaticListableBeanFactory, StaticWebApplicationContext, XmlBeanFactory, XmlWebApplicationContext
BeanFactory接口是访问 Spring bean 容器的根接口。
这是Spring bean 容器的基本客户端工具视图;其他接口,如 ListableBeanFactory 和 ConfigurableBeanFactory 可用于特定需求。
该接口由包含许多 bean 定义的对象实现,每个定义由一个字符串名称唯一标识。根据 bean 定义,工厂将返回包含对象的独立实例(原型设计模式)或单个共享实例(单例设计模式的更好替代方案,其中实例是范围内的单例)工厂)。将返回哪种类型的实例取决于 bean 工厂配置。从 Spring 2.0 开始,根据具体的应用程序上下文(例如 Web 环境中的“请求”和“会话”范围),可以在更多范围中使用。
BeanFactory充当Spring应用程序组件的中央注册表,并读取并存储了应用程序组件的配置数据。这种方法优点的讨论,请参阅“Expert One-on-One J2EE Design and Development”的第 4 章和第 11 章。
通常,BeanFactory 会加载存储在配置源(例如 XML 文档)中的 bean 定义,并使用 org.springframework.beans 包来配置 bean。但是,实现可以简单地返回它根据需要直接在 Java 代码中创建的 Java 对象。对如何存储定义没有限制:LDAP、RDBMS、XML、属性文件等。鼓励实现支持 bean 之间的引用(依赖注入)。
4.2使用ApplicationContext依赖注入
Spring框架中 ApplicationContext 接口有多种实现,例如:
- ClassPathXmlApplicationContext
- XmlWebApplicationContext
- FileSystemXmlApplicationContext
Spring 框架提供了 ApplicationContext 接口的几种实现:ClassPathXmlApplicationContext 和 FileSystemXmlApplicationContext 用于独立应用程序,以及 WebApplicationContext 用于 Web 应用程序。
为了组装 bean,容器使用配置元数据,它可以是 XML 配置或注解的形式。
这是手动实例化容器的一种方法:
ApplicationContextcontext=newClassPathXmlApplicationContext("AppContext.xml"); Orderorder= (Order) factory.getBean("order"); OrderServiceservice=newOrderService(order); service.save();
这种方式比较原始,是早期使用Spring IOC容器加载配置文件,创建对象的过程。当然后续可以把order对象注入到需要的地方。比如业务逻辑层orderService。
5、依赖注入的方式
Spring 框架中的依赖注入可以通过构造函数、setter器 或字段来完成。
5.1构造函数
Spring在基于构造函数的依赖注入DI的情况下,Spring IOC容器将调用带有参数的构造函数,每个参数代表我们要设置的依赖项,例如订单的价格,或者数据库连接Connection对象的地址、账号、密码等参数。
Spring IOC容器会按类型解析每个参数,然后是属性名称,以及用于唯一的索引。 让我们使用注解查看 bean 的配置及其依赖项:
publicclassAppConfig { publicOrderorder() { returnnewOrderImpl(); } publicOrderServiceorderService() { returnnewOrderService(order()); } }
@Configuration 注解表明该类是 bean 定义参数的配置数据来源。 我们还可以将它添加到多个配置类中。我们在方法上使用@Bean 注解来定义一个 bean。 如果我们不指定自定义名称,那么 bean 名称将默认为方法名称。
对于具有默认单例singleton作用域的 bean,Spring 首先检查 bean 的缓存实例是否已经存在,如果不存在则只创建一个新实例。 如果我们使用原型prototype作用域,容器会为每个方法调用请求返回一个新的 bean 实例。
注解代码方式,也可以通过XML配置文件替换。创建 bean 配置的另一种方法是通过 XML 配置:
<beanid="order"class="org.alibaba.OrderImpl"/><beanid="orderService"class="org.alibaba.OrderService"><constructor-argtype="OrderImpl"index="0"name="order"ref="order"/></bean>
两种方式各有优劣,目前为了灵活性普遍选择XML方式配置容器参数。
5.2Setter器
简单理解,被注入的对象提供Setter器,Spring IOC容器把创建的对象通过Setter器注入进去。对于基于 setter 的 DI,Spring IOC 容器会在调用无参数构造函数或无参数静态工厂方法实例化 bean 后,再调用我们类的 setter 方法。 让我们使用Java注解创建这个配置,然后注入对象:
publicBooleansaveOrder( Orderorder) { Orderorder=newOrder(); orderDAO.setOrder(order); returntrue; }
这种方式目前使用的也比较少,这几作为对比例子告诉大家IOC的不同之处。我们可以为同一个 bean 组合Spring基于构造函数和基于 setter 的注入类型。每种注入方式都有自己的利弊。 Spring 官方建议对强制依赖项使用基于构造函数的注入,对可选依赖项使用基于 setter 的注入。当然两种方式都可以使用,
5.3字段注入
Spring IOC目前提供了更为简单的IOC方式@Autowired。我们可以直接使用基于字段的 DI,我们可以通过使用 @Autowired 注解标记依赖项来注入依赖项。这种方法目前最流行的方式。例子代码如下:
"/Users") (publicclassUsersController { privateIUserServiceuserService;//UserService, UserServiceImpl"/index") (publicModelAndViewindex() { List<User>listUsers=userService.getAllUsers(); ModelAndViewmv=newModelAndView("/Users/index"); mv.addObject("listUsers", listUsers); returnmv; } }
@Autowired允许 Spring IOC容器通过检查已定义的 bean 来自动解决协作 bean 之间的依赖关系。
使用 XML 配置自动装配 bean 有4种模式:
- no:默认值——这意味着 bean 不使用自动装配,我们必须显式命名依赖项。
- byName:自动装配是根据属性的name名称完成的,因此 Spring IOC容器会寻找与需要设置的属性name名称相同的 bean。
- byType:类似于 byName 自动装配,仅基于属性的type类型。 这意味着 Spring 将寻找具有相同类型属性的 bean 来设置。 如果该类型的 bean 不止一个,框架会抛出异常。
- 构造函数:自动装配是基于构造函数参数完成的,这意味着 Spring 将寻找与构造函数参数类型相同的 bean。
例如,让我们将上面定义的 order bean 按类型自动装配到 OrderService bean 中:
autowire=Autowire.BY_NAME) (publicBooleansaveOrder( Orderorder) { Orderorder=newOrder(); orderDAO.setOrder(order); returntrue; }
当然IOC的代码配置也可以通过XML来实现:
<beanid="orderService"class="org.alibaba.OrderService"autowire="byType"><beanid="order"class="org.alibaba.Order"autowire="byName"></bean>
5.4 延迟实例化
默认情况下,Spring IOC容器在初始化期间创建和配置所有单例 bean,可能会影响程序的启动性能。 为了避免这种情况,我们可以在 bean 配置中使用值为 true 的延迟初始化属性对象:
<beanid="orderService"class="org.alibaba.OrderService"lazy-init="true"><beanid="order"class="org.alibaba.Order"lazy-init="true"></bean>
因此, order bean 只会在第一次被请求时被初始化创建对象,而不是在java程序启动时被初始化。 这样做的好处是更快的Java程序启动初始化时间,但代价是我们在请求 bean 之前不会发现任何配置错误,这可能是应用程序已经运行后的几个小时甚至几天。如果有XML配置错误,不能再第一时间发现改正,需要发送一次请求进行检查。
6、总结
在本文中,我们介绍了Spring 框架中最重要的IOC控制反转和DI依赖注入的概念,Spring IOC容器机制,和关键的容器类型。并进行了Spring IOC例子代码的说明。
此外,我们可以在 Spring Framework 参考文档中了解 IoC 和 DI 的 Spring代码实现,https://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#beans-dependencies。
后续开发主要是Spring Boot为主,Spring Boot又大量使用@Autowired注解,来简化IOC的实现代码,本文作为后续课程的学习基石,必不可少,必须掌握Spring IOC的工作原理和底层机制。这个也是Java高级职位面试考察的重要知识点。