Search code examples
jakarta-eedependency-injectionmockingcdi

How to Inject no interface, external library classes into JavaEE application?


I am fairly new to JavaEE and also the concept of dependency injection. However I do have a fair understanding of it even though I do not know all the ways it can be used.

I have a Local Interface as below:

@Local
public interface MyInterfaceLocal {
    SomeType getMeSometype();
}

The class that implements this interface is stateless EJB.

@Stateless
public class MyInterfaceImpl {
    public SomeType getMeSomeType() {
        //Some implementation details...
        ExternalLibraryClass externalLib = new ExternalLibrary(arg1, arg2);
        return externalLib.externalLibMethod();
    }
}

Now the problem is, how can I avoid instantiating the externalLib and let this be injected in someway? For example if this was another EJB that I created with an interface, then I could simply let the EJB container handle the instantiation, with an @EJB annotation like below.

@Stateless
public class MyInterfaceImpl {
    @EJB
    AnotherInterface anotherInterfaceImpl;

    public SomeOtherType getMeSomeType() {
        //Some implementation details...

        return anotherInterfaceImpl.someMethod();
    }
}

I want to be able to do (something like) this for external library that I am using because, that allows me to:

  1. Change the underlying external library that is currently being used with minimal change to my code-base. May be change to a better one if need arises.
  2. Easily inject a mock when I want to unit test the MyInterfaceImpl class.

I have so far looked at-

  1. Creating a wrapper method whose parameter is the ExternalLibrary and thus can perform some kind of manual method parameter injection. This still makes my implementation tightly coupled to the underlying library. (Or I am not doing it right)

  2. Using Context & Dependency Injection container to do the injection (like how the EJB container does. I am aware its not the same). Researched about the ability of using Producers. Although I understand what Producers do with respect to CDI, I am not able to wrap my head around how I can make use of this? Or even if I am on the right path?

UPDATE: I found a few articles that helped me understand the CDI Producers better and I tried going by that approach but faced another problem. So now I have:

ExternalLibraryProducer.java

public class ExternalLibraryProducer {
    @Produces
    private ExternalLibraryClass1 extrnalLibraryClassProducer() {
        return new ExternalLibraryClass1("SomeString", 7);
        //The constructor actually takes a string and another commplex type 
        //as parameters. I am keeping it a little simple here.
        //I am trying to set the ExternalLibraryClass1() arguments 
        //programmatically at runtime.
    }
}

Now the constructor of the object that I want to produce, takes in parameters, lets say a String and integer. I thought I could create a Qualifier to pass in these parameters to produce the object that I want.

ExternalLibraryClass1Qualifier.java

@Qualifier
@Retention(RUNTIME)
@Target({METHOD})
public @interface ExternalLibraryClass1Qualifier {
    String argument1();
    Int argyment2(); //This is actually another complex type. Keeping it 
    //simple here.
}

Now what I want to do is, I want the argument values to be set programmatically at runtime (assume, from a properties file). And I am not able to figure out how to do this. So my final injection would look like below.

@Stateless
public class MyInterfaceImpl {
    @Inject
    @ExternalLibraryClass1Qualifier(argument1 = "something", argument2 = 7)
    ExternalLibrary externalLib;

    public SomeType getMeSomeType() {
        //Some implementation details...
        return externalLib.externalLibMethod();
    }
}

Thanks for any guidance.


Solution

  • CDI producers are a way to go here, the only question is, how exactly do you want to build them.

    Since you said that the arguments come from a property file, I would suggest that you invert the approach and let producer method check that property file and extract the values (or ask any "reader" which did that before and caches it):

    @Produces
    private ExternalLibraryClass1 produceExternalLibraryInstance() {
        // read the properties from the file or from any cache you use
        String arg1 = PropertyReader.getarg1();
        Integer arg2 = PropertyReader.getArg2();
        // create object with those args
        return new ExternalLibraryClass1(arg1, arg2);
    }
    

    With this you obviously need no qualifier and can then simply do @Inject ExternalLibraryClass1.

    Just note that producer will be invoked based on when you create an object which injects ExternalLibraryClass1 - make sure you can grab the args from property file this soon. (should't be trouble when produces does the reading, might be trickier if you have some cache in place)