spring有三种装配机制:
- 在 XML 中进行显式配置
- 在 Java 中进行显式配置
- 隐式的 bean 发现机制和自动装配
推荐使用优先级:自动装配 > JavaConfig > XML
自动化装配
从两个角度实现自动化装配:
- 组件扫描(component scanning):Spring 会自动发现应用上下文中所创建的 bean
- 自动装配(autowiring):Spring 自动满足 bean 之间的依赖
注册bean
@Configuration
声明这是一个配置类,相当于spring的xml配置文件
@Component
表明该类会作为组件类,并告知 Spring 要为这个类创建 bean
Spring 应用上下文中所有的 bean 都会给定一个 ID,默认 Spring 会根据类名为其指定一个 ID,即将类名的第一个字母变为小写
如果想自定义 bean 的 ID,则要将将期望的 ID 作为值传递给 @Component 注解,如下述代码
@Componet("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc {
......
}
还有另外一种为 bean 命名的方式,使用 Java 依赖注入规范(Java Dependency Injection)中所提供的 @Named 注解
package soundsystem;
import javax.inject.Named;
@Named("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc {
......
}
@ComponentScan
默认会扫描与配置类相同的包以及这个包下的所有子包,查找带有 @Component 注解的类,在 Spring 中自动为其创建一个 bean
想要指定不同的基础包,需要在 @ComponentScan 的 value 属性中指明包的名称
@Configuration
@ComponentScan("soundsystem")
public class CDPlayerConfig { }
如果想更加清晰地表明所设置的是基础包,可以通过 basePackages 属性进行配置
@ComponentScan(basePackages={"soundsystem", "video"})
可以设置多个基础包,只需要将 basePackages 属性设置为要扫描包的一个数组
@ComponentScan(basePackages={"soundsystem", "video"})
上述代码中基础包以 String 类型表示是不安全的,如果重构代码所指定的基础包可能会出现错误
除了将包设置为简单的 String 类型之外,@ComponentScan 还可以将包指定为包中所包含的类或接口,这些类所在的包将会作为组件扫描的基础包
@ComponentScan(basePackageClasses={CDPlayer.class, DVDPlayer.clas})
可以考虑在包中创建一个用来进行扫描的空标记接口(marker interface)
XML 启用组件扫描
<context:component-scan base-package="soundsystem" />
使用 context:component-scan
元素
装配bean
@Autowired
自动装配就是让 Spring 自动满足 bean 依赖的一种方法,在满足依赖的过程中,会在 Spring 应用上下文中寻找匹配某个 bean 需求的其他 bean
package soundsystem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
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;
}
public void play() {
cd.play();
}
}
@Autowired 注解可以用在类的任何方法上,Spring 会尝试满足方法参数上所声明的依赖,如果有且只有一个 bean 匹配依赖需求的话,那 么这个 bean 将会被装配进来。如果没有匹配的 bean 或有多个 bean 都能满足依赖关系,Spring 将会抛出一个异常
@Autowired(required=false)
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
可以将 @Autowired 的 required 属性设置为 false 来避免没有匹配的 bean,没有匹配的 bean 的话,Spring 将会让这个 bean 处于未装配的状态,显然这样是不安全的,需要在代码进行额外的检查
可以使用 Java 依赖注入规范(Java Dependency Injection)中所提供的 @Inject 注解代替 @Autowired 注解
Java 中进行显式配置
不使用 @ComponentScan 注解
注册bean
@Bean
@Bean
public CompactDisc sgtPeppers() {
return new SgtPeppers();
}
@Bean 注解会告诉 Spring 这个方法将会返回一个对象,该对象要注册为 Spring 应用上下文中的 bean。方法体中包含了最终产生 bean 实例的逻辑
bean 的 ID 与带有 @Bean 注解的方法名是一样的,可以通过 name 属性指定一个不同的名字
@Bean(name="lonelyHeartsClubBand")
只要保证最后返回一个对应实例,中间的逻辑可以完全自定义
装配bean
两种方法:
- 引用创建 bean 的方法
- 请求一个类作为参数
引用创建 bean 的方法
@Bean
public CDPlayer cdPlayer() {
return new CDPlayer(sgtPeppers());
}
@Bean
public CDPlayer anotherCDPlayer() {
return new CDPlayer(sgtPeppers());
}
因为 sgtPeppers() 方法上添加了 @Bean 注解, Spring 将会拦截所有对它的调用,并确保直接返回该方法所创建的 bean,而不是每次都对其进行实际的调用
默认情况下,Spring 中的 bean 都是单例的,Spring 会拦截对 sgtPeppers() 的调用并确保返回的是 Spring 所创建的 bean,两个 CDPlayer bean 会得到相同的 SgtPeppers 实例
请求一个类作为参数
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc) {
return new CDPlayer(compactDisc);
}
cdPlayer() 方法请求一个 CompactDisc 作为参数,当spring调用该方法时,会自动在应用上下文寻找 CompactDisc 装配
这种方法显然比引用创建 bean 的方法更好,因为在组件扫描是通过自动扫描或者XML配置时仍能使用
XML 中进行显式配置
在使用 XML 时,需要在配置文件的顶部声明多个 XML 模式(XSD)文件,这些文件定义了配置 Spring 的 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
http://www.springframework.org/schema/context" >
<!-- configuration details go here />
</beans>
注册bean
<bean class="soundsystem.SgtPeppers" />
创建这个 bean 的类通过 class 属性来指定的,并且要使用全限定的类名(包名 + 类型名)
没有指定ID的情况下 bean 将会根据全限定类名来进行命名。在本例中,bean 的 ID 将会是 “soundsystem.SgtPeppers#0” 以此类推
如果这个 bean 需要被引用则最好设置 id 属性,没有需求的话则没有必要设置
<bean id="compactDisc" class="soundsystem.SgtPeppers" />
装配bean
构造器注入
<constructor-arg>
元素
注入 bean 引用
<bean id="cdPlayer" class="soundsystem.CDPlayer">
<constructor-arg ref="compactDisc">
</bean>
<constructor-arg>
元素会告知 Spring 要将一个 ID 为 compactDisc 的 bean 引用传递到 CDPlayer 的构造器中
注入字面量
<bean id="compactDisc" class="soundsystem.BlankDisc">
<constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
<constructor-arg value="The Beatles" />
</bean>
使用 <constructor-arg>
元素的 value 属性
注入集合
//字面量
<bean id="compactDisc"
class="soundsystem.BlankDisc"
c:_0="Sgt. Pepper's Lonely Hearts Club Band"
c:_1="The Beatles">
<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>
<!-- ...other tracks omitted for brevity... -->
</list>
</constructor-arg>
</bean>
//引用
<bean id="beatlesDiscography"
class="soundsystem.Discography" >
<constructor-arg>
<list>
<ref bean="sgtPeppers" />
<ref bean="whiteAlbum" />
<ref bean="hardDaysNight" />
<ref bean="revolver" />
...
</list>
</constructor-arg>
</bean>
//set元素
<bean id="compactDisc" class="soundsystem.BlankDisc" >
<constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
<constructor-arg value="The Beatles" />
<constructor-arg>
<set>
<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>
<!-- ...other tracks omitted for brevity... -->
</set>
</constructor-arg>
</bean>
使用 list 元素或 set 元素,set 元素会删除重复的内容
属性注入
混合配置
在 JavaConfig 中引用 XML 配置
package soundsystem;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;
@Configuration
@Import(CDPlayerConfig.class)
//@Import({CDPlayerConfig.class, CDConfig.class})
@ImportResource("classpath:")
public class SoundSystemConfig {
}
@Import 导入 config 类
@ImportResource 导入 XML配置
在 XML 配置中引用 JavaConfig
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="soundsystem.CDConfig" />
<import resource="cdplayer-config.xml" />
</beans>
使用 import
元素导入其他XML配置
使用 bean
元素导入JavaConfig配置
自动装配的歧义性
如果不仅有一个 bean 能够匹配结果的话,这种歧义性会阻碍 Spring 自动装配属性、构造器参数或方法参数
标示首选的 bean
在声明 bean 的时候,通过将其中一个可选的 bean 设置为首选 (primary)bean 能够避免自动装配时的歧义性
不能将具有歧义性的 bean 中多个 bean 设为首选
组件扫描
@Component
@Primary
public class IceCream implements Dessert { ... }
Java 配置 bean
@Bean
@Primary
public Dessert iceCream() {
return new IceCream();
}
XML 配置 bean
<bean id="iceCream" class="com.desserteater.IceCream" primary="true" />
限定自动装配的 bean
@Primary 只能标示一个优先的可选方案,但是 Spring 的限定符能够随意的选择出一个你想要的唯一的 bean
限定符要用到@Qualifier 注解,@Qualifier 注解的参数就是想要注入的 bean 的限定符
所有使用 @Component 注解声明的类都会创建为 bean,默认情况下 bean 的限定符和其 ID 相同,并且 bean 的 ID 为首字母变为小写的类名
所以这里注入的是 IceCream 类的实例
@Autowired
@Qualifier("iceCream")
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}
创建自定义的限定符
使用默认的 bean ID 作为限定符有时候可能发生问题,当 bean 的类名改变时,bean 的 ID 和其限定符都会发生变化,导致对应的注入的设置也得修改。因此可以自定义限定符将其固定下来,当 bean 的类名改变时其限定符也不会改变
@Component
@Qualifier("cold") //设置 bean 的限定符
public class IceCream implements Dessert { ... }
上述代码将 bean 的限定符设置为 cold,因此修改bean 的类名不会影响到限定符
@Autowired
@Qualifier("cold") //限定自动装配
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}
当通过 Java 配置显式定义 bean 的时候,@Qualifier 也可以与 @Bean 注解一起使用
@Bean
@Qualifier("cold")
public Dessert iceCream() {
return new IceCream();
}
使用自定义的限定符注解
使用自定义的 @Qualifier 值时,最佳实践是将限定符设置为 bean 的一种特征或描述
但是如果多个 bean 都具备相同特性,就需要通过多个限定将范围缩小到唯一一个 bean,当 Java 不允许在同一个条目上重复出现相同类型的多个注解,所以需要创建自定义的限定符注解,它本身要使用 @Qualifier 注解来标注
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD,
ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold { }
上述代码创建了一个自定义的 @Cold 注解,它与注解 @Qualifier("cold") 效果相同,但是现在一个条目上可以有多个类似的限定符注解
//不允许在同一个条目上重复出现相同类型的多个注解,这种写法是错误的
@Component
@Qualifier("cold")
@Qualifier("creamy")
public class IceCream implements Dessert { ... }
//使用 @Cold 和 @Creamy 替代多个 @Qualifier 注解
@Component
@Cold
@Creamy
public class IceCream implements Dessert { ... }
1 条评论
写的很好