Search code examples
javajakarta-eeclassloadercdiweld

How to inject a CDI managed bean that exists in another classloader


I am working on a framework which will be used in Java EE applications and therefore is likely to be deployed in the \lib directory of an EAR file.

The framework will use CDI to programmatically lookup and inject beans that are located in the Java EE application that is using the framework. The problem I've got is when the Provider.get() method from javax.enterprise.Provider<T> is called by my framework to get an instance of the bean, Weld throws a UnsatisfiedResolutionException.

To check this isn't an issue related to CDI I've also tried using MyClass myClass = Class.forName(clazz).newInstance(); to get an instance of the class but a ClassNotFoundException is thrown.

The structure of EAR file I'm using for testing purposes is as follows:

MyTestApp.ear
+\lib\MyFramework.jar <----Contains the framework invoking the Provider.get() method
+MyTestApp.jar        <----Contains the bean I want to inject

My test application's EAR contains an application.xml file which includes <library-directory>lib</library-directory>.

I believe this problem is occurring because the bean I want to inject exists in a separate classloader. i.e. \lib\MyFramework.jar is in a different classloader to MyTestApp.jar. I found this SO question which seems to suggest this is the case. Given that I'm developing a framework I don't believe the answer in the question is a viable solution for my needs.

I'm intrigued to find out whether creating a CDI portable extension would allow me to get an instance of the bean I want to use, but don't have enough experience in this area. Using @Observes ProcessAnnotatedType<T> I can see beans that exist outside of the \lib directory in an EAR file, including the ones I want to programmatically inject.

My questions are:

  1. Am I correct in assuming this problem is occurring because \lib\MyFramework.jar and MyTestApp.jar are in separate classloaders?

  2. Is there anything I can do using CDI that will allow my framework when deployed in the \lib directory of an EAR file to make the Provider.get() method call to avoid Weld throwing a UnsatisfiedResolutionException?

  3. Is there anything I can do outside of CDI to achieve same result?


Update

I've now tried moving MyFramework.jar to the root of the EAR file and also including the jar module in the application.xml file but the container fails to start the application due to a CDI unsatisfied dependency exception. The bean referenced in the exception can be injected when MyFramework.jar is located in the \lib directory and is a different bean to the one referenced in my question.


Solution

  • 1 : yes

    2 : actually I don't know

    3 : Yes, you must understand the ear classloader hierarchy, the jars in ear lib directory are loaded at the ear level and so available in all child classloaders (there is one child classloader per component in the ear).

    It means that MyFramework.jar is visible from MyTestApp.jar ear child classloader but the inverse is false.

    see In java EE, which jars should I put in the library dir?

    You can either :

    • move MyTestApp.jar to ear lib directory (MyFramework.jar can be either in lib dir and reference MyTestApp.jar or at ear root)
    • move MyFramework.jar at ear root and reference MyTestApp.jar in its manifest classpath

    see Deployment of multiple, depended CDI jars in one EAR