目录
- 正文
- Spring IoC容器
- 1. IoC和Bean简介
- 2. 容器概览
- 2.1 配置元数据
- 2.2 实例化容器
- 2.3 容器的使用
- 3. Bean概览
- 3.1 Bean的命名规范
- 3.2 实例化Bean
- 使用构造器实例化
- 使用静态工厂方法实例化
- 使用实例工厂方法实例化
- 4. 依赖
- 4.1 依赖注入(DI)
- 基于构造函数的依赖注入
- 单词
用了这么久的spring框架却一直不知其所以然,直接在网上看别人的攻略又感觉差了点什么,因此决定系统的看一下spring官方的英文文档。本系列博客将按照每一篇文章对应官方文档中的一个小节的形式发布,文章内容分为正文和单词两部分。
笔者的英文水平一般,正文中的引用翻译也只是部分翻译,故本文只是spring文档阅读过程中的记录和分享,不可作为译文参考。
官方文档地址:https://spring.io/projects/spring-framework 欢迎转载,转载时注明原作者即可。
正文
本文所列部分对于spring框架而言都是核心技术,缺一不可。
Spring IoC容器
1. IoC和Bean简介
Foremost amongst these is the Spring Framework’s Inversion of Control (IoC).
IoC is also known as dependency injection (DI).
如果要在spring框架所有核心技术中挑出一个最重要的,那一定是IoC(控制反转)
/DI(依赖注入)
(是的,它们其实是同一个东西)。以下是关于IoC
的定义:
It is a process whereby objects define their dependencies (that is, the other objects they work with) only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The then injects those dependencies when it creates the bean.
IoC指的是对象仅通过构造函数的参数
、工厂方法的参数
、或者是被构造函数或工厂方法创建后返回的属性
来定义自身依赖的过程。当容器创建bean时就会将那些已经被定义过的依赖注入进去。
IoC的两个关键包分别是org.springframework.beans
和org.springframework.context
。
BeanFactory
是提供框架配置和基本功能的接口,它的配置机制使得我们可以轻易的管理任何类型的对象。BeanFactory
的子接口ApplicationContext
则补充了一些企业开发相关的功能,ApplicationContext
同时也是BeanFactory
的超集。
在spring中,被IoC容器实例化、组装或以其他方式管理的对象称为bean
。各种bean
以及它们之间的依赖关系反映在spring容器配置的元数据
中。
此处文档中还有一句
Otherwise, a bean is simply one of many objects in your application.
,我的理解是此处的bean
指的是javabean
,希望有大佬解惑
2. 容器概览
org.springframework.context.ApplicationContext
接口代表着spring IoC容器,它负责通过读取元数据
获取有关对象的配置说明并实例化、配置和组装bean
。配置元数据
通常以xml
、注解
或java代码
的形式表示,元数据
中包含了组成应用程序的对象以及这些对象之间复杂的依赖关系。spring已经预设了一些ApplicationContext
接口的实现,如ClassPathXmlApplicationContext
和FileSystemXmlApplicationContext
等。下图展示了spring的工作原理视图:
2.1 配置元数据
开发者可以通过配置元数据
告诉spring容器如何实例化、配置和组装对象,传统情况下(spring2.5以前)以xml
格式表示(如果用springboot
的话一般不需要写配置文件,而是通过在标注了@Configuration
的类里添加带@Bean
的方法),spring配置由容器必须管理的至少一个或多个bean
定义组成。
Java configuration typically uses @Bean-annotated methods within a @Configuration class.
- 从spring2.5开始,spring框架开始支持注解配置模式
- 从spring3.0开始,spring框架开始支持代码配置模式
以下是一个xml格式配置元数据的例子:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 引入其他的xml文件 -->
<import resource="xxx.xml"/>
<import resource="resources/yyy.xml"/>
<import resource="/resources/zzz.xml"/>
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions go here -->
</beans>
2.2 实例化容器
ApplicationContext
的构造函数可以从参数(一个或多个字符串)中获取到外部资源路径,然后再从这些外部资源中读取配置元数据
,以下是通过ClassPathXmlApplicationContext
构造ApplicationContext
的例子:
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
除了xml
外,也可以通过dsl(Groovy)
定义bean。但是由于我没有在项目中实际使用过,所以在此不作举例。
2.3 容器的使用
ApplicationContext
是一个高级接口,它维护着不同的beans及其依赖
的注册表。通过方法
T getBean(String name, Class<T> requiredType)
可以获取到bean的实例。以下是一个使用容器的例子:
// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// use configured instance
List<String> userList = service.getUsernameList();
我们不仅可以通过getBean()
方法获取到bean
实例,spring还提供了一些其他的方法达到相同的目的,但是我们基本上用不到这些方法。而且按照官方的说法,我们甚至连getBean()
都不需要调用,业务逻辑不需要依赖spring的API。以web为例,推荐的做法是通过注解(@Service
、@Controller
等)声明bean
。
3. Bean概览
spring IoC
容器管理着许多通过配置元数据
创建的bean
。
对于容器本身而言,bean定义
被表示为BeanDefinition
对象,它包含了以下元数据:
- 包限定类名,指向bean的实际实现类;
- bean在容器中的行为,包括作用域、生命周期回调等;
- 使bean正常工作所需的其他bean的引用,即依赖;
- 其他配置,如连接池的连接数限制或大小限制等。
以下是一个bean定义
的例子:
<bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
<property name="accountDao" ref="accountDao"/>
<property name="itemDao" ref="itemDao"/>
<!-- additional collaborators and configuration for this bean go here -->
</bean>
3.1 Bean的命名规范
在spring容器中,每个bean至少有一个标识符,且每个标识符必须具有唯一性。一般情况下一个bean只会有一个标识符(使用id
属性),如果需要多个标识符的话,额外的标识符被称为alias(别名)
。
在xml格式的配置元数据中, 可以使用id
属性、name
属性或两者来指定 bean 标识符,两者都允许使用字母、数字、某些特殊字符来精确描述bean的特征。
id
和name
不是必填项,如果没有为bean指定id
和name
,容器会自动为bean生成一个唯一的name
。但是如果使用者需要通过ref元素
按name
查找bean,则必须手动指定bean的name
属性。
bean的命名遵循驼峰命名法
:首字母小写,其后每一个单词的首字母大写。比如:password、userAccount、fatSuperMan等。bean的名称最好与其作用相关,方便阅读和理解。
关于aliases
:
在大型系统中,相同的bean在每个子系统都有不同的对象定义,这时我们可以使用alias
来为bean定义别名:
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
在上面的例子中,对于同一个beanmyApp-dataSource
,子系统A通过subsystemA-dataSource
引用,子系统B通过subsystemB-dataSource
引用。以上配置使得myApp-dataSource
、subsystemA-dataSource
、subsystemB-dataSource
三个名称指向了同一个对象,并且不会与任何其它定义产生冲突。
3.2 实例化Bean
bean定义
本质上是用来创建一个或多个对象的,当容器被使用时就会使用bean定义
中封装的配置元数据
查找或创建对象。如果使用的是xml格式的配置元数据,则必须在BeanDefinition
对象中指定class
属性需要初始化的对象的类型,
内部类
如果要为静态嵌套类配置bean定义,则必须使用嵌套类的二进制名称。
例如,如果在com.example包中有一个名为SomeThing的类,并且此SomeThing类具有一个名为OtherThing的静态嵌套类,则bean定义上的class属性值将为com.example.SomeThing $ OtherThing。
请注意,在名称中使用$字符可以将嵌套类名与外部类名分开。
使用构造器实例化
通过构造方法创建bean时,所有普通类都可以使用并与spring兼容。也就是说,对于一个已经开发了一部分或已经完工,希望能使用spring框架的工程,所有已开发的类不需要实现额外的接口,只需要简单的在配置元数据
中指定bean即可。但是某些特定的bean使用的IoC类型可能需要在对应的类中添加默认的无参构造函数
。此外,Spring IoC容器也可以管理不符合JavaBean规范的类,例如,如果需要使用绝对不符合JavaBean规范的旧连接池,Spring也可以对其进行管理。
以下是一个使用构造器实例化bean的示例:
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
在该例子中,class
属性指定了返回对象的类型。
使用静态工厂方法实例化
当定义使用静态工厂方法创建的bean时,有两个关键属性需要声明:
class
属性指定指定包含静态工厂方法的类factory-method
属性指定工厂方法本身的名称
以下是一个使用静态工厂方法实例化bean的示例:
<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>
// 工厂bean对应的java类
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
在该例子中,class
属性指定的是包含工厂方法的类(而不是返回对象的类型),factory-method
属性则指定了工厂方法的名称。
使用实例工厂方法实例化
实例工厂方法进行实例化的原理是从容器调用现有bean的非静态方法来创建新bean。这种方法需要声明的关键属性如下:
factory-bean
属性指定当前容器(或父容器)中bean的名称,该容器包含要调用以创建对象的实例方法factory-method
属性指定工厂方法本身的名称
以下是一个使用实例工厂方法实例化bean的示例:
<!-- 包含createInstance()方法的工厂bean -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- 此处注入bean需要的依赖项 -->
...
</bean>
<!-- 通过工厂bean创建的bean,可以通过同一个工厂bean中不同的工厂方法创建不同的bean -->
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
<bean id="accountService"
factory-bean="serviceLocator"
factory-method="createAccountServiceInstance"/>
// 工厂bean对应的java类
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
private static AccountService accountService = new AccountServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
public AccountService createAccountServiceInstance() {
return accountService;
}
}
这种方法表明了工厂bean本身可以通过依赖注入(DI)进行管理和配置。
4. 依赖
A typical enterprise application does not consist of a single object. Even the simplest application has a few objects that work together to present what the end-user sees as a coherent application.
典型的企业应用不会只包含一个对象,即使是最简单的应用也一定是多个对象互相协同工作,最终呈现用户所看到的连贯应用程序。
4.1 依赖注入(DI)
依赖注入
是一个过程,通过这个过程,对象只能通过构造函数参数
,工厂方法的参数
或在被构造函数或工厂方法创建后返回的属性
来定义它们的依赖关系(即它们使用的其他对象),然后容器在创建bean时注入这些依赖项。这个过程基本上是就是创建bean的过程本身的反转(由此得到另一个名称IoC(控制反转)
),它通过使用类的结构
或服务定位器模式
来定位依赖项或控制依赖项的实例化。
遵循DI规范的代码逻辑更清晰,并且耦合度,更容易解耦。并且由于对象并不知道其依赖项的位置或者其具体的实现类,也不需要查找依赖项,单元测试也因此变得更容易编写。
DI存在两种主要变体:基于构造函数的依赖注入(Constructor-based dependency injection)
和基于Setter的依赖注入(Setter-based dependency injection)
。
基于构造函数的依赖注入
基于构造函数的依赖注入由容器调用具有多个参数的构造函数
来完成,每个参数表示一个依赖项。
以下是一个只能基于构造函数进行依赖注入的类:
public class SimpleMovieLister {
// SimpleMovieLister依赖于MovieFinder
private MovieFinder movieFinder;
// 一个构造函数,以便Spring容器可以注入一个MovieFinder
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// 实际使用注入的MovieFinder的业务逻辑...
}
这个类没有什么特别之处。 它是一个POJO,不依赖于spring容器特定的接口、基类或注释。
如果bean定义的构造函数参数中不存在潜在的歧义,那么在bean定义中定义构造函数参数的顺序就是在实例化bean时将这些参数提供给适当的构造函数的顺序。上例子:
package x.y;
public class ThingOne {
public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
// ...
}
}
<beans>
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg ref="beanTwo"/>
<constructor-arg ref="beanThree"/>
</bean>
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
</beans>
假设ThingTwo
和ThingThree
类与继承无关,则不存在潜在的歧义。那么以上xml配置就可以正常运行,不需要在<constructor-arg />
元素中显式的声明构造函数参数索引或类型
。
待续…
以上示例同样适用于调用具有特定参数的静态工厂方法
来构造bean。
单词
Inversion of Control 控制反转(IoC)
dependency injection 依赖注入(DI)
sub-interface 子接口
enterprise-specific 企业特定的
superset 超集
metadata 元数据
instantiate 实例化
annotation 注解
fine-grained 细粒度
alias 别名