Friday, December 5, 2014

WebLogic class loader - analysis and configuration options

Class loader mechanics in WebLogic server may be difficult to understand. This document presents various configuration options available in WebLogic showing its unexpected but documented, and expected but undocumented behaviors. JDeveloper project is attached to this work. 

Introduction
WebLogic uses parent first class loading logic. It means that everything what is on the system level will be loaded first, and application libraries are the last on the list. WebLogic has two options to favor the application, one of them is based on filtering class loader giving ability to block serving of particular class/resource by system class loader. Another one, relays on web module configuration. Note that the resource loading behavior is different when filtering class loader is used. Apart from configured filtering, application resources get a preference over system resources. Finally preference of web module, looks like a historical option, and should be not used. Going through following 9 class loading test cases you will understand  how thing works. There are two more, examples showing how leveraging JAR specification one may influence EAR class loaders. 

Test environment
Test environment is based on application build out of WAR and EJB modules. The application lookups several locations for different types of resources/classes:

component
classes / resources
archive file
JVM
java.lang.String
n/a
WebLogic
weblogic.Server.class, WebLogic_CMP_RDBMS.xml,  commonj.work.Work
n/a
WebLogic's domain/lib
file1.txt, fileA.txt, WebLogic_CMP_RDBMS.xml,  commonj.work.Work
aaaFile1.jar
WebLogic's domain/lib
file1.txt, fileZ.txt, WebLogic_CMP_RDBMS.xml,  commonj.work.Work
zzzFile2.jar
Application
file1.txt,  WebLogic_CMP_RDBMS.xml,  commonj.work.Work

Library module A
file1.txt, fileA.txt, WebLogic_CMP_RDBMS.xml,  commonj.work.Work
aaaFile1.jar
Library module Z
file1.txt, fileZ.txt, WebLogic_CMP_RDBMS.xml,  commonj.work.Work
zzzFile2.jar
EJB module
file1.txt,  WebLogic_CMP_RDBMS.xml,  commonj.work.Work

WAR module
file1.txt,  WebLogic_CMP_RDBMS.xml,  commonj.work.Work


A test packages contains both unique and duplicated classes and resources. Such combination is prepared by intention to present/validate different data layout scenarios combined with range of WebLogic configuration options. The test environment assumes that you have access only to standard user level interfaces provided by WebLogic: (a) domain lib directory, (b) shared libraries, and (c) application level configuration options. 

