Как программно генерировать новый управляемый бин в Spring - PullRequest
4 голосов
/ 30 октября 2019

Этот пример - всего лишь фиктивный пример, показывающий проблему, с которой я столкнулся, поэтому не слишком увлекайтесь альтернативными способами для решения конкретной проблемы здесь. Мой вопрос больше о понимании правильной техники для решения проблем типа Spring

Скажем, у меня есть управляемый компонент 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. Как правильно получить новые экземпляры такого компонента?

То, что у меня сейчас есть, это то, что bean-компонент не управляемый (нет @Component, нет @Value), и у меня есть управляемая служба (Контроллер), которая вызывает конструкторобычный Info POJO явно. Что-то вроде

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

Проблема с этим решением заключается в том, что оно передает информацию об активном профиле контроллеру просто для того, чтобы передать его конструктору Info, когда концептуально контроллер не должен знать опостроение информационного компонента. Это одна из точек внедрения зависимости

Я подумал о некоторых возможных решениях:

  • Имеем ссылку на InfoFactory FactoryBean в контроллере итогда return factory.getObject();. Но действительно ли мне нужно создавать новый класс для такого простого случая?
  • Есть фабричный метод @Bean, который создает управляемый компонент. По-прежнему существует проблема, заключающаяся в том, что метод явно создает экземпляр Info POJO, поэтому самому ему необходимо выполнить инъекцию Spring. Кроме того, это полный шаблон.

Конструкция боба Info настолько тривиальна, что я думаю, что есть более простой способ сделать это весной. Есть ли?

Ответы [ 3 ]

1 голос
/ 30 октября 2019

Все зависит от того, что меняется. Вы можете использовать protoype scope, как показано в ответе Рухула. Однако проблема здесь заключается в том, что экземпляр прототипа будет внедрен только один раз при создании бина InfoRestController.

Вы можете попробовать использовать область действия request - при автоматическом подключении бинас такой областью новый экземпляр создается для каждого 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;
    }
}

Такжеимейте в виду, что экземпляр Info, введенный в InfoRestController, будет проксирован, поэтому в этом примере вы будете возвращать экземпляр прокси с некоторыми дополнительными полями. Чтобы преодолеть это, мы могли бы просто сделать копию значений из внедренного компонента:

@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
}
1 голос
/ 31 октября 2019

Недостающий кусок головоломки был javax.inject.Provider. Я не знал об этом, но у него был именно тот интерфейс, который я искал. Окончательное решение состояло в том, чтобы действительно позволить Spring управлять компонентом (Info) и использовать 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();
  }
}

. Этот bean-компонент имеет область действия Prototype, предложенную ruhul. Я пробовал это раньше, но без Provider я все еще застрял. Вот как вернуть его в контроллер

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

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

Ради полноты я нашел другой, более уродливый способ сделать это, вставив пружину ApplicationContext, а затем используя context.getBean("info"), но зависимость отвесенний контекст и название строки были запахом. Решение с Provider гораздо более сфокусировано

1 голос
/ 30 октября 2019

Кажется, вам нужен новый управляемый объект, когда пришел запрос. Чтобы достичь этого, вы можете пометить свой Бин @Scope("prototype"), что решит вашу проблему.

Бин с областью прототипа будет возвращать новый экземпляр каждый раз, когда его запрашивают из контейнера.

Чтобы узнать больше, как работает прототип, посмотрите на эту страницу:

  1. Spring - Пример области применения прототипа с использованием аннотации @Scope
  2. 3. Прототип Scope
...