Pasha
Pasha

Reputation: 1932

How to create a factory-method with arguments?

Could you please help me to get rid of ApplicationContext?

I have a factory so that all book instances are spring-beans. I think it's a good decision to make all beans spring-beans.

@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);
    }

}

Here is a configuration class with a @Beanmethod that is used to instantiate a new instance of Book class:

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

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

}

Here is my Book entity class:

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

    @Id
    @GeneratedValue
    private int id;

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

}

I have more one idea - to annotate BookFactoryImpl with @Configuration and move @Bean method in it but in this case, my factory will be turned into @Configuration class with the broken lifecycle.

What do you think, what is the best way to implement a factory and how to reduce external dependencies like ApplicationContext?

Or maybe it's nice to make all factories as @Configuration classes with @Bean methods, how do you think?

Upvotes: 1

Views: 373

Answers (2)

Mark Bramnik
Mark Bramnik

Reputation: 42551

I usually use the following approach:

Define the singleton bean that will contain a dependency on factory:

    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)
         ....
      }
    }

Now Define a prototype for 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);
   }
}

Notice, Provider is of type "javax.inject.Provider", in order to use id, import (for example in maven):

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

Spring can handle this since 4.x ( I guess 4.1) without any additional configuration

Of course, this approach eliminates that need to inject application context to the factory and in general to maintain a factory

One drawback is that it doesn't allow building the object with arguments, these arguments have to be specified in runtime.

There is also another approach, a runtime generation of sub-class in conjunction with @Lookup annotation, its described Here but IMO Provider approach is better.

Upvotes: 0

Adam Siemion
Adam Siemion

Reputation: 16039

No, it is not a good idea to make every single class in your application managed by Spring.

JPA entities usually should be instantiated by your code inside Spring managed beans.

Upvotes: 3

Related Questions