太強了!Spring Boot 中 12 種獲取 Bean 的神操作,最后一種你想不到
環境:SpringBoot3.4.2
1. 簡介
在 Spring Boot 日常開發中,獲取 Bean 是極為頻繁且關鍵的操作。無論是處理業務邏輯的服務層,還是負責數據交互的持久層,亦或是控制請求響應的控制器層,都離不開對 Bean 的調用。
然而,面對復雜多變的業務需求和架構設計,單一的 Bean 獲取方式往往難以滿足開發者的需求。有時我們需要根據特定的條件、場景或設計模式來靈活獲取 Bean。此時,掌握多種獲取 Bean 的方法就顯得尤為重要。
在本篇文章中我們將介紹 12 種 獲取 Bean 方式,能幫助我們更高效、更優雅地應對各種開發挑戰。它們能讓我們在面對不同業務場景時,迅速找到最適合的 Bean 獲取策略,從而提升開發效率,優化代碼結構,讓 Spring Boot 應用程序更加健壯和靈活。
2.實戰案例
準備如下類:
@Service
@Qualifier("us")
public class UserService {
public Object query() {
return "User" ;
}
}
在接下來的示例中我們將通過12種方式來獲取上面定義的UserService bean對象。
2.1 使用@Autowired
@Component
@Order(1)
public class ObatinBeanRunner1 implements CommandLineRunner {
@Autowired
private UserService userService ;
@Override
public void run(String... args) throws Exception {
System.err.println("方法1: 【@Autowired】 - " + this.userService.query()) ;
}
}
最常用的方式,適用于 Spring 管理的組件(如 Controller、Service 等)。
注意:官方已不推薦通過字段注入的方式了。如下官方說明:
圖片
2.2 使用@Resource
@Component
@Order(2)
public class ObatinBeanRunner2 implements CommandLineRunner {
@Resource
private UserService userService ;
@Override
public void run(String... args) throws Exception {
System.out.println("方法2: 【@Resource】 - " + this.userService.query()) ;
}
}
需要按名稱注入 Bean(JDK 自帶注解),可以通過指定name屬性來設置按照名稱注入。
2.3 使用@Inject
@Component
@Order(3)
public class ObatinBeanRunner3 implements CommandLineRunner {
@Inject
private UserService userService ;
@Override
public void run(String... args) throws Exception {
System.err.println("方法3: 【@Inject】 - " + this.userService.query()) ;
}
}
使用該注解,需要引入如下依賴:
<dependency>
<groupId>jakarta.inject</groupId>
<artifactId>jakarta.inject-api</artifactId>
</dependency>
@Inject注解是JSR-330標準注解,同時該注解還可以與@Named一起使用來限定注入的bean。
2.4 構造函數注入
@Component
@Order(4)
public class ObatinBeanRunner4 implements CommandLineRunner {
private final UserService userService ;
public ObatinBeanRunner4(UserService userService) {
this.userService = userService ;
}
@Override
public void run(String... args) throws Exception {
System.out.println("方法4: 【構造函數注入(官方推薦)】 - " + this.userService.query()) ;
}
}
Spring 官方推薦的方式,保證依賴不可變,避免空指針異常。
2.5 使用ApplicationContext
@Component
@Order(5)
public class ObatinBeanRunner5 implements CommandLineRunner, ApplicationContextAware {
private ApplicationContext context ;
@Override
public void run(String... args) throws Exception {
UserService userService = this.context.getBean(UserService.class) ;
System.err.println("方法5: 【通過ApplicationContext#getBean方法】 - " + userService.query()) ;
}
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
this.context = context ;
}
}
需要運行時動態獲取 Bean(如工具類、工廠模式)。
2.6 使用BeanFactory
@Component
@Order(6)
public class ObatinBeanRunner6 implements CommandLineRunner, BeanFactoryAware {
private BeanFactory beanFactory ;
@Override
public void run(String... args) throws Exception {
UserService userService = this.beanFactory.getBean(UserService.class) ;
System.out.println("方法6: 【通過BeanFactory#getBean方法】 - " + userService.query()) ;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory ;
}
}
相比ApplicationContext更加輕量,功能也要少。
2.7 使用ObjectProvider
@Component
@Order(7)
public class ObatinBeanRunner7 implements CommandLineRunner {
private final ObjectProvider<UserService> userServiceProvider ;
public ObatinBeanRunner7(ObjectProvider<UserService> userServiceProvider) {
this.userServiceProvider = userServiceProvider;
}
@Override
public void run(String... args) throws Exception {
UserService userService = this.userServiceProvider.getIfUnique() ;
if (userService != null) {
System.err.println("方法7: 【ObjectProvider】 - " + userService.query()) ;
}
}
}
解決循環依賴或可選依賴(Bean 可能不存在),安全獲取bean,并且還支持流式操作。我們不僅可以使用 ObjectProvider 還可以使用 ObjectFactory 或Optional。
2.8 使用@Lookup
@Component
@Order(8)
public abstract class ObatinBeanRunner8 implements CommandLineRunner {
@Lookup
public abstract UserService getUserService() ;
@Override
public void run(String... args) throws Exception {
UserService userService = this.getUserService() ;
System.out.println("方法8: 【@Lookup】 - " + userService.query()) ;
}
}
每次調用都需要新的原型(Prototype)Bean 實例。通常可以通過該注解解決單例bean依賴原型bean的情況。
2.9 使用BeanFactoryUtils
@Component
@Order(9)
public class ObatinBeanRunner9 implements CommandLineRunner, BeanFactoryAware {
private ListableBeanFactory beanFactory ;
@Override
public void run(String... args) throws Exception {
UserService userService = BeanFactoryUtils.beanOfType(this.beanFactory, UserService.class) ;
System.err.println("方法9: 【BeanFactoryUtils】 - " + userService.query()) ;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (beanFactory instanceof ListableBeanFactory bf) {
this.beanFactory = bf ;
}
}
}
該類是Spring自帶的工具類,其還是依賴BeanFactory。
2.10 使用BeanFactoryAnnotationUtils
@Component
@Order(10)
public class ObatinBeanRunner10 implements CommandLineRunner, BeanFactoryAware {
private ListableBeanFactory beanFactory ;
@Override
public void run(String... args) throws Exception {
UserService userService = BeanFactoryAnnotationUtils.qualifiedBeanOfType(beanFactory, UserService.class, "us") ;
System.out.println("方法10: 【BeanFactoryAnnotationUtils】 - " + userService.query()) ;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (beanFactory instanceof ListableBeanFactory bf) {
this.beanFactory = bf ;
}
}
}
該工具類,我們可以用來獲取那些使用了@Qualifier注解的具體bean對象。
2.11 自定義工具類
@Component
public class SpringUtils implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext context) {
SpringUtils.context = context;
}
public static <T> T getBean(Class<T> beanClass) {
return context.getBean(beanClass);
}
}
通過定義靜態方法,這樣我們可以在Spring容器完全啟動完成或,在任意類中使用了。
@Component
@Order(11)
public class ObatinBeanRunner11 implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
UserService userService = SpringUtils.getBean(UserService.class) ;
System.err.println("方法11: 【靜態方法】 - " + userService.query()) ;
}
}
2.12 使用@Value+SpEL
@Component
@Order(12)
public class ObatinBeanRunner12 implements CommandLineRunner {
@Value("#{@userService}")
private UserService userService ;
@Override
public void run(String... args) throws Exception {
System.err.println("方法12: 【@Value + SpEL】 - " + userService.query()) ;
}
}
通過SpEL表達式來引用其它的bean,內部會自動的進行類型的轉換。注意:這里的userService是對應的bean名稱。
以上12種方法最終輸出結果如下:
圖片