4

Say we have a FileLoader Interface:

public interface FileLoader {

  default String loadFile(String fileId) {
    // Default business logic
    return "Default implementation for FileLoader. Loading file" + fileId;
  }
}

And different implementations for different countries:

public class USAFileLoader implements FileLoader {

  @Override
  public String loadFile(String fileId) {
    // ... Specific business logic for USA
    return "USA implementation for FileLoader. Loading file" + fileId;
  }
}
public class FRAFileLoader implements  FileLoader {

  @Override
  public String loadFile(String fileId) {
    // ... Specific business logic for France
    return "France implementation for FileLoader. Loading file" + fileId;
  }
}

And we create an endpoint to load files:

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class FileUploadController {

  FileLoader fileLoader;

  @PostMapping("/load/{fileId}/{countryCode}")
  public String loadFile(@PathVariable String fileId, @PathVariable String countryCode) {

    fileLoader = ... // Inject the right loader based on countryCode

    return fileLoader.loadFile(fileId);
  }
}

How can I inject the right FileLoader at runtime for every request, based on countryCode? I've found something in Spring called FactoryBean that apparently may work, but I'm now sure if it's the right tool, or if this is the right way to address this problem. Also, I don't know how injection will behave with requests being proccessed at the same time.

Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183
  • 1
    Does this answer your question? [Inject bean at runtime reading properties file](https://stackoverflow.com/questions/31281003/inject-bean-at-runtime-reading-properties-file) – Catalin Pirvu Apr 25 '20 at 20:34
  • 1
    Did you hear about service locator and factory patterns? https://stackoverflow.com/questions/8325619/whats-the-difference-between-the-service-locator-and-the-factory-design-pattern/28390028 – Lemmy Apr 25 '20 at 20:42

2 Answers2

2

The best thing you can do here using run time polymorphism, add one more abstract method in interface FileLoader for country code

public interface FileLoader {

 default String loadFile(String fileId) {
   // Default business logic
  return "Default implementation for FileLoader. Loading file" + fileId;
 }

 public abstract String getCountryCode();
}

And then implement it in every implementation class with return the appropriate country code

public class USAFileLoader implements FileLoader {

  @Override
  public String loadFile(String fileId) {
    // ... Specific business logic for USA
    return "USA implementation for FileLoader. Loading file" + fileId;
   }

  public String getCountryCode(){
   return "USA";
  }
}

And then you can Autowire all beans of type FileLoader into List and call loadFile on appropriate bean

@RestController
public class FileUploadController {

 @Autowire
 List<FileLoader> fileLoaders;

  @PostMapping("/load/{fileId}/{countryCode}")
  public String loadFile(@PathVariable String fileId, @PathVariable String countryCode) {

return fileLoaders.stream()
                  .filter(f->f.getCountryCode().equlas(countryCode))
                  .findFirst()
                  .map(loader->loader.loadFile(fileId))
                  .orElse(()-> FileLoader.super.loadFile(fileId));  //calling interface default method
  }
} 
Ryuzaki L
  • 37,302
  • 12
  • 68
  • 98
  • This will work, but I'd prefer to add another layer rather than put extra logic into the controller. Something like `FileLoaderService#loadFile(fileId, countryCode)` – Christopher Schneider Apr 27 '20 at 15:31
  • So if the application needs to serve 160 countries, all of the loaders will be injected on runtime and after a stream lookup only one of them will be used. This doesn't make a lot of sense from my pov as another dev who wondering the solution. I would expect an abstract factory utilization in some way where you could pass the country code and retrieve the respective loader while keeping the spring's injection container in mind. On that direction, I would declare the factory as first class dependency of this controller. – edigu Sep 18 '21 at 21:11
1

You can receive a bean with another way at runtime using ApplicationContext::getBean:

@Autowired
ApplicationContext

@PostMapping("/load/{fileId}/{countryCode}")
public String loadFile(@PathVariable String fileId, @PathVariable String countryCode) {

    FileLoader fileloader = (FileLoader) applicationContext.getBean(countryCode);
    return fileLoader.loadFile(fileId);
}

However, I'd recommend creating a service layer that aggregates the country-specific implementations and uses a factory pattern. There is nothing bad on such implementation.

Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183