0

I'm using Weld for Cdi in a JavaSE application.
Some of my services come in two flavors. Distinction in CDI via Qualifier (@Italian or @Chinese). Most service code is located in a a shared superclass.
This superclass uses other services. Those with a common implementation are simply injected in the superclass (TimerService). But if there is a specific implementation, it depends on the subclass which implementation is to be chosen.
In the example below: When ItalianFoodController is calling service.cookSoup(), it should use an Italian recipe for the soup...

public abstract class FoodService {
    @Inject TimerService timerService;

    abstract protected RecipeService getRecipeService();

    protected void cookSoup() {
        getRecipeService().getSoupRecipe();
        timerService.setTimer(20);
        ...
    }
}

@ApplicationScoped @Italian
public class ItalianFoodService extends FoodService {
    @Inject @Italian RecipeService recipeService;

    @Override
    protected RecipeService getRecipeService() {
        return recipeService;
    }
    ...
}

@ApplicationScoped @Chinese
public class ChineseFoodService extends FoodService {
    @Inject @Chinese RecipeService recipeService;
    ...
}

public class ItalianFoodController {
    @Inject @Italian ItalianFoodService service;
    ...
    public void cook() {
        service.cookSoup();
    }
}

The example is working fine.
My question is: Is there a CDI-pattern to get rid of getRecipeService()?
The most intuitiv approach would be:

public abstract class FoodService {
    @Inject RecipeService recipeService;
    ...    
}
public class ItalianFoodService extends FoodService {
    @Inject @Italian RecipeService recipeService;
    ...    
}

But this does not work because recipeService will be hidden but not overridden by the subclass.

groovedigga
  • 233
  • 3
  • 13

2 Answers2

0

Yes, there is, and it is called Instance.select(qualifier). And this is how it will work:

public abstract class FoodService {

    @Inject
    TimerService timerService;

    @Any
    @Inject
    Instance<RecipeService> recipeServices;

    private final Annotation recipeSelector;

    FoodService(){
        //this is just one way you can get hold of the qualifier for each soup service
        // another way is to look into AnnotationLiteral
        recipeSelector = Arrays.stream(getClass().getDeclaredAnnotations())
        .filter((ann) -> annot.annotationType().isAnnotationPresent(Qualfiier.class))
        .findFirst()
        .orElseThrow(()-> new someexception());
    }

    protected void cookSoup() {
        RecipeService service = recipeServices.select(recipeSelector).get().getSoupRecipe();
        timerService.setTimer(20);
    }
}

More details on this: http://docs.jboss.org/cdi/spec/2.0/cdi-spec.html#dynamic_lookup and http://docs.jboss.org/cdi/spec/2.0/cdi-spec.html#annotationliteral_typeliteral

maress
  • 3,533
  • 1
  • 19
  • 37
  • Thx for you're idea about dealing with the qualifier in the constructor. It lead me to a different approach (see answer below). I use Instance<> sometimes if I need multiple CDI-instances. But in this case it doesn't help me to make the code more readable as in my first approach. – groovedigga Jul 18 '18 at 14:08
0

Thinking about @maress' answer I came up with a different approach which allows me to use the injected service quite intuitively.

Solution using constructor injection:

public abstract class FoodService {
    protected RecipeService recipeService;

    FoodService (RecipeService recipeService) {
        this.recipeService = recipeService;
    }
}

public class ItalianFoodService extends FoodService {
    // Only needed if ItalianRecipeService holds additional methods.
    @Inject @Italian ItalianRecipeService recipeService;

    @Inject
    ItalianFoodService(@Italian RecipeService recipeService) {
       super(recipeService);
    }
}

Alternatively the same can be achieved via

Solution using @PostConstruct:

public abstract class FoodService {
    protected RecipeService recipeService;
}

public class ItalianFoodService extends FoodService {
    // Only needed if ItalianRecipeService holds additional methods.
    @Inject @Italian ItalianRecipeService recipeService;

    @PostConstruct
    postConstruct() {
       super.recipeService = recipeService;
    }
}

Both solutions are quite short and readable while the one with constructor injection is slightly more explizit about the injection.

groovedigga
  • 233
  • 3
  • 13