JEE - 注入 bean 列表

JEE - inject a list of beans

我想注入一个 beans 列表。我在网上搜索过,但发现的并不多。我试过这个:https://onlysoftware.wordpress.com/2011/07/10/injecting-lists-cdi-jsf/ 但是用豆子。

查看:

@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);
    }
}

注解:

@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;
    }
}

主类:

@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);
        }
    }
}

列表已生成并将按预期显示。但是如果我按下按钮,我会得到一个 NPE -> messageBean 没有被注入。

所以我的问题是:是否可以注入一个 beans 列表? 我想如果这可能的话,也应该可以向列表中添加一个元素。但是一步一个脚印。

首先 使用 new 创建 bean 会绕过 CDI,自然不会发生注入 。这就是 NPE 的原因。

您可以通过两种方式解决该问题:(1) 正如您所说,列出 CDI 管理的 bean 并将其注入到任何需要的地方,或者 (2) 制作 DemoView 一个普通 Java 对象,由您管理,而不是 CDI,并且仍然有 CDI 生成并注入此类内容的列表。

解决方案 2

解决方案(2)更简单,我认为对某些情况更合适。我不知道引发问题的用例的确切细节,但显然 DemoView 是某种 UI 组件。当尝试使用 UI 组件(如 JSF、JavaFX)作为 CDI bean 时,存在严重的冲突:哪个框架将创建对象?无论如何,将 DemoView 更改如下:

// No scope annotation!
public class DemoView extends VerticalLayout {
    // No inject!
    private MessageBean messageBean;

    public DemoView(MessageBean messageBean) {
        this.messageBean = messageBean;
    }

    ...
}

生产者变为:

@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.
}

如果您不为生成的视图提供范围,则 List 隐含 @Dependent 范围。要非常小心使用它,如果注入到长期存在的组件(例如某些东西@ApplicationScoped),您可能需要手动销毁它。

解决方案 1

如果您真的想要解决方案 (1),即生成一个托管 bean 列表,您必须考虑到对于正常作用域的 bean,CDI 只保留 1 个 bean活动范围并且无法管理更多(至少没有限定符来区分实例)。我 猜测 @UIScoped 是一个正常范围。如果你想自己管理 bean 的创建,你还必须 (a) 使它成为 @Dependent-scoped 和 (b) 自己管理它的生命周期。为了创建一个 @Dependent-scoped bean 的许多实例,注入一个 Instance<DemoView>.

因此,DemoView 将更改为:

@Dependent
public class DemoView extends VerticalLayout {
    ...
}

制作人(一般概念和未经测试的代码,这可能有微妙的陷阱):

@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);
    }
}

警告:Instance@Dependent beans 容易出现内存泄漏,如果使用不当 - 请确保正确使用它们!!!