Implement a simple factory pattern with Spring 3 annotations

JavaSpringAnnotationsFactory Pattern

Java Problem Overview


I was wondering how I could implement the simple factory pattern with Spring 3 annotations. I saw in the documentation that you can create beans that call the factory class and run a factory method. I was wondering if this was possible using annotations only.

I have a controller that currently calls

MyService myService = myServiceFactory.getMyService(test);
result = myService.checkStatus();

MyService is an interface with one method called checkStatus().

My factory class looks like this:

@Component
public class MyServiceFactory {

	public static MyService getMyService(String service) {
		MyService myService;
		
		service = service.toLowerCase();
		
		if (service.equals("one")) {
			myService = new MyServiceOne();
		} else if (service.equals("two")) {
			myService = new MyServiceTwo();
		} else if (service.equals("three")) {
			myService = new MyServiceThree();
		} else {
			myService = new MyServiceDefault();
		}
		
		return myService;
	}
}

MyServiceOne class looks like this :

@Autowired
private LocationService locationService;

public boolean checkStatus() {
      //do stuff
}

When I run this code the locationService variable is alwasy null. I beleive this is because I am creating the objects myself inside the factory and autowiring is not taking place. Is there a way to add annotations to make this work correctly?

Thanks

Java Solutions


Solution 1 - Java

The following worked for me:

The interface consist of you logic methods plus additional identity method:

public interface MyService {
    String getType();
    void checkStatus();
}

Some implementations:

@Component
public class MyServiceOne implements MyService {
    @Override
    public String getType() {
        return "one";
    }

    @Override
    public void checkStatus() {
      // Your code
    }
}

@Component
public class MyServiceTwo implements MyService {
    @Override
    public String getType() {
        return "two";
    }

    @Override
    public void checkStatus() {
      // Your code
    }
}

@Component
public class MyServiceThree implements MyService {
    @Override
    public String getType() {
        return "three";
    }

    @Override
    public void checkStatus() {
      // Your code
    }
}

And the factory itself as following:

@Service
public class MyServiceFactory {

    @Autowired
    private List<MyService> services;

    private static final Map<String, MyService> myServiceCache = new HashMap<>();

    @PostConstruct
    public void initMyServiceCache() {
        for(MyService service : services) {
            myServiceCache.put(service.getType(), service);
        }
    }

    public static MyService getService(String type) {
        MyService service = myServiceCache.get(type);
        if(service == null) throw new RuntimeException("Unknown service type: " + type);
        return service;
    }
}

I've found such implementation easier, cleaner and much more extensible. Adding new MyService is as easy as creating another spring bean implementing same interface without making any changes in other places.

Solution 2 - Java

You are right, by creating object manually you are not letting Spring to perform autowiring. Consider managing your services by Spring as well:

@Component
public class MyServiceFactory {

    @Autowired
    private MyServiceOne myServiceOne;

    @Autowired
    private MyServiceTwo myServiceTwo;

    @Autowired
    private MyServiceThree myServiceThree;

    @Autowired
    private MyServiceDefault myServiceDefault;

	public static MyService getMyService(String service) {
		service = service.toLowerCase();
		
		if (service.equals("one")) {
			return myServiceOne;
		} else if (service.equals("two")) {
			return myServiceTwo;
		} else if (service.equals("three")) {
			return myServiceThree;
		} else {
			return myServiceDefault;
		}
	}
}

But I would consider the overall design to be rather poor. Wouldn't it better to have one general MyService implementation and pass one/two/three string as extra parameter to checkStatus()? What do you want to achieve?

@Component
public class MyServiceAdapter implements MyService {

    @Autowired
    private MyServiceOne myServiceOne;

    @Autowired
    private MyServiceTwo myServiceTwo;

    @Autowired
    private MyServiceThree myServiceThree;

    @Autowired
    private MyServiceDefault myServiceDefault;

	public boolean checkStatus(String service) {
		service = service.toLowerCase();
		
		if (service.equals("one")) {
			return myServiceOne.checkStatus();
		} else if (service.equals("two")) {
			return myServiceTwo.checkStatus();
		} else if (service.equals("three")) {
			return myServiceThree.checkStatus();
		} else {
			return myServiceDefault.checkStatus();
		}
	}
}

