杰瑞科技汇

Java中如何获取Spring Bean?

下面我将从最佳实践其他方式,全面地为你介绍如何在 Java 中获取 Spring Bean。

Java中如何获取Spring Bean?-图1
(图片来源网络,侵删)

哪种方式最好?

场景 推荐方式 优点 缺点
Spring MVC 控制器、Service、Repository 等组件 依赖注入 强烈推荐,代码优雅、类型安全、易于测试、符合 IoC 思想。
工具类、第三方库、非 Spring 管理的类 ApplicationContextAware 非常灵活,可以在任何需要的类中获取 Bean。 需要实现接口,增加了与 Spring 的耦合度。
在静态方法中获取 Bean @PostConstruct + 实例变量 解决了静态方法无法直接注入的问题,保持了类型安全。 需要一个额外的非静态实例来持有 Bean。
main 方法或测试中 ApplicationContext 适合启动时一次性获取,或进行集成测试。 手动获取,容易出错,不适合在业务代码中使用。
旧版 Spring (XML 配置) BeanFactory / ApplicationContext 传统方式,兼容性好。 代码冗长,类型不安全(需要强制类型转换)。

核心结论: 优先使用依赖注入,只有在无法使用依赖注入的特殊情况下,才考虑其他方式。


依赖注入 - 最佳实践

这是最推荐、最优雅的方式,通过在类的字段上使用 @Autowired@Resource 注解,Spring 容器会在初始化时自动为你注入所需的 Bean。

优点:

  • 代码简洁:无需手动获取,代码更易读。
  • 类型安全:编译器会检查类型,减少运行时错误。
  • 易于测试:进行单元测试时,可以轻松地 Mock 依赖的 Bean。
  • 低耦合:类本身不需要知道 Bean 是如何创建和管理的。

示例代码:

Java中如何获取Spring Bean?-图2
(图片来源网络,侵删)
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource; // 或 org.springframework.beans.factory.annotation.Autowired;
@Service
public class MyService {
    // 方式1: @Autowired (按类型注入,如果同类型有多个Bean,再按名称)
    @Autowired
    private AnotherService anotherService1;
    // 方式2: @Resource (按名称注入,默认字段名)
    @Resource(name = "anotherService2") // 指定Bean的名称
    private AnotherService anotherService2;
    public void doSomething() {
        anotherService1.performTask();
        anotherService2.performTask();
    }
}

实现 ApplicationContextAware 接口

当你需要在一个非 Spring 管理的类(例如一个工具类 Utils)中获取 Spring Bean 时,可以实现 ApplicationContextAware 接口,Spring 会在初始化该 Bean 时,将 ApplicationContext 实例注入进来。

优点:

  • 非常灵活:可以在任何需要的地方获取任何 Bean。

缺点:

  • 增加了耦合度:你的类现在依赖于 Spring 框架的 ApplicationContextAware 接口。
  • 需要注意线程安全ApplicationContext 本身是线程安全的,但如果你用它来缓存 Bean,需要自己处理并发问题。

示例代码:

Java中如何获取Spring Bean?-图3
(图片来源网络,侵删)
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component // 必须被Spring管理才能接收到回调
public class SpringContextHolder implements ApplicationContextAware {
    private static ApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringContextHolder.applicationContext = applicationContext;
    }
    /**
     * 从静态变量ApplicationContext中获取Bean, 自动转型为所赋值对象的类型.
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) {
        return (T) applicationContext.getBean(name);
    }
    /**
     * 从静态变量ApplicationContext中获取Bean, 自动转型为所赋值对象的类型.
     */
    public static <T> T getBean(Class<T> clazz) {
        return applicationContext.getBean(clazz);
    }
    // ... 其他重载方法
}

使用方式:

public class MyUtilityClass {
    public void doWork() {
        // 通过工具类获取Bean
        MyService myService = SpringContextHolder.getBean(MyService.class);
        myService.doSomething();
    }
}

在静态方法中获取 Bean

这是一个常见的特殊场景,静态方法属于类级别,而 Spring 的依赖注入是针对实例的,直接在静态方法上使用 @Autowired 是无效的。

解决方案是结合方式二@PostConstruct

示例代码:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import jakarta.annotation.PostConstruct;
@Component
public class StaticServiceHolder {
    private static MyService myService;
    @Autowired
    private MyService tempMyService; // 临时实例,用于接收注入
    @PostConstruct
    public void init() {
        // 将注入的实例赋值给静态变量
        myService = this.tempMyService;
    }
    // 提供一个静态方法来获取Bean
    public static void performStaticTask() {
        if (myService != null) {
            myService.doSomething();
        }
    }
}

使用方式:

public class Main {
    public static void main(String[] args) {
        // 直接调用静态方法
        StaticServiceHolder.performStaticTask();
    }
}

main 方法或测试中获取

在应用程序入口(如 main 方法)或集成测试中,你可能需要手动获取 ApplicationContext,然后从中获取 Bean。

示例代码 (Spring Boot 应用):

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        // 启动Spring应用,获取ApplicationContext
        ConfigurableApplicationContext context = SpringApplication.run(MyApplication.class, args);
        // 手动获取Bean
        MyService myService = context.getBean(MyService.class);
        myService.doSomething();
        // 使用完毕后关闭上下文(可选)
        // context.close();
    }
}

示例代码 (JUnit 测试):

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
@SpringBootTest
public class MyServiceTest {
    // 方式1: 直接在测试类中注入(推荐)
    @Autowired
    private MyService myService;
    @Test
    public void testDoSomething() {
        myService.doSomething();
    }
    // 方式2: 从上下文中获取
    @Autowired
    private ApplicationContext applicationContext;
    @Test
    public void testGetBeanFromContext() {
        MyService anotherService = applicationContext.getBean(MyService.class);
        anotherService.doSomething();
    }
}

旧版方式 (不推荐)

在 Spring 2.5 之前,或者在使用 XML 配置时,通常会通过 BeanFactoryApplicationContextgetBean() 方法来获取。

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
// 旧版XML方式
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
MyService myService = (MyService) factory.getBean("myService"); // 需要强制类型转换,且需要知道Bean名称

这种方式现在基本已被淘汰,因为它冗长、不安全且不易维护。

总结与最佳实践

  1. 首选依赖注入:在所有 Spring 管理的组件(@Service, @Controller, @Component, @Repository 等)中,始终使用 @Autowired@Resource 进行依赖注入,这是最干净、最安全、最符合设计原则的方式。

  2. 工具类使用 ApplicationContextAware:当你有一个无法使用依赖注入的工具类或辅助类时,实现 ApplicationContextAware 是一个可行的方案,但请务必谨慎使用,因为它会增加代码与 Spring 的耦合。

  3. 静态方法使用 @PostConstruct:解决静态方法中获取 Bean 的需求,不要试图在静态字段上直接使用 @Autowired

  4. 避免在业务代码中手动获取:尽量避免在常规业务逻辑中通过 ApplicationContext 手动获取 Bean,这是一种反模式,会使代码变得难以测试和维护,只在启动类、测试或特殊工具类中使用。

分享:
扫描分享到社交APP
上一篇
下一篇