I would like to inject a list of beans. I searched in the net, but found not very much. I tried this: https://onlysoftware.wordpress.com/2011/07/10/injecting-lists-cdi-jsf/ but with beans.
View:
@UIScoped
public class DemoView extends VerticalLayout {
@Inject
private MessageBean messageBean;
private Button button;
public DemoView() {
getStyle().set("border", "1px solid");
button = new Button("Click me", event -> Notification.show(messageBean.getMessage()));
}
public void init() {
removeAll();
add(new Label("oh no!"));
add(button);
}
}
Annotation:
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Qualifier
public @interface ViewList {
}
InitBean:
@ApplicationScoped
public class AppInitBean implements Serializable {
@Produces
@Named(value = "viewNamedList")
@ViewList
public List<DemoView> getViews() {
return this.generateViews();
}
private List<DemoView> generateViews() {
List<DemoView> views = new ArrayList<DemoView>(5);
for (int i = 1; i <= 5; i++) {
DemoView emp = new DemoView();
views.add(emp);
}
return views;
}
}
MainClass:
@Route("")
public class MainView extends VerticalLayout implements BeforeEnterObserver {
@Inject
@ViewList
private List<DemoView> viewList;
@Override
public void beforeEnter(BeforeEnterEvent event) {
removeAll();
add(new Label("whatever"));
for (DemoView demoView : viewList) {
demoView.init();
add(demoView);
}
}
}
The list is produced and will be shown like expected. But if I push the button, I get a NPE -> messageBean is not injected.
So my question is: Is it even possible to inject a list of beans? I guess if this is possible, it should be also possible to add a element to the list. But one step after an other.
First and foremost creating a bean with new
bypasses CDI and, naturally, injections do not happen. That is the reason for the NPE.
You can address the problem in two ways: (1) Just as you say, make a list of CDI-managed beans and inject it to whatever asks for it, or (2) make DemoView
a plain Java object, managed by you, not CDI, and still have CDI produce & inject a list of such things.
Solution (2) is simpler, and I would argue more appropriate for some cases. I do not know the exact details of the use case that drives the question, but obviously the DemoView
is some kind of UI component. When trying to use UI components (like JSF, JavaFX) as CDI beans there is a serious conflict: which framework will create the object? Anyway, change DemoView
as follows:
// No scope annotation!
public class DemoView extends VerticalLayout {
// No inject!
private MessageBean messageBean;
public DemoView(MessageBean messageBean) {
this.messageBean = messageBean;
}
...
}
And the producer becomes:
@ApplicationScoped
public class AppInitBean implements Serializable {
// Inject the collaborators required by DemoView here
@Inject
private MessageBean messageBean;
@Produces
@Named(value = "viewNamedList")
@ViewList
// I would argue you should define a scope here - @UIScoped maybe?
// This would be the scope of the produced list (and it seems appropriate)
public List<DemoView> getViews() {
return this.generateViews();
}
private List<DemoView> generateViews() {
List<DemoView> views = new ArrayList<DemoView>(5);
for (int i = 1; i <= 5; i++) {
DemoView emp = new DemoView(messageBean); // pass the messageBean
views.add(emp);
}
return views;
}
// You may also consider adding a disposer method,
// if the list of DemoViews needs special cleanup logic.
}
If you do not provide a scope for the produced views, the List
is implicitly @Dependent
-scoped. Be very careful with it, you may need to manually destroy it if injected to a long-lived component (e.g. something @ApplicationScoped
).
If you really want solution (1), i.e. producing a list of managed beans you have to take into account that for normal-scoped beans, CDI keeps exactly 1 bean in the active scope and cannot manage more (at least without qualifiers to differentiate the instances). I am guessing that @UIScoped
is a normal scope. If you want to manage the creation of a bean yourself, you also have to (a) make it @Dependent
-scoped and (b) manage its lifecycle yourself. In order to create many instances of a @Dependent
-scoped bean, inject an Instance<DemoView>
.
So, DemoView
would change to:
@Dependent
public class DemoView extends VerticalLayout {
...
}
And the producer (general concept & untested code, this might have subtle traps):
@ApplicationScoped
public class AppInitBean implements Serializable {
// Inject the collaborators required by DemoView here
@Inject
private Instance<DemoView> demoViewInstance;
@Produces
@Named(value = "viewNamedList")
@ViewList
@UIScoped // Do give the list a scope!
public List<DemoView> getViews() {
return this.generateViews();
}
private List<DemoView> generateViews() {
List<DemoView> views = new ArrayList<DemoView>(5);
for (int i = 1; i <= 5; i++) {
DemoView emp = demoViewInstance.get(); // creates new instance for @Dependent beans
views.add(emp);
}
return views;
}
// Definitely add a disposer method
void disposeViews(@Disposes @ViewList List<DemoView> views) {
views.forEach(demoViewInstance::destroy);
}
}
WARNING: Instance
and @Dependent
beans are prone to memory leaks, if not properly used - make sure you use them properly!!!