IoC是spring框架的核心技术,AOP是IoC的一个应用案例,spring-aop可以解决java企业应用中80%的场景。spring集成最成熟的AspectJ(aop的增强)
1、IoC容器
1.1、Spring Ioc容器和beans
Spring Ioc即控制反转,也被称为依赖注入dependency injection (DI),通过定义对象的依赖关系,在创建对象的时候,容器注入它的依赖,可以通过属性、构造器参数、工厂方法参数注入依赖对象。
org.springframework.beans 和 org.springframework.context 两个包是Spring Ioc容器的基础包, BeanFactory
接口提供一种先进地机制来管理各种类型的对象,ApplicationContext
是BeanFactory的一个子接口,它增加如下的能力:
- 容易集成spring-aop特性
- Message resource 处理(国际化)
- Event应用
- 应用层面指定contexts,例如:在web应用环境中,
WebApplicationContext
简而言之,BeanFactory
提供结构框架和基础功能,ApplicationContext
提供更多企业级指定的功能,ApplicationContext
是BeanFactory
的一个超集,接下来将使用ApplicationContext
来描述Ioc容器
在Spring中 对象组成了应用的骨架,通过IoC管理。故Spring IoC被称作beans,即:Spring IoC容器管理应用的beans。一个bean就是一个对象,实例化、装配都是通过Spring IoC管理。反之,一个简单地bean是你应用中许多对象中的一个,他们相互依赖,通过反射来完成。
1.2、容器概述
这个接口org.springframework.context.ApplicationContext
代表了Spring IoC容器,并且负责实例化、配置、装配beans。容器获得指令,什么样的对象是实例化、配置、装配通过读取配置元数据,配置元数据是xml、java注解或java代码,它让你表达组成你应用程序之间对象的依赖。
Spring提供这个ApplicationContext
接口的一些实现类,在一个单独的应用程序中,通常是创建一个 ClassPathXmlApplicationContext
或 FileSystemXmlApplicationContext
的实例,在过去的一段时间里是使用xml方式配置元数据。你也能够通过java注解或java代码的方式配置元数据。
在大多数应用场景中,清晰的用户代码是没有必要实例化一个或者更多地Spring IoC容器,例如:在web场景中,一个简单的八行样板文件是在web.xml文件中(参照1.15.4节),如果使用 Spring Tool Suite ,你也能轻松的创建样板配置通过较少的按键和敲击键盘。
下面描述了Spring是怎样高水平工作的,应用程序类和配置元数据相结合,在之后ApplicationContext
是被创建和初始化,您有一个完全配置和可执行的系统或应用程序
1.2.1. 配置元数据
在上面的图片显示,Spring IoC容器消费配置元数据,配置元数据集代表着具体的领域对象,作为开发者,告诉Spring容器实例化、配置、装配你应用程序的对象。
配置元数据是传统的提供一个简单和直觉地xml格式,之后大部分的章节将使用xml的格式来传达Spring IoC的特性。
注意!配置元数据不仅仅只有xml的方式,还有java的方式,目前,大多数应用都是使用基于java的配置方式。
以下将列举Spring IoC容器使用其他格式的元数据:
- 基于注解的配置:Spring2.5支持基于注解的方式配置元数据
- 基于java的配置:从Spring3.0,Spring JavaConfig项目提供许多特性,成为了Spring框架的核心。因此,你能够在应用程序的外部定义bean通过java的方式而不是xml的方式。为了使用这个新特性,看 @Configuration, @Bean, @Import, 和 @DependsOn 注解。
Spring容器管理地Spring 配置必须有一个或者超过一个的bean定义组成。基于xml的元数据配置bean,
这些bean定义组成应用程序精确的对象,通常,你定义的服务层对象,数据访问层对象(DAOs),展现层对象如Struts Action实例,基础设施对象如Hibernate SessionFactories, JMS Queues等等。通常,在容器里面,没有细腻度的配置领域对象。因为它通常是DAOs和业务逻辑创建和加载领域对象(这就是DO、DTO、PO的由来吧),然而,你也能使用Spring集成的AspectJ注入对象.[ Using AspectJ to dependency-inject domain objects with Spring.
](https://docs.spring.io/spring-framework/docs/5.2.4.RELEASE/spring-framework-reference/core.html#aop-atconfigurable)。
如下展示了基于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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="..." class="..."> 1,2
<!-- 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>
1、id属性是一个字符串,标识了单个的bean定义 2、class属性定义bean的类型,使用全限定类名
id的值也是可以引用到协作的bean,这个例子没有显示引用到协作的bean,更多地看依赖Dependencies
1.2.2.实例化容器
ApplicationContext
构造器参数接收一个或者多个字符串的路径参数,容器加载配置元数据来自多样化地外部资源,例如:本地文件系统、java CLASSPATH等等。
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
在之后关于Spring IoC容器的学习,你可能想要了解关于Spring Resource的抽象,它提供了一个便利的机制读取一个输入流来自locations定义的一个URI语法。特别地,Resource path通常构造应用上下文,作为描述在Application Contexts and Resource Paths
下面的例子显示服务层对象(services.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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- services -->
<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>
<!-- more bean definitions for services go here -->
</beans>
下面的例子显示数据访问层对象(daos.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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="accountDao"
class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions for data access objects go here -->
</beans>
以上的例子,通过ref表达了bean直接的依赖关系
组成基于XML配置元数据
bean的定义可以跨域多个xml,这通常是有用。每一个单独的xml文件配置代表着一个逻辑层或者一个模块在你的应用中。
你能够使用应用上下文构造器加载来自多个的XML文件的bean定义,构造器获得多Resource定位。二者则一的,使用一个或者多个
<beans>
<import resource="services.xml"/>
<import resource="resources/messageSource.xml"/>
<import resource="/resources/themeSource.xml"/>
<bean id="bean1" class="..."/>
<bean id="bean2" class="..."/>
</beans>
之上的例子,额外的bean定义加载来自三个文件services.xml, messageSource.xml, 和 themeSource.xml。在
这是可能的,但是不推荐,引用文件在父类目录使用相对
../
路径,特别的,这个也是不推荐的classpath:
URLs,例如:classpath:../services.xml
,在运行的时候会选择最近的classpath根和查找在它的父目录,Classpath改变可能导致选择的目录错误。 推荐你使用全限定资源定位替代相对路径,例如:file:C:/config/services.xml
或classpath:/config/services.xml
,然而,你能结合你的应用配置绝对路径,例如:${…}
,这种方式在运行时是被替换的。
1.2.3. 使用容器
ApplicationContext
是一个高级工厂接口,它维护不同bean的登记和它们的依赖关系。使用这个方法T getBean(String name, Class<T> requiredType)
,你能够取回你的bean的一个实例。
ApplicationContext
这个接口让你读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();
一个最灵活多变的接口GenericApplicationContext
结合读代理,例如用XmlBeanDefinitionReader
读取xml文件bean定义,如下所示:
GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();
你能够混合相匹配诸如读代理在相同的ApplicationContext
,读bean定义从不同的来源。
你能够使用getBean
方法取回你的实例,这个接口ApplicationContext
也有一些其他的方法对于取回实例。但是,理想的做法,你的应用代码绝不应该直接使用它。确实,你的应用代码不应该根本地调用getBean()
这个方法,且在Spring APIs根本没有依赖。例如:Spring集成web框架提供依赖注入对多web框架组件,诸如,作为控制器、JSF管理bean,让你声明依赖在指定的bean通过元数据(例如:自动注入注解@autowire)
1.3. Bean 概述
Spring IoC 容器管理一个或多个bean,用你提供的配置元数据创建bean(例如:元数据格式是XML
在容器里面,这些bean定义是被描述作为BeanDefinition
对象。包含如下的信息:
- 全限定类名,通常,bean的定义精确的实现类
- bean行为配置元素,bean的状态是在容器中(scope,生命周期回调,等等)
- bean的工作需要引用其他bean,引用也是被其他合作者或依赖者调用。
- 其他地配置在新创建的对象,例如:池的大小或者连接数据在一个bean,管理一个连接池。 元数据转换到一个properties制造每一个bean定义,下面的表显示这个 properties
属性 | 翻译在 |
---|---|
Class | 实例化ben(后续加上) |
Name | 命名bean |
Scope | bean的域 |
Constructor arguments | 依赖注入 |
Properties | 依赖注入 |
Autowiring mode | 装配合作者 |
Lazy initialization mode | 懒实例化beans |
Initialization method | 初始化回调 |
Destruction method | 销毁回调 |
ApplicationContext
接口也允许登记一个存在的对象在容器之外,通过访问应用上下文BeanFactory接口的getBeanFactory()方法完成。BeanFactory接口的一个实现类DefaultListableBeanFactory
。DefaultListableBeanFactory
支持两种登记bean地方式,如:registerSingleton(..)和registerBeanDefinition(..)方法,然而,典型的应用工作是单独地用bean定义通过规则地bean定义元数据。
注意!,bean的元数据和手工单列的登记应该尽可能早的完成,为了容器能正确地处理它们自动装配期间和其他内省的步骤。在一定程度上重写元数据和存在的单例也是支持的,在运行时登记新的bean是非官方支持的,可能会导致并发访问异常、不一致地bean状态,或者两者同时发生。
#### 1.3.1. 命名地 Beans 每一个bean有一个或者多个标识符,这些标识符必须是独一无二地在容器里,一个bean通常仅仅有一个标识符,然而,如果超过一个,额外地能够考虑使用别名。
在基于xml的配置元数据,你能使用id属性,name属性,或者两者结合使用来指定bean的标识符,id属性让你指定精确地一个bean,通常,命名是遵循驼峰式命名规则(‘myBean’, ‘someService’, etc.),但是他们也能包含指定的字符,对于bean如果你要引入其他地别名,你可以指定它们的name属性,分隔是通过逗号 (,)
或者分号(;)
、或者空格。在Spring 3.1早期版本,id属性是定义作为xsd:ID
类型,为了约束字符串。在3.1,它定义了一个xsd:string
类型,注意bean id独一无二性仍然是能够通过容器完成,不在是通过xml解析来强制。
你是没有必要提供id或name对于一个bean,如果你没有清晰地提供一个id或name,对于这个bean容器能产生一个独一无二的名字。然而,如果你想要引用到这个bean通过名字,通过使用ref元素或者使用服务层定位风格查找(lookup),你必须提供一个bean地名字,对于不提供一个名字是关联到内部bean和自动装配协作者的应用场景。
bean命名的便利性
使用驼峰式的命名,如:
accountManager
,accountService
,userDao
,loginController
等 命名地bean约束,使得你的配置是容易读和理解地。如果你使用AOP,这是有益地,当使用advice设置到bean关联通过名字。
注意!,使用组件扫描在classpath下,Spring对没有命名的组件产生名字,获取简单类名并且把类名的第一个字母置为小写。然而,在特殊的场景下,超过一个字符且两者的第一个和第二个字符都是大写,源命名被保留,相同的规则是定义在
java.beans.Introspector.decapitalize
(Spring使用的规则)
在bean定义之外定义别名
在一个bean定义,你能够为这个bean提供一个或者多个名字,id和nanme联合使用,通过id指定一个名字,通过name指定多个名字,对于同一个bean的这些名字是等价于别名的,在一些场景这是有用的,例如:让应用中的每一个组件都通过bean名字引用到相同的依赖项,bean的名字是指定到组件他自己。
指定所有的别名对于bean实际定义总是有点不适合。然而,有时恰当地引入bean的别名,为其他地方的使用界定。这通常在大的系统场景下,配置通常是分散在各个子系统中,每一个子系统有他自己定义的bean,在基于xml的元数据配置,你能够使用<alias/>
元素完成,如下:
<alias name="fromName" alias="toName"/>
如上例子,在同一个容器中,一个bean的命名也可能是fromName,在之后使用这个别名界定,引用到toName(有效解决了同一个容器相同bean的引用错误问题)。
例如,配置元数据对于子系统A可能引用到一个DataSource通过名字subsystemA-dataSource
,配置元数据对于子系统B可能引用到一个DataSource通过名字 subsystemB-dataSource
,当组合两个子系统变成主系统时,主系统引用DataSourc通过名字myApp-dataSource
,三个名字引用到同一个对象,你能够增加如下的别名定义到配置元数据中:
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
现在每一个组件和主系统都能够通过独一无二的名字引用到dataSource,并且保证不也其他bean定义冲突,他们是引用到相同的对象(有效的创建了一个命名空间)。
java 配置
如果你使用java配置,@Bean这个注解也是能够提供别名的,更多的看以下章节。
#### 1.3.2. Beans实例化 一个bean定义本质是一个秘方,能够创建一个或者多个对象。容器在被询问时查看命名bean的菜谱,并使用该bean定义封装的配置元数据创建(或获取)实际对象。
如果使用基于xml的配置元数据,你需要指定对象的类型(class),这样就可以实例化对象。在
- 通常,指定bean的class属性是为了构建对象,容器通过反射调用它的构造器创建对象,和java代码里面new一个对象很相似。
- 指定精确地class包含静态工厂方法对于创建对象,之后容器调用静态工厂方法创建对象,返回的对象类型来自静态工厂方法可能是相同的类或者其他完整类。
内部类命名
如果你想配置一个静态内部类的bean定义,你不得不使用二元命名内部类。
例如,在包com.example下有一个类是
SomeThing
,并且这个类有一个静态内部类是OtherThing
,那么在bean定义上class属性值为:com.example.SomeThing$OtherThing
。
注意,使用
$
字符是为了区分内部类来自外部类。
实例化使用构造器
当你创建一个对象通过构造器的方式,所有的普通类都是兼容Spring。即,类的开发者不需要实现指定的接口或者指定风格的代码。简单的指定bean的class即可。然而,这取决于您对特定bean使用的IoC类型,你可能需要提供一个默认(为空)构造器。
Spring IoC容器可以管理任何你想要它管理的类。这是没有限制的,对于管理true的JavaBean。大多数Spring使用者更喜欢精确的JavaBeans用一个默认无参构造器和模式化的getters和setters在属性之后,你也能有外部的非bean风格的类在容器中。例如:你需要使用传统的连接池,它完全没有附着到指定的JavaBeans,Spring也能管理他。
基于xml配置元数据,你能够指定如下的bean 类
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
更多详细的关于提供构造器参数(如果必须)和在对象被构建后设置对象实例属性。看依赖注入章节。
实例化用静态工厂方法
当定义了一个bean,你创建通过静态工厂方法,使用class属性指定这个类,它包含静态工厂方法,使用factory-method
这个属性指定class属性指定类里面的静态工厂方法名。你也能够调用这个工厂方法(用可选的参数,在之后讨论)返回一个对象(单例、多例等域对象)。
下面的bean定义指定这个bean的创建是通过调用静态工厂方法。这个定义没有指定方法的对象类型,仅仅这个类包含工厂方法。例如:这个createInstance()
方法必须是一个静态方法,以下例子显示了如何定义一个静态工厂方法:
<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>
具体的类如下:
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
对于更多提供可选工厂方法参数和设置对象实例属性,对象是从工厂方法返回,更多的看依赖和配置详情章节
实例化使用实例工厂方法
和静态工厂方法相似,一个实例工厂方法调用容器创建的一个存在bean的非静态工厂方法实例化对象。要使用这个机制,class
属性丢弃,指定factory-bean
属性,指定的这个名字在当前容器中(或者父)且包含这个实例方法对于创建对象。
通过属性factory-method
指定实例工厂的方法名。下面的例子显示怎样配置的这个bean:
<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<!-- the bean to be created via the factory bean -->
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
下面的例子显示协作的类:
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
}
工厂类也能够持有超过一个的实例工厂方法,如下显示:
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
<bean id="accountService"
factory-bean="serviceLocator"
factory-method="createAccountServiceInstance"/>
协作类如下:
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也能够被管理和配置依赖注入,看依赖和配置详情章节。
在Spring的文档,
factory bean
引用到容器中的一个bean是被配置,并且创建是通过静态和非静态工厂方法。相比之下,FactoryBean
(注意大小写)引用到Spring指定的FactoryBean
依赖关系
一个企业级的应用中通常不是由一个单个对象组成(或者是Spring的一个bean),甚至一个简单的应用都有少数的对象组成,他们相互联系。下面的章节将解释你如何定义一些数量的bean,使得这些bean相互协作来完成目标。
1.4.1. 依赖注入
依赖注入(DI)是一个凭借对象定义他们的依赖(即:他们工作的其他对象)仅仅通过构造器参数,工程方法参数,或属性,在对象被创建或者是从工厂方法返回后设置这个对象上的实例的一个流程。当容器创建了这个bean因而注入他相关的依赖,这个流程是基本的反转(因而, Inversion of Control),bean自己控制它自己的实例化或定位它的依赖通过使用直接构造器或者服务定位模式。
使用DI能使代码更简洁,当提供对象及其依赖项解耦是更有效的。对象不需要查找它的依赖和不需要知道定位或类的依赖,作为一个结果,你的类是容易测试的,特别的上当依赖在基于接口或抽象类的时候,允许mock的实现对于单元测试。
DI有两种方式,基于构造器的依赖注入和基于setter依赖注入。
基于构造器的依赖注入
基于构造器的DI是这样完成的,通过容器调用有一定数量参数的构造器,每一个参数代表一个依赖。和调用指定参数的静态工厂方法构造的bean是等价的,这个讨论对待的参数和构造器、静态工厂方法相似。下面的例子显示使用构造器依赖注入:
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on a MovieFinder
private MovieFinder movieFinder;
// a constructor so that the Spring container can inject a MovieFinder
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
注意,上面的这个例子类没有什么特别之处,就是一个POJO,不依赖容器特定的接口、基类或注解
构造器参数注入依赖
构造器参数注入通过匹配参数的类型注入,如果没有潜在不明确构造器参数存在bean定义。构造器参数的顺序是定义在bean的定义,当bean实例化的时候能匹配到具体参数顺序的构造器进行注入。考虑如下的类:
package x.y;
public class ThingOne {
public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
// ...
}
}
假设类ThingTwo
和ThingThree
是没有继承相关联的,没有潜在的不明确存在。因此,下面的配置是工作良好的,你不需要在<constructor-arg/>
元素上指定明确的构造器参数顺序或明确的类型
<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>
当引用另外一个bean时,类型是已知的,匹配能够发生(就像前面的例子一样)。当一个简单类型被使用,例如:<value>true</value>
,Spring不能够决定这个类型的值,所有不能够通过类型来匹配,考虑如下的类:
package examples;
public class ExampleBean {
// Number of years to calculate the Ultimate Answer
private int years;
// The Answer to Life, the Universe, and Everything
private String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
构造器参数类型匹配
在上面的场景,如果你清晰地的指定构造器参数类型通过使用type
属性,容器能够通过简单类型匹配使用的类型。如下显示:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
除了解决含糊不清的多个简单类型值外,指定一个索引解决含糊不清的两个相同类型的构造器参数类型。
注意,索引是从0开始的。
构造器参数命名
你也能够使用构造器参数命名消除歧义,如下:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
请记住,为了使这个工作在盒子之外,你的代码必须编译在debug模式,以至于Spring能够查找构造器的参数名字。如果你不想编译你的代码用debug模式,你能够使用JDK的注解@ConstructorProperties
清晰的指定构造器的参数。如下显示怎么样做:
package examples;
public class ExampleBean {
// Fields omitted
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
基于Setter的依赖注入
基于Setter的DI注入是通过调用无参构造器或静态无参工厂方法实例化bean后调用bean的setter方法完成。
下面的例子显示一个类是仅仅通过基于setter的注入,这是一个传统的POJO,他不依赖于容器特定的接口、基类、或注解来完成。
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on the MovieFinder
private MovieFinder movieFinder;
// a setter method so that the Spring container can inject a MovieFinder
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
ApplicationContext
容器管理的bean是支持基于构造器和基于setter的依赖注入(DI),在一些依赖已经通过构造器的方式注入后,他也支持基于setter的DI。你也可以以BeanDefinition
的形式配置依赖项。你能够结合PropertyEditor
实例变换属性从一个格式到另外一个格式。然而,Spring的使用者是不直接使用这些类工作的(即:编程的方式)宁愿用xml的bean定义,注解组件(即:@Component
, @Controller
等等),或者在基于java的@Configuration
类下的@Bean
注解方法。这些资源在容器内部转换为BeanDefinition
的一个实例,并用于加载整个Spring IoC容器实例。
基于构造器或基于setter的DI?
自从你可以混合使用基于构造器或者基于setter的注入后,这是一个很好的实践经验,对于强制的依赖使用构造器,对于可选的依赖使用setter方法或者配置方法。注意使用这个注解
@Required
在setter方法上使得属性是必须的依赖的。然而,使用编程化的校验构造器参数的注入更可取。
Spring团队通常主张使用构造器的注入方式,因为它让你实现应用组件为不可变对象,并且保证必须的依赖是不为空的。此外,基于构造器的组件注入总是返回给客户端代码一个完整实例化的状态,值得注意的是,大量的构造器参数是坏代码的味道,意味着这个类可能有太多的职责,应该进行重构,以更好地解决关注事项的适当分离。
Setter注入应该主要用于可选的依赖项,这些依赖项可以在类中分配合理的默认值。否则,非空检查必须在代码使用依赖项的任何地方执行。setter注入的一个好处是,setter方法使该类的对象能够在以后重新配置或重新注入。通过JMX mbean进行管理是setter注入的一个引人注目的用例。
使用对特定类最有意义的DI风格。有时,在处理你没有源码的第三方类时,这个选择是为你做出。例如:如果第三方类没有暴露任何的setter方法时,因而基于构造器的注入仅仅是可获得的形式。
依赖解析流程
容器执行bean的依赖解析如下:
- 使用所有描述bean的配置元数据初始化并创建
ApplicationContext
实例,配置元数据能够通过xml、java代码、注解指定。 - 对于每一个bean,它的依赖是表现在属性、构造器参数、静态工厂方法参数的形式,当这个bean是被实际的创建的时候,这些依赖是提供到这个bean。
- 每一个属性、构造器参数值都是被精确的设置,或者引用到其他bean在容器。
- 属性和构造器参数值能够转换到指定的精确类型,默认地, Spring能转换提供的字符串格式的到所有内建的类型,例如:
int
,long
,String
,boolean
等等。
Spring容器在创建时验证每一个bean的配置。然而,在实际创建bean之前,不好设置bean的属性。当容器创建时,Beans是被单例的并且预初始化是被创建。域是定义在Bean Scopes
。否则,仅仅在bean是被请求时才创建。创建一个bean可能导致创建一个bean的图(连锁创建多个bean),如它的依赖和它依赖的依赖是被创建和分配。请注意,这些依赖项之间的解析不匹配可能在后期出现,即:受影响的bean第一次创建时出现。
循环依赖
如果你主要使用构造器依赖注入,这是可能创建一个无法解决的循环依赖场景。
例如:A类需要一个实例化的B类,通过构造器注入,并且B类需要一个实例化的A类通过构造器注入。如果你将A类和B类配置为互相注入,Spring IoC容器在运行时能够发现这个循环引用。并且抛出
BeanCurrentlyInCreationException
异常。
一种可能解决的方式是,编辑源码的一些类使用setters注入而不是构造器注入。或者,避免使用构造器注入而仅仅使用setter注入。换句话说,尽管这不是推荐的做法,你能够配置循环依赖用setter注入。(也就是说构造器注入有循环依赖问题,setter则不需要关心,都可以注入)
不像典型的案例(没有循环依赖),一个循环依赖在A和B直接,迫使其中的一个bean优先初始化他自己注入到另外一个bean。
你通常应该相信Spring能够做正确的事情。它发现问题,例如:引用bean不存在和循环依赖,在容器加载阶段。当bean是真正创建时Spring尽可能晚地设置属性和解析依赖。这意味着,当你请求一个对象,如果对象创建有一个问题或者是在依赖创建的时候,正确加载的Spring容器能够产生异常。例如:这个bean抛出一个无效或丢失属性。这潜在的延迟一些配置问题的出现,这就是为什么ApplicationContext
默认预实例化bean原因。在之后真正需要的时候在花费预付的时间和内存,当你真正创建ApplicationContext
时你能发现一些配置问题。你仍然能够重写默认行为, 使得bean是懒初始化,而非预实例化。
如果没有循环依赖存在,当一个或者多个协作的bean是注入到一个依赖bean时,每个协作bean在被注入到依赖bean之前都已完全配置。这意味着,如果bean A有一个依赖bean B,Spring IoC 容器调用A上的setter方法优先完成B的配置。换一句话说,bean实例化(如果不是一个预实例化的单例),它的依赖是设置的,并且相关生命周期方法是被调用(例如:init初始化方法或者 InitializingBean callback method)
依赖注入的例子
如下样例显示基于xml的setter的依赖注入配置。xml的一部分是bean的定义配置:
<bean id="exampleBean" class="examples.ExampleBean">
<!-- setter injection using the nested ref element -->
<property name="beanOne">
<ref bean="anotherExampleBean"/>
</property>
<!-- setter injection using the neater ref attribute -->
<property name="beanTwo" ref="yetAnotherBean"/>
<property name="integerProperty" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
下面的例子显示了一致的ExampleBean
类:
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public void setBeanOne(AnotherBean beanOne) {
this.beanOne = beanOne;
}
public void setBeanTwo(YetAnotherBean beanTwo) {
this.beanTwo = beanTwo;
}
public void setIntegerProperty(int i) {
this.i = i;
}
}
上面的例子,setter被声明为与XML文件中指定的属性相匹配,下面的例子显示使用基于构造器的依赖注入:
<bean id="exampleBean" class="examples.ExampleBean">
<!-- constructor injection using the nested ref element -->
<constructor-arg>
<ref bean="anotherExampleBean"/>
</constructor-arg>
<!-- constructor injection using the neater ref attribute -->
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg type="int" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
下面的例子显示了相一致的类:
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public ExampleBean(
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
this.beanOne = anotherBean;
this.beanTwo = yetAnotherBean;
this.i = i;
}
}
bean定义中指定的构造器参数通常是ExampleBean
类中的构造器参数。
现在考虑一个多样的例子,这儿,不是使用构造器的注入,Spring容器调用一个静态工厂方法返回一个实例对象。
<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
<constructor-arg ref="anotherExampleBean"/>
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
下面的例子显示相一致的类:
public class ExampleBean {
// a private constructor
private ExampleBean(...) {
...
}
// a static factory method; the arguments to this method can be
// considered the dependencies of the bean that is returned,
// regardless of how those arguments are actually used.
public static ExampleBean createInstance (
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
ExampleBean eb = new ExampleBean (...);
// some other operations...
return eb;
}
}
静态工厂方法的参数通过<constructor-arg/>
元素提供,就像实际上使用了构造函数一样。工厂方法返回的类的类型不必与包含静态工厂方法的类的类型相同(尽管在本例中是相同的)。实例(非静态)工厂方法可以以本质上相同的方式使用(除了使用工厂bean属性而不是class属性),详细的我们在这里不讨论。
1.4.2. 依赖和配置详情
作为在前面的章节提及的,你能够定义bean的属性和构造器参数引用其他管理的bean(协作)或内联作为值定义。基于xml的元数据配置支持在bean下配置<property/>
和constructor-arg/>
元素来达到目的。
直接指定值(元数据类型,String等等)
这个value
属性,你可以指定属性和构造器参数的值。Spring反转服务通常反转字符串String
的值到精确的属性或参数类型。下面的例子显示多样化的值是被设置:
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- results in a setDriverClassName(String) call -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="masterkaoli"/>
</bean>
下面的例子使用 p-namespace
简化xml的配置:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://localhost:3306/mydb"
p:username="root"
p:password="masterkaoli"/>
</beans>
上面的xml是最简洁的。然而,打字错误是在运行时发现而不是在编写时,除非你使用IDE(如:IntelliJ IDEA
或者 Spring Tool Suite
)支持属性自动完成,当你创建bean定义时。例如:IDE会有高的援助推荐。
你也能够配置一个java.util.Properties
实例,如下:
<bean id="mappings"
class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<!-- typed as a java.util.Properties -->
<property name="properties">
<value>
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
</value>
</property>
</bean>
Spring容器反转<value>
元素里面的值进入java.util.Properties
实例通过使用PropertyEditor
类完成。这是非常简短的,它是Spring团队喜欢使用嵌套
idref元素
idref元素只是将容器中另一个bean的id(字符串值——而不是引用)传递给
<bean id="theTargetBean" class="..."/>
<bean id="theClientBean" class="...">
<property name="targetName">
<idref bean="theTargetBean"/>
</property>
</bean>
上面的bean定义是等价于下面的例子:
<bean id="theTargetBean" class="..." />
<bean id="client" class="...">
<property name="targetName" value="theTargetBean"/>
</bean>
第一种形式比第二种好,使用idref
标签让容器在部署时校验引用,命名的bean精确的存在。第二种方式,在客户端的bean上的targetName
属性的值没有在执行之前校验。在客户端
bean的方式只有在初始化是才能发现打字错误。如果客户端bean是prototype
bean,这个打字错误和异常只能在容器部署的后才能发现。
注意
idref
元素local
属性在4.0 beans XSD后不在支持,因为它不再提供超过常规bean引用的值,当你升级到4.0 xml模式时,改变你存在的idref local
元素到idref bean
。
这个元素<idref/>
带来的值是在 AOP 拦截
的ProxyFactoryBean bean定义中配置(至少在Spring2.0之前版本),使用<idref/>
元素你能够指定拦截器ID来检查拼写错误。
引用到其他bean(协作)
ref
元素是在<constructor-arg/>
和<property/>
里面,这里,设置属性指定的bean值是引用到容器中的另外一个bean,被引用的bean是要设置其属性的bean的依赖项,在设置属性之前,根据需要对其进行初始化(如果协作的bean是单例,可能容器早已实例化)。所有引用最终都是对另外一个对象的引用。域或验证取决于你指定DI或命名的其他对象通过bean
或parent
属性。
。。。。 内部bean
元素
<bean id="outer" class="...">
<!-- instead of using a reference to a target bean, simply define the target bean inline -->
<property name="target">
<bean class="com.example.Person"> <!-- this is the inner bean -->
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
</bean>
</property>
</bean>
内部bean是没有必要定义一个ID或名字(name)。如果指定,容器不使用这样的值作为标识符。在创建bean时容器也是忽略域(scope)标识,因为内部bean总是匿名并且是通过外部bean创建的。这是不可能在封闭的bean中单独的访问内部bean或者是注入他们到协作的bean中。
最为一个特例,这是可能受到一个来做定制的域的销毁回调,例如:一个单例bean里面包含一个请求域的内部bean。内部bean实例的创建与包含它的bean绑定在一起,但是销毁是让他参与在请求域的生命周期里。这不是常见的情况,内部bean通常会共享包含他们bean的域。
集合
,
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- results in a setAdminEmails(java.util.Properties) call -->
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.org</prop>
<prop key="support">support@example.org</prop>
<prop key="development">development@example.org</prop>
</props>
</property>
<!-- results in a setSomeList(java.util.List) call -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!-- results in a setSomeMap(java.util.Map) call -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key ="a ref" value-ref="myDataSource"/>
</map>
</property>
<!-- results in a setSomeSet(java.util.Set) call -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>
映射键或值的值,或设置值,也能够是以下任意元素:
bean | ref | idref | list | set | map | props | value | null
集合合并
Spring容器也支持合并集合,开发者能够定义一个, <map/>,
, <map/>,
这个章节是在讨论合并父-子元素机制。读者可能不熟悉父和子bean的定义,可以读相关章节在继续如下内容。
如下的例子演示集合合并:
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.com</prop>
<prop key="support">support@example.com</prop>
</props>
</property>
</bean>
<bean id="child" parent="parent">
<property name="adminEmails">
<!-- the merge is specified on the child collection definition -->
<props merge="true">
<prop key="sales">sales@example.com</prop>
<prop key="support">support@example.co.uk</prop>
</props>
</property>
</bean>
<beans>
注意,在子bean的属性名字adminEmails
里面的
administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk
子Properties集合值继承所有父<props/>
元素下的所有属性,子集合的support
值重写父集合的值。
合并行为是和这些集合类型相似的, <map/>, and
<list/>
元素,语义是关联到集合List
类型(即,值的顺序概念)是维护,父集合值的顺序高于所有子集合的值顺序,在Map, Set, 和 Properties集合类型的案例,没有顺序存在。然而,没有顺序语义是对集合有影响,对于容器内部使用的关联Map、Set和Properties实现类型的集合类型,没有有效的排序语义。
集合合并局限性
你不能够合并不同类型的集合(例如Map和List)。如果你想尝试这样做,一个恰当的异常是抛出。merge
属性必须指定在子bean定义上。指定merge
属性在父bean定义上是多于的,并且不会产生期望合并的结果。
强类型集合
在Java 5版本引入了泛型,你能够使用强类型集合。也就是说,声明一个集合Collection
类型,它仅仅包含String
的元素,如果你使用Spring依赖注入一个强类型的Collection
集合进入一个bean,您可以利用Spring的类型转换支持,在将强类型Collection实例的元素添加到Collection之前,将其转换为适当的类型,下面的java类和bean定义显示怎么做:
public class SomeClass {
private Map<String, Float> accounts;
public void setAccounts(Map<String, Float> accounts) {
this.accounts = accounts;
}
}
<beans>
<bean id="something" class="x.y.SomeClass">
<property name="accounts">
<map>
<entry key="one" value="9.99"/>
<entry key="two" value="2.75"/>
<entry key="six" value="3.99"/>
</map>
</property>
</bean>
</beans>
当这个bean something
的这个属性accounts
准备注入时,关于强类型Map<String, Float>的元素类型的泛型信息可以通过反射获得。因此, Spring的类型转换基础结构将各种值元素识别为Float类型,并且string的值(9.99, 2.75, and 3.99)是被转换到一个精确的Float
类型。
null和空字符值
Spring处理properties的空参数到一个空的字符串,基于xml的配置元数据片段,设置email
属性到一个空的String
值(“”)。
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
上面的例子是等价于下面的java代码:
exampleBean.setEmail(null);
使用p-名称空间的XML快捷方式
p-namespace允许您使用bean元素的属性(而不是嵌套的
Spring支持基于XML Schema定义的名称空间的可扩展配置格式。本章讨论的bean配置格式是在XML Schema文档中定义的。但是,p-namespace并没有在XSD文件中定义,它只存在于Spring的核心中。
以下例子显示两个xml片段(第一个使用标准的xml,第二个使用p-namespace)解析到相同的结果:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="classic" class="com.example.ExampleBean">
<property name="email" value="someone@somewhere.com"/>
</bean>
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="someone@somewhere.com"/>
</beans>
这个例子显示了在bean定义中用p-namespace定义了bean的一个属性email
。告诉spring包含一个属性声明。正如前面所提到的,这个p-namespace没有一个schema定义,所有你能够设置属性的名称到java的属性名称。
如下的例如包含超过两个的bean定义,两者都引用到另外一个bean:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="john-classic" class="com.example.Person">
<property name="name" value="John Doe"/>
<property name="spouse" ref="jane"/>
</bean>
<bean name="john-modern"
class="com.example.Person"
p:name="John Doe"
p:spouse-ref="jane"/>
<bean name="jane" class="com.example.Person">
<property name="name" value="Jane Doe"/>
</bean>
</beans>
这个示例不仅包含使用p-namespace的属性值,而且还使用一种特殊的格式来声明属性引用。第一个bean定义使用
p-namespace不像标准XML格式那样灵活。例如,声明属性引用的格式与以Ref结尾的属性冲突,而标准XML格式不会。我们建议您谨慎地选择您的方法,并与您的团队成员进行沟通,以避免生成同时使用所有三种方法的XML文档。
使用c-namespace的XML快捷方式