Spring实战 二 Bean装配
先导
三种方式
Spring提供了三种方式来装配Bean,XML只是其中一种,还有通过JavaConfig,就是使用一个Java类来配置,这种配置的好处是配置就是程序,你可以使用Java的任何语法进行各种个性化的配置。还有一种是自动装配。
大部分时间自动装配就可以了,能用自动装配的时候就自动装配,再次的选择就是JavaConfig,实在不行再使用XML。
自动装配
自动装配比较简单,而且也是大部分时候使用的。
下面来看示例中的几个接口
// 代表一张CD光碟
public interface CompactDisc {
void play();
}
// 代表一种播放器
public interface MediaPlayer {
void play();
}
下面我们定义一个CD的实现类,@Component
是Spring的一个注解,代表它是一个Spring的组件,Spring会自动扫描这些组件,Spring会为组件创建Bean。
@Component
public class SgtPeppers implements CompactDisc {
private String title = "Sgt. Pepper's Lonely Hearts Club Band";
private String author = "The Beatles";
@Override
public void play() {
System.out.println("Playing " + title + " by " + author);
}
}
下面我们来定义一个配置类,因为咱得告诉Spring扫描哪个包下的组件,要不然它怎么知道扫啥。
@Configuration
@ComponentScan
public class CDPlayerConfiguration { }
@Configuration
代表了这个类是一个配置类,而@ComponentScan
代表了它要进行组件扫描,如果你不指定任何参数,它就是扫描和配置类相同的包下的组件。可以通过basePackges
指定要扫描的包,也可以通过basePackageClasses
来通过类来指定类所在的包。
嘶,我们创建了一个配置类去指定Spring扫描哪个包,那么是不是还得指定Spring去哪扫描配置类????
创建一个测试类,@ContextConfiguration
注解指定了注解类,这个参数名叫classes
,复数形式,哈哈哈证明你可以以数组的形式传递多个配置类。@RunWith
是Spring为JUnit测试框架提供的一个Runner,先忽略它就好。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CDPlayerConfiguration.class)
public class CDPlayerTest {
@Autowired private CompactDisc cd;
@Test
public void testCdNotNull() {
assertNotNull(cd);
}
}
注意注意,这里我们在测试类里声明了一个CompactDisc
类型的属性,因为Spring已经能够通过配置类扫描组件并且读取到我们的Bean了,所以我们可以直接让Spring将这个Bean注入,使用@Autowired
注解让Spring自动去容器中匹配一个Bean来注入。
应该是能通过测试。。。。。。哈哈哈。。。
下面来编写CDPlayer
类,来播放CompactDisc
,同样使用@Component
注解来声明,默认的组件名就是类名首字母小写,也就是cDPlayer
,现在我们给它指定了一个新名字。
@Component("MyCDPlayer")
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
@Autowired
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
@Autowired
public void setCompactDisc(CompactDisc cd) {
this.cd = cd;
}
@Override
public void play() {
cd.play();
}
}
@Autowired
可以添加在构造器上也可以添加在一个普通方法上,会将参数中需要的类型给注入。一般情况下使用一个办法即可。如果指定的类型在容器中找不到可以装配的Bean,那么Spring就会抛出个异常,如果你有这种不必须注入的Bean的需求,使用require=false
即可。
然后我们修改测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CDPlayerConfiguration.class)
public class CDPlayerTest {
@Autowired private MediaPlayer player;
@Test
public void testPlay() {
player.play();
}
}
可以使用
@Named
代替@Component
,使用@Inject
代替@Autowired
,这两个是Java的依赖注入标准提供的。
现在我们的第一个使用自动装配的例子就写完了。
通过Java代码装配Bean
删除掉那些@Component
和@Autowired
,并且删除掉配置类中的自动扫描注解@ComponentScan
@Configuration
public class CDPlayerConfiguration {
@Bean
public CompactDisc setCompactDisc(){
return new SgtPeppers();
}
@Bean
public MediaPlayer setMediaPlayer() {
CDPlayer player = new CDPlayer();
player.setCompactDisc(setCompactDisc());
return player;
}
}
@Bean
声明一个Bean,并且没有了@Component
,你要自己把它们变成Bean让Spring给扫描到。创建出来的Bean的名字就是方法名。
对于setCompactDisc
我们直接使用new来创建一个对象作为Bean。
而对于setMediaPlayer
我们用了set方法,这没有一个规则,你可以用任何Java语法来返回这个对象作为Bean。
在player.setCompactDisc
时,我们调用了另一个Bean的方法,是不是每次都会重新创建一个Bean对象?默认情况下Spring中的所有Bean都是单例的,Spring会拦截所有的Bean方法的调用,只返回它最初返回的那个对象。
也就是说无论你构造多少个CDPlayer,它们持有的都是同一个CompactDisc。
如果这样你看着很费解,还有另一种写法,比较符合依赖注入的思想,我也比较喜欢这种写法。
@Bean
public MediaPlayer setMediaPlayer(CompactDisc cd) {
CDPlayer player = new CDPlayer();
player.setCompactDisc(cd);
return player;
}
这就像依赖注入了,好像是我们在装配MediaPlayer Bean时,向Spring请求了一个CompactDisc
类型的Bean,然后Spring去给我注入这个对象。
测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CDPlayerConfiguration.class)
public class CDPlayerJavaConfigTest {
@Autowired private MediaPlayer player;
@Test
public void testPlay(){
player.play();
}
}
在测试中我们同样使用了@Autowired
,这证明我们可以混合使用三种装配方式。
使用XML配置
XML配置太繁琐了,能不用则不用。
Spring的XML配置文件要以beans为一个根标签,代表其中存储了很多Bean。
这其中有一些aop相关的xsd文件,我们用不到,但我也不像摘出来了。
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
</beans>
使用bean
标签声明一个bean,以下是最简单的一个Bean声明
<bean class="io.lilpig.springlearn.springlearn01.chapter02.xmlconfig.SgtPeppers"/>
通过class
指定一个Bean的完全限定名,默认情况下,Spring为该bean分配的Id为它的全限定类名#0
,#0代表它是同类型的Bean中第一个声明的,如果再有一个这个类型的Bean那么它就是#1。
一般情况下我们会指定一个id,而不用自动生成的
<bean id="sgtpeppers" class="io.lilpig.springlearn.springlearn01.chapter02.xmlconfig.SgtPeppers"/>
Spring发现这个bean
配置的时候,会调用它的默认构造器创建一个实例。
如果需要完成依赖注入,可以通过给类添加其他构造器,并在XML文件中配置调用这个构造器。
<bean id="cdplayer" class="io.lilpig.springlearn.springlearn01.chapter02.xmlconfig.CDPlayer">
<constructor-arg ref="sgtpeppers"/>
</bean>
constructor-arg
就是构造函数的参数,Spring会自动匹配这些参数,调用对应的构造器。ref
指定了一个其他bean的引用,Spring会将这个bean自动注入到构造函数中。
还有一种办法是使用Spring3.0及以上才会提供的c
属性。首先在XML头中引入一个c命名空间。
xmlns:c="http://www.springframework.org/schema/c"
注意这里使用了c:cd-ref
来指定一个构造器参数,语法是c:构造器参数名-ref
。虽然简洁了不少,但总感觉使用构造器参数名作为属性还是有点不对劲。
<bean id="cdplayer"
class="io.lilpig.springlearn.springlearn01.chapter02.xmlconfig.CDPlayer"
c:cd-ref="sgtpeppers">
</bean>
还可以用c:_0-ref
,这里_数字
是表示构造器中第几个参数。
我的想法就是这东西尽量别用,虽然比consructor-arg
简单。
如果想注入字面量,可以使用value
属性,先给SgtPeppers
类添加一个构造器
public SgtPeppers(String title, String author) {
this.title = title;
this.author = author;
}
<bean id="sgtpeppers" class="io.lilpig.springlearn.springlearn01.chapter02.xmlconfig.SgtPeppers">
<constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band"/>
<constructor-arg value="The Beatles"/>
</bean>
使用c命名空间就是c:_title="xxx"
,去掉了后面的-ref
我们看看如何注入一个列表数据,加入歌曲音轨属性。
private List<String> tracks;
public SgtPeppers(String title, String author, List<String> tracks) {
this.title = title;
this.author = author;
this.tracks = tracks;
}
修改play方法
@Override
public void play() {
System.out.println("Playing " + title + " by " + author);
for (String track : tracks) {
System.out.println("- Track: " + track);
}
}
你可以在外面注入一个null,但这好像并没什么意义,因为本来就是null
<bean id="sgtpeppers" class="io.lilpig.springlearn.springlearn01.chapter02.xmlconfig.SgtPeppers">
<constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band"/>
<constructor-arg value="The Beatles"/>
<constructor-arg><null/></constructor-arg>
</bean>
通过<list>
可以传递一个列表数据。
<constructor-arg>
<list>
<value>Sgt. Pepper's Lonely Hearts Club Band</value>
<value>With a Little Help from My Friends</value>
<value>Lucy in the Sky with Diamonds</value>
<value>Getting Better</value>
<value>Fixing a Hole</value>
</list>
</constructor-arg>
除了使用value
,当然也可以使用ref
来进行传递任何引用类型的列表。
<set>
标签和<list>
标签都被允许,区别就是Spring创建对象的时候创建的是一个Set
还是List
当然XML的方式也可以使用set方法来注入
<bean id="cdplayer" class="io.lilpig.springlearn.springlearn01.chapter02.xmlconfig.CDPlayer">
<property name="compactDisc" ref="sgtpeppers"/>
</bean>
也有对应的简洁命名空间p
xmlns:p="http://www.springframework.org/schema/p"
<bean
id="cdplayer"
class="io.lilpig.springlearn.springlearn01.chapter02.xmlconfig.CDPlayer"
p:compactDisc-ref="sgtpeppers"/>
对于p就不过多介绍了,和c差不多。
p和c好像不能注入集合类型的数据,其实是可以的,使用util命名空间。好吧我承认我已经烦了,这可能就是为什么其它的配置方式被发明出来。
<?xml version="1.0" encoding="UTF-8"?>
<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"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-4.0.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
在这里使用util:list
定义了一个列表,并通过c传递给构造器了,并且c命名空间可以和constructor-arg
混合使用
<util:list id="trackList">
<value>Sgt. Pepper's Lonely Hearts Club Band</value>
<value>With a Little Help from My Friends</value>
<value>Lucy in the Sky with Diamonds</value>
<value>Getting Better</value>
<value>Fixing a Hole</value>
</util:list>
<bean id="sgtpeppers" class="io.lilpig.springlearn.springlearn01.chapter02.xmlconfig.SgtPeppers" c:tracks-ref="trackList">
<constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band"/>
<constructor-arg value="The Beatles"/>
</bean>
混合配置
混合配置多个JavaConfig
在一个大项目中,配置项可能有好多好多,不久一个Java文件就会显得笨重,难以容纳。
可以在一个JavaConfig中通过@Import
标签导入另一个JavaConfig,不过更推荐的办法是使用一个总配置文件,其中什么也不写,只是整合所有的其他配置文件
@Configuration
@Import(value = {
XXXConfig.class,
YYYConfig.class
})
public class AppConfiguration {}
配置JavaConfig和XML
通过@ImportResource
可以配置两种配置文件。
@Configuration
@Import(value = {
XXXConfig.class,
YYYConfig.class
})
@ImportResource(value = {
"classpath:knight.xml",
"classpath:cdplayer.xml"
})
public class AppConfiguration {}
XML拆分配置
如果只有一个XML文件,那么在大项目中,它将很快失控。
import
标签可以导入其他xml。
<import resource="knights.xml"/>
bean
标签可以将其他JavaConfig导入到XML中
<bean class="io.lilpig.springlearn.springlearn01.chapter02.javaconfig.CDPlayerConfiguration"></bean>
然后直接引用其中的Bean。
而且你也可以创建一个父级XML专门用于整合各种其他的XML和JavaConfig而不创建Bean。