This is still poorly designed because adding new MyService implementation requires MyServiceAdapter modification as well (SRP violation). But this is actually a good starting point (hint: map and Strategy pattern).

Solution 3 - Java

Following answer of DruidKuma

Litte refactor of his factory with autowired constructor:

@Service
public class MyServiceFactory {

    private static final Map<String, MyService> myServiceCache = new HashMap<>();

    @Autowired
    private MyServiceFactory(List<MyService> services) {
        for(MyService service : services) {
            myServiceCache.put(service.getType(), service);
        }
    }

    public static MyService getService(String type) {
        MyService service = myServiceCache.get(type);
        if(service == null) throw new RuntimeException("Unknown service type: " + type);
        return service;
    }
}

Solution 4 - Java

Why not add the interface FactoryBean to MyServiceFactory (to tell Spring that it's a factory), add a register(String service, MyService instance) then, have each of the services call:

@Autowired
MyServiceFactory serviceFactory;

@PostConstruct
public void postConstruct() {
    serviceFactory.register(myName, this);
}

This way, you can separate each service provider into modules if necessary, and Spring will automagically pick up any deployed and available service providers.

Solution 5 - Java

You can manually ask Spring to Autowire it.

Have your factory implement ApplicationContextAware. Then provide the following implementation in your factory:

@Override
public void setApplicationContext(final ApplicationContext applicationContext) {
    this.applicationContext = applicationContext;
}

and then do the following after creating your bean:

YourBean bean = new YourBean();
applicationContext.getAutowireCapableBeanFactory().autowireBean(bean);
bean.init(); //If it has an init() method.

This will autowire your LocationService perfectly fine.

Solution 6 - Java

You could also declaratively define a bean of type ServiceLocatorFactoryBean that will act as a Factory class. it supported by Spring 3.

A FactoryBean implementation that takes an interface which must have one or more methods with the signatures (typically, MyService getService() or MyService getService(String id)) and creates a dynamic proxy which implements that interface

Here's an example of implementing the Factory pattern using Spring

One more clearly example

Solution 7 - Java

Based on solution by Pavel Černý here we can make an universal typed implementation of this pattern. To to it, we need to introduce NamedService interface:

    public interface NamedService {
       String name();
    }

and add abstract class:

public abstract class AbstractFactory<T extends NamedService> {

    private final Map<String, T> map;

    protected AbstractFactory(List<T> list) {
        this.map = list
                .stream()
                .collect(Collectors.toMap(NamedService::name, Function.identity()));
    }

    /**
     * Factory method for getting an appropriate implementation of a service
     * @param name name of service impl.
     * @return concrete service impl.

     */
    public T getInstance(@NonNull final String name) {
        T t = map.get(name);
        if(t == null)
            throw new RuntimeException("Unknown service name: " + name);
        return t;
    }
}

Then we create a concrete factory of specific objects like MyService:

 public interface MyService extends NamedService {
           String name();
           void doJob();
 }

@Component
public class MyServiceFactory extends AbstractFactory<MyService> {

    @Autowired
    protected MyServiceFactory(List<MyService> list) {
        super(list);
    }
}

where List the list of implementations of MyService interface at compile time.

This approach works fine if you have multiple similar factories across app that produce objects by name (if producing objects by a name suffice you business logic of course). Here map works good with String as a key, and holds all the existing implementations of your services.

if you have different logic for producing objects, this additional logic can be moved to some another place and work in combination with these factories (that get objects by name).

Solution 8 - Java

You could instantiate "AnnotationConfigApplicationContext" by passing all your service classes as parameters.

@Component
public class MyServiceFactory {

    private ApplicationContext applicationContext;

    public MyServiceFactory() {
        applicationContext = new AnnotationConfigApplicationContext(
                MyServiceOne.class,
                MyServiceTwo.class,
                MyServiceThree.class,
                MyServiceDefault.class,
                LocationService.class 
        );
        /* I have added LocationService.class because this component is also autowired */
    }

    public MyService getMyService(String service) {
    
        if ("one".equalsIgnoreCase(service)) {
            return applicationContext.getBean(MyServiceOne.class);
        } 

        if ("two".equalsIgnoreCase(service)) {
            return applicationContext.getBean(MyServiceTwo.class);
        } 

        if ("three".equalsIgnoreCase(service)) {
            return applicationContext.getBean(MyServiceThree.class);
        } 

        return applicationContext.getBean(MyServiceDefault.class);
    }
}

Solution 9 - Java

I suppose you to use org.springframework.beans.factory.config.ServiceLocatorFactoryBean. It will much simplify your code. Except MyServiceAdapter u can only create interface MyServiceAdapter with method MyService getMyService and with alies to register your classes

Code

bean id="printStrategyFactory" class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean">
		<property name="YourInterface" value="factory.MyServiceAdapter" />
	</bean>
	
	<alias name="myServiceOne" alias="one" />
	<alias name="myServiceTwo" alias="two" />

Solution 10 - Java

I have recently worked on similar requirement where in I wanted to use factory pattern but I was not okay with the if else logic which keeps on growing in future and violates the Single Responsibility Principle.

First step, create an interface and have a getType() method, in the given context it will return "one", "two" etc, otherwise it can be anything. This is the common solution most of the people suggested above.

public interface MyService {
    String getType();
    void checkStatus();
}

Some implementations:

@Component
public class MyServiceOne implements MyService {
    @Override
    public String getType() {
        return "one";
    }

    @Override
    public void checkStatus() {
      // Your code
    }
}

@Component
public class MyServiceTwo implements MyService {
    @Override
    public String getType() {
        return "two";
    }

    @Override
    public void checkStatus() {
      // Your code
    }
}

@Component
public class MyServiceThree implements MyService {
    @Override
    public String getType() {
        return "three";
    }

    @Override
    public void checkStatus() {
      // Your code
    }
}

And the factory itself as following:

@Service
public class MyServiceFactory {

    @Autowired
    private List<MyService> services;

    public static MyService getService(final String type) {
        return services
       .stream().filter(service -> type.equals(service.getType()))
       .findFirst()
       .orElseThrow(throw new RuntimeException("Unknown service type: " + type));
        
    }
}

This solution doesn't require extra Map for storing key value of the instances against the type. This solution is extensible without any further code change, as the factory has List auto wiring, so any future implementation of MyService would be easily work. And hence the Single Responsibility Principle is also ensured.

I have used streams() and predicates as I was using Java 8, for earlier version simple for loop would do the work.

Solution 11 - Java

Try this:

public interface MyService {
 //Code
}

@Component("One")
public class MyServiceOne implements MyService {
 //Code
}

@Component("Two")
public class MyServiceTwo implements MyService {
 //Code
}

Solution 12 - Java

Following the answer from DruidKuma and jumping_monkey

You can also include optional and make your code a bit nicer and cleaner:

 public static MyService getService(String type) {
        return Optional.ofNullable(myServiceCache.get(type))
                .orElseThrow(() -> new RuntimeException("Unknown service type: " + type));
 }

Solution 13 - Java

This is a variation of the above answers that creates fresh instances.

If the Service depends only on Spring managed beans.

public interface MyService {
 //Code
}

@Component("One")
@Scope("prototype")
public class MyServiceOne implements MyService {
 //Code
   public MyServiceOne(Dependency dep){
   ...
   }
}

@Component("Two")
@Scope("prototype")
public class MyServiceTwo implements MyService {
 //Code
}

public class Factory {
    Map<String,MyService> services;
    ApplicationContext context;
    Dependency dep; 

    public Factory(Map<String, MyService> components, ApplicationContext context, Dependency dep) {
     ...
    }

    MyService service(String type){
        return context.getBean(services.get(type).getClass());
    }
}

@Configuration
public class Config {
       @Bean
       Factory languageFactory(Map<String,Service> map, ApplicationContext context, Dependency dep){
        return new Factory(map,context,dep);
    }
}



If you want to include custom parameters in the factory method that are not all managed by Spring you can try one of the following sketched below

  1. ensure a Bean can be instantiated when first discovered by adding empty constructors
@Component("One")
@Scope("prototype")
public class MyServiceOne implements MyService {
 //Code
   public MyServiceOne(){
   ...
   }

   public MyServiceOne(Dependency dep){
   ...
   }

   public MyServiceOne(Dependency dep, Integer myFactoryValue){
   ...
   }
}

  1. Or you manually create them in the configuration to be discovered
\\ no longer available in autoscan
public class MyServiceOne implements MyService {
 //Code
   public MyServiceOne(){
   ...
   }

   public MyServiceOne(Dependency dep, Integer myFactoryValue){
   ...
   }
}

@Configuration
public class Config {
       @Bean("One")
       @Scope("prototype")      
       Service serviceOne(){
            // used only for dynamic discovery
            return new ServiceOne();
       }
       ...

       @Bean
       Factory languageFactory(Map<String,Service> map, ApplicationContext context, Dependency dep){
        return new Factory(map,context,dep);
    }
}

And both solutions allow you to define your factory method like this

public class Factory {
    ....

    MyService service(String type, Integer someParameter){
        // you provide the parameters for the constructor
        return context.getBean(services.get(type).getClass(),dep,someParameter);
    }
} 

Solution 14 - Java

Spring won't Autowire beans if there isn't a clear path as to which one it should use at build time. Since the factory doesn't change, you can Autowire your LocationService there and pass it down to your different services. This can get a bit cumbersome if your classes have more than one dependency e.g. services, repos, etc.

If you don't intend on having a lot of dependencies for your "MyService" classes you can do this:

@Component
public class MyServiceFactory(){

    @Autowired
    LocationService locationService;
    
    public static MyService getMyService(String service){
        service = service.toLowerCase();
        switch(service){
            case "one":
                return new MyServiceOne(locationService);
            case "two":
                return new MyServiceTwo(locationService);
            case "three":
                return new MyServiceThree(locationService);
            default:
                return new MyServiceDefault(locationService);
        }
    }
}

Your MyServiceOne class:

@Service
public class MyServiceOne implements MyService{

    public LocationService locationService;

    public MyServiceOne(LocationService service){
        locationService = service;
    }

    @Override
    public checkStatus(){
        // your code
    }
}

MyService interface

interface MyService{
    boolean checkStatus();
}

Solution 15 - Java

public interface MyService {
    public void save(); 
//Code
}

@Component("One")
public class MyServiceOne implements MyService {
 //Code
    public void save(){
      System.out.println("one");
    }
}

@Component("Two")
public class MyServiceTwo implements MyService {
 //Code
    public void save(){
      System.out.println("two");
    }
}

public class FatoryClass{
    @Autowired
   //@Qualifier("One") //This is your default child class use qualifier or just my sample
    MyService One;
    public MyService setMyservice(int typeId){
      switch(typeId){
          case 1:
            One = new MyServiceTwo();
            break;
          default:
            System.out.println("Default child instance");
      }
      return One;
    }
}

@Service
public class serviceComponent{
    @Autowired
    FatoryClass facto;

    public void setFactoryMethod(int typeId){
        facto.setMyService(typeId);
        facto.save();
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionblong824View Question on Stackoverflow
Solution 1 - JavaDruidKumaView Answer on Stackoverflow
Solution 2 - JavaTomasz NurkiewiczView Answer on Stackoverflow
Solution 3 - JavaPavel ČernýView Answer on Stackoverflow
Solution 4 - JavaJeff WangView Answer on Stackoverflow
Solution 5 - JavarogermenezesView Answer on Stackoverflow
Solution 6 - JavakyiuView Answer on Stackoverflow
Solution 7 - JavaAndreas GeleverView Answer on Stackoverflow
Solution 8 - JavaOlivierTerrienView Answer on Stackoverflow
Solution 9 - JavaMikhailView Answer on Stackoverflow
Solution 10 - JavaSanjay BharwaniView Answer on Stackoverflow
Solution 11 - JavaViren DholakiaView Answer on Stackoverflow
Solution 12 - Javadextertron_View Answer on Stackoverflow
Solution 13 - JavaRadu IonescuView Answer on Stackoverflow
Solution 14 - JavajwsinnerView Answer on Stackoverflow
Solution 15 - JavaQaziView Answer on Stackoverflow