杰瑞科技汇

java spring获取bean

下面我将从传统方式现代推荐方式,详细讲解如何在 Spring 中获取 Bean,并分析各自的优缺点和适用场景。

java spring获取bean-图1
(图片来源网络,侵删)

核心概念:IoC 容器 和 Bean

在开始之前,需要理解两个基本概念:

  1. IoC (Inversion of Control) 控制反转:是一种设计思想,将对象的创建和依赖管理从应用程序代码中转移到外部容器(即 Spring IoC 容器)。
  2. Bean:由 Spring IoC 容器实例化、组装和管理的对象,你交给 Spring 管理的 Java 对象就是 Bean。

Spring IoC 容器的主要接口是 ApplicationContext,它是 Bean 工厂的核心,提供了更高级的功能。


通过 @Autowired 自动注入 (最推荐)

这是最常用、最符合 Spring 设计思想的方式,你不需要主动去“获取” Bean,而是通过依赖注入,让 Spring 自动将需要的 Bean “注入”到你的类中。

字段注入

直接在类的字段上使用 @Autowired 注解。

java spring获取bean-图2
(图片来源网络,侵删)
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class UserService {
    // @Autowired 也可以省略,前提是启用了 @ComponentScan 并且类只有一个构造器
    @Autowired
    private UserRepository userRepository;
    public String getUserName(Long id) {
        return userRepository.findById(id).getName();
    }
}

优点

  • 代码简洁,非常直观。

缺点

  • 无法进行单元测试:直接对 UserService 进行单元测试时,无法模拟或注入 UserRepository,因为字段是 private final 的,且没有提供 setter。
  • 违反了依赖倒置原则:类与 Spring 框架紧密耦合,@Autowired 注解本身是 Spring 特有的。
  • 隐藏了依赖关系:类的依赖关系不像构造器注入那样清晰可见。

注意:从 Spring 4.3 开始,如果一个类只有一个构造器,@Autowired 可以省略,Spring 会自动将构造器参数注入。

Setter 注入

通过 public 的 setter 方法来注入 Bean。

java spring获取bean-图3
(图片来源网络,侵删)
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class OrderService {
    private PaymentService paymentService;
    @Autowired
    public void setPaymentService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }
}

优点

  • 可进行单元测试:可以轻松地在测试类中调用 setPaymentService() 方法来注入 mock 对象。
  • 可选依赖:可以将 @Autowiredrequired 属性设置为 false(默认为 true),表示该依赖是可选的。

缺点

  • 对象可能处于不一致状态:在对象构造完成但 setter 方法被调用之前,对象可能处于一个不完整或无效的状态,另一个线程可能在 setter 调用前就调用了该对象的方法。

构造器注入 (最佳实践)

这是目前公认的最佳注入方式,通过类的构造器来注入所有依赖。

import org.springframework.stereotype.Service;
@Service
public class ProductService {
    private final InventoryService inventoryService;
    private final NotificationService notificationService;
    // @Autowired 在只有一个构造器的情况下可以省略
    public ProductService(InventoryService inventoryService,
                          NotificationService notificationService) {
        this.inventoryService = inventoryService;
        this.notificationService = notificationService;
    }
}

优点

  • 依赖不可变:依赖被声明为 final,确保它们在对象创建后不会被修改,线程更安全。
  • 依赖关系明确:类的所有依赖都通过构造器参数清晰地暴露出来,一看便知。
  • 对象状态一致:在对象被创建时,所有依赖都已准备好,对象总是处于一个完整、有效的状态。
  • 易于单元测试:非常容易进行单元测试,只需在 new 对象时传入 mock 依赖即可。
  • 符合最佳实践:被 Spring 官方和 Java 社区广泛推荐。

通过 ApplicationContext 编程式获取 (不推荐,除非特殊场景)

在某些特殊情况下,你可能无法使用依赖注入,例如在工具类、@Configuration 类的静态方法中,或者在与 Spring 生命周期无关的第三方代码中,这时,你可以通过 ApplicationContext 来手动获取 Bean。

实现 ApplicationContextAware 接口

