Search code examples
javajakarta-eedependency-injectioncdi

Specify some (but not all) constructor parameters at runtime


Suppose I have the following service interfaces defined (as well as implementations):

interface Service1 { /* Methods omitted for brevity */ }
interface Service2 { /* Methods omitted for brevity */ }

I also have this interface that cannot be modified (it's out of my hands):

interface YouAreNotAllowedToModifyMe {
  Object createSomething();
}

Further suppose that I have a class that implements the YouAreNotAllowedToModifyMe interface and accepts implementations of those services along with the current HttpServletRequest. Note that the request is an implementation detail and NOT part of the interface.

class Demo implements YouAreNotAllowedToModifyMe {

  private final HttpServletRequest request;
  private final Service1 service1;
  private final Service2 service2;

  @Inject
  public Demo(HttpServletRequest request, Service1 service1, Service2 service2) {
    this.request = request;
    this.service1 = service1;
    this.service2 = service2;
  }

  public Object createSomething() {
    // Use the request, service1, and service2 in order to create the object.
  }
}

Demo cannot be instantiated until runtime because it requires the current HttpServletRequest, however I would like CDI injection to take care of injecting the other parameters. How can I do that?

Essentially, I would like for this to be possible (it's not):

class Foo {

  @Inject
  private final Instance<Demo> demoInstance;

  public void doSomething(HttpServletRequest request) {

    // This is pretend. This won't really work, though I wish it would.
    // Essentially, I pass in the request and CDI takes care of everything else.
    Demo demo = demoInstance.get(request);
  }
}

Solution

  • You cannot use a constructor with half arguments injected and half provided by hand. You have however alternatives:

    • Create a producer for the HttpServletRequest. Better yet, use Delta Spike. Then you can actually inject the HttpServletRequest!
    • Modify Demo to be a factory for instances of YouAreNotAllowedToModifyMe, rather than implementing it:

      @ApplicationScoped //probably-but not necessarily???
      public class Demo {
          @Inject
          private final Service1 service1;
          @Inject
          private final Service2 service2;
      
          public YouAreNotAllowedToModifyMe makeYouAreNotAllowedToModifyMe(HttpServletRequest request) {
              return new YouAreNotAllowedToModifyMeImpl(request);
          }
      
          private class YouAreNotAllowedToModifyMeImpl implements YouAreNotAllowedToModifyMe {
              YouAreNotAllowedToModifyMeImpl(HttpServletRequest request) {
                  // do what you must...
              }
      
              // implement it!
              // remember, you can actually use service1 &
              // service 2 from the enclosing instance
          }
      }