java 用 CDI 机制代替基于工厂的对象创建

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6621403/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 16:39:41  来源:igfitidea点击:

Replace factory-based object creation with CDI mechanism

javafactoryfactory-patterncdijboss-weld

提问by Sebastian Wramba

I wanted to introduce CDI (Weld) to our project and now having some trouble with objects that are manually constructed.

我想在我们的项目中引入 CDI(焊接),现在手动构建的对象有一些问题。

So we have some classes implementing the IReportinterface, which have a field that should be injected. This is null at runtime because all of those classes are being generated by the ReportFactoryin a class ReportController.

所以我们有一些实现IReport接口的类,它们有一个应该注入的字段。因为所有这些类是由产生这是在运行时零ReportFactory一类ReportController

private Map<String,Object> generateReport(ReportInfo ri, ...) {
// some input validation
    IReport report = ReportControllerFactory.getReportInstance( ri.getClassName() );
// ...
}

I am aware that I can use the @Producesannotation together with another custom annotation in the ReportControllerFactory, but how do I use the @Injectfor a variable that can only be created, after some validation was done, insidea method? And how would I submit the parameter ri.getClassName()? The object riis not known when the ReportControlleris constructed.

我知道我可以将@Produces注释与 中的另一个自定义注释一起使用ReportControllerFactory,但是如何使用@Injectfor 只能在完成某些验证后方法中创建的变量?我将如何提交参数ri.getClassName()?对象riReportController构造时未知。

Thank you very much!

非常感谢你!

Kind regards, Sebastian

亲切的问候,塞巴斯蒂安

Edit at Jul 08, 2011 (10:00):

2011 年 7 月 8 日 (10:00) 编辑:

ReportFactory class:

报表工厂类:

public static IReport getReportInstance( String className ) throws ReportException {

    IReport report = null;

    try {
        Class<?> clazz = Class.forName( className );
        report = (IReport) clazz.newInstance();
    }
    catch ( Exception e ) { … }        

    return report;
}

Edit 2 (Selection of the right report implementation)

编辑2(选择正确的报表实现)

The report instance is selected by some paths that go from the JSF frontend to the ReportController. The ManagedBean calls a session bean, which has several methods, depending on which button was pressed where. All those methods set the report name and call the more generic method sendOrGetReport. This method selects the unique key for the specified report from the database and decides whether to send an e-mailor immediately deliverthe report. Let's assume it should be delivered.

报表实例由一些从 JSF 前端到 ReportController 的路径选择。ManagedBean 调用会话 bean,该会话 bean 有多种方法,具体取决于按下哪个按钮的位置。所有这些方法都会设置报告名称并调用更通用的方法sendOrGetReport。此方法从数据库中选择指定报告的唯一键,并决定是发送电子邮件还是立即发送报告。让我们假设它应该被交付。

Then the ReportControllercomes into play. He fetches a ReportInfoobject based upon the unique key and other information provided by the methods above and calls the ReportFactoryto create a report of type ri.getClassName().

然后ReportController开始发挥作用。他ReportInfo根据上述方法提供的唯一键和其他信息获取对象,并调用ReportFactory以创建类型为 的报告ri.getClassName()

Fancy, huh? I think this whole section might need some refactoring. If you don't see any easy spot, I skip the @Injectin the report implementation and do a JDNI lookup for that resource.

花哨吧?我认为整个部分可能需要一些重构。如果您没有看到任何简单的地方,我会跳过@Inject报告实现中的 并对该资源进行 JDNI 查找。

回答by yoda

In order to dynamically create the right report class a small change to the accepted answer will solve the problem. The solution is the fact that Instance<...> gives you the list of all beans that are of a certain type. A produces annotation is not needed.

为了动态创建正确的报告类,对已接受的答案进行小幅更改即可解决问题。解决方案是 Instance<...> 为您提供某种类型的所有 bean 的列表。不需要生产注释。

Make a factory class that can select between the injected instances at runtime

创建一个可以在运行时在注入的实例之间进行选择的工厂类

