How can I choose a service implementation depending on a request parameter on SpringBoot? I can do this by manually instantiating the service, but that's not making use of the Spring Injection feature.
I can also Autowire the two services separately but I'm thinking that if I have more implementations that would pollute the class.
Here's a crude example:
#RestController
class RestControllerTest {
#Autowired
PizzaService pizzaService;
public void bakePizza(#RequestParam("type") String type,#RequestParam("extra") String extra) {
if (type.equals("cheese")) {
//set pizzaService Cheese Implementation
pizzaService = new CheezePizza();
} else {
//set PizzaService vegetable Impleentation;
pizzaService = new VegetablePizza();
}
pizzaService.prepareIngredients(extra);
pizzaService.bakePizza();
}
}
public abstract class PizzaService {
String ingredients;
public abstract void prepareIngredients(String exraIngredient);
public void bakePizza() {
System.out.println("baking pizza with " + ingredients);
}
}
class CheezePizza extends PizzaService {
#Override
public void prepareIngredients(String exraIngredient) {
ingredients = "Cheese " + exraIngredient;
}
}
class VegetablePizza extends PizzaService {
#Override
public void prepareIngredients(String exraIngredient) {
ingredients = "Vegetable " + exraIngredient;
}
}
You could autowire list of beans of same type. So let's say you add getType() to your PizzaService and register every type as spring bean.
public abstract class PizzaService {
abstract String getType();
}
#Component
class CheezePizza extends PizzaService {
#Override
public String getType() {
return "cheese";
}
}
#Component
class VegetablePizza extends PizzaService {
#Override
public String getType() {
return "vegetable";
}
}
#RestController
class RestControllerTest {
private final Map<String, PizzaService> pizzaServices;
public RestControllerTest(List<PizzaService> services) {
pizzaServices = services.stream().collect(Collectors.toMap(PizzaService::getType, Function.identity()));
}
public void bakePizza(#RequestParam("type") String type, #RequestParam("extra") String extra) {
PizzaService pizzaService = pizzaServices.get(type); // remember of handling missing type
pizzaService.prepareIngredients(extra);
pizzaService.bakePizza();
}
}
Another way is to use name your beans by convention i.e. cheesePizza, vegetablePizza and then use ApplicationContext#getBean(type + "Pizza") but I like first approach better, because it's less magical.
Related
#SpringBootApplication
public class SfgDiApplication {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(SfgDiApplication.class, args);
PetController petController = ctx.getBean("petController", PetController.class);
System.out.println("--- The Best Pet is ---");
System.out.println(petController.whichPetIsTheBest());
}
#Controller
#ResponseBody
public class PetController {
public PetController(PetService petService) {
this.petService = petService;
}
private final PetService petService;
#GetMapping("pet-type")
public String whichPetIsTheBest(){
return petService.getPetType();
}
}
public interface PetService {
String getPetType();
}
#Service("cat")
public class CatPetService implements PetService {
#Override
public String getPetType() {
return "Cats Are the Best!";
}
}
#Profile({"dog", "default"})
public class DogPetService implements PetService {
public String getPetType(){
return "Dogs are the best!";
}
}
application.properties
spring.profiles.active=dog
Result
--- The Best Pet is ---
Cats Are the Best!
I don't understand why cats are here. I can even comment the properties out, but cats are still here. I would like to see dogs.
What can I try next?
It looks like the DogService is not a bean. So in the end you only have a single bean (CatService) and this one will be picked all the time.
You should define a bean of DogPetService with #service.
You should add the cat profile to CatPetService.
Like this :
#Service("cat")
#Profile({"cat"})
public class CatPetService implements PetService {
#Override
public String getPetType() {
return "Cats Are the Best!";
}
}
#Profile({"dog", "default"})
#Service("dog")
public class DogPetService implements PetService {
public String getPetType(){
return "Dogs are the best!";
}
}
I think you can achieve what you want like this:
#Configuration("petConfig")
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
public class PetConfig {
#Value("${spring.profiles.active}")
private String type;
#Bean(name = "petService")
#Primary
public PetService petService() {
PetService petService = null;
if ("cat".equals(type)) {
petService = new CatPetService();
}
if ("dog".equals(type)) {
petService = new DogPetService();
}
return petService;
}
}
Annotate both CatPetService and DogPetService with #Service. This way you can easily adapt the code without hardcodings and duplication.
I try to implement chain of responsibility with quarkus 2.10.0.
I have class IssueChangeChain and no one IssueChangeChainLink doesn't inject in field links.
#ApplicationScoped
public class IssueChangeChain {
#Inject
#All
List<IssueChangeChainLink> links;
public void processIssueChange(JiraChangeDTO change) {
logger.info("Try to process " + change + " through " + links);
if(change == null)
return;
links.forEach(link ->{
var changeItem = link.getChangeItem(change);
if (changeItem != null) {
link.processChangeItem(changeItem);
}
});
}
void setLinks(List<IssueChangeChainLink> links) {
this.links = links;
}
}
And three classes implemented IssueChangeChainLink
#ApplicationScoped
public class IssueCreationChainLink implements IssueChangeChainLink<AddJiraIssueDTO> {
#Override
public AddJiraIssueDTO getChangeItem(JiraChangeDTO change) {
...
}
#Override
public void processChangeItem(AddJiraIssueDTO changeItem) {
...
}
private static final Logger log = LoggerFactory.getLogger(IssueCreationChainLink.class);
}
#ApplicationScoped
public class SprintChangeChainLink implements IssueChangeChainLink<IssueAddSprintDTO> {
#Override
public IssueAddSprintDTO getChangeItem(JiraChangeDTO change) {
...
}
#Override
public void processChangeItem(IssueAddSprintDTO changeItem) {
...
}
private static final Logger log = LoggerFactory.getLogger(SprintChangeChainLink.class);
}
What should I do to inject List of beans?
#Inject
List<?> instances;
does not work in Quarkus as this is not supported in CDI. In CDI it is looking for a bean which is a List.
What you should do is inject
#Inject
#All
Instance<IssueChangeChainLink> instances;
Instance from CDI implements Iterable, which you can then use .stream() or .forEach() from to iterate over beans.
I have multiple beans with #Component implementing a generic interface.
I have a class where I use methods from these beans. Instead of autowiring each bean separately, I was figuring out of autowiring the interface as a list.
But how do I call the methods of different beans when I just autowire the interface.
public interface Generic {
}
#Component
public class A implements Generic{
.....
public void test{
}
}
#Component
public class B implements Generic{
.....
public void read{}
}
#component class C {
#Autowired
List <Generic> mylist; // type of list is Generic
Now I need to access read from B and test from A
With your new question you CAN do it like this way but I think it's better to use 2 seperate lists instead of the instanceOf.
public interface Generic { }
public interface GenericTestable extends Generic {
void test()
}
public interface GenericReader extends Generic {
void read()
}
#Component
public class A implements GenericTestable {
public void test() { }
}
#Component
public class B implements GenericReader {
public void read() { }
}
#Component
public class C {
private final List<Generic> genericItems;
#Autowired
public C(List<Generic> genericItems) {
this.genericItems = genericItems;
}
public void callThem() {
this.genericItems.forEach(this::callMethodOnSpecificType);
}
private void callMethodOnSpecificType(Generic genericItem) {
if (genericItem instanceof GenericTestable) {
((GenericTestable) genericItem).test();
} else if (genericItem instanceof GenericReader) {
((GenericReader) genericItem).read();
}
}
}
But I think that something like this is a better approach. Cleaner and faster code. Remove the Generic interface and use only the 2 separate interfaces.
public class BetterC {
private final List<GenericTestable> genericTestables;
private final List<GenericReader> genericReaders;
#Autowired
public BetterC(List<GenericTestable> genericTestables, List<GenericReader> genericReaders) {
this.genericTestables = genericTestables;
this.genericReaders = genericReaders;
}
public void callTestables() {
this.genericTestables.forEach(GenericTestable::test);
}
public void callReaders() {
this.genericReaders.forEach(GenericReader::read);
}
}
How about using the Generic interface to route the call ?
From the java-doc api : Ordered
Ordered is an interface that can be implemented by objects that should
be orderable, for example in a Collection. The actual order can be
interpreted as prioritization, with the first object (with the lowest
order value) having the highest priority.
import org.springframework.core.Ordered;
public interface Generic extends Ordered {
void call();
}
----
#Component
public class A implements Generic {
#Override
public int getOrder() {
return 2;
}
#Override
public void call() {
test();
}
public void test() {
System.out.println("Call test()");
}
}
----
#Component
public class B implements Generic {
#Override
public int getOrder() {
return 1;
}
#Override
public void call() {
read();
}
public void read() {
System.out.println("call read()");
}
}
----
#Component
public class C {
List<Generic> list;
public C(List<Generic> list) {
this.list = list;
}
public void testMethod() {
for(Generic g: list) {
g.call();
}
}
}
try this in your implementation
#Component
#Qualifer("a")
public class A implements Generic{ ..... public void test(){ }}
#Component
#Qualifer("b")
public class B implements Generic{ ..... public void read(){ }}
#component
public class C {
#Autowired
Generic a;
#Autowired
Generic b;
... call respective methods
}
I have Spring converter which uses Spring Data REST's component called EnumTranslator
#Component
public class TranslationStringToSpecificationStatusEnumConverter implements Converter<String, Specification.Status> {
private final EnumTranslator enumTranslator;
#Autowired
public TranslationStringToSpecificationStatusEnumConverter(EnumTranslator enumTranslator) {
this.enumTranslator = enumTranslator;
}
#Override
public Specification.Status convert(String source) {
return enumTranslator.fromText(Specification.Status.class, source);
}
}
Recommended way to register such converter is to subclass RepositoryRestConfigurerAdapter as follows:
#Configuration
public class RepositoryRestConfig extends RepositoryRestConfigurerAdapter {
private final TranslationStringToSpecificationStatusEnumConverter converter;
#Autowired
public RepositoryRestConfig(TranslationStringToSpecificationStatusEnumConverter converter) {
this.converter = converter;
}
#Override
public void configureConversionService(ConfigurableConversionService conversionService) {
conversionService.addConverter(converter);
super.configureConversionService(conversionService);
}
}
When I run the Spring Boot application, it fails on the following:
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| translationStringToSpecificationStatusEnumConverter defined in file ...
↑ ↓
| org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration (field java.util.List org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration.configurers)
↑ ↓
| repositoryRestConfig defined in file ...
└─────┘
So there is circular bean dependency.
How can I register the converter above so that I don't introduce circular bean dependency?
To make it work:
#Override
public void configureConversionService(ConfigurableConversionService conversionService) {
conversionService.addConverter(String.class, Status.class, new StringToTranslatedEnumConverter<>(Status.class));
super.configureConversionService(conversionService);
}
First I created utility class that help me work with Spring beans in unmanaged objects:
#Component
public final class SpringUtils {
#Autowired private ApplicationContext ctx;
private static SpringUtils instance;
#PostConstruct
private void registerInstance() {
instance = this;
}
public static <T> T getBean(Class<T> clazz) {
return instance.ctx.getBean(clazz);
}
}
Then I created the converter:
public class StringToTranslatedEnumConverter<T extends Enum<T> & TranslatedEnum> implements Converter<String, T> {
private final ConcurrentMapCache cache;
private EnumTranslator enumTranslator;
private Class<T> type;
public StringToTranslatedEnumConverter(Class<T> type) {
this.type = type;
cache = new ConcurrentMapCache(type.getName());
}
#Override
public T convert(String from) {
if (enumTranslator == null) {
enumTranslator = SpringUtils.getBean(EnumTranslator.class);
}
Cache.ValueWrapper wrapper = cache.get(from);
if (wrapper != null) {
//noinspection unchecked
return (T) wrapper.get();
}
T translatedEnum = enumTranslator.fromText(type, from);
cache.put(from, translatedEnum);
return translatedEnum;
}
}
UPDATED
TranslatedEnum - it's interface-marker, used to mark enums which translation is only need.
public interface TranslatedEnum {
}
public enum Status implements TranslatedEnum {
CREATED, DELETED
}
The solution to this problem is Spring Core specific. In order to break circle bean dependency cycle, we have to delay setting converter in RepositoryRestConfig. It can be achieved with setter injection:
#Component
public class RepositoryRestConfig extends RepositoryRestConfigurerAdapter {
private TranslationStringToSpecificationStatusEnumConverter converter;
#Override
public void configureConversionService(ConfigurableConversionService conversionService) {
conversionService.addConverter(converter);
super.configureConversionService(conversionService);
}
#Autowired
public void setConverter(TranslationStringToSpecificationStatusEnumConverter converter) {
this.converter = converter;
}
}
You can find how to solve it in this commit by Greg Turnquist: https://github.com/pmihalcin/custom-converter-in-spring-data-rest/commit/779a6477d76dc77515b3e923079e5a6543242da2
Take the following general abstract class:
#Configurable
public abstract class TestEntityRoot {
public abstract String print();
}
And a subclass:
#Configurable
public class TestEntity extends TestEntityRoot{
private TestEntityService testEntityService;
#Autowired
public void setTestEntityService(TestEntityService testEntityService) {
this.testEntityService = testEntityService;
}
#Override
public String print() {
return testEntityService.print();
}
}
When call controller:
#RestController
public class TestEntityController {
#GetMapping(name = "/test")
public String print() {
TestEntity entity = new TestEntity();
return entity.print();
}
}
everything ok. But if call like this:
#RestController
public class TestEntityController {
#GetMapping(name = "/test")
public String print() {
TestEntityRoot entity = new TestEntity();
return entity.print();
}
}
i get null pointer. Is it possible that second example work?
In the second case you create manually the class rather than using spring's bean. Autowire the bean instead. See
#RestController
public class TestEntityController {
#Autowired
private TestEntity entity
#GetMapping(name = "/test")
public String print() {
return entity.print();
}
}