【问题标题】:JEE - inject a list of beansJEE - 注入 bean 列表
【发布时间】:2018-11-29 13:13:25
【问题描述】:

我想注入一个 bean 列表。我在网上搜索,但发现不是很多。我试过这个: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 没有注入。

所以我的问题是:甚至可以注入 bean 列表吗? 我想如果这是可能的,也应该可以在列表中添加一个元素。而是一步一个脚印。

【问题讨论】:

    标签: jakarta-ee cdi


    【解决方案1】:

    首先使用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&lt;DemoView&gt;

    因此,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 bean 很容易出现内存泄漏,如果使用不当 - 请确保正确使用它们!!!

    【讨论】:

    • 非常感谢,解决方案 1 正是我所需要的。
    • Here 是一篇关于你需要知道的Instance 陷阱的有趣文章。
    猜你喜欢
    • 1970-01-01
    • 2014-10-20
    • 1970-01-01
    • 1970-01-01
    • 2017-07-09
    • 2013-05-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多