Spring之旅
Spring是一个开源框架,最早由Rod Johnson创建。是为了解决企业级开发的复杂性而创建的,但不仅仅局限与服务器端的开发,任何Java应用都能在简单性、可测试性和松耦合性方面从Spring获益。
为了降低Java开发的复杂性,Spring采用以下4种关键策略:
- 基于POJO的轻量级和最小侵入性编程;
- 通过依赖注入和面向接口实现松耦合;
- 基于切面和惯例进行声明式编程;
- 通过切面和模板减少样板式代码。
注:POJO(Plain Ordinary Java Object) , 简单的Java对象,实际就是普通的JavaBeans。是为了避免和EJB混淆所创造的简称。
Spring成功的关键在于依赖注入和AOP,它们也是Spring框架的核心部分。
DI是组装应用对象的一种方式,借助这种方式对象无需知道依赖来自何处或者依赖的实现方式。对象在运行期赋予它们所依赖的对象,依赖对象通常会通过接口了解所注入的对象,这样的话就能确保低耦合。
AOP可以帮助应用将散落在各处的逻辑汇集于一处—切面。当Spring装配bean时,这些切面能够在运行期编织起来,这样就能非常有效地赋予bean新的行为。
创建应用对象之间的协作关系的行为通常称为装配,这也是依赖注入的本质。
装配Bean
Spring框架的核心是Spring容器,容器负责管理应用中组件的生命周期,他会创建这些组件并保证他们的依赖能够得到满足,这样的话,组件才能完成预定的任务。
Spring提供的三种主要装配机制:
- 在XML中进行显式配置;
- 在Java中进行显式配置;
- 隐式的bean发现机制和自动装配。
尽可能使用自动装配机制,显式配置越少越少。当不得不使用显式配置时,推荐使用类型安全且比XML更加强大的JavaConfig,但如果JavaConfig中没有实现时,可以使用XML。
自动化装配bean
Spring从两个角度来实现自动化装配:
- 组件扫描:Spring会自动应用上下文所创建的bean。
- 自动装配:Spring自动满足bean之间的依赖。
创建可被发现的bean
@Component注解表明某个类会作为组件类,告知Spring要为这个类创建bean。
组件扫描默认不启用,需要显式配置Spring,从而命令它去寻找带有@Component 注解的类,并为其创建bean。
可以用@ComponentScan注解在Spring中启用组件扫描。如果没有其它配置的话,会默认扫描该类所在的包及其这个包的子包,查找带有@Component 注解的类。
也可以通过XML来启用组件扫描:
<beans ...>
<context:component-scan base-package="soundssystem" />
</beans>
为组件扫描的 bean 命名
Spring 应用上下文中所有的bean都会定一个ID。若没有明确的为某个bean设置ID,Spring会根据类名为其指定一个ID。具体来讲,是将类名的第一个字母变为小写。
也可以自定义ID,将ID作为值传递给@Component 注解。
如:
@Component("lonelyHeartsClub")
当然还可以使用Java依赖注入规范中提供的@Named 注解来为bean设置ID。
Spring支持@Named 作为 @Component 注解的替代方案,两者有些略微差异,但是在大多数场景中是可以相互替换的。
设置组件扫描基础包
前面提到过如果没有为@ComponentScan 设置任何属性,它会按照默认规则,会以配置类所在的包作为基础包来扫描组件。
可以通过basePackages来指定基础包,并可以用一个数组来设置多个基础包。
@ComponentScan(basePackages="soundsystem")
@ComponentScan(basePackages={"soundsystem","video"})
除了将包设置为简单的String类型外,@ComponentScan还可以将其指定为包中所包含的类或接口。
@ComponentScan(basePackageClasses="Player.class")
可以考虑创建一个用来进行扫描的空标记接口(marker interface),通过标记接口的的方式,依然能够保持对重构友好的接口引用,但是可以避免引用其它任何实际的应用程序代码。
通过为bean添加注解实现自动装配
自动装配就是让Spring自动满足bean依赖的一种方法,在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需求的其他bean。
为了声明要进行自动装配,我们可以借助Spring的@Autowired注解。
不管是构造器,setter方法还是其他的方法,Spring都会尝试满足方法参数上所声明的依赖。假如有且只有一个bean匹配依赖需求的话,那么这个bean会被装配进来。
如果没有匹配的bean,那么在应用上下文创建的时候,Spring会抛出一个异常。为了避免这些异常,可以将@Autowired的required属性设置为false。
将required 属性设置为false时,Spring会尝试执行自动装配,但如果没有匹配的bean的话,Spring会让这个bean处于未装配状态。
如果有多个bean都能满足依赖关系,Spring将会抛出一个异常,表明不确定要选择哪个bean进行自动装配。
@Autowired 是 Spring的特定的注解,可以替换为@Inject(源于Java依赖注入规范),两者有细微的却别,但在大部分的情况下,都可以替换。
通过Java代码装配bean
有时候自动化装配方案行不通,比如将第三方库中的组件装配到你的应用中,只能采用显式装配的方式,可选两种方案:Java和XML。
JavaConfig是更好的方案,因为它更强大、类型安全并且重构友好。它是Java代码,就像应用程序中的其他Java代码一样。但是与其他的Java代码又有区别,它与应用程序中的的业务逻辑和领域代码不同,它是配置代码;这意味着它不应该包含任何业务逻辑,JavaConfig也不应该侵入到哑无逻辑代码中。通常把JavaConfig放到单独的包中,使它与其他的应用程序逻辑分离开。
创建配置类
创建JavaConfig 类的关键在于为其添加@Configuration注解,@Configuration注解表明这个类是一个配置类,该类应该包含注解Spring应用上下文中如何创建bean的细节。
声明简单的bean
在JavaConfig中声明bean,要编写一个方法,这个方法会创建所需类型的实例,然后给这个方法添加@Bean注解,@Bean注解告诉Spring这个方法返回一个对象,该对象要注册为Spring应用上下文的bean。默认情况下,bean的ID与带有@Bean注解的命名方法是一样的,当然可以用name属性来指定一个不同的名字。
@Bean(name="lonelyHeartsClubBand")
public CompactDisc sgtPeppers() {
return new SgtPeppers();
}
方法体返回了一个新的实例,这里使用Java进行描述,可以发挥Java提供的所有功能。
借助 JavaConfig 实现注入
1、最简单的方法
在JavaConfig中装配bean的最简单的方式就是引用创建bean的方法。
没有使用默认的构造器来创建实例,而是调用了需要传入对象的构造器来创建该实例。
@Bean
public CDPlayer cdPlayer() {
return new CDPlayer(sgtPeppers);
}
sgtPeppers()方法添加了@Bean注解,Spring会拦截所有对它的调用,并确保直接返回该方法所创建的bean,而不是每次都其进行实际的调用。
在软件领域,完全可以将同一个SgtPeppers实例注入到任意数量的其它bean中。
2、一种更易理解的方法
@Bean
public CDPlayer(CompactDisc compactDisc){
return new CDPlayer(compactDisc);
}
cdPlayer()方法请求一个CompactDisc作为参数,当Spring调用cdPlayer()创建CDPlayer bean时,它会自动装配一个CompactDisc到配置的方法中。这样,也可以将CompactDisc注入到CDPlayer构造器中,而且不用明确引用CompactDisc的 @Bean方法。
通过这种方式引用其他bean通常是最佳的选择,因为它不会要求将CompactDisc声明到同一个配置中,甚至它可以通过组件扫描自动发现或者通过XML进行配置。
也可以采用其他风格的DI配置。
比如通过setter方法来注入;
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
CDPlayer cdPlayer = new CDPlayer(compactDisc);
cdPlayer.setCompactDisc(compactDisc);
return cdPlayer;
}
带有@Bean注解的方法可以采用任何必要的Java功能来产生bean实例。
通过XML装配bean
创建 XML 配置规范
在使用XML为Spring装配bean之前,要创建一个新的配置规范,即创建一个XML文件,并且要以<beans>元素为根。在使用XML时,要在配置文件顶部声明多个XML模式(XSD)文件,这些文件定义了配置Spring的XML元素。可借助Spring Tool Suite这个工具来创建和管理配置文件,并可以检查配置的合法性。
用来装配bean的最基本的XML元素包含在spring-beans模式中。
声明一个简单的
元素类似于JavaConfig中的@Bean注解。
<bean class="soundsystem.SgtPeppers" />
创建这个bean类通过class属性来指定,并且要使用全限定的类名。
没有明确给定ID,这个bean将会“soundsystem.SgtPeppers#0”,其中,“#0”是一个计数的形式,用来区分相同类型的其他 bean,可以借助id属性,为每个bean设置名字。
借助构造器注入初始化bean
构造器注入bean引用
<bean id="cdPlayer" class="soundsystem.CDPlayer">
<constructor-arg ref="compactDisc">
</bean>
告诉Spring要将一个ID为compactDisc的bean引用传递到CDPlayer的构造器中。
<bean id="CDPlayer" class="soundsystem.CDPlayer" c:cd-ref="compactDisc"/>
c: c-命名空间的前缀,接下来就是要装配的构造器的参数名。
-ref:命名的约定,它会告诉Spring,正在装配的是一个bean的引用,这个bean的名字是compactDisc,而不是字面量“compactDisc”。
也可以使用参数在整个参数列表中的位置信息,即参数索引。
<bean id="CDPlayer" class="soundsystem.CDPlayer" c:_0-ref="compactDisc"/>
如果只有一个构造器参数,根本不用去标示参数。直接用_ref即可。
将字面量注入到构造器中
<bean id="CDPlayer" class="soundsystem.CDPlayer">
<constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
<constructor-arg value="The Beatles" />
</bean>
使用value属性表明给定的值要以字面量的形式注入到构造器中。
也可以使用c-命名空间来表示
<bean id="CDPlayer" class="soundsystem.CDPlayer">
<c:_title="Sgt. Pepper's Lonely Hearts Club Band" />
<c:_artist="The Beatles" />
</bean>
或者用参数索引装配相同的字面量值
<bean id="CDPlayer" class="soundsystem.CDPlayer">
<c:_0="Sgt. Pepper's Lonely Hearts Club Band" />
<c:_1="The Beatles" />
</bean>
XML不允许某个元素的多个属性具有相同的名字。只有一个构造器参数,就可以简单地用下划线表示。
<bean id="CDPlayer" class="soundsystem.CDPlayer">
<c:_="Sgt. Pepper's Lonely Hearts Club Band" />
</bean>
装配集合
可以用<list>元素处理列表:
<bean id="CDPlayer" class="soundsystem.CDPlayer">
<constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
<constructor-arg value="The Beatles" />
<constructor-arg>
<list>
<value>A</value>
<value>B</value>
<value>C</value>
<value>D</value>
<value>E</value>
</list>
</constructor-arg>
</bean>
也可以用<ref>元素来代替<value>,实现bean引用列表的装配。
同理,可以按照同样的方式使用<set>元素,当是set时,所有重复的元素都会被忽略掉,存放顺序也不会得到保证。
无论哪种情况下,<set>或<list>都可以用来装配List、Set甚至数组。
设置属性
一般来说,对强依赖使用构造器注入,而对可选性依赖使用属性注入。
<bean id="cdPlayer" class="soundsystem.CDPlayer">
<property name="compactDisc" ref="compactDisc" />
</bean>
<property>在这里,它引用了ID为compactDisc的bean,并通过setCompactDisc()方法将其注入到compactDisc属性中。
Spring提供了p-命名空间来代替<property>。为了启用p-命名空间,要在XML中进行声明。
<bean id="cdPlayer" class="soundsystem.CDPlayer"
p:compactDisc-ref="compactDiSc" />
属性的名字使用了“p:”前缀,表明我们所设置的是一个属性。接下来就是要注入的属性名。最后属性名以“-ref ”结尾,提示Spring要装配的是一个引用,而不是字面量。
将字面量注入到属性中
借助元素的value来装配属性:
<bean id="CDPlayer" class="soundsystem.CDPlayer">
<property name = "title" value="Sgt. Pepper's Lonely Hearts Club Band" />
<property name = "artist" value="The Beatles" />
<property name="tracks">
<list>
<value>A</value>
<value>B</value>
<value>C</value>
<value>D</value>
<value>E</value>
</list>
</property>
</bean>
还可以使用p-命名空间的属性来完成该功能,与c-命名空间一样,装配bean引用与装配字面量的唯一区别在于是否带有“-ref”后缀,如果没有,所装配的就是字面量。
我们不能用p-命名空间来装配集合,没有便利的方式使用p-命名空间来指定一个值(或bean引用)的列表,可以使用Spring的util-命名空间的一些功能来简化。
要使用必须先声明util-命名空间及其模式。
借助util-命名空间中<util:list>元素建立一个新的bean来实现上述功能
<util:list id="trackList">
<value>A</value>
<value>B</value>
<value>C</value>
<value>D</value>
<value>E</value>
</util:list>
现在可以将上面的bean注入到BlankDisc的tracks属性中
<bean id="compactDisc"
class="soundsystem.BlankDisc"
p:title = "Sgt. Pepper's Lonely Hearts Club Band"
p:artist = "The Beatles"
p:tracks-ref="trackList" />
元素 | 描述 |
---|---|
<util:constant> | 引用某个类型的public static域,并将其暴露为bean |
<util:list> | 创建一个java.util.List类型的bean,其中包含值和引用 |
<util:map> | 创建一个java.util.Map类型的bean,其中包含值和引用 |
<util:set> | 创建一个java.util.Set类型的bean,其中包含值和引用 |
<util:properties> | 创建一个java.util.Properties类型的bean |
<util:property-path> | 引用一个bean的属性(或内嵌属性),其中包含值和引用 |
导入和混合配置
在Spring中,所有类型的配置方案都不是互斥的。所以可以将混合在一起使用。
在JavaConfig中引用XML配置
如果一个Config需要用到另一个Config,我们需要将这两个类组合起来,一种方式是使用@Import注解导入;另一个方式是创建一个更高级别 的Config,在这个类中使用@Import将两个配置组合在一起。
现在我们想通过XML来配置BlankDisc,那么该如何让Spring同时加载它和其他基于Java的配置呢?
可以用ImportResource注解,我们可以这样来配置
...
@Configuration
@Import(CDPlayerConfig.class)
@ImportResource("classpath:cd-config.xml")
public class SoundSystemConfig{
}
在XML配置中引用JavaConfig
在XML中,可以用<import>元素来拆分XML配置。即可以在XML配置文件中使用<import>元素来引用该文件(xml文件)。
但是我们想引入一个JavaConfig类呢?事实上并没有XML元素可以导入JavaConfig元素。但是为了将JavaConfig类导入到XML配置中,我们可以这样声明bean。
<beans ...>
<bean class="soundsystem.CDConfig" />
<bean id="cdPlayer"
class="soundsystem.CDPlayer"
c:cd-ref="compactDisc" />
</beans>
采用这种方式,两种配置——其中一个用XML描述,另一个使用JavaConfig描述被组合了起来。
类似的,可以创建一个更高层次的配置文件,只负责将两个或者更多个配置组合起来。
注意
不管通过JavaConfig还是XML进行装配,通常都要创建一个根配置(root Configuration),这个配置将两个或者更多装配类和/或 XML文件组合起来;也会在这里启用组件扫描(通过<context:component-scan>或@ComponentScan)。