在 Singleton class 中自动装配一个 Spring bean

Autowire a Spring bean in a Singleton class

我正在尝试在 Singleton class 中自动装配一个 bean,我知道避免手动自动装配总是最好的主意,但是这个 class 被用在很多地方所以我不想更改此 class.

的调用者

Runner.java

@Component
public class RunnerClass {
    @Autowired
    public ConfigService configService;
}

ConfigService.java

@Service
public class ConfigService {
    private ConfigServiceDAO = ConfigServiceDAO.getInstance();
}

ConfigServiceDAO.java

public class ConfigServiceDAO {

    //Bean I want to autowire here....
    @Autowired
    ConfigServiceDAOBuilder DAOBuilder

    public static ConfigServiceDAO getInstance() {
        return SingletonHolder.INSTANCE;
    }

    private static class SingletonHolder {
        public static final ConfigServiceDAO INSTANCE = new ConfigServiceDAO();

        private SingletonHolder() {}
    }
}

ConfigServiceDAO 中的 DAOBuilder 始终为 null,这是有道理的,因为我的理解是当手动实例化 class 时,不会发生 spring 注入。 如果我想将 ConfigServiceDAO 保留为非 spring 组件,这里的解决方案是什么?

====编辑==== 我知道可以将 ConfigServiceDAO 作为 spring 组件并自动装配所有依赖项。 但是来自不同包的很多 classes 已经调用 ConfigServiceDAO.getInstance().someMethod() 所以我想正确的问题是,将 spring 组件自动装配到手动实例化的 class 的最佳方法是什么。

看了你的评论我很困惑,所以让我这样说。您所指的手动自动装配是 Spring 依赖注入方式。 每当您使用任何具有默认范围实例的 Spring 刻板印象注释时,始终是 Singleton。

您的 ConfigService class 有问题。 你把事情搞混了,你应该用@configuration 创建一个单独的配置 class 并为 class ConfigServiceDAO 创建 Bean,如下所示

@Configuration
Class Config{ 

@Bean
public ConfigServiceDAO configServiceDAO( ){
return ConfigServiceDAO.getInstance();
}
}

然后在 ConfigService class 中自动装配 ConfigServiceDAO。使用此 Spring 将以正确的顺序解析所有依赖项,DAOBuilder 不应为空。

我不知道你的用例,但你不能在 Spring bean 之外使用 @Autowired 注释。 但是,如果您确实需要从非 Spring 代码段访问 Spring bean,您可以像下面那样进行。然而,这是一种非常不 Spring 的设计依赖关系的方式。

import org.springframework.context.ApplicationContext;

public enum ApplicationContextHolder {
    INSTANCE;

    private ApplicationContext applicationContext;

    public ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }
}

那么你有一个配置class:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;

@Configuration
public class SomeConfig {
    @Autowired
    private ApplicationContext applicationContext;

    @PostConstruct
    public void init() {
        ApplicationContextHolder.INSTANCE.setApplicationContext(applicationContext);
    }
}

然后在您的 DAO 中 class 您将获得对您感兴趣的构建器 bean 的引用。像这样:

public class ConfigServiceDAO {
    public static ConfigServiceDAO getInstance() {
        return SingletonHolder.INSTANCE;
    }

    private static class SingletonHolder {
        public static final ConfigServiceDAO INSTANCE = 
        ApplicationContextHolder.INSTANCE.getApplicationContext().getBean(ConfigServiceDAOBuilder.class).buildConfigServiceDAO()

        private SingletonHolder() {}
    }
}

同样,这是一种非常不 Spring 的做事方式。

据我了解你想要什么:由 Spring ConfigServiceDAOBuilder 创建。之后将其注入 class ConfigServiceDAO 的非托管对象。您可以在实例化 Spring 应用程序上下文后执行此操作。 For example with CommanLineRunner:

@Component
public class CommandLineAppStartupRunner implements CommandLineRunner {
    @Autowired
    ConfigServiceDAOBuilder DAOBuilder

    @Override
    public void run(String...args) throws Exception {
        ConfigServiceDAO.getInstance().init(DAOBuilder);
    }
}

ConfigServiceDAO 中必须是帮助注册所有需要的 bean 的方法 init

Spring 仅在它自己管理的 bean 中处理 @Autowired。 所以你有两个选择:

  1. 摆脱单例 - 如果您正在使用 spring,它在应用程序上下文中已经是单例。这是迄今为止最好的一般方法(假设调用您的单例的应用程序的其他部分也是 spring 驱动的)。我认为您不应该害怕更改 ConfigServiceDAO.getInstance.method() - IDE 中的重构工具将完成这项工作。

  2. 如果你做不到 1,请不要在单例中使用自动装配注释 - 无论如何它都没用,相反,当你配置了应用程序上下文时(在侦听器中 spring例如,在应用程序启动时发出),通过反射手动调用 appCtx.getBean(ConfigServiceDAOBuilder.class) 和 "inject it" 来访问 ConfigServiceDAOBuilder bean,这就是 Spring 对 [=34 的作用=] 无论如何管理 beans:

@EventListener
public void onApplicationReadyEvent(ApplicationReadyEvent event) {
  ConfigServiceDAOBuilder builder = 
    event.getApplicationContext().getBean(ConfigServiceDAOBuilder.class);

    ConfigServiceDao dao = ConfigServiceDAO.getInstance();
    dao.setDaoBuilder(builder); // or alternatively by reflection
}

作为旁注,考虑使用方法 setDaoBuilder 作为私有包,以保护单例免受意外调用 setter