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

时间:2011-07-08 07:38:19

标签: java factory factory-pattern cdi jboss-weld

我想向我们的项目介绍CDI(Weld),现在手动构建的对象遇到了一些麻烦。

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

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

我知道我可以在@Produces中使用ReportControllerFactory注释和其他自定义注释,但如何将@Inject用于只能创建的变量,经过一些验证后,里面一个方法?我如何提交参数ri.getClassName()?构建ri时,对象ReportController未知。

非常感谢!

亲切的问候,  塞巴斯蒂安

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

ReportFactory类:

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

编辑2(选择正确的报告实施)

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

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

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

3 个答案:

答案 0 :(得分:12)

为了动态创建正确的报告类,对接受的答案进行小的更改将解决问题。解决方案是Instance&lt; ...&gt;为您提供特定类型的所有bean的列表。不需要生成注释。

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

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

现在需要动态选择报告的类可以使用此工厂。

public class ReportCreator {

    @Inject
    private ReportFactory reportFactory;

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

答案 1 :(得分:8)

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

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

这个想法是使用托管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;
}
...

使用尽可能多的beantype IReport bean

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

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

像这样的自动化用法

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

有关详细信息,请参阅herehere

如果我没有误解你的问题,这应该会带你前进 - 随时发表更多问题/意见。

<强>更新

如果这样的事情符合你的要求,那么一切都可能很简单:

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

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

使用这样的用法

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

答案 2 :(得分:1)

好的,如果我没有错,基于doc(这是正确的框架吗?http://docs.jboss.org/seam/3/latest/reference/en-US/html/solder-beanmanagerprovider.html),你的工厂需要得到一个BeanManager单例的句柄(要么注入它,或者从框架中调用一些访问者),并按照

的方式做一些事情
Class<?> clazz = Class.forName( className );
report = beanManager.getBean(clazz);

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

抱歉,如果我弄错了;希望它有所帮助。