【问题标题】:Injecting objects into singleton class with spring annotations使用弹簧注释将对象注入单例类
【发布时间】:2016-11-10 07:01:37
【问题描述】:

如何使用spring注解将对象注入单例类?

我在下面的 sn-p 中有一些类似的代码,我想将 B 类的对象注入其中。

public class A {
    private B b;
    private static A instance;

    private A () {
        set some timer tasks
        ...
    }

    public A getInstance() {
        if (instance == null) { instance = new A(); }
        return instance;
    }

当我在 b 对象上方使用 @Inject 时,我有 NullPointerException。

public final class SessionHolder {

private static SessionHolder instance;
@Inject
@Getter
@Setter
private PdbIdContainer pdbIdContainer;

private Map<UUID, SessionData> sessionMap;

private SessionHolder() {
    this.sessionMap = new ConcurrentHashMap<>();
    pdbIdContainer.update();
    TimerTask timerTask1 = new TimerTask() {
        @Override
        public void run() {
            Date d = new Date();
            sessionMap.entrySet().stream().filter(map -> TimeUnit.MILLISECONDS.toMinutes(
                    d.getTime() - map.getValue().getLastUseTime().getTime()) >= Integer.parseInt(
                    AppController.getConfig().getSessionInterval())).forEach(map -> sessionMap.remove(map.getKey()));
        }
    };
    TimerTask timerTask2 = new TimerTask() {
        @Override
        public void run() {
            pdbIdContainer.update();
        }
    };
    Timer timer = new Timer();
    timer.scheduleAtFixedRate(timerTask1,
            Integer.parseInt(AppController.getConfig().getSessionMapDelay()),
            Integer.parseInt(AppController.getConfig().getSessionMapInterval()));
    timer.scheduleAtFixedRate(timerTask2,
            Integer.parseInt(AppController.getConfig().getPdbIdsSetDelay()),
            Integer.parseInt(AppController.getConfig().getPdbIdsSetInterval()));
}


public static SessionHolder getInstance() {
    if (instance == null) {
        instance = new SessionHolder();
    }
    return SessionHolder.instance;
}

public static SessionData getSession(UUID id) {
    return getInstance().sessionMap.get(id);
}

public static UUID createSession(StructureContainer structure) {
    UUID id = UUID.randomUUID();
    getInstance().sessionMap.put(id, new SessionData(structure, new Date()));
    return id;
}
}

【问题讨论】:

  • 在所有其他注入中使用@Autowired

标签: java spring dependency-injection


【解决方案1】:

在 Spring 应用程序中,您不需要也不应该创建单例类。当您创建单例 bean 时,Spring 将确保在上下文中仅存在此类的单个实例(单例是默认 bean 范围)。

你的班级应该是这样的:

@Component
public class SessionHolder {

    private PdbIdContainer pdbIdContainer;

    private Map<UUID, SessionData> sessionMap;

    @Autowired // you can omit @Autowired if you use Spring 4.3 or higher
    SessionHolder(PdbIdContainer pdbIdContainer) {
        this.pdbIdContainer = pdbIdContainer;
        this.sessionMap = new ConcurrentHashMap<>();
        pdbIdContainer.update();
        TimerTask timerTask1 = new TimerTask() {
            @Override
            public void run() {
                Date d = new Date();
                sessionMap.entrySet().stream().filter(map -> TimeUnit.MILLISECONDS.toMinutes(
                        d.getTime() - map.getValue().getLastUseTime().getTime()) >= Integer.parseInt(
                        AppController.getConfig().getSessionInterval())).forEach(map -> sessionMap.remove(map.getKey()));
            }
        };
        TimerTask timerTask2 = new TimerTask() {
            @Override
            public void run() {
                pdbIdContainer.update();
            }
        };
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(timerTask1,
                                  Integer.parseInt(AppController.getConfig().getSessionMapDelay()),
                                  Integer.parseInt(AppController.getConfig().getSessionMapInterval()));
        timer.scheduleAtFixedRate(timerTask2,
                                  Integer.parseInt(AppController.getConfig().getPdbIdsSetDelay()),
                                  Integer.parseInt(AppController.getConfig().getPdbIdsSetInterval()));
    }

    public SessionData getSession(UUID id) {
        return sessionMap.get(id);
    }

    public UUID createSession(StructureContainer structure) {
        UUID id = UUID.randomUUID();
        sessionMap.put(id, new SessionData(structure, new Date()));
        return id;
    }
}

【讨论】:

  • 非常非常非常感谢。有用。我可以确定 spring 只允许在每次运行我的 web 应用程序时设置这些计时器任务一次吗?
  • 每个应用程序上下文一次。如果您有多个应用程序上下文,请确保此包仅被一个扫描。
  • 顺便说一句,对于调度任务,请查看@Scheduled 注释。它会简化你的代码。
  • 这是一个非常好的观点(即让 spring 去做)。 @Piotrowy 如果您担心,请在实例化时将日志记录添加到类中,并检查以确保您看到它一次。正如 Maciej 所提到的,有一些方法可以让 spring 重新创建 bean,所以如果这特别敏感的话,无论如何长期来看都是值得的。
【解决方案2】:

如果可行,我建议使用 CDI 注释(javax.inject.Inject 中的@Inject),而不是 Spring 的 @Autowired。这样,如果您需要将另一个移动到另一个 DI 提供程序,您就不会被 Spring 绑定。

public class A {

    @Inject //or @Autowired - Spring 
    private B b;
    private static A instance;

【讨论】:

  • 我认为这不能回答问题。
  • 问题是如何将 B 类注入单例... 10 分钟后,在提供答案后,提供了更多信息,表明 B 类可能不是 Spring 管理的。所以,它确实回答了最初的问题。
  • @Mechkov 您多久更换一次 DI 提供商?我建议坚持使用 Spring 注释,因为您不会在 javax.inject 中找到所有 Spring 注释的等价物。
  • @MaciejWalkowiak 这取决于需求。仅仅因为我们在我之前的项目中使用了 Spring DI(连同 MVC),并不意味着我们不能切换到 Weld,在 Wildfly 和 Jersey 上运行以取代 Spring MVC。因此,考虑到这种变化进行编码总是更好的 imo。
【解决方案3】:

如果 Spring 不管理对象的生命周期,我认为它不会开箱即用地注入。

启用此功能的唯一方法是打开编译时或加载时方面的编织。

您可以完全跳过注入,并获取应用上下文的一个实例并从中检索您的 B 实例。

更多详情:Dependency Injection into Spring non-managed beans

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-19
    • 2018-03-13
    • 2011-01-02
    • 1970-01-01
    • 2012-12-06
    • 1970-01-01
    相关资源
    最近更新 更多