Preparing the environment
To prepare a test environment you need (a) WebLogic server (tested on 10.3.6, but should work on >>9), and (b) JDeveloper >=12.1.3. After getting sources from github (https://github.com/rstyczynski/ClassLoaderTest), opening and compiling the project perform the following actions:
1. copy ResourceA/aaaFile1.jar to DOMAIN/lib
2. copy ResourceZ/zzzFile2.jar to DOMAIN/lib
3. deploy ResourceA/aaaFile1.jar as shared library with deployment order 75
4. deploy ResourceZ/zzzFile2.jar as shared library with deployment order 50
5. deploy ClassLoaderTest application with deployment order 100

Verify that you have access to /console, /classLoaderTest, and /wls-cat on your WebLogic.

Scenarios

1. Default - system class loader has precedence
In default configuration, an application delegates class/resource load to parent class loader, who delegates to it's parent, up to the system class loader. Each requested class/resource is provided by the parent class loader, unless not found. Class not found exception, reported by the parent, lets current classloader lookup for a class. If not found, class load is done by a child class loader, down to an application. In default mode any class/resource existing in the application is overwritten by system level artifacts. 

Default class loader works in following order:

Resource: jvm >> system >> domain/lib >> app/lib >> app/lib/MANIFEST >> app/APP-INF/classes >> app/APP-INF/lib >> app/ejb >> app/ejb/MANIFEST >> app/war/META-INF/classes >> app/war/META-INF/lib

Classes: jvm >> system >> domain/lib >> app/lib >> app/lib/MANIFEST >> app/APP-INF/classes >> app/APP-INF/lib >> app/ejb >> app/ejb/MANIFEST >> app/war/META-INF/classes >> app/war/META-INF/lib

Open /classLoaderTest/analyzeWEB application and note that all WLS resources are loaded from weblogic.jar. Domain lib is used to provide file1.txt resource. It's interesting that java.lang.String reports no class loader and can not be loaded as resource. I do not know answer why. Open /classLoaderTest/analyzeEJB to notice that all classes and resources are loaded from the same place as for WEB module. Note that all class loaders are the same for both components with one small difference: WAR module has additional class loader (ChangeAwareClassLoader) on top of EJB.

Take a look at things reported by classLoaderTest/analyzeWEB:
1. Loaded resource - shows physical location of file used as source of resource
2. Loaded class - returns identifier of class loader . Note that hexadecimal identifier may be used to identify each class loader
3. Hierarchy of class loaders and context class loaders
4. Class loader finder - returns list of jars and directories used by application class loaders to locate classes/resources
5. Class path - class path reported by each class loader

Please note three elements of class path reported by class finder for EJB class loader:
1. /cache/EJBCompilerCache/1ivyb128s4rrp
2. /tmp/_WL_user/classloadertest1/t8c7sx/lib/adf-loc.jar
3. /tmp/_WL_user/classloadertest1/t8c7sx/lib/../adf

It's absolutely important that all three are at the from of EJB module. Interesting is that EAR application "steals" class loader from EJB module, by adding APP level paths at the front of EJB jar. This observation may be critical in some cases.

2. Application configured with prefer-application-packages turned on
To influence "parent first" class loading behavior WebLogic provides ability to filter class loadings. By using prefer-application-packages it's possible to block loading of certain classes from system level class loaders. It's done by FilteringClassLoader. In this step we checks side effect of  turning on "prefer-application-packages" option. Due to possibly implementation bug activation of filtering class loader blocks resource load by system class loaders. 

Add below lines to APP/META-INF/weblogic-application.xml

    <prefer-application-packages>
        <package-name>none.*</package-name>
    </prefer-application-packages>

Take a look at things reported by /classLoaderTest/analyzeWEB:
1. Resources are loaded from application level packages. Specifically from EJB package, what means that EJB has precedence over WAR. Note that weblogic.* package is taken anyway from system space - we have no weblogic.Server.class resource in application space. 
2. Classes are loaded as before - as filtering was done on non existing none.* package

Resource: app/lib >> app/lib/MANIFEST >> app/APP-INF/classes >> app/APP-INF/lib >> app/ejb >> app/ejb/MANIFEST >> app/war/META-INF/classes >> app/war/META-INF/lib

Classes: jvm >> system >> domain/lib >> app/lib >> app/lib/MANIFEST >> app/APP-INF/classes >> app/APP-INF/lib >> app/ejb >> app/ejb/MANIFEST >> app/war/META-INF/classes >> app/war/META-INF/lib

3. Application configured with prefer-application-packages configured for package(s)

Add below lines to APP/META-INF/weblogic-application.xml

    <prefer-application-packages>
        <package-name>commonj.work.*</package-name>
    </prefer-application-packages>

Take a look at things reported by classLoaderTest/analyzeWEB:
1. Resources are loaded from application level package. 
2. Resource common.work.Work is loaded from application level package. 
3. Class common.work.Work is loaded from application space.

4. Application configured with prefer-application-packages configured for package(s) and associated shared library

Add below lines to APP/META-INF/weblogic-application.xml

    <library-ref>
        <library-name>file1</library-name>
        <exact-match>true</exact-match>
    </library-ref>
    
    <prefer-application-packages>
        <package-name>commonj.work.*</package-name>
    </prefer-application-packages>

Take a look at things reported by classLoaderTest/analyzeWEB:
1. Resources are loaded from library level package. 
2. Resource common.work.Work is loaded from library level package. 
3. It's not possible to load class common.work.Work. WebLogic reports error: java.lang.ClassNotFoundException: Class bytes found but defineClass()failed for: 'commonj.work.Work'. Note that this class in both application and library.

4. Application configured with prefer-application-resources turned on

Add below lines to APP/META-INF/weblogic-application.xml

    <prefer-application-resources>
        <resource-name>none.*</resource-name>
    </prefer-application-resources>

Note that situation looks like a default behavior. Resources requires exact configuration.

4. Application configured with all prefer-application-resources 

Add below lines to APP/META-INF/weblogic-application.xml

    <prefer-application-resources>
        <resource-name>*</resource-name>
    </prefer-application-resources>

Deployment failed with error: "Error processing annotations" with nested exception: 

weblogic.j2ee.dd.xml.AnnotationProcessException: [EJB:015001]Unable to link class model.SessionEJBBean in Jar /home/oracle/Oracle/Middleware/user_projects/domains/SOA_domain/servers/AdminServer/tmp/_WL_user/classloadertest1/t8c7sx/ClassLoaderTest_Model_ejb.jar : java.lang.NoClassDefFoundError: java/lang/Object

It looks like filtering class loader is doing it's work well. Interesting is that deployment / annotations processing is done from level of application class loader. Sounds very good.

5. Application configured with configured list of prefer-application-resources

Add below lines to APP/META-INF/weblogic-application.xml

    <prefer-application-resources>
        <resource-name>commonj.work.*</resource-name>
        <resource-name>file1.txt</resource-name>
    </prefer-application-resources>

Take a look at things reported by classLoaderTest/analyzeWEB:
1. Selected resources are loaded from application level package. 
2. Not listed resources are provided by a system class path.
3. Class load order is not affected.

6. Application configured with configured list of prefer-application-resources and library

Add below lines to APP/META-INF/weblogic-application.xml

    <library-ref>
        <library-name>file1</library-name>
        <exact-match>true</exact-match>
    </library-ref>

    <prefer-application-resources>
        <resource-name>commonj.work.*</resource-name>
        <resource-name>file1.txt</resource-name>
    </prefer-application-resources>

Take a look at things reported by classLoaderTest/analyzeWEB:
1. Selected resources are loaded from library level package. 
2. Not listed resources are provided by system class path.
3. Class load order is not affected.

7. Application configured with configured list of prefer-application-resources, library, and prefer-web-inf-classes

Add below lines to APP/META-INF/weblogic-application.xml

    <library-ref>
        <library-name>file1</library-name>
        <exact-match>true</exact-match>
    </library-ref>

    <prefer-application-resources>
        <resource-name>commonj.work.*</resource-name>
        <resource-name>file1.txt</resource-name>
    </prefer-application-resources>

Add below lines to WAR/WEB-INF/weblogic.xml

<container-descriptor>
    <prefer-web-inf-classes>true</prefer-web-inf-classes>
</container-descriptor>

Take a look at things reported by classLoaderTest/analyzeWEB:
1. Resources are loaded from web component
2. Resources non existing in web are taken from library
3. Classes are loaded from web component

Take a look at things reported by classLoaderTest/analyzeEJB:
1. Selected resources are loaded from library level package. 
2. Not listed resources are provided by system class path.
3. Class load order is not affected.

Such configuration shows how strong is configuration of prefer-web-inf-classes. It shows as well, that the application gets puzzled as both classes are resources are handled now in different way for WEB and EJB modules. Note that according to Oracle, neither prefer-application-packages nor prefer-application-resources can be specified when prefer-web-inf-classes is turned on in weblogic.xml.

8. Libraries in EAR /lib

Add below lines to APP/META-INF/weblogic-application.xml

    <library-ref>
        <library-name>file1</library-name>
        <exact-match>true</exact-match>
    </library-ref>

    <prefer-application-resources>
        <resource-name>commonj.work.*</resource-name>
        <resource-name>file1.txt</resource-name>
    </prefer-application-resources>

Add below lines to WAR/WEB-INF/weblogic.xml

<container-descriptor>
    <prefer-web-inf-classes>true</prefer-web-inf-classes>
</container-descriptor>

Execute following steps to reconfigure jar location:
1. open application deployment profile
2. select application assembly
3. click on ResourceA.jar/aaFile1
4. enter "lib" in "Path in EAR" field

Take a look at things reported by classLoaderTest/analyzeWEB:
1. Resources are loaded from web component
2. Resources non existing in web are taken from app/lib
3. Classes are loaded from web component

Take a look at things reported by classLoaderTest/analyzeEJB:
1. Selected resources are loaded from app/lib level package. 
2. Not listed resources are provided by system class path.
3. Class load order is not affected.

Check Class loader finder to find out that  app/lib has precedence before library

9. Libraries in EAR APP-INF/lib

Add below lines to APP/META-INF/weblogic-application.xml

    <library-ref>
        <library-name>file1</library-name>
        <exact-match>true</exact-match>
    </library-ref>

    <prefer-application-resources>
        <resource-name>commonj.work.*</resource-name>
        <resource-name>file1.txt</resource-name>
    </prefer-application-resources>

Add below lines to WAR/WEB-INF/weblogic.xml

<container-descriptor>
    <prefer-web-inf-classes>true</prefer-web-inf-classes>
</container-descriptor>

Execute following steps to reconfigure jar location:
1. open application deployment profile
2. select application assembly
3. click on ResourceA.jar/aaaFile1
4. enter "lib" in "Path in EAR" field
3. click on ResourceB.jar/zzzFile2
4. enter "APP-INF/lib" in "Path in EAR" field

Take a look at things reported by classLoaderTest/analyzeWEB:
1. Resources are loaded from web component
2. Resources non existing in web are taken from app/lib
3. Resources non existing in web, app/lib are taken from APP-INF/lib
4. Classes are loaded from web component

Take a look at things reported by classLoaderTest/analyzeEJB:
1. Selected resources are loaded from app/lib level package. 
2. Not listed resources are provided by system class path.
3. Class load order is not affected.

Check Class loader finder to find out that  APP/lib has precedence before app/lib and library.

10. Libraries in EAR root directory 
Putting jar packages in EAR root directory does not have any effect. It should be possible to use MANIFEST.MF/Class-Path: to configure use of those jars, however it does work only for JAR packages, not EAR ones. To make use of these JARs you should move them to APP-INF/lib - officially supported by WebLogic way to add third party libraries to app's classpath. There is one possibility leveraging JAR specification. EJB packages are standard jars, thus it's possible to specify Class-Path in JAR's MANIFEST.MF to append packages to class path right after the EJB.

Do the following:
1. Open model project - the one with EJB
2. Create file classpath.txt with below content. Add second empty line - it's critical for MANIFEST merge.

Class-Path: aaaFile1.jar

3. Configure adding if MANIFEST.MF in JAR deployment profile
4. Configure manifest merge - point to classpath.txt file
5. Generate JAR file. Verify that manifest contains Class-Path: line
6. Open application deployment profile
7. Select application assembly
8. Select ResourceA.jpr and aaaFile1 file
9. Empty "Path in EAR" field - jar will be located in EAR's root

Using /classLoaderTest/analyzeWEB check class loader finder to find out the right after /tmp/_WL_user/classloadertest1/t8c7sx/ClassLoaderTest_Model_ejb.jar there is added /tmp/_WL_user/classloadertest1/t8c7sx/aaaFile1.jar. This technique may be used to add third party classes used by the EJB module, however cannot be used to overwrite resources in the module.

11. Libraries in EAR root directory to be added as preferred
Another possibility to add libraries at the from of a root of EJB jar package it to use EAR/JAR/MANIFEST/ADF hack. It's not very clean solution, but may be used as, exactly this technique, is used by Oracle ADF framework. 

Do the following:
1. Add new project e.g. classpath
2. Create file classpath.txt with below content. Add second empty line - it's critical for MANIFEST merge.

Class-Path: ../zzzFile2.jar ../aaaFile1.jar

3. Configure adding if MANIFEST.MF in JAR deployment profile
4. Configure manifest merge - point to classpath.txt file
5. Generate JAR file. Verify that manifest contains Class-Path: line
6. Open application deployment profile
7. Select application assembly
8. Select Classpath.jpr and classpath file
9. Enter lib in Path in EAR field

After this you should see that both resources and classes are loaded from app/jar packages. In Classloader finder you will see something like this: /lib/../aaaFile1.jar. What a hack! It may be interesting that exactly such technique is used by a standard ADF packaging.

Classloader Analysis Tool
CAT tools is the very best tools to analyze class loader nuances. Sometimes CAT crashes on NPE on accessing application. Apart from this issue, if working, presents very interesting view on the class loader related things. One thing is presented in wrong way by CAT - order of files for certain class loader. It looks that CAT author has computed difference between class loaders' class paths, and during this process has sorted output. Tool prepared for this test, shows raw order of files, however does not show data for system level class loaders.

References
Source code: https://github.com/rstyczynski/ClassLoaderTest (JDeveloper >=12.1.3)

###


No comments:

Post a Comment