Implement a simple factory pattern with Spring 3 annotations

Viewed 141951

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 always null. I believe 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

15 Answers

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

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

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.

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

Try this:

public interface MyService {
 //Code
}

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

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

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

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();
}
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();
    }
}
Related