Introduction

Below we have a nice decoupled Service Factory example in Java, using Spring Boot. What is very cool about below approach is that you can add as many service implementations as required by declaring a new Spring component that implements your Service interface. No need to update the Service Factory itself.

Service Interface

package com.horazmakes.servicefactorytest;

public interface Service {

	public void process();
	public String getName();
	
}

This is the contract all of our future service implementations will have to honor. The process() method will do the main task in each service. On the other hand, by calling the method getName() we will be able to identify every service.

ServiceFactory

package com.horazmakes.servicefactorytest;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;

import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
public class ServiceFactory {

	private List<Service> services;
	
	private static final Map<String, Service> serviceMap = new HashMap<>();
	
	public ServiceFactory(List<Service> services) {
		this.services = services;
	}
	
	@PostConstruct
	private void initServiceFactory() {
		log.info("Initializing ServiceFactory...");
		for (Service service : services) {
			log.info("Adding {}...", service.getName());
			serviceMap.put(service.getName(), service);
		}
	}
	
	public Service getServiceByName(String name) {
		Service service = serviceMap.get(name);
		
		if (service == null) {
			throw new RuntimeException("Unknown service " + name);
		}
		
		return service;
	}
	
}

The ServiceFactory will be annotated with @Component so that we can use it as a Spring bean later and also it participates in dependency injection mechanism during application startup, initializing the factory with all available services.

The ServiceFactory has a services list. Spring will automatically instantiate each service (@Component) and inject all of them to ServiceFactory through constructor injection. All that Spring takes to add your service to the ServiceFactory is that your Service implements Service implementation above.

The method initServiceFactory() will be called just once, after the ServiceFactory object has been created (it is annotated with @PostConstruct). The initialization method will populate the serviceMap that will hold the names of all the found services and reference to all of them.

Finally, by using the method getServiceByName(String name) we can dynamically fetch our different services at runtime. Since the objects will be instantiated during application startup, the method will return the reference to the object right away if the passed name is good, otherwise it will throw an exception.

Service implementation

Now we can declare as many services as required, implementing Service interface shown above. Spring will automatically add to our ServiceFactory.

package com.horazmakes.servicefactorytest.impl;

import org.springframework.stereotype.Component;

import com.horazmakes.servicefactorytest.Service;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
public class ServiceA implements Service {

	@Override
	public void process() {
		log.info("Processing with ServiceA...");

	}

	@Override
	public String getName() {
		return "ServiceA";
	}

}
package com.horazmakes.servicefactorytest.impl;

import org.springframework.stereotype.Component;

import com.horazmakes.servicefactorytest.Service;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
public class ServiceB implements Service {

	@Override
	public void process() {
		log.info("Processing with ServiceB...");

	}

	@Override
	public String getName() {
		return "ServiceB";
	}

}

As you can see we have to annotate our services with @Component and implement Service interface.

Using the ServiceFactory

package com.horazmakes.servicefactorytest.controller;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.horazmakes.servicefactorytest.Service;
import com.horazmakes.servicefactorytest.ServiceFactory;

@RestController
@RequestMapping("/service-factory-test")
public class ServiceFactoryTest {
	
	private ServiceFactory serviceFactory;
	
	public ServiceFactoryTest(ServiceFactory serviceFactory) {
		this.serviceFactory = serviceFactory;
	}

	@GetMapping("/process/{serviceName}")
	public ResponseEntity<Void> process(@PathVariable(name = "serviceName") String serviceName) {
		try {
			Service service = serviceFactory.getServiceByName(serviceName);
			service.process();
			
			return new ResponseEntity<Void>(HttpStatus.OK);
		} catch (Exception e) {
			e.printStackTrace();
			return new ResponseEntity<Void>(HttpStatus.INTERNAL_SERVER_ERROR);
		}
	}
	
}

In order to use our ServiceFactory we have to inject the Spring bean, then we can get services dynamically, by their name.

By horaz

Hello, my name is Horacio Conde, Vic’s proud father, an apprentice maker and a computer science engineer. I live in Mexico City and I’ve been working professionally in software development for more than fifteen years now. I’m very interested in technologies such as programming, The Internet of Things (IoT) (Arduino, Raspberry Pi), electronics, physical computing, automation, woodworking.

Leave a Reply

Your email address will not be published. Required fields are marked *