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 { ... }
最后修改:2023 年 06 月 05 日
如果觉得我的文章对你有用,请随意赞赏