【问题标题】:Autowire a Spring bean in a Singleton class在 Singleton 类中自动装配 Spring bean
【发布时间】:2020-02-03 23:52:26
【问题描述】:

我正在尝试在 Singleton 类中自动装配一个 bean,我知道避免手动自动装配总是一个最好的主意,但是这个类在很多地方都被使用,所以我不想改变这个的调用者类。

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,这是有道理的,因为我的理解是当手动实例化类时,不会发生spring 注入。 如果我想将 ConfigServiceDAO 保留为非弹簧组件,这里的解决方案是什么?

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

【问题讨论】:

  • 如果由于手动构造而无法正常工作,那么显而易见的答案可能是不依赖自动布线。
  • 我知道.. 只是 ConfigServiceDaoBuilder 是一个具有自动装配 bean 的组件
  • 1.如果 ConfigServiceDAO.java 是您在此处删除的整个类,则它不是单例。我可以轻松调用新的 ConfigServiceDAO()。 2. 你正在把它变成一个组件/bean,因为 Spring 不管理实例,所以使用 @Autowired 没有意义。 3.关联是——Runner有一个ConfigService,ConfigService有一个ConfigServiceDAO。您不想让 Spring 管理 bean 却创建它的单例实例,为什么不利用 Spring 特性呢? (除非您有多个容器并且 ConfigDAO 是跨上下文共享的单例)

标签: java spring spring-boot dependency-injection autowired


【解决方案1】:

我不知道您的用例,但您不能在 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;
    }
}

那么你就有了一个配置类:

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 类中,您将获得对您感兴趣的构建器 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 的做事方式。

【讨论】:

    【解决方案2】:

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

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

    2. 1234563 ,通过调用appCtx.getBean(ConfigServiceDAOBuilder.class) 并通过反射手动“注入”来访问ConfigServiceDAOBuilder bean,这就是Spring 对spring 托管bean 所做的:
    @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 的影响

    【讨论】:

      【解决方案3】:

      据我了解您想要什么:由 Spring ConfigServiceDAOBuilder 创建。之后将其注入到类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 中必须是方法init,这有助于注册所有需要的bean。

      【讨论】:

        【解决方案4】:

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

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

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

        然后在 ConfigService 类中自动装配 ConfigServiceDAO。有了这个 Spring 将以正确的顺序解决所有依赖项,并且 DAOBuilder 不应该为空。

        【讨论】:

        • PowerLove 明确表示不希望 ConfigServiceDAO 作为 spring 组件
        • 我的意思是,如果这是唯一的方法,我可以做到这一点,并使所有依赖注入成为弹簧方式。只是我不想改变它,因为来自这么多包的这么多类调用像 ConfigServiceDAO.getInstance.method() 这样的方法
        猜你喜欢
        • 1970-01-01
        • 2022-08-20
        • 1970-01-01
        • 2014-03-11
        • 1970-01-01
        • 2011-01-23
        • 2016-09-27
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多