Real code example: Spring boot with OpenFeign client, Hystrix and Spring Service Locator pattern — Part 1

This is a real Spring boot demo with :

  • Openfeign client to connect to fake APi server JsonPlaceholder.
  • Hystrix command asynchronous execution.
  • Spring service locator pattern for dynamically locating services.

Github repository:
https://github.com/sunhong87/springboot-openfeign-client — Master branch

All comments and contributions are welcome.

This article will be separated into three parts which will explain code base, technologies are implemented.

Firstly, we are going to discover Spring service locator pattern.

Part 1: Spring service locator

Before going deep into code, I recommend you to read through these article for better understanding this pattern.

  1. Introduction, key differences and small implementation:
    https://www.baeldung.com/java-service-locator-pattern
  2. Introduction, explanation and small implementation:
    https://springframework.guru/service-locator-pattern-in-spring/#:~:text=Introduction%3A%20Service%20Locator%20Pattern&text=Additionally%2C%20Spring%20provides%20a%20form,has%20on%20the%20concrete%20implementation
    .

In this demo, I use this pattern for defining various file parser base on content type, we will have a JSON file parser for JSON upload file, CSV parser for CSV upload file. We can expand our services for other types easily.

Legacy service factory

Before using this pattern, we usually using switch/case for selecting services base on input argument(s).

For example:

@Service
public class InputParserFactory {
@Autowired
private InputJsonParser jsonParser;
@Autowired
private InputCsvParser csvParser;
public InputParser getParser(InputContentType contentType) {
switch (contentType) {
case JSON:
return jsonParser;
case CSV:
return csvParser;
default:
return jsonParser;
}
}
}

This pattern is hard to expand our services type. Let’s imagine someday we want to add new parser for XML, we have to change this class. If the application has only one factory class, you may remember to do it but what if we have tons of services factory?

A. Service abstraction:

Firstly, we define a service interface which will define our services abstract method(s). Here is InputParser interface.

public interface InputParser {
List<PostModel> parse(Reader reader);
}

B. Service factory:

Next, we define a service factory interface, which has only one method, to give back service base on content type

public interface InputParserFactory {
public InputParser getParser(InputContentType contentType);
}

A factory method argument must be a string, but can also be an integer or an enumeration. In the latter cases, the bean factory resolves the bean by stringify-ing (for example via toString) the value.
From: https://springframework.guru/

C. Service beans:

Finally, we define our real service implementation

@Component(InputContentType.Constant.JSON_TEXT)
@Slf4j
public class InputJsonParser implements InputParser {
...
}
@Component(InputContentType.Constant.CSV_TEXT)
@Slf4j
public class InputCsvParser implements InputParser {
...
}

D. Factory bean:

Last, we need to register our service factory with Spring factory bean

@Configuration
public class AppServiceConfig {
@Bean("parserFactory")
public FactoryBean<Object> inputParserFactory(){
ServiceLocatorFactoryBean factoryBean = new ServiceLocatorFactoryBean();
factoryBean.setServiceLocatorInterface(InputParserFactory.class);
return factoryBean;
}
}

E. Bonus:

In order to haver better control bean name, because we don’t want to define constant String for every bean, later if we want to change name, we have to go to every class and change the constant String.

To prevent this job, we can centrallize the bean name by an enum, therefore, if any changes isneeded, we only need to change ion one place.

We create an enum class, which have Sring argument constrcutor, so every enum element must accept a String argument, this string argument will be constant value from an inner interface. Example:

public enum InputContentType {
JSON(Constant.JSON_TEXT), CSV(Constant.CSV_TEXT);
private String typeName;
private InputContentType(String typeName) {
this.typeName = typeName;
}
@Override
public String toString() {
return this.typeName;
}
public static InputContentType fromContentType(final String contentType) {
return Stream.of(InputContentType.values()).filter(type -> type.toString().equalsIgnoreCase(contentType))
.findFirst()
.orElseThrow(() -> new UnsupportedMediaTypeStatusException("Unsupport conten type " + contentType));
}
public interface Constant {
String JSON_TEXT = MediaType.APPLICATION_JSON_VALUE;
String CSV_TEXT = "text/csv";
}
}

Behind the scene

Because we already register InputParserFactory interface with ServiceLocatorFactoryBean through method setServiceLocatorInterface(…), Spring framework creates a dynamic proxy which implements that interface, delegating to an underlying BeanFactory during runtime.

When we call getParser(InputContentType contentType) method of InputParserFactory interface passing the bean id as an argument, Spring framework will call InputContentType.toString(), pass the value as argument to the method BeanFactory.getBean(String id), return the bean whose name is passed as an argument to the getBean() method of the BeanFactory.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store