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">
<bean id="parent" class="com.example.spring.bean.collection.CollectionMerge">
<property name="list">
<list >
<value>1</value>
<value>2</value>
</list>
</property>
</bean>
<bean id="child" parent="parent" >
<property name="list">
<list merge="true">
<value>1</value>
<value>2</value>
</list>
</property>
</bean>
</beans>
测试代码
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class MapMergeTests {
@Autowired
@Qualifier("child")
private CollectionMerge service;
@Test
public void testSimpleProperties() throws Exception {
assertEquals(4, service.getList().size());
}
}
经过动手实验,证明Spring合并对于List类型,并无覆盖。接下来,我们看看其源码实现
MutablePropertyValues.mergeIfRequired()方法
private PropertyValue mergeIfRequired(PropertyValue newPv, PropertyValue currentPv) {
Object value = newPv.getValue();
//属性值是否可合并类
if (value instanceof Mergeable) {
Mergeable mergeable = (Mergeable) value;
//属性值是否设置了merge=true
if (mergeable.isMergeEnabled()) {
//合并当前属性
Object merged = mergeable.merge(currentPv.getValue());
return new PropertyValue(newPv.getName(), merged);
}
}
return newPv;
}
上面代码中Mergeable
接口共有5个实现类ManagedList
,ManagedArray
,ManagedMap
,ManagedSet
,ManagedProperties
ManagedList.merge(Object parent)方法
public List<E> merge(Object parent) {
//防御性抛异常
if (!this.mergeEnabled) {
throw new IllegalStateException("Not allowed to merge when the 'mergeEnabled' property is set to 'false'");
}
if (parent == null) {
return this;
}
//不能合并非List类型集合
if (!(parent instanceof List)) {
throw new IllegalArgumentException("Cannot merge with object of type [" + parent.getClass() + "]");
}
List<E> merged = new ManagedList<E>();
//注意顺序,先增加父bean中的value值,所以文档中说父集合元素索引位置靠前
merged.addAll((List) parent);
merged.addAll(this);
return merged;
}
译注:我勒个去,为了找这段代码,洒家差点累吐血。由此可见,译者是非常用心的用生命去翻译文档。
合并限制
不能合并不同类型的集合,比如合并Map
和List
(译注:上面的源码中有这样的代码,不知聪明的小读者是否注意到了)。如果你非得这么干,那么就会抛出个异常。merge
属性必须指定给父-子继承结构bean中的子bean,如果指定给了父集合则无效,不会产生预期的合并结果。
强类型集合
Java5中出现了范型,所以可以给集合使用强类型限制。比如说,声明一个只含有String
类型的Collection
。若使用Spring 注入一个强类型Collection
给一个bean,那么就可以利用Spring的类型转换特性 ,该特性能将给定的值转换成合适的类型值,然后赋值给你的强类型Collection
。
public class Foo {
private Map<String, Float> accounts;
public void setAccounts(Map<String, Float> accounts) {
this.accounts = accounts;
}
}
看,飞碟
<beans>
<bean id="foo" class="x.y.Foo">
<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>
在foo
bean的accounts
属性注入前,强类型集合Map<String,Float>
的泛型信息通过反射获取。因此Spring的类型转换机制识别出元素的value类型将会转换为Float
,9.99,2.75,3.99
将会转换成Float
类型。
<h5 id=beans-null-element
>Null值和空字串</h5>
Spring treats empty arguments for properties and the like as empty Strings. The following XML-based configuration metadata snippet sets the email property to the empty String value ("").
Spring对于属性的空参数转换为空字串。下面的XML片段,设置值email属性为空格字串("")
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
上面的xml配置相当于这样的java代码
exampleBean.setEmail("")
<null/>
元素处理null值:
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
上面配置相当于
exampleBean.setEmail(null)
<h5 id='beans-p-namespace'>XML简写p命名空间</h5>
p-命名空间能让你使用bean
元素属性替代内嵌property/>
元素,用来描述属性值或者协作类。
Spirng支持命名空间扩展配置,命名空间基于XML Schema定义。本章讨论的beans
配置格式定义在XML Schema文档中。然而,p命名空间并不是在XSD文件中,而是存在于Spring核心中。
下面XML片段解释了:1使用了标准XML,第2个使用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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="classic" class="com.example.ExampleBean">
<property name="email" value="foo@bar.com"/>
</bean>
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="foo@bar.com"/>
</beans>
上例中解释了,在bean定义中使用p-namespace设置email
属性。它告诉Spring这里有一个property
声明。前面提到过,p-namespace 并不存在schema定义,所以p
可以修改为其他名字。
译注,干活都是译者自己撰写用于验证,而非参考手册原版中的内容,之所以验证,是因为原版E文有看不懂的地方、或者翻译拿不准、或者就是闲来无事、或者就是为了凑篇幅,这些事儿得通过写代码验证搞定了
up fuck goods上干货
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ppp="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="p-namespace" class="com.example.spring.bean.p.PNamespaceBean"
ppp:email="foo@email.com"/>
</beans>
注意p命名空间的用法xmlns:ppp="http://www.springframework.org/schema/p"
和ppp:email="foo@email.com"
go on fuck goods继续干货
public class PNamespaceBean {
private String email;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
下面样例中,2个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
http://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>
样例中2个p-namespace设置属性值,出现了一种新的格式声明引用。第一个bean中,使用了<property name="spouse" ref="jane"/>
应用bean jane.第二个bean中,使用了p:spouse-ref="jane"
做了相同的好事儿,此时,spouse
是属性名,然而-ref
表示这不是直接量而是引用另一个bean。
译注 好事儿,我小时候虽然做好事儿不留名,但是总能被发现,令我非常苦恼。我的妈妈常常揪着我的耳朵问:这又是你干的好事儿吧。
<h5 id="beans-c-namespace">c-namespace命名空间</h5>
和p-namespace相似,c-namespace,是Spring3.1中新出的,允许行内配置构造参数,而不需使用内嵌的constructor-arg
元素
用c
:namespace重构构造注入
<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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bar" class="x.y.Bar"/>
<bean id="baz" class="x.y.Baz"/>
<!-- traditional declaration -->
<bean id="foo" class="x.y.Foo">
<constructor-arg ref="bar"/>
<constructor-arg ref="baz"/>
<constructor-arg value="foo@bar.com"/>
</bean>
<!-- c-namespace declaration -->
<bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com"/>
</beans>
c:
namespace和p:
使用了相同机制(ref
后缀表示引用),通过names设置构造参数。因为它未定义在XSD schema中(但是存在于Spring内核中),所以需要先声明。
有一些情况比较特殊,不能识别或者看到构造参数(比如无源码且编译时无调试信息),此时可以求助于参数索引:
<!-- c-namespace index declaration -->
<bean id="foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz"/>
由于XML语法,索引标记需要
_
开头作为XML属性名称,而不能使用数字开头(尽管某些ID支持)
在实际中,构造注入(name匹配/类型匹配/索引匹配)是非常高效的,一般情况下,推荐使用 name匹配方式配置。
<h5 id='beans-compound-property-names'>复合属性</h5>
在设置bean属性时,可以使用复合或者内嵌属性,组件路径可以有多长写多长,除了最后一个属性,其他属性都不能为null
。看下面的bean定义
<bean id="foo" class="foo.Bar">
<property name="fred.bob.sammy" value="123" />
</bean>
bean foo
有属性fred
,fred
有属性bob
,bob
有属性sammy
,最后的sammy
属性赋值"123"
。在beanfoo
构造后,fred
属性和bob
属性都不能为null
否则抛异常NullPointerException
<h4 id='beans-factory-dependson'>使用depends-on</h4>
若bean是另个bean的依赖,通常是指该bean是另个bean的属性。在XML中通过<ref/>
元素配置实现。然而,bean之间并不全是直接依赖。举个栗子,类中有个静态初始化需要出发,像注册数据库驱动这样的。depends-on
属性能强制这些先决条件首先完成执行初始化,然后再去使用它(比如用于注入)。
下面的样例中,展示了使用depends-on
来表达bean之间的依赖关系:
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
也可以依赖多个bean,为depends-on
属性值提供一个bean name列表,用逗号,空白,分号分隔。
<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" />
在单例bean中,
depends-on
属性既可以设定依赖的初始化时机,也可以相应的设定依赖的销毁时机。在bean被销毁之前,bean使用depdnds-on
属性定义的依赖bean会首先被销毁。因此depends-on
也能控制销毁顺序。
<h4 id='beans-factory-lazy-init'>延迟初始化</h4>
ApplicationContext
的各种实现默认的初始化处理过程,都是尽早的创建、配置所有的单例bean。通常,这种预先实例化是非常好的,因为在配置的错误或者环境问题立刻就能暴露出来,而不是数小时甚至数天后才发现。若不需要此行为,可以通过设置lazy-initialized
延迟加载来阻止预先初始化。lazy-initialized
bean告诉Ioc容器,只有在第一次请求的时候采取初始化,而不是在启动容器时初始化。
在XML中,属性lazy-init
控制<bean/>
元素的初始化。
<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.foo.AnotherBean"/>
ApplicationContext
解析上面的配置,在启动时,不会预先初始化这个标记为lazy的bean,为标记lazy的bean则会立刻初始化。
如果一个非延迟的单例bean依赖了lazy延迟bean,ApplicationContext
会在启动时就创建lazy延迟bean,因为它必须满足单例bean依赖。延迟bean注入给单例bean,就意味着,它不会延迟加载的。
通过设置<beans/>
元素的default-lazy-init
属性,可以设置容器级别的延迟加载。看样例:
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
<h4 id='beans-factory-autowire'>自动装配</h4>
Spring容器提供自动装配,用于组织bean之间的依赖。可以让Spring,通过检查ApplicationContext
内容完成自动注入。自动装配有以下优点:
- 自动装配能明显减少属性和构造参数的配置。(在这方面,还有其他的机制也能达到此目的,比如bean 模板,在后面的章节里有详细的讲解)
- 自动装配可扩展性非常好。比如,给类增加了依赖,无需修改配置,依赖类就能自动注入。因此,自动装配在开发期非常有用,在代码不稳定时,无需修改编码即可完成切换。
在XML中,设置<bean/>
元素的autowire
属性来设定bean的自动装配模式。自动装配有5种模式。可以选择任意一种,来设置bean的装配模式。(译注,这不是废话么,有5中模式,每种都能随便用,假设有一种不能用,那就是4种模式了么)
Table 5.2. 自动装配模式
模式 | 解释 |
---|---|
no | (默认的)非自动装配。必须使用ref 元素定义bean引用。对于大规模系统,推荐使用,明确的指定依赖易于控制,清楚明了。它在某种程度上说明了系统的结构。 |
byName | 通过属性名称property name自动装配。Spring会查找与需要自动装配的属性同名bean。举个栗子,若在bean定义中设置了by name方式的自动装配,该bean有属性master (当然了,还得有个setMaster(..)写属性方法),Spring会查找一个名叫master 的bean,并将它注入给master 熟悉。 |
byType | 若在容器中存在一个bean,且bean类型与设置自动装配bean的属性相同,那么将bean注入给属性。若与属性同类型的bean多于1个,则会抛出我们期待已久的致命异常,也就意味着这个bean也许不适合自动注入。若不存在匹配的bean,啥都不会发生;属性也不会设置,然后就没有然后了。 |
constructor | Analogous to byType, but applies to constructor arguments. If there is not exactly one bean of the constructor argument type in the container, a fatal error is raised.和byType模式类似,但是应用于构造参数。若在容器中不存在与构造参数类型相同的bean,那么接下来呢,抛异常呗,还能干啥? |
byType或者constructor自动装配模式,可以装配arrays数组和范型集合。 这种个情况
下,容器内匹配类型的bean才会注入给依赖。若key类型是String
,你也能自动装配强类型Maps
。一个自动装配的Map,所有 匹配value类型的bean都会注入Map.value
,此时,Map的key就是相应的bean的name。
可以结合自动装配和依赖检查,依赖检查会在装配完成后执行。
<h5 id='beans-autowired-exceptions'>自动装配的局限和缺点</h5>
自动装配最好是贯穿整个项目。若不是全部或大部分使用自动装配,而仅仅自动装配一两个bean定义,可能会把开发者搞晕。(译注,最可能的原因是,开发者对自动装配机制不熟悉,或者想不到项目中居然还使用了自动装配模式,当发生问题时,擦的,找都没地方找去,调试信息里只能看到经过横切织入事务代理的proxy)
总结局限和缺点:
- 属性中和构造参数明确的依赖设置会覆盖自动装配。不能自动装配所谓的简单属性,比如原始类型,
Strings
和Classes
(简单属性数组)。这是源于Spring的设计。 - 和明确装配相比,自动装配是不确切的。正如上面的列表中提到的,Spring谨慎避免匹配模糊,若真的匹配不正确,则导致错误发生,Spring 管理的对象之间的关系记录也变的不明确了。
- 对于根据Spring容器生成文档的工具,装配信息将变的无用。
- 容器内多个bean定义可能会匹配设置为自动装配的
setter
方法或者构造参数的类型。对于arrays,collections,或者maps,这不是个问题。然而对于期望单一值的依赖,这种歧义将不能随意的解决。如果发现多个类型匹配,将会抛出异常 .
在后面的场景中,给你几条建议:
- 放弃自动装配,使用明确装配
- 避免通过在bean定义中设置
autowire-candidate
属性为false的方式来设置自动装配,下一章节会讲 - 通过设置
<bean/>
袁术的primary
属性为true
来指定单个bean定义作为主候选bean。 - 使用基于注解的配置实现更细粒度的控制,参看Section 5.9, “Annotation-based container configuration”.
<h5 id='beans-factory-autowire-candidate'>排除自动装配bean</h5>
在每个bean的设置中,你可以排除bean用于自动装配。XML配置中,设置<bean/>
元素的autowire-candidate
属性为false
;容器将不使用该bean自动装配。(包括注解配置,像@Autowired
)
- 使用对bean名字进行模式匹配来对自动装配进行限制。其做法是在<beans/>元素的'default-autowire-candidates'属性中进行设置。比如,将自动装配限制在名字以'Repository'结尾的bean,那么可以设置为"Repository“。对于多个匹配模式则可以使用逗号进行分隔。注意,如果在bean定义中的'autowire-candidate'属性显式的设置为'true' 或 'false',那么该容器在自动装配的时候优先采用该属性的设置,而模式匹配将不起作用。译注这一段翻译是从网上copy过来的,我勒个擦,得赶紧睡觉去了*
这些设置非常有用。但是这些被排除出自动注入的bean是不会自动注入到其他bean,但是它本身是可以被自动注入的。
<h4 id='beans-factory-method-injection'>方法注入</h4>
一般情况,容器中的大部分的bean都是单例的。当单例bean依赖另一个单例bean,或者一个非单例bean依赖另个非单例bean是,通常是将另一个bean定义成其他bean的属性。当bean的生命周期不同时,那么问题来了。假设单例bean A依赖非单例bean(prototype) B,也许会在每个方法里都需要B。容器之创建了一个单例bean A,因此只有一次将B注入的机会。A调用B,需要很多B的实例 ,但是容器不会这么干。
解决办法是放弃一些IoC控制反转。令A实现接口ApplicationContextAware
,此时A能够感知容器,即获取ApplicationContext
,每次当A调用B时,调用容器的getBean("B")方法用以创建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 框架。方法注入,是SpringIoc容器的高级特性,能够简洁的满足此场景。
想要了解更多的方法注入,参看此博客
<h5 id='beans-factory-lookup-method-injection'>查找式方法注入</h5>
查找式是指,容器为了覆盖它所管理的bean的方法,在容器范围内查找一个bean作为返回结果。通常是查找一个原型(prototype)bean,就像是上面章节中提到过的场景。Srping框架,使用CGLIB
类库生成动态子类的字节码技术,覆盖方法。
为了能让动态子类能运行,其父类不能是
final
类,被覆盖的方法也不能是final
。还有,你得自己测试父类是否含有abstract
方法,如果有,需要你提供默认实现。最后,被方法注入的对象不能序列化。Spring 3.2以后,不需要CGLIB
的类路径了,因为CGLIB
被打包入了org.springframework 包,和Spring-core 这个jar包在一起了。既是为了方便也是为了避免CGLIB
包与应用中用到的CGLIB
包冲突。
来看看前面提到的CommandManager
类的代码片段,Spring容器会动态的覆盖createCommand()
方法的实现。这样CommandManager
类就不会依赖任何Spring API了。下面是修改过后的
package fiona.apple;
// 不再有 Spring imports!
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
//
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
//okay....但是方法实现在哪里?
protected abstract Command createCommand();
}
在含有被注入方法的类中(像CmmandManager
类),被注入方法需要使用以下签名
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
动态生成子类会实现抽象方法。若该方法不是抽象的,动态生成自来则会重写在源类中的方法。配置如下:
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="command" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- inject dependencies here as required -->
</bean>
<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="command"/>
</bean>
在commandManager
类调用createCommand
方法时,动态代理类将会被识别为commandManager
返回一个command
bean的实例。将command
bean设置成prototype
,一定要小心处理。若被设置成了singleton
,每次调用将返回同一个command
bean。
感兴趣的小读者也找找
ServiceLocatorFactoryBean
类(在org.springframework.beans.factor.config
包)来玩玩。使用ServiceLocatorFactoryBean
类的处理手法和另一个类ObjectFactoryCreatingFactoryBean
类似,但是ServiceLocatorFactoryBean
类与虚拟指定你自己的lookup接口(查找接口),这与Spring指定lookup接口略有不同。详情参看这些类的javadocs
译注,今天加班晚了点,到家时23:30了,可是今天还没翻。进了家门,打开电脑,翻一小节再说,要不估计睡不着觉了。对于我自己的毅力,我还是有相当的认识的,比如:无论咳的多么严重,都能坚持抽烟,由此可见一斑。以上是玩笑。我的意志力并不强,但是意志薄弱也有意志薄弱的积极的正面的意义,比如我养成了每天翻点东西的习惯,哪怕就是再困、再饿、再累,也得翻译一下,因为要是不翻译的话,我就得跟自己的习惯作斗争了,准确的说是和自己斗争,而我却又没有与自己斗争的念想,我根本打不过我自己,就这样,我又翻了一小节
<h5 id="beans-factory-arbitrary-method-replacement">任意方法替换</h5>
还有一种方法注入方式,不如lookup method
注入方式好用,可以用其他bean方法实现替换受管理的bean的任意方法。你可以跳过本节,当真的需要时再回来也是可以的。
在xml配置中,设置replaced-method
元素,就可用其他实现来替换已经部署的bean中存在的方法实现。考虑下面的类,有一个我们要重写的方法computeValue
public class MyValueCalculator {
public String computeValue(String input) {
// balbalba
}
// 其他方法...
}