在进行显式配置时,与 XML 配置相比,使用 Java 代码装配 bean 是更好的方案,因为它更为强大、类型安全并且对重构友好,它就像应用程序中的其他 Java 代码一样。通常会将配置类放到单独的包中,使它与其他的应用逻辑分离开来。
创建配置类
在 Spring 中,创建配置类的关键在于为其添加 @Configuration 注解,@Configuration 注解表明这个类是一个配置类,该类应该包含在 Spring 应用上下文中如何创建 bean 的细节。代码如下:
package org.example.javaconfig;
import org.springframework.context.annotation.Configuration;
// 为类添加 @Configuration 注解,表明该类是一个配置类
@Configuration
public class CDPlayerConfig {
}
声明简单的 bean
首先,创建一个 CompactDisc 的接口及其实现类 SgtPeppers。
package org.example.javaconfig;
public interface CompactDisc {
void play();
}
package org.example.javaconfig;
import org.springframework.stereotype.Component;
public class SgtPeppers implements CompactDisc {
private String title = "Sgt. Pepper's Lonely Hearts Club Band";
private String artist = "The Beatles";
@Override
public void play() {
System.out.println("Playing " + title + " by " + artist);
}
}
要在 Spring 的配置类中声明 bean,需要编写一个方法来创建所需类型的实例,然后给这个方法添加 @Bean 注解。例如,下面的代码声明了 CompactDisc 类的 bean:
package org.example.javaconfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CDPlayerConfig {
@Bean
public CompactDisc sgtPeppers(){
return new SgtPeppers();
}
}
@Bean 注解会告诉 Spring 这个方法将会返回一个对象,该对象要注册为 Spring 应用上下文中的 bean。方法体中包含了最终产生 bean 实例的逻辑。
默认情况下,bean 的 ID 与带有 @Bean 注解的方法名是一样的。在上面的代码中,bean 的名字将会是 sgtPeppers。如果想为其设置一个不同的名字的话,那么可以重命名该方法,也可以通过 @Bean 的 name 属性指定一个不同的名字:
package org.example.javaconfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CDPlayerConfig {
// 使用 @Bean 的 name 属性指定 bean 的名称
@Bean(name="lonelyHeartsClubBand")
public CompactDisc sgtPeppers(){
return new SgtPeppers();
}
}
不论采用什么方法来为 bean 命名,bean 声明都是非常简单的。方法体返回一个新的 SgtPeppers 实例。这里是使用 Java 来进行描述的,所以可以使用 Java 的所有功能,只要最终生成一个 CompactDisc 实例即可。
借助 JavaConfig 实现注入
首先,创建一个 MediaPlayer 接口及其实现类 CDPlayer 类。在 CDPlayer 的构造函数中依赖了 CompactDisc 类。
package org.example.javaconfig;
public interface MediaPlayer {
void play();
}
package org.example.javaconfig;
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
// 在 CDPlayer 的构造函数中,依赖的 CompactDisc 类
public CDPlayer(CompactDisc cd){
this.cd = cd;
}
@Override
public void play() {
cd.play();
}
}
在上面的代码中,当需要声明一个 CDPlayer bean 时,它依赖于 CompactDisc。在这种情况下,使用 Java 代码装配 bean 最简单的方式就是引用创建 bean 的方法。例如
package org.example.javaconfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CDPlayerConfig {
@Bean
public CompactDisc sgtPeppers(){
return new SgtPeppers();
}
@Bean
public CDPlayer cdPlayer(){
return new CDPlayer(sgtPeppers());
}
}
cdPlayer() 方法与sgtPeppers() 方法一样,同样使用了 @Bean 注解,这表明这个方法会创建一个 bean 实例并将其注入到 Spring 应用上下文中。所创建的 bean ID 为 cdPlayer,与方法名相同。
cdPlayer() 的方法体与 sgtPeppers() 稍微有些区别。在这里没有使用默认的构造函器构建实例,而是调用了需要传入 CompatcDisc 对象的构造器来创建 CDPlayer 实例。
看起来,CompactDisc 是通过调用 sgtPeppers() 得到的,但情况并非完全如此。因为 sgtPeppers() 方法上添加了 @Bean 注解,Spring 将会拦截所有对它的调用,并确保直接返回该方法所创建的 bean,而不是每次都对其进行实际调用。
比如,我们引入了一个其他的 CDPlayer bean,它和之前的那个 bean 完全一样
@Bean
public CDPlayer cdPlayer() {
return new CDPlayer(sgtPeppers());
}
@Bean CDPlayer anotherCDPlayer(){
return new CDPlayer(sgtPeppers());
}
默认情况下,Spring 中的 bean 都是单例的,我们并不需要为第二个 CDPlayer bean 创建完全相同的 SgtPeppers 实例。所以,Spring 会拦截对 sgtPeppers() 的调用并确保返回的是 Spring 所创建的 bean,也就是 Spring 本身在调用 sgtPeppers() 时所创建的 CompactDisc bean。因此,两个 CDPlayer bean 会得到相同的 SgtPeppers 实例。
除了通过调用方法来引用 bean 外,还可以通过方法的参数来引用 bean:
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc) {
return new CDPlayer(compactDisc);
}
在这里,CDPlayer() 方法请求一个 CompactDisc 作为参数。当 Spring 调用 cdPlayer() 创建 CDPlayer bean 时,它会自动装配一个 CompactDisc 到配置方法之中。然后,方法体就可以按照合适的方式来使用它。借助这种技术,cdPlayer() 方法也能够将 CompactDisc 注入到 CDPlayer 的构造器中,而且不用明确引用 CompactDisc 的 @Bean 方法。
通过这种方式引用其他的 bean 通常是最佳选择,因为它不会要求将 CompactDisc 声明到同一个配置类之中。在这里甚至没有要求 CompactDisc 必须要在配置类中声明,实际上可以通过组件扫描功能自动发现或者通过 XML 来进行配置。可以将配置分散到多个配置类、XML 文件以及自动扫描和装配 bean 之中,只要功能完整健全即可。不管 CompactDisc 是采用什么方式创建出来的,Spring 都会将其传入到配置方法中,并用来创建 CDPlayer bean。
前面使用的是 CDPlayer 的构造器实现了注入,但是我们完全可以采用其他的方式注入。比如,使用 Setter 方法注入 CompactDisc,代码看起来应该像这样:
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
CDPlayer cdPlayer = new CDPlayer(compactDisc);
cdPlayer.setCompactDisc(compactDisc);
return cdPlayer;
}
注意,带有 @Bean 注解的方法可以采用任何必要的 Java 功能来产生 bean 实例。构造器和 Setter 方法只是其中的两个简单例子。