Как создать фабричный метод с аргументами?

Не могли бы вы помочь мне избавиться от ApplicationContext?

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

@Component
public class BookFactoryImpl implements BookFactory {

    private final ApplicationContext applicationContext;

    @Autowired
    public BookFactoryImpl(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    @Override
    public Book createBook(String name) {
        return applicationContext.getBean(Book.class, name);
    }

}

Вот класс конфигурации с методом @Bean, который используется для создания экземпляра нового экземпляра класса Book:

@Configuration
@ComponentScan({"factory"})
public class AppConfig {

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    @Lazy
    public Book book(String name) {
        return Book.builder().name(name).build();
    }

}

Вот мой класс сущностей Book:

@Entity
@NoArgsConstructor
@AllArgsConstructor
@Getter
@EqualsAndHashCode
@ToString
@Builder
public class Book {

    @Id
    @GeneratedValue
    private int id;

    @Basic(fetch = FetchType.LAZY)
    private String name;

}

У меня есть еще одна идея - аннотировать BookFactoryImpl с помощью @Configuration и переместить в него метод @Bean, но в этом случае моя фабрика превратится в класс @Configuration с нарушенным жизненным циклом.

Как вы думаете, как лучше всего реализовать фабрику и как уменьшить внешние зависимости, такие как ApplicationContext?

Или, может быть, неплохо было бы сделать все фабрики @Configuration классами с @Bean методами, как вы думаете?


person Pasha    schedule 02.09.2018    source источник
comment
Как используются экземпляры Book? что звонит BookFactory.createBook()?   -  person Adam Siemion    schedule 02.09.2018
comment
Предположим, что psvm() вызывает его. Не важно, просто клиентский код   -  person Pasha    schedule 02.09.2018
comment
какова цель создания новых экземпляров Book? Я думаю, вы хотите сохранить их в БД? Используются ли они репозиторием?   -  person Adam Siemion    schedule 02.09.2018
comment
да, например, я могу их сохранить - это сущности   -  person Pasha    schedule 02.09.2018


Ответы (2)


Нет, не рекомендуется делать каждый класс в вашем приложении управляемым Spring.

Сущности JPA обычно должны создаваться вашим кодом внутри управляемых компонентов Spring.

person Adam Siemion    schedule 02.09.2018
comment
Не могли бы вы подробно объяснить? Вы имеете в виду просто вызвать конструктор класса Book в клиентском коде? Что, если я хочу, чтобы на нем был init-метод? - person Pasha; 02.09.2018
comment
В приведенном вами фрагменте Builder тоже нет смысла. Если ваша сущность имеет 1 или 2 атрибута, строитель — это чрезмерная инженерия. Так что просто сделайте new Book().setName(name);. Читайте также о преимуществах неизменяемой модели. - person Adam Siemion; 02.09.2018
comment
Я хотел бы иметь неизменяемые объекты, где это возможно. Если у меня есть изменяемые данные, я мог бы просто использовать метод @Lookup, а затем установить - person Pasha; 02.09.2018

Я обычно использую следующий подход:

Определите одноэлементный компонент, который будет содержать зависимость от фабрики:

    public class MyService {
     private final Provider<Book> bookFactory;

     public MyService(Provider<Book> bookFactory) {
         this.bookFactory = bookFactory;
     } 
     public void doSomething() {
        Book book = bookFactory.get();
        book.setNumberOfReaders(numOfReaders); // this is a drawback, book is mutable, if we want to set runtime params (like numberOfReaders)
         ....
      }
    }

Теперь определите прототип для book bean:

@Configuration 
public class MyConfiguration {

   @Bean
   @Scope("prototype")
   public Book book(...) {
      return new Book(...);
   }

   @Bean // scope singleton by default
   public MyService myService(Provider<Book> bookFactory) {
       return new MyService(bookFactory);
   }
}

Обратите внимание, что Provider имеет тип «javax.inject.Provider», чтобы использовать идентификатор, импорт (например, в maven):

<dependency>
   <groupId>javax.inject</groupId>
   <artifactId>javax.inject</artifactId>
   <version>1</version>
</dependency>

Spring может справиться с этим, начиная с 4.x (я думаю, 4.1) без какой-либо дополнительной настройки.

Конечно, этот подход избавляет от необходимости внедрять контекст приложения в фабрику и вообще поддерживать фабрику.

Одним из недостатков является то, что он не позволяет создавать объект с аргументами, эти аргументы должны быть указаны во время выполнения.

Существует также другой подход, генерация подкласса во время выполнения в сочетании с аннотацией @Lookup, описанная Здесь, но подход IMO Provider лучше.

person Mark Bramnik    schedule 02.09.2018
comment
Кажется, проще создать метод поиска, вызвать его и использовать сеттер - person Pasha; 02.09.2018