簡體   English   中英

如何在 Spring 中以編程方式生成新的托管 bean

[英]How to generate new managed bean programmatically in Spring

這個例子只是一個虛擬的例子來展示我遇到的問題,所以不要太着迷於用其他方法來解決這里的具體問題。 我的問題更多是關於了解解決 Spring 中的一類問題的正確技術

假設我有一個托管 bean Info

@Component
public class Info {
  private final String activeProfile;
  private final Instant timestamp;

  public Info(@Value("${spring.profiles.active}") String activeProfile) {
    this.activeProfile = activeProfile;
    this.timestamp = Instant.now();
  }
}

這里的關鍵是 bean 需要由 Spring 注入的東西(我的示例中的活動配置文件)以及每次創建 bean 時都會更改的東西(我的示例中的時間戳)。 因為后者,我不能使用Singleton scope。 獲取此類 bean 的新實例的正確方法是什么?

我目前擁有的是,bean不是托管的(沒有@Component ,沒有@Value ),我有一個托管服務(一個控制器),它顯式地調用常規Info POJO 的構造函數。 就像是

@RestController
public class InfoRestController { 
  @GetMapping
  public Info getInfo(@Value("${spring.profiles.active}") String activeProfile) {
    return new Info(activeProfile);
  } 
}

此解決方案的問題在於,它將活動配置文件的知識泄露給 controller 只是為了將其傳遞給Info的構造函數,而從概念上講,controller 不應該知道構造 Info bean。 這是依賴注入的要點之一

我想到了一些潛在的解決方案:

  • 在 controller 中有一個InfoFactory FactoryBean的引用,然后return factory.getObject(); . 但是我真的需要為這樣一個簡單的案例創建一個新的 class 嗎?
  • 有一個構造托管 bean 的@Bean工廠方法。 這仍然存在該方法顯式實例化Info POJO 的問題,因此它本身需要對其進行 Spring 注入。 此外,這是完整的樣板文件。

Info bean 的構造非常簡單,我想在 Spring 中有一種更簡單的方法可以實現這一點。 在那兒?

當請求到來時,您似乎需要一個托管的新 object。 為此,您可以使用@Scope("prototype")標記您的 Bean,這將解決您的問題。

具有原型 scope 的 bean 將在每次從容器請求時返回不同的實例。

要了解更多,原型是如何工作的,請查看此頁面:

  1. Spring - 原型 scope 示例使用 @Scope 注釋
  2. 3.原型Scope

這一切都取決於變化的事物是什么。 您可以使用protoype scope,如 ruhul 的回答所示。 然而,這里的問題是原型實例只會在創建InfoRestController bean 時注入一次。

您可以嘗試使用request scope - 當您使用此類 scope 自動裝配 bean 時,將為每個 HTTP 請求創建一個新實例:

@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class Info {
    private final String activeProfile;
    private final Instant timestamp;

    public Info(@Value("${spring.profiles.active}") String activeProfile) {
        this.activeProfile = activeProfile;
        this.timestamp = Instant.now();
    }
}

這將確保為每個請求創建一個新的Info實例:

@RestController
public class InfoRestController {

    private Info info;

    @Autowired
    public InfoRestController(Info info) {
        this.info = info;
    }

    @GetMapping("/test")
    public Info get() {
        return info;
    }
}

另請記住,注入InfoRestControllerInfo實例將被代理,因此在此示例中,您將返回一個帶有一些附加字段的代理實例。 為了克服這個問題,我們可以從注入的 bean 中復制一個值:

@RestController
public class InfoRestController {

    private Info info;

    @Autowired
    public InfoRestController(Info info) {
        this.info = info;
    }

    @GetMapping("/test")
    public Info get() {
        return Info.of(info.getActiveProfile(), info.getTimestamp()); // create a copy
    }
}

@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class Info {

    private final String activeProfile;
    private final Instant timestamp;

    @Autowired // this constructor should be used by spring
    public Info(@Value("${spring.profiles.active}") String activeProfile) {
        this.activeProfile = activeProfile;
        this.timestamp = Instant.now();
    }

    private Info(String activeProfile, Instant timestamp) { // private constructor
        this.activeProfile = activeProfile;
        this.timestamp = timestamp;
    }

    public static Info of(String activeProfile, Instant instant) { //static factory method
        return new Info(activeProfile, instant);
    }

    // getters
}

缺少的部分是javax.inject.Provider 我不知道它,但它具有我正在尋找的界面。 最終的解決方案是確實讓 Spring 管理 bean ( Info ) 並在 rest controller 中使用Provider 這是 bean,幾乎和 OP 中的一樣

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Info {
  private final String activeProfile;
  private final Instant timestamp;

  public Info(@Value("${spring.profiles.active}") String activeProfile) {
    this.activeProfile = activeProfile;
    this.timestamp = Instant.now();
  }
}

如 ruhul 所建議的,該 bean 具有原型 scope。 我之前嘗試過,但沒有Provider我仍然卡住了。 這是如何在 controller 中返回它

@RestController
public class InfoRestController { 
  @Autowire
  private Provider<Info> infoProvider;

  @GetMapping
  public Info getInfo() {
    return infoProvider.get();
  } 
}

為了完整起見,我通過注入 spring ApplicationContext然后使用context.getBean("info")找到了另一種更丑陋的方法,但是對 spring 上下文和字符串名稱的依賴是一種氣味。 Provider的解決方案更加專注

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM