How inject list of bean in Quarkus? - quarkus

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.

Related

Dependency injection of object initialised by specific value - CDI and Spring

I have a legacy application which uses Spring for bean management (AnnotationConfigWebApplicationContext) and CDI (inject, brought by jersey-spring dependency) for DI.
I have the following situation:
#Service
#RequestScoped
#Scope(value = "request")
public class InjectedClass {
private SomeEnum someAttribute;
public void getSomeAttribute() {
return someAttribute;
}
}
#Service
public class MiddleLayer {
#Inject
public MiddleLayer(InjectedClass injectedClass) {
}
private void middleLayerMethod() {
if (someAttribute == SomeEnum.Y) {
// do something specific
}
}
}
// controller
public class SomeController {
#Inject
// In this flow injectedClass instance is initialised with SomeAttribute = Y by ContainerRequestFilter
public SomeController(MiddleLayer middleLayer) {
}
public void someMethod () {
MethodResult result = middleLayer.middleLayerMethod();
// do some additional things with result
}
}
#Component
public class PeriodicActivity {
#Inject
// I need this MiddleLayer to be injected with injectedClass instance where SomeAttribute = X, since it doesn't go via request filter
public PeriodicActivity(MiddleLayer middleLayer) {
}
public void method () {
MethodResult result = middleLayer.middleLayerMethod();
// do some other things with result
}
}
Without DI what I need to happen would look like this:
public class PeriodicActivity() {
InjectedClass injectedClassObjA = new InjectedClass();
injectedClassObjA.setSomeAttribute(X);
MiddleLayer middleLayer = new MiddleLayer(injectedClassObjA);
middleLayer.middleLayerMethod();
}
I am looking to do something similar with dependencies.
After some reading I am starting to wonder whether it is possible.

factory pattern using spring

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.

How to register Converter in Spring Data Rest application

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

How does Spring beans work with Prototype scope?

I have following spring bean with Prototype scope. In the AppRunner class, I want a new bean to injected by spring within the for loop (if loop count is 2, then i want only 2 new beans to be injected).
But spring injects a new bean every time the setter methods of the SimpleBean is called.
SimpleBean.java
#Component
#Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode =
ScopedProxyMode.TARGET_CLASS)
public class SimpleBean {
private String id;
private Long value;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Long getValue() {
return value;
}
public void setValue(Long value) {
this.value = value;
}
}
AppRunner.java
#Component
public class AppRunner {
#Autowired
SimpleBean simpleBean;
public void execute(List<Output> results){
List<SimpleBean> finalResults = new ArrayList<SimpleBean>();
for(Output o : results){
simpleBean.setId(o.getAppId());
simpleBean.setValue(o.getAppVal());
finalResults.add(simpleBean);
}
}
}
Output.java
public class Output {
private String appId;
private Long appVal;
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public Long getAppVal() {
return appVal;
}
public void setAppVal(Long appVal) {
this.appVal = appVal;
}
}
Unfortunately prototype scope doesn't work like this. When your AppRunner bean is instantiated by the container it is asking for its dependencies. Then a new instance of SimpleBean is created. This instance stays as dependency. Prototype scope starts working when you will have multiple beans with dependency on SimpleBean. Like:
#Component
class BeanOne {
#Autowired
SimpleBean bean; //will have its own instance
}
#Component
class BeanTwo {
#Autowired
SimpleBean bean; //another instance
}
There is one rather straightforward update which can lead to your desired behaviour. You can remove autowired dependency and ask for a new dependency in your loop from context. It would look like this.
#Component
public class AppRunner {
#Autowired
ApplicationContext context;
public void execute(List<Output> results){
List<SimpleBean> finalResults = new ArrayList<SimpleBean>();
for(Output o : results) {
SimpleBean simpleBean = context.getBean(SimpleBean.class);
simpleBean.setId(o.getAppId());
simpleBean.setValue(o.getAppVal());
finalResults.add(simpleBean);
}
}
}
Other option could be technique called Method injection. It is described in the relevant documentation for prototype scope. You can take a look here 7.5.3 Singleton beans with prototype-bean dependencies

Get parent bean in prototype bean that gets injected

