1.4.2。依赖性和详细配置
如上一节所述,您可以将bean属性和构造函数参数定义为对其他托管bean(协作者)的引用或内联定义的值。Spring的基于XML的配置元数据为此目的在其定义元素中的最后一个元素。在这里,您将bean的指定属性的值设置为对容器管理的另一个bean的引用。被引用的bean是要设置其属性的bean的依赖项,并且在设置属性之前根据需要初始化它(如果协作者是单例bean,它可能已经被容器初始化了)。所有引用最终都是对另一个对象的引用。范围和验证取决于是否通过bean或父属性指定其他对象的ID或名称。
通过标记的bean属性指定目标bean是最通用的形式,它允许创建对同一容器或父容器中任何bean的引用,而不管它是否在同一XML文件中。bean属性的值可以与目标bean的id属性相同,或者与目标bean的name属性中的一个值相同。下面的例子展示了如何使用ref元素:
//someBean 可以是bean的id 也可以是bean的name
<ref bean="someBean"/>
ref元素的local属性在ref4.0 Bean XSD中不再受支持,
因为它不再提供常规bean引用上的值。升级到4.0模式时,将现有ref local引用更改ref bean为。
集合
、、、元素分别设置Java集合类型list、set、map、properties的属性和参数。下面的例子展示了如何使用它们:
<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>
map的key或value,或set的value 也可以是以下任意元素:
bean | ref | idref | list | set | map | props | value | null
集合合并
Spring容器还支持合并集合。应用开发者可以定义一个父元素、、或,让子元素、、或继承和覆盖父元素集合的值。也就是说,子集合的值是合并父集合和子集合的元素的结果,子集合元素覆盖父集合中指定的值。
关于合并的这一节讨论父-子bean机制。不熟悉父bean和子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属性的元素上使用了merge=true属性。当容器解析并实例化子bean时,生成的实例具有一个adminEmails属性集合,该集合包含将子bean的adminEmails集合与父bean的adminEmails集合合并的结果。下面的清单显示了结果:
administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk
子属性集合的值集继承父属性中的所有属性元素,子属性支持值的值覆盖父属性集合中的值。
这种合并行为类似地应用于、和集合类型。在元素的特定情况下,将维护与列表集合类型(即值的有序集合的概念)相关联的语义。父元素的值位于所有子元素列表的值之前。对于Map、Set和Properties集合类型,不存在排序。因此,对于位于容器内部使用的关联映射、集合和属性实现类型下的集合类型,排序语义不起作用。
集合合并的局限性
您不能合并不同的集合类型(例如Map和List)。如果尝试这样做,将会抛出异常。
该merge属性必须在下面的继承的子集合定义中指定。
merge在父集合定义上指定属性是多余的,不会导致所需的合并。
强类型集合
随着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 的accounts属性时,
可以通过反射获得something有关强类型的元素类型的泛型信息Map<String,Float>。
因此,Spring的类型转换基础结构将各种值元素识别为type Float,
并将字符串值(9.99, 2.75和 3.99)转换为实际Float类型。
空字符串值和空字符串值
Spring将属性之类的空参数视为空字符串。以下基于xml的配置元数据片段将email属性设置为空字符串值("")。
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
前面的示例等效于以下Java代码:
exampleBean.setEmail("");
该元素处理null的值。以下清单显示了一个示例:
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
前面的配置等效于下面的Java代码:
exampleBean.setEmail(null);
带有p-名称空间的XML快捷方式
p-名称空间允许您使用bean元素的属性(而不是嵌套的元素)来描述与之合作的bean的属性值,或者两者都使用。
Spring支持带有名称空间的可扩展配置格式,名称空间基于XML模式定义。本章中讨论的bean配置格式是在XML模式文档中定义的。但是,p-名称空间没有在XSD文件中定义,只存在于Spring的核心中。
下面的示例显示了两个解析为相同结果的XML片段(第一个使用标准XML格式,第二个使用p-名称空间):
<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>
该示例显示了email在bean定义中调用的p-namespace中的属性。这告诉Spring包含一个属性声明。如前所述,p名称空间没有架构定义,因此可以将属性名称设置为属性名称。
下一个示例包括另外两个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-名称空间的属性值,而且还使用一种特殊格式来声明属性引用。第一个bean定义使用来创建一个从bean john到bean jane的引用,第二个bean定义使用p:spouse-ref="jane"作为一个属性来完成完全相同的工作。在本例中,spouse是属性名,而-ref部分表明这不是一个直接的值,而是对另一个bean的引用。
p-名称空间不如标准XML格式灵活。
例如,声明属性引用的格式与以Ref结尾的属性冲突,而标准XML格式不会。
我们建议您仔细选择您的方法,并与您的团队成员沟通,
以避免同时生成使用所有三种方法的XML文档。
The p-namespace is not as flexible as the standard XML format.
For example, the format for declaring property references clashes
with properties that end in Ref,whereas the standard XML format does not.
We recommend that you choose your approach carefully
and communicate this to your team members to avoid producing XML documents
that use all three approaches at the same time.
这段没看懂,这里测试了以Ref结尾的属性也是可以用 p:xxxRef-ref
具有c-namespace的XML快捷方式
与使用p-namespace的XML快捷方式类似,Spring 3.1中引入的c-namespace允许使用内联属性配置构造函数参数,而不是嵌套构造函数参数元素。说白了就是p-namespace替换property,c-namespace替换constructor-arg
下面的示例使用c:名称空间执行与 基于构造函数的依赖注入相同的操作:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c" <!-- 新增该条 -->
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
<!-- 带有可选参数名称的传统声明 -->
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg name="thingTwo" ref="beanTwo"/>
<constructor-arg name="thingThree" ref="beanThree"/>
<constructor-arg name="email" value="something@somewhere.com"/>
</bean>
<!-- 带有参数名称的c-名称空间声明 -->
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>
</beans>
对于构造函数参数名不可用的罕见情况(通常是在编译字节码时没有调试信息的情况下),可以使用回退到参数索引,如下所示:
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
c:_2="something@somewhere.com"/>
复合属性名称 一个bean中嵌套另外一个bean 并需要给内部的bean赋值
在设置bean属性时,可以使用复合或嵌套属性名,只要路径的所有组件(最终属性名除外)都不为空。考虑下面的bean定义:
<bean id="something" class="things.ThingOne">
<property name="fred.bob.sammy" value="123" />
</bean>
所述something bean具有fred属性,该属性具有bob属性,
bob属性具有sammy 特性,并且最终sammy属性被设置为值123。
something bean的fred属性和fred的bob属性在构造bean之后一定不能为null。
否则,将会引发NullPointerException。
1.4.3。使用depends-on
如果一个bean是另一个bean的依赖项,则通常意味着将一个bean设置为另一个bean的属性。通常,您可以使用基于XML的配置元数据中的 元素来完成此操作。
但是,有时bean之间的依赖性不太直接。一个示例是何时需要触发类中的静态初始值设定项,例如用于数据库驱动程序注册。该depends-on属性可以在初始化使用此元素的bean之前显式强制初始化一个或多个bean。以下示例使用该depends-on属性表示对单个bean的依赖关系:
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
要表达对多个bean的依赖性,就需要用逗号隔开多个名称
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
<property name="manager" ref="manager" />
</bean>
<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
depends-on属既可以指定初始化时间依赖项,
也可以指定对应的销毁时间依赖项(仅在单例bean中)。被依赖的bean会晚于依赖bean之后销毁
<bean id="serviceOneRef" name="serviceOneName" class="org.springframework.example.service.ServiceOne"
destroy-method="destroyed"/>
public void destroyed(){
System.out.println("ServiceOne destroy");
}
<bean id="serviceTwo" class="org.springframework.example.service.ServiceTwo"
p:name="asdfasdf" depends-on="serviceOneRef"
destroy-method="destroyed" />
public void destroyed(){
System.out.println("serviceTwo destroy");
}
console:
serviceTwo destroy
ServiceOne destroy
1.4.4。懒加载bean
默认情况下,作为初始化过程的一部分,ApplicationContext实现会急切地创建和配置所有的单例bean。通常,这种预实例化是可取的,因为配置或周围环境中的错误是立即发现的,而不是几小时甚至几天后发现的。
当这种行为不合适时,您可以通过将bean定义标记为延迟初始化来防止单例bean的预实例化。延迟初始化的bean告诉IoC容器在第一次请求bean实例时(而不是在启动时)创建bean实例。
在XML中,这种行为是由元素的lazy-init属性控制的,如下面的例子所示:
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>
当ApplicationContext使用前面的配置时,
lazy bean不会在ApplicationContext启动时急切地预实例化,
not.lazy bean则会被急切地预实例化。
然而,当懒加载的bean是非懒加载的单例bean的依赖项时,ApplicationContext在启动时则会创建懒加载的bean,因为它必须满足单例的依赖项。
您还可以通过使用元素的primary设置为true,将其指定为主候选bean定义。
1.放弃自动装配,改成指定bean注入 @Qualifier 或者 xml的ref属性都可以
2.
<bean id="serviceOne" class="org.springframework.example.service.ServiceOne" />
<bean id="serviceTwo" class="org.springframework.example.service.ServiceTwo" />
两个bean都继承同一个接口CusService,如果有自动装配如下
@Autowired
private CusService service;
则启动时候会报错
如果给serviceOne增加属性autowire-candidate="false"
<bean id="serviceOne" class="org.springframework.example.service.ServiceOne" autowire-candidate="false" />
则所有的自动装配CusService的接口都会优先装配serviceTwo
3.情况同2 还可以将serviceTwo 增加primary="true"
<bean id="serviceTwo" class="org.springframework.example.service.ServiceTwo" primary="true" />
从自动装配中排除Bean
在每个bean的基础上,您可以从自动装配中排除一个bean。在Spring的XML格式中,将元素的autowire-candidate设置为false。容器使得特定的bean定义对自动装配基础设施不可用(包括注释风格配置,如@Autowired)。
autowire-candidate属性被设计为只影响基于类型的自动装配。
它不影响按名称的显式引用,即使指定的bean没有标记为自动装配候选,
也会解析显式引用。因此,如果名称匹配,按名称自动装配仍然会注入一个bean。
元素在其default-autowire-candidates属性接收一个patterns 字符串,意思是根据patterns 字符串匹配到的所有合格的beanName的autowire-candidates会被设置为true,不合格的会被设置为false
patterns 字符串接受一个或多个匹配模式,多个patterns 字符串之间可以用逗号隔开。 例如 (Repository,Service,*Dao) 这种组合模式
bean本身的autowire-candidates属性优于的default-autowire-candidates属性生效
这些技术对于那些您永远不希望通过自动装配被注入到其他bean中的bean非常有用。这并不意味着被排除的bean本身不能使用自动装配进行配置。相反,该bean本身仅仅是不作为其他bean的自动连接候选对象。
public static final String AUTOWIRE_CANDIDATE_ATTRIBUTE = "autowire-candidate";
//获取autowire-candidate 这个值默认就是true
String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);
//判断 是不是定义的当前bean是不是 default
if (isDefaultValue(autowireCandidate)) {
//如果是default
//查找当前bean 所在beans的default-autowire-candidates属性 找到配置的patterns表达式
//如果表达式为空 就不处理setAutowireCandidate属性值 这样该属性依旧是true
String candidatePattern = this.defaults.getAutowireCandidates();
if (candidatePattern != null) {
//表达式不为空 判断当前beanName 是否在表达式范围内
//在范围内就setAutowireCandidate设置为true
//否则设置为false
String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern);
bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));
}
}
else {
//如果autowireCandidate 不是 default 是true 那就设置为true
//是false 那就设置为false
bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate));
}
1.4.6。方法注入
在大多数应用场景中,容器中的大多数bean是 singletons。
当单例Bean需要与另一个单例Bean协作或非单例Bean需要与另一个非单例Bean协作时,通常可以通过将一个Bean定义为另一个Bean的属性来处理依赖性,生命周期相同的类互相注入时没有问题。
当bean的生命周期不同时会出现问题。假设单例bean A需要使用非单例(原型)bean B,也许在A的每个方法调用上都使用。容器仅创建一次单例bean A,因此只有一次机会来设置属性。每次需要一个容器时,容器都无法为bean A提供一个新的bean B实例。
一个解决方案是放弃某些控制反转。您可以通过实现接口ApplicationContextAware ,并使每次容器 A都需要容器 B 的调用来请求(通常是新的)bean B实例,从而使bean A知道容器。以下示例显示了此方法:ApplicationContextAware.getBean("B")
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;
// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public Object process(Map commandState) {
// grab a new instance of the appropriate Command
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
protected Command createCommand() {
// notice the Spring API dependency!
return this.applicationContext.getBean("command", Command.class);
}
public void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
前面的内容是不理想的,因为业务代码知道并耦合到Spring框架。方法注入是Spring IoC容器的一项高级功能,使您可以干净地处理此用例。
您可以在此博客条目中了解有关方法注入动机的更多信息。
Lookup Method注入
Lookup Method注入是指容器覆盖容器管理bean上的方法并返回容器中另一个已命名bean的查找结果的能力。查找通常涉及原型bean,如上一节所述的场景。Spring框架通过使用来自CGLIB库的字节码生成动态生成覆盖该方法的子类来实现这种方法注入。
- 要使这个动态子类工作,Spring bean容器子类的类不能是final,要覆盖的方法也不能是final。
- 单元测试具有抽象方法的类需要您自己创建类的子类,并提供抽象方法的存根实现。
- 具体的方法对于组件扫描也是必要的,这需要具体的类来拾取。
- 另一个关键的限制是,Lookup Method 不能与工厂方法一起工作,特别是与配置类中的@Bean方法一起工作,因为在这种情况下,容器不负责创建实例,因此不能动态地创建运行时生成的子类。
对于CommandManager前面的代码片段中的类,Spring容器动态地覆盖该createCommand() 方法的实现。该CommandManager班没有任何Spring的依赖,如下所示:
package fiona.apple;
public abstract class CommandManager {
public Object process(Object commandState) {
// 获取适当的命令接口的新实例
Command command = createCommand();
// 在(希望是全新的)命令实例上设置状态
command.setState(commandState);
return command.execute();
}
// 实现类在哪呢???
protected abstract Command createCommand();
}
在包含要注入的方法的客户端类中(本例为CommandManager),要注入的方法需要以下形式的签名:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
如果方法为abstract,则动态生成的子类将实现该方法。否则,动态生成的子类将覆盖原始类中定义的具体方法。考虑以下示例:
<!-- 作为原型部署的有状态bean(非单例)-->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- inject dependencies here as required -->
</bean>
<!-- commandProcessor使用statefulCommandHelper-->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="myCommand"/>
</bean>
标识为commandManager的bean在需要myCommand bean的新实例时调用它自己的createCommand()方法。是否要将myCommand bean部署为原型,必须仔细确认自己的需求。如果是单例,则每次都返回相同的myCommand bean实例。
或者,在基于注释的组件模型中,您可以通过@Lookup注释声明一个查找方法,如下面的示例所示:
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup("myCommand")
protected abstract Command createCommand();
}
或者,更惯用的是,您可以依赖于目标bean根据查找方法的声明的返回类型来解析:
public abstract class CommandManager {
public Object process(Object commandState) {
MyCommand command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup
protected abstract MyCommand createCommand();
}
请注意,您通常应该使用具体的存根实现来声明这种带注释的查找方法,以便它们与Spring的组件扫描规则兼容,其中抽象类在默认情况下会被忽略。此限制不适用于显式注册或显式导入的bean类。
访问范围不同的目标bean的另一种方法是ObjectFactory/ Provider注入点(https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans-factory-scopes-other-injection)。
您可能还会发现ServiceLocatorFactoryBean(在 org.springframework.beans.factory.config包装中)有用。
任意方法替换
方法替换注入的形式是用另一个方法实现替换托管bean中的任意方法的能力。这个十分不常用,您可以跳过本节的其余部分,等到您真正需要此功能再来看。
对于基于xml的配置元数据,您可以使用replaced-method元素将一个已部署bean的现有方法实现替换为另一个方法实现。考虑下面的类,它有一个名为computeValue的方法,我们想要覆盖它:
public class MyValueCalculator {
public String computeValue(String input) {
// some real code...
}
}
实现该org.springframework.beans.factory.support.MethodReplacer 接口的类提供了新的方法定义,如以下示例所示:
/**
*用于覆盖现有的computeValue(String input)
*实现在MyValueCalculator
*/
public class ReplacementComputeValue implements MethodReplacer {
public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
//获取输入值,使用它,并返回计算结果
String input = (String) args[0];
...
return ...;
}
}
用于部署原始类并指定方法覆盖的Bean定义类似于以下示例:
<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
<replaced-method name="computeValue" replacer="replacementComputeValue">
<arg-type>String</arg-type>
</replaced-method>
</bean>
<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
您可以在元素内使用一个或多个元素 来指示要覆盖的方法的方法签名。仅当方法重载且类中存在多个变体时,才需要对参数签名。为了方便起见,参数的类型字符串可以是完全限定类型名称的子字符串。
例如,以下所有都是java.lang.String匹配项 :
java.lang.String
String
Str
因为参数的数量通常足以区分每个可能的选择,所以通过让您仅键入与参数类型匹配的最短字符串,此快捷方式可以节省很多输入