让你的类实现 ApplicationContextAware 接口,Spring 会在初始化时调用 setApplicationContext() 方法,将 ApplicationContext 实例注入进来。

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class SpringContextHolder implements ApplicationContextAware {
    private static ApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringContextHolder.applicationContext = applicationContext;
    }
    public static <T> T getBean(Class<T> clazz) {
        return applicationContext.getBean(clazz);
    }
    public static Object getBean(String name) {
        return applicationContext.getBean(name);
    }
    public static <T> T getBean(String name, Class<T> clazz) {
        return applicationContext.getBean(name, clazz);
    }
}

使用方法

// 在其他任何地方都可以这样调用
UserService userService = SpringContextHolder.getBean(UserService.class);

优点

  • 灵活,可以在任何地方获取 Bean。

缺点

  • 强耦合:你的类与 Spring API 紧密耦合,失去了 Spring 的解耦优势。
  • 难以测试:单元测试时需要额外处理 ApplicationContext 的模拟。
  • 代码侵入性强:污染了业务代码,使其不再是纯粹的 POJO。

注入 ApplicationContext

通过 @AutowiredApplicationContext 注入到你的类中,然后使用它来获取 Bean。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
@Component
public class MyComponent {
    @Autowired
    private ApplicationContext applicationContext;
    public void someMethod() {
        UserService userService = applicationContext.getBean(UserService.class);
        // ... 使用 userService
    }
}

优缺点:与实现 ApplicationContextAware 类似,同样存在强耦合的问题,只是实现方式略有不同。


通过 @Resource 注入 (JSR-250 标准)

@Resource 是 Java EE 规范(JSR-250)中的注解,Spring 也对其提供了支持,它的注入逻辑与 @Autowired 略有不同。

import javax.annotation.Resource;
import org.springframework.stereotype.Service;
@Service
public class ReportService {
    // 默认按名称注入,查找名为 "reportGenerator" 的 Bean
    @Resource
    private ReportGenerator reportGenerator;
    // 可以指定名称
    @Resource(name = "emailNotifier")
    private Notifier notifier;
    // 如果没有 name 属性,则按类型注入,类似于 @Autowired
    @Resource
    private Logger logger;
}

@Autowired 的区别

  • @Autowired:默认按类型进行注入,如果找到多个同类型的 Bean,会再按名称进行匹配,可以通过 @Qualifier("beanName") 来指定具体的 Bean 名称。
  • @Resource:默认按名称进行注入,如果找不到指定名称的 Bean,则会再按类型进行匹配,可以通过 @Resource(type = SomeClass.class) 来指定类型。

总结与最佳实践

方式 优点 缺点 适用场景
@Autowired (构造器) 依赖不可变、关系清晰、状态一致、易于测试 代码比字段注入稍多 强烈推荐,适用于绝大多数需要注入依赖的 Spring 管理的 Bean。
@Autowired (字段) 代码简洁 无法测试、耦合度高、依赖不明确 不推荐,仅适用于快速原型或极简单的组件。
@Autowired (Setter) 可选依赖、可测试 对象状态可能不一致 适用于可选的、非核心的依赖。
ApplicationContext 灵活,可在非 Spring 管理的类中使用 强耦合、代码侵入、难以测试 最后的选择,工具类、静态方法、第三方集成等无法使用依赖注入的场景。
@Resource JSR-250 标准,按名注入 逻辑不如 @Autowired 直观 当你明确希望按名称注入,或者项目同时使用 Java EE 和 Spring 时。

最终建议

  1. 优先使用构造器注入 (@Autowired on constructor):这是最健壮、最清晰、最符合最佳实践的方式,让你的所有依赖都通过构造器声明,final 修饰。
  2. 避免字段注入:除非有非常特殊且合理的理由(某些框架的代理类需要),否则不要在业务代码中使用字段注入。
  3. 谨慎使用编程式获取:只有在依赖注入完全无法使用的“绝境”中,才考虑通过 ApplicationContext 来获取 Bean,这是一种“反模式”,会破坏 Spring 的核心优势。
  4. 了解 @Resource:知道它的存在和与 @Autowired 的区别,但在新项目中,通常优先使用 @Autowired + @Qualifier 的组合来精确控制注入。
分享:
扫描分享到社交APP
上一篇
下一篇