Search code examples
javafactoryfactory-patterncdijboss-weld

Replace factory-based object creation with CDI mechanism


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

So we have some classes implementing the IReport interface, which have a field that should be injected. This is null at runtime because all of those classes are being generated by the ReportFactory in a class 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 @Produces annotation together with another custom annotation in the ReportControllerFactory, but how do I use the @Inject for a variable that can only be created, after some validation was done, inside a method? And how would I submit the parameter ri.getClassName()? The object ri is not known when the ReportController is constructed.

Thank you very much!

Kind regards, Sebastian

Edit at Jul 08, 2011 (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)

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-mail or immediately deliver the report. Let's assume it should be delivered.

Then the ReportController comes into play. He fetches a ReportInfo object based upon the unique key and other information provided by the methods above and calls the ReportFactory to create a report of type ri.getClassName().

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


Solution

  • 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.

    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.

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

    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 here and here for 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;
    ...
    }