上篇文章介绍的IOC的理念与优点,这一章我们介绍如何将自己开发的Bean装配到Spring IOC容器中。
Spring 配置3种方式
-
在XML文件中显式配置
-
在Java 接口实现配置
-
隐式Bean 的发现机制和自动装配原则
Spring 如何选择配置?
这方面,并没有唯一的正确答案。你所做出的选择必须要适合你和你的项目。而且,谁说我们只能选择其中的一种方案呢?Spring的配置风格是可以互相搭配的,所以你可以选择使用XML装配一些bean,使用Spring基于Java的配置(JavaConfig)来装配另一些bean,而将剩余的 bean让Spring去自动发现。
装配方式的优先级
在现实的工作中,这 3 种方式都会被用到,并且在学习和工作之中常常混合使用,所以这里给出一些关于这 3 种优先级的建议:隐式Bean的发现机制和自动装配原则
首选自动装配机制,显示配置越少越好。
优点:减少程序开发者的决定权,简单又不失灵活。
Java 的接口和类中实现配置
在没有办法使用自动装配原则情况,我们考虑此类方法。
优点:减少xml配置文件,也更为容易。
xml文件配置
最后选择xml文件配置。
优点:简单易懂
典型场景:
当使用三方类时,并不是我们开发,也无法修改代码,这个时候通过XML方式配置使用。
自动化装配Bean
从两个角度来实现自动化装配
-
组件扫描(component scaning)
-
自动装配(autowing)
组件扫描
Spring 会自动发现应用上下文所创建的Bean。
自动装配(autowing)
Spring 自动满足Bean之间的依赖。组件扫描和自动装配组合在一起就能发挥出强大的威力,它们能够将你的显式配置降低到最少。
代码实现
在这个MP3和流式媒体音乐的时代,磁带显得陈旧,随着以物理载体方式越来越少。尽管如此,磁带为我们阐述DI如何运行提供一个很好的样例。如果不将磁带放入磁带播放器中,那么磁带其实没有太大用处,所有可以说磁带依赖于磁带播放器。
磁带接口
public interface Tape {
void play();
}
熊猫播放器实现类
@Component
public class PandaPlayer implements Tape {
private String singer = "周杰伦";
private String songName = "双截棍";
public void play() {
System.out.println("开始播放:"+singer+"的"+songName);
}
}
配置类
@Configuration
@ComponentScan
public class TapeConfig {
}
测试类
@RunWith(SpringJUnit4ClassRunner.class)
// 自动装配的配置
@ContextConfiguration(classes = TapeConfig.class)
public class PlayerTest {
// 1、通过扫描包的方式实现装配bean,使用@Component注解或@Named 注解进行配置
@Autowired
private Tape tape;
@Test
public void player(){
assertNotNull(tape); tape.play();
}
}
测试结果
开始播放:周杰伦的双截棍
如果没有其他配置的话,@ComponentScan默认会扫描与配置类相同的包。因为TapeConfig类位于com.automatic.service包中,因此Spring 将会扫描这个包以及这个包下的所有子包,查找带有@Component注解的类。这样的话,就能发现Tape,并且会在Spring中自动为 其创建一个bean。
自动装配
简单来说,自动装配就是让Spring自动满足bean依赖的一种方法,在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需求的其他bean。为了声明要进行自动装配,我们可以借助Spring的@Autowired注解。
使用@Autowired是放到构造方法中,@Autowired注解不仅能够用在构造器上,还能用在属性的Setter方法上或者普通方法中。
Player播放接口
public interface Player {
void play();
}
索尼播放器实现类
@Component
public class SonyPlayer implements Player {
private Tape tape;
@Autowired
public SonyPlayer (Tape tape) {
this.tape = tape;
}
public void play() {
System.out.println("----start----");
tape.play();
System.out.println("----end----");
}
}
测试方法
@Autowired
private Player player;
@Test public void player(){
assertNotNull(player);
player.play();
}
测试结果
----start----
开始播放:周杰伦的双截棍
----end----
通过Java代码装配Bean
尽管在很多场景下通过组件扫描和自动装配实现Spring的自动化配置是更为推荐的方式,但有时候自动化配置的方案行不通,因此需要明确配置Spring。比如说,你想要将第三方库中的组件装配到你的应用中,在这种情况下,是没有办法在它的类上添加@Component和@Autowired注解的,因此就不能使用自动化装配的方案了。
下面的例子是参考《Sring实战第四版》,觉得比较容易理解:
CD播放类
public class CDPlayer {
private CompactDisc cd;
public void play() {
cd.play();
}
// 省略setget方法
}
播放接口
public interface CompactDisc {
void play();
}
歌曲
public class HardDaysNight implements CompactDisc {
private String title = "Hard. DaysNight's Lonely Hearts Club Band";
private String artist = "The Hard";
public void play() {
System.out.println("Playing " + title + " by " + artist);
}
}
public class Revolver implements CompactDisc {
private String title = "Revolver. Revolver's Lonely Hearts Club Band";
private String artist = "The Revolver";
public void play() {
System.out.println("Playing " + title + " by " + artist);
}
}
public class SgtPeppers implements CompactDisc {
private String title = "Sgt. Pepper's Lonely Hearts Club Band";
private String artist = "The Beatles";
public void play() {
System.out.println("Playing " + title + " by " + artist);
}
}
public class WhiteAlbum implements CompactDisc {
private String title = "White. Album's Lonely Hearts Club Band";
private String artist = "The White";
public void play() {
System.out.println("Playing " + title + " by " + artist);
}
}
重点在这里Config配置类
/*
* Spring 的组件扫描默认是不启用的,需要显式配置启用组件扫描去寻找被
@Component 注解修饰的组件类,并为其创建 bean 实例。
* */
/*
* 标记类 CDPlayerConfig 是 Spring 的配置类,通过 java 代码定义
Spring 的装配规则。
* 该类应该包含在Spring应用上下文中如何创建bean的细节。
* */
@Configuration
public class CDPlayerConfig {
/*
* 要在 JavaConfig 中声明 bean ,我们需要编写一个方法,这个方法会创建所需类型的实例,然后给这个方法添加 @Bean 注解。
*
* @Bean 注解会告诉 Spring 这个方法将会返回一个对象,该对象要注册为 Spring 应用上下文中的 bean 。
* 方法体中包含了最终产生 bean 实例的逻辑。
*
* 默认,bean 的 ID 和方法名是一样的,如下 bean 的 ID 为 sgtPeppers 。
* 如果想手动为 bean 指定一个 ID ,可以使用 @Bean 的 name 属性,如:
* @Bean(name="lonelyHeartsClubBand")
* */
@Bean
public CompactDisc sgtPeppers() {
return new SgtPeppers();
}
// 这里是使用 Java 创建 bean,因此我们可以发挥 Java 提供的所有功能,只要最终生成一个 CompactDisc 实例即可。例如:
@Bean
public CompactDisc randomBeatlesCD() {
CompactDisc cd;
int choice = (int) Math.floor(Math.random() * 4);
switch (choice) {
case 1:
cd = new SgtPeppers();
break;
case 2:
cd = new WhiteAlbum();
break;
case 3:
cd = new HardDaysNight();
break;
default:
cd = new Revolver();
break;
}
return cd;
}
/*
* Spring 装配方式一:
* 在JavaConfig中装配bean的最简单方式就是引用创建bean的方法。
* */
@Bean
public CDPlayer cdPlayer() {
// return new CDPlayer(sgtPeppers());
return new CDPlayer(new WhiteAlbum());
}
/*
* Spring 装配方式二:
* 当 Spring 调用 cdPlayer() 创建 CDPlayer bean 的时候,它会自动装配一个 CompactDisc 到配置方法之中。
* 然后,方法体就可以按照合适的方式来使用它。
*
* 通过这种方式引用其他的 bean 通常是最佳的选择,因为它不会要求将 CompactDisc 声明到同一个配置类之中。
* 在这里甚至没有要求 CompactDisc 必须要在 JavaConfig 中声明,
* 实际上它可以通过组件扫描功能自动发现或者通过 XML 来进行配置。
* 你可以将配置分散到多个配置类、XML 文件以及自动扫描和装配 bean 之中,
* 只要功能完整健全即可。不管 CompactDisc 是采用什么方式创建出来的,
* Spring 都会将其传入到配置方法中,并用来创建 CDPlayer bean 。
*
* 当配置类中有多个同类型的 bean 时,此时可以使用 @Qualifier 注解来指定参数注入的是哪一个具体的 bean 。
* */
@Bean
public CDPlayer cdPlayer(@Qualifier("randomBeatlesCD") CompactDisc compactDisc) {
return new CDPlayer(compactDisc);
}
/*
* 我们也可以采用属性赋值的方式来注入依赖值,这里所存在的可能性仅仅受到Java语言的限制。
* */
// @Bean
// public CDPlayer cdPlayer(@Qualifier("sgtPeppers")
CompactDisc compactDisc) {
// CDPlayer cdPlayer = new CDPlayer();
// cdPlayer.setCd(compactDisc);
// return cdPlayer;
// }
}
测试方法
@Autowired
private CDPlayer cdPlayer;
@Test
public void cdShouldNotBeNull() {
assertNotNull(cdPlayer);
cdPlayer.play();
}
测试结果
Playing Revolver. Revolver's Lonely Hearts Club Band by The Revolver
每次运行的结果是不同,因为randomBeatlesCD()方法。
XML文件装配Bean
使用xml文件进行配置,配置比较简单易懂。
public class ComplexAssembly {
private Long id;
private List<String> list;
private Map<String, String> map;
private Properties properties;
private Set<String> set;
private String[] array;
// 省略setget方法
}
这个 Bean 没有任何的实际意义,只是为了介绍如何装配这些常用的集合类:
xml文件
<bean id="complexAssembly" class="com.xml.service.ComplexAssembly">
<!-- 装配Long类型的id -->
<property name="id" value="1"/>
<!-- 装配List类型的list -->
<property name="list">
<list>
<value>value-list-1</value>
<value>value-list-2</value>
<value>value-list-3</value>
</list>
</property>
<!-- 装配Map类型的map -->
<property name="map">
<map>
<entry key="key1" value="value-key-1"/>
<entry key="key2" value="value-key-2"/>
<entry key="key3" value="value-key-2"/>
</map>
</property>
<!-- 装配Properties类型的properties -->
<property name="properties">
<props>
<prop key="prop1">value-prop-1</prop>
<prop key="prop2">value-prop-2</prop>
<prop key="prop3">value-prop-3</prop>
</props>
</property>
<!-- 装配Set类型的set -->
<property name="set">
<set>
<value>value-set-1</value>
<value>value-set-2</value>
<value>value-set-3</value>
</set>
</property>
<!-- 装配String[]类型的array -->
<property name="array">
<array>
<value>value-array-1</value>
<value>value-array-2</value>
<value>value-array-3</value>
</array>
</property>
</bean>
测试方法
@Test
public void getXmlComplexAssembly() {
ApplicationContext context = new ClassPathXmlApplicationContext(
new String[]{"applicationContext.xml"}
);
ComplexAssembly complexAssembly = (ComplexAssembly) context.getBean("complexAssembly");
System.out.println(complexAssembly.getId());
System.out.println(complexAssembly.getMap().get("key1"));
}
测试结果
1
value-key-1
总结:
List 属性为对应的 <list> 元素进行装配,然后通过多个 <value> 元素设值
Map 属性为对应的 <map> 元素进行装配,然后通过多个 <entry> 元素设值, 只是 entry 包含一个键值对(key-value)的设置
Properties 属性为对应的 <properties> 元素进行装配,通过多个 <property> 元素设值,只是 properties 元素有一个必填属性 key ,然后可以设置值
Set 属性为对应的 <set> 元素进行装配,然后通过多个 <value> 元素设值
对于数组而言,可以使用 <array> 设置值,然后通过多个 <value> 元素设值。
命名空间装配
除了上述的配置之外, Spring 还提供了对应的命名空间的定义,只是在使用命名空间的时候要先引入对应的命名空间和 XML 模式(XSD)文件。
-
p-命名空间
-
c-命名空间
-
util-命名空间
p标签
使用p命名空间需要添加
<!-- 使用p标签前 -->
<bean id="complexAssembly"
class="com.xml.service.ComplexAssembly">
<property name="id" value="1"/>
</bean>
<!-- 使用p标签后 -->
<bean name="complexassembly2" class="com.xml.service.ComplexAssembly" p:id="2" />
c标签
通过这个类举例子
package pojo;
public class Student {
int id;
String name;
public Student(int id, String name)
{
this.id = id; this.name = name;
}
// setter and getter
}
在 c-命名空间和模式声明之后,我们就可以使用它来声明构造器参数了:
<!-- 引入 c-命名空间之前 -->
<bean name="student1" class="pojo.Student">
<constructor-arg name="id" value="1" />
<constructor-arg name="name" value="学生1"/>
</bean>
<!-- 引入 c-命名空间之后 -->
<bean name="student2" class="pojo.Student"
c:id="2" c:name="学生2"/>
util标签
<!-- 引入util-命名空间之前 -->
<property name="list">
<list>
<ref bean="bean1"/>
<ref bean="bean2"/>
</list>
</property>
<!-- 引入util-命名空间之后 -->
<util:list id="list">
<ref bean="bean1"/>
<ref bean="bean2"/>
</util:list>
<util:list>
只是 util-命名空间中的多个元素之一,下表提供了 util-命名空间提供的所有元素:
Spring 作用域
总 结
在Spring中装配bean的三种主要方式:自动化配置、基于Java的显式配置以及基于XML的显式配置。不管你采用什么方式,这些技术都描述了Spring应用中的组件以及这些组件之间的关系。
本文中源码:
https://github.com/xiaonongOne/spring-bean
下面是微信公众号二维码,欢迎来骚扰。
网友评论