public class ReportFactory {

@Inject Instance<IReport> availableReports;

public IReport createReport(String type) {

   for (IReport report: availableReports) {
      if (report.getType().equals(type)) { //or whatever test you need
         return report;
      }
   }
   return null;
}

Now the class that needs a dynamically selected report, can use this factory.

现在需要动态选择报表的类,可以使用这个工厂。

public class ReportCreator {

    @Inject
    private ReportFactory reportFactory;

    public void createReport(String type) {
        IReport report = reportFactory.createReport(type);
        report.execute();
    }
 }

回答by jan groth

The idea behind CDI (and other DI-frameworks) in order to manage dependencies is to take over control of the managed beans lifecycle. This implies - among other things - that you cannot interfere with the creation of managed beans if you want it to be managed by the container.

为了管理依赖关系,CDI(和其他 DI 框架)背后的想法是接管对托管 bean 生命周期的控制。这意味着 - 除其他外 - 如果您希望它由容器管理,则不能干扰托管 bean 的创建。

Certainly this does not mean that your scenario is unsolvable, you just have to change your perspective a bit ;-)

当然,这并不意味着您的场景无法解决,您只需要稍微改变一下观点;-)

The idea is to work with managed beans (obviously), but let your own logic decide which instance of all available instances is the correct.

这个想法是使用托管 bean(显然),但让您自己的逻辑决定所有可用实例中的哪个实例是正确的。

...
@Inject Instance<IReport> availableReports;
...
@Produces
public IReport createReport() {
   IReport result;
   for (IReport report: availableReports) {
      // choose correct instance, you might want to query the injection
      // point or any attached qualifier a bit more in order to 
      // determine which is the correct instance
      if ...
         result = report;
      ...
   }
   return result;
}
...

With as many beans of beantype IReport as you like

使用任意数量的 beantype IReport bean

public class AReport implements IReport {
...
@Inject
...
}

public class BReport implements IReport {
...
@Inject
...
}

And an automagical usage like this

像这样的自动用法

public class MyStuff {
...
@Inject
IReport myReport;
...
}

See hereand herefor more information.

请参阅此处此处了解更多信息。

If I did not misunderstand your problem, this should bring you forward - feel free to post further questions / comments.

如果我没有误解你的问题,这应该会让你前进 - 随时发布进一步的问题/评论。

UPDATE:

更新

Everything might be just dead simple if something like this fits your requirements:

如果这样的事情符合您的要求,一切都可能非常简单:

@AReport
public class AReport implements IReport {
...
@Inject
...
}

@BReport
public class BReport implements IReport {
...
@Inject
...
}

With the usage like this

像这样的用法

public class MyStuff {
...
@Inject
@AReport
IReport myAReport;
...
@Inject
@BReport
IReport myBReport;
...
}

回答by phtrivier

Ok, so if I'm not too wrong, based on the doc ( Is this the proper framework ? http://docs.jboss.org/seam/3/latest/reference/en-US/html/solder-beanmanagerprovider.html), your factory would need to get a handle to the BeanManager singleton (either get it injected, or call some accessor from the framework) , and do something along the lines of

好的,所以如果我没有错的话,根据文档(这是合适的框架吗?http://docs.jboss.org/seam/3/latest/reference/en-US/html/solder-beanmanagerprovider。 html),您的工厂需要获得 BeanManager 单例的句柄(要么注入它,要么从框架调用一些访问器),并按照以下方式做一些事情

Class<?> clazz = Class.forName( className );
report = beanManager.getBean(clazz);

Assuming your CDI has been configured to handle each of the possible class name, you should get the correct bean. Now this might always be the same instance ; I don't know if this is what you need, sorry.

假设您的 CDI 已配置为处理每个可能的类名,您应该获得正确的 bean。现在这可能总是同一个实例;我不知道这是否是您需要的,抱歉。

Sorry If I'm mistaken ; hoping it helps.

对不起,如果我弄错了;希望它有帮助。