JCL is a configurable, dynamic and extensible custom classloader that loads java classes directly from Jar files and other sources. The motivation was to create isolated classloaders, which can be easily integrated with IoC frameworks like Spring and with web applications.
The entire library, including it’s code-base and documentation is available under the terms and conditions of Apache License 2.0.
To use JCL, download and build the JCL project and put jcl(-core-2.x).jar and dependencies in the application’s classpath. See the build section below and also check out the downloads section for more details on the available builds and source.
JCL is also available in Maven Central Repository, which makes it very easy to use in maven projects as a dependency.
JCL is a multi-module maven project that has four modules as shown below:
JCL
|---> core
|---> spring
|---> web
|---> test-jcl
|---> test-web
The test-* modules create resources to run the unit tests and a sample web application. The core module builds an artifact with all the main JCL class files. The spring module generates an artifact for spring integration.
To build JCL, check-out the source code of JCL2 from github and run the following command:
mvn clean install
This command will create jcl-core and jcl-spring artifacts, and will also create a sample war file to test JCL web integration. It will also copy the dependencies of each artifact in the dependecies folder.
JCL requires JDK 1.5 or later; and will pull in all its dependencies using maven.
Below is a little tutorial on JCL v2 with examples. Please also see the overview section for installation.
Version 2.x now deprecates version 1.x so we recommend users to switch to version 2, which was a complete refactoring of version 1 and has a better design with a lot more features. Following is a list of some updates and changes in v2.x:
- Total refactoring of version 1
- Maven is being used for project management instead of ANT
- Added more robust spring and web support
- Added OSGi boot loading
- JCL 2 is more customizable and configurable
- etc. etc.
JCL is a light weight API and has only a few but useful classes. Here is a simple example on how to programmatically use JCL. JarClassLoader has an arguments-constructor and add methods that take jar-file/class-folder paths, URLs and InputStreams.
JarClassLoader jcl = new JarClassLoader();
//Loading classes from different sources
jcl.add("myjar.jar");
jcl.add(new URL("http://myserver.com/myjar.jar"));
jcl.add(new FileInputStream("myotherjar.jar"));
jcl.add("myclassfolder/");
//Recursively load all jar files in the folder/sub-folder(s)
jcl.add("myjarlib/");
JclObjectFactory factory = JclObjectFactory.getInstance();
//Create object of loaded class
Object obj = factory.create(jcl, "mypack.MyClass");
Now we can use reflection to invoke methods of this object. It is also possible to create object proxies and call methods the normal way, this is done by using cast(able) methods available in JclUtils class explained later.
In order to access the created JarClassLoader instance from any where in the application a JclContext must be created. The DefaultContextLoader provides a way to create this context for a single programmatically created JarClassLoader instance.
JarClassLoader jcl = new JarClassLoader();
jcl.add("myjarlib/");
DefaultContextLoader context=new DefaultContextLoader(jcl);
context.loadContext();
Now “jcl” can be accessed from anywhere in the application as follows.
JarClassLoader jcl=JclContext.get(); // returns the Default JCL instance
It is also possible to create an XML configuration file for JCL and use XmlContextLoader to create the context.
One of the motivations behind JCL was to be able to create multiple isolated classloaders in a single web application hosted on application servers like JBoss, Tomcat etc. This is achieved with JclContextLoaderListener.
The JCL configuration is put in a XML file and then the JclContextLoaderListener is used to load the context. Below is an example of JCL XML configuration.
Note: as of v2.6 jcl-web dependency is required for the following.
<?xml version="1.0" encoding="UTF-8"?>
<jcl-context>
<jcl name="jcl1">
<loaders>
<loader name="jcl.parent">
<order>3</order>
<enabled>true</enabled>
</loader>
<loader name="jcl.local">
<order>1</order>
<enabled>true</enabled>
</loader>
<loader name="jcl.current">
<enabled>false</enabled>
</loader>
<loader name="jcl.thread">
<enabled>true</enabled>
</loader>
<loader name="jcl.system">
<enabled>true</enabled>
</loader>
<loader name="jcl.bootosgi">
<enabled>true</enabled>
<strict>true</strict>
<bootDelegation>mypack.system.*</bootDelegation>
</loader>
<loader name="custom" class="mypack.MyLoader">
<order>2</order>
<enabled>true</enabled>
</loader>
</loaders>
<sources>
<source>webapp:WEB-INF/mylib/myjar.jar</source>
<source>webapp:myotherjar.jar</source>
</sources>
</jcl>
<jcl name="jcl2">
<sources>
<source>webapp:WEB-INF/myjarlib/</source>
</sources>
</jcl>
</jcl-context>
In this example two classloaders are created in a single context. The source paths starting with webapp: are treated as paths to internal web application jar files and folders. After this configuration, the context can be loaded in the web application by adding the JclContextLoaderListener in the application’s web.xml file.
<context-param>
<param-name>jcl-context</param-name>
<param-value>
classpath:jcl.xml
</param-value>
</context-param>
<listener>
<listener-class>org.xeustechnologies.jcl.web.JclContextLoaderListener</listener-class>
</listener>
The JCL instances can then be accessed by name from anywhere in the web application as shown below.
JarClassLoader jcl1=JclContext.get("jcl1");
JarClassLoader jcl2=JclContext.get("jcl2");
The JCL project also includes a sample web application that demonstrates JCL web-app integration. The jcltest.war file is created in the test-web module at jcl-project/test-web/target when the project is built. The content of the war file is also under the same module and is located in jcl-project/test-web/src/main folder. The exploded war folder contains all the files that clearly shows the usage of JCL in web applications.
After deploying the sample web application, go to http://server-name:8080/jcltest; this will print “Hello World” on the screen with classloader details. This message is returned from a JCL-loaded object, see jcl-project/test-web/src/main/jcltest.war/index.jsp for more details.
JCL can be used with spring framework. JCL v2 provides Spring XML extension to load beans via JCL. The JCL bean is created using the new jcl:jcl element and spring beans can reference the jcl bean by using the jcl:jcl-ref element as shown in the example below. These elements are defined in the jcl-schema.xsd. This also works well in a web application using the Spring ContextLoaderListener. In addition to this JCL v2 also supports the older way of Spring integration, please see the spring-test.xml file provided with the source.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jcl="http://www.xeustechnologies.org/schema/jcl"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.xeustechnologies.org/schema/jcl http://www.xeustechnologies.org/schema/jcl/jcl.xsd">
<jcl:jcl id="jcl1">
<constructor-arg>
<list>
<value>target/test-jcl.jar</value>
</list>
</constructor-arg>
</jcl:jcl>
<jcl:jcl id="jcl2">
<constructor-arg>
<list>
<value>target/test-jcl.jar</value>
</list>
</constructor-arg>
</jcl:jcl>
<bean id="test1" class="org.xeustechnologies.jcl.test.Test">
<jcl:jcl-ref ref="jcl1" />
<constructor-arg ref="name"/>
<property name="lastName">
<value>Zafar</value>
</property>
</bean>
<bean id="name" class="java.lang.String">
<jcl:jcl-ref ref="jcl2" />
<constructor-arg>
<value>Kamran</value>
</constructor-arg>
</bean>
</beans>
Java does not allow casting objects of types loaded in a different classloader; such casting attempt results in a ClassCastException. But sometimes it is necessary to cast objects to obtain interface references loaded in the current classloader. E.g. suppose that an API implementation is loaded using JCL and the API itself is loaded in the current classloader, now it is easy to use the interface reference to invoke methods than using reflection. JCL provides a few ways to obtain interface references and to convert the JCL-loaded objects into castable objects. This is actually done by internally creating proxies for the JCL-loaded objects. JCL, as of version 2.2, can also create cglib dynamic proxies apart from the regular jdk proxies, which makes it possible to create proxies for any class not just interfaces. It is also possible to create auto-proxies/castable-objects from the object factory, which is very handy because then the objects can be used and casted the normal way.
In version 2.2 and later, the JclObjectFactory can be used to auto-create “castable” objects, by passing true to the getInstance method. Now every time an object is created, it can be casted to references in the current classloader without the need of JclUtils. By default JCL2 uses jdk proxies but that can be changed by specifying the proxy provider, as shown in the example below.
JarClassLoader jcl = new JarClassLoader();
jcl.add("myapi-impl.jar"); //Load jar file
// Set default to cglib (from version 2.2.1)
ProxyProviderFactory.setDefaultProxyProvider( new CglibProxyProvider() );
//Create a factory of castable objects/proxies
JclObjectFactory factory = JclObjectFactory.getInstance(true);
//Create and cast object of loaded class
MyInterface mi = (MyInterface) factory.create(jcl,"myapi.impl.MyInterfaceImpl");
Auto proxying can also be dynamically enabled using a jvm command line argument:
-Djcl.autoProxy=true
By default the object factory creates objects of classes loaded in JCL without proxies. So in order to cast/clone the objects, JclUtils class is used as shown in the example below:
JarClassLoader jcl = new JarClassLoader();
jcl.add("myapi-impl.jar"); //Load jar file
//Create default factory
JclObjectFactory factory = JclObjectFactory.getInstance();
//Create object of loaded class
Object obj = factory.create(jcl,"myapi.impl.MyInterfaceImpl");
//Obtain interface reference in the current classloader
MyInterface mi = JclUtils.cast(obj, MyInterface.class);
//Convert the object into a castable object in the current classloader (jdk proxy)
MyInterface mi1 = (MyInterface) JclUtils.toCastable(obj, MyInterface.class);
//Clone "Serializable" object into a castable object in the current classloader
//The JCL-loaded object must implement Serializable
//This method is now deprecated and the use of deepClone is recommended
MyInterface mi2 = (MyInterface) JclUtils.clone(obj);
//Clone any object Serializable and non-Serializable
MyInterface mi2 = (MyInterface) JclUtils.deepClone(obj);
JCL v2 by default looks for classes in five places in order, local JCL class loader (order=1), current class loader (order=2), parent class loader (order=3), in thread context class loader (order=4) and then in system class loader (order=5). There is a sixth class loader available for Osgi boot delegation; it is described later in the document. In addition to built-in loaders, more class loaders can be added by extending the org.xeustechnologies.jcl.ProxyClassLoader class. Each loader has a loading order and the default loading orders can be changed. Below is an example that shows how to do this:
JarClassLoader jcl=new JarClassLoader();
jcl.add("myjar.jar"); // Add some class source
jcl.getSystemLoader().setOrder(1); // Look in system class loader first
jcl.getLocalLoader().setOrder(2); // if not found look in local class loader
jcl.getParentLoader().setOrder(3); // if not found look in parent class loader
jcl.getThreadLoader().setOrder(4); // if not found look in thread context class loader
jcl.getCurrentLoader().setOrder(5); // if not found look in current class loader
// A custom class loader that extends org.xeustechnologies.jcl.ProxyClassLoader
MyLoader loader=new MyLoader();
loader.setOrder(6);
jcl.addLoader(loader); //Add custom loader
JCL also provides a way to enable/disable classloaders. This can be done both programmetically and by passing a JVM -D arg.
JarClassLoader jcl=new JarClassLoader();
jcl.add("myjar.jar"); // Add some class source
//Disable parent class loader
jcl.getParentLoader().setEnabled(false);
Similarly a -D argument can be passed to the VM; this is done by passing a boolean values to the loader’s class name.
-Dorg.xeustechnologies.jcl.AbstractClassLoader$ParentLoader=false\
-Dmypack.MyLoader=false
Sometimes it is required to delegate some classes to the parent loader; this is part of the OSGi Spec for boot delegation. This can be enabled by passing the following argument to the JVM.
-Dosgi.bootdelegation=true
And as spec’d, you can also pass classes and packages as a JVM argument
-Dorg.osgi.framework.bootdelegation=mypack.*
By default the OSGi boot delegation is not strict and if the class is not found in the parent classloader then JCL will try to find it in other loaders. OSGi boot delegation can be made strict and a ClassNotFoundException is thrown if the class is not found in parent.
-Dosgi.bootdelegation.strict=true
Note: The order of OSGi boot loader cannot be changed. The osgi boot delegation can also be turned on programetically, please see the LoadTest.java provided with the source.
Some other JCL tweaks.
JCL by default ignore class collisions and the first class that is found gets loaded. It is also possible to throw exception on collision, this is done as follows:
-Djcl.suppressCollisionException=false
JCL by default ignores all missing jars/class sources. This default behaviour can be changes both programmatically and on runtime:
-Djcl.suppressMissingResourceException=false
JCL uses an isolated log4j logger, which sometimes spits out a lot of lines on the console. The default log level has now been lowered to INFO but to disable it entirely and use the parent log config, pass the following argument to JVM. Version 2.3 and above does not use Log4J.
-Djcl.isolateLogging=false
<dependency>
<groupId>org.xeustechnologies</groupId>
<artifactId>jcl-core</artifactId>
<version>2.6</version>
</dependency>