I would like to have a Bean and a SubBean like this:
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
#Component
public class SubBean implements ApplicationContextAware{
private Object parent;
public void setApplicationContext(ApplicationContext ctx){
this.parent = doSomeMagicToGetMyParent(ctx);
}
public Object getParent(){
return parent;
}
}
#Component
public class SomeBean implements InitializingBean{
#Resource
private SubBean sub;
public void afterPropertiesSet(){
Assert.isTrue(this == sub.getParent());
}
}
The trick I want to achieve is, that the SubBean automagically gets a reference to the Bean it got injected into. Because the scope of the subbean is prototype, it will get injected as a new instance in every parent that wants it to get injected.
My big idea is to exploit this pattern to write a LoggerBean which can be injected into normal beans. The subbean should work just like a SLF4J Logger.
So does anyone know the magic to make this work? :)
EDIT: I've found a solution to do this with a custom BeanPostProcessor:
#Component
public class DependencyInjectionAwareBeanPostProcessor implements BeanPostProcessor {
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
for (Field f : bean.getClass().getFields()) {
if (f.getType().isInstance(IDependencyInjectionAware.class)) {
ReflectionUtils.makeAccessible(f);
try {
IDependencyInjectionAware diAware = (IDependencyInjectionAware) f.get(bean);
diAware.injectedInto(bean);
} catch (IllegalArgumentException e) {
ReflectionUtils.handleReflectionException(e);
} catch (IllegalAccessException e) {
ReflectionUtils.handleReflectionException(e);
}
}
}
return bean;
}
}
Here is the Interface:
public interface IDependencyInjectionAware {
void injectedInto(Object parent);
}
And here a Bean using it:
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
#Component
public class SomeAwareBean implements IDependencyInjectionAware {
private Object parent;
public void injectedInto(Object parent){
this.parent = parent;
}
public Object getParent(){
return parent;
}
}
Here a test with a normal Bean which works perfectly:
#Component
public class UsingBean implements InitializingBean {
#Resource
private SomeAwareBean b;
public void afterPropertiesSet(){
Assert.notNull(b); //works
Assert.isTrue(b.getParent() == this); //works
}
}
Though, when using the same with a normal class which gets the depedencies injected via #Configurable, the test fails:
#Configurable
public class UsingPlainClass implements InitializingBean {
#Resource
private SomeAwareBean b;
public void afterPropertiesSet(){
Assert.notNull(b); //works
Assert.isTrue(b.getParent() == this); //fails because null is returned
}
}
So this seems to have gotten me to another question: Why won't my custom BeanPostProcessor run on a #Configurable classes? Maybe I have to resort to AspectJ afterall...
EDIT: Just to update the status. I did not implement this afterall because this is overengineering...
I find this simpler:
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
#Component
public class SubBean implements ApplicationContextAware{
private Object parent;
public void setApplicationContext(ApplicationContext ctx){
...
}
public Object getParent(){
return parent;
}
//ADDED CODE
public void setParent(Object parent) {
this.parent = parent;
}
//END ADDED CODE
}
#Component
public class SomeBean implements InitializingBean{
private SubBean sub;
//ADDED CODE
#Resource
public void setSub(SubBean sub) {
this.sub = sub;
sub.setParent(this);
}
//END ADDED CODE
public void afterPropertiesSet(){
Assert.isTrue(this == sub.getParent());
}
}
Fixed several bugs with the solution given by the original poster:
import java.lang.reflect.Field;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.util.ReflectionUtils;
public interface DependencyInjectionAware {
void injectedInto(final Object bean, final String beanName);
public static class DependencyInjectionAwareBeanPostProcessor implements
BeanPostProcessor {
private static final Logger logger = Logger.getLogger(DependencyInjectionAwareBeanPostProcessor.class);
#Override
public Object postProcessBeforeInitialization(final Object bean,
final String beanName) {
return bean;
}
#Override
public Object postProcessAfterInitialization(final Object bean,
final String beanName) {
for (final Field f : bean.getClass().getDeclaredFields()) {
logger.info("scanning field " + f.getName() + " of bean " + beanName + " (class= " + bean.getClass() + ")");
if (DependencyInjectionAware.class.isAssignableFrom(f.getType())) {
ReflectionUtils.makeAccessible(f);
try {
final DependencyInjectionAware diAware = (DependencyInjectionAware) f.get(bean);
diAware.injectedInto(bean, beanName);
} catch (final IllegalArgumentException e) {
ReflectionUtils.handleReflectionException(e);
} catch (final IllegalAccessException e) {
ReflectionUtils.handleReflectionException(e);
}
}
}
return bean;
}
}
}

Resources