JBoss.org Community Documentation

10.1. Defining classloaders

In order to create POJO instances we must first load the corresponding POJO classes into the JVM using a classloader. JBoss Microcontainer does this by using the classloader associated with the current thread of execution, i.e. the thread context classloader. But what do we mean by the current thread of execution and who sets its context classloader?

In all of our examples so far we have created applications that only use a single thread. This is the 'primordial' thread created by the main() function:


public static void main(String[] args) {}

As the only thread in the application (other than the garbage collecton thread which is created for every Java application) this executes all of the code including calling the deploy() method of our BasicXMLDeployer:


public class BasicXMLDeployer extends BasicKernelDeployer
{
    ...
    public KernelDeployment deploy(final URL url) throws Throwable
   {
       ...

        long start = System.currentTimeMillis();

        Unmarshaller unmarshaller = factory.newUnmarshaller();
        KernelDeployment deployment = (KernelDeployment) unmarshaller.unmarshal(url.toString(), resolver);
         if (deployment == null)
            throw new RuntimeException("The xml " + url + " is not well formed!");
        deployment.setName(url.toString());

        if (trace)
        {
            long now = System.currentTimeMillis();
            log.trace("Parsing " + url + " took " + (now-start) + " milliseconds");
        }

        deploy(deployment);

        if (trace)
        {
            long now = System.currentTimeMillis();
            log.trace("Deploying " + url + " took " + (now-start) + " milliseconds");
        }

        return deployment;
    }

    ...
}

The deploy() method takes a URL representing the location of an XML deployment descriptor and passes it as a string to an unmarshaller. The unmarshaller parses the XML, converts it into an object representation called a KernelDeployment and sets its name. The KernelDeployment object is then passed to the deploy() method of the superclass BasicKernelDeployer. This iterates through all of the beans in the deployment creating a context for each one which it subsequently passes to the contoller's install() method to peform the deployment. The controller then performs actions on each context to take the bean from one state to another: NOT_INSTALLED, DESCRIBED, CONFIGURED, INSTANTIATED, CREATE, START, INSTALLED. Once a bean reaches the INSTALLED state then it's considered to be deployed.

Important

During the deployment process, as the controller performs the Instantiate action, the thread context classloader is used to load the bean's class into the JVM. As all of the controller code is executed by the thread that calls controller.install() we refer to this as the current thread of execution.

Therefore when using the BasicXMLDeployer the classloader used to load bean classes is retrieved from the thread that calls the BasicXMLDeployer's deploy() method as this goes on to call controller.install() which subsequently executes the Instantiate action. For all of our examples this is the primordial thread whose context classloader is set on startup to the classloader that loaded the application, i.e. the application classloader. This means that in our examples the microcontainer can load any bean classes present on the application, extension or boot classpaths.

What happens though if we create our own threads in the application and use these to call the deploy() method of our BasicXMLDeployer?

If you create a thread then by default its context classloader will be set to the parent thread's context classloader. As any hierarchy of threads is ultimately rooted at the primordial thread this means that by default they will all use the application classloader. To change this you simply need to call setContextClassLoader() on a thread at runtime. This is typically done on the current thread using Thread.currentThread().setContextClassloader().

Note

If you choose not to use the BasicXMLDeployer and instead use the equivalent aspectized deployer then the classloader used to load bean classes is taken from the thread that calls the process() method of the MainDeployer. This is because the MainDeployer process() method iterates through all of the registered deployers calling their process() methods which in turn call controller.install() to perform the deployment.

Now that we know the default classloader comes from the current thread of execution, which if not set explicitly is set on startup to be the application classloader, how can we change it to load bean classes from other locations?

If you are using the BasicXMLDeployer or its equivalent aspectized deployer then you can define classloaders for entire deployments or individual beans by including <classloader> elements within the XML deployment descriptor. To specify a different classloader for all the beans in a deployment you need to include a <classloader> element above all of the <bean> elements:


<deployment>
    <classloader><inject bean="deploymentCL"/></classloader>

    <bean name="Bean1" ... >
        ...
    </bean>

    <bean name="Bean2" ... >
        ...
    </bean>

</deployment>

If instead you wish to override either the default classloader or a deployment classloader for a particular bean then you need to include a <classloader> element within the <bean> definition itself:


<deployment>
    <classloader><inject bean="deploymentCL"/></classloader>

    <bean name="Bean1" ... >
        <classloader><inject bean="beanCL"/></classloader>
        ...
    </bean>

    <bean name="Bean2" ... >
        ...
    </bean>

</deployment>

Finally you can choose to use the default classloader for a bean instead of the deployment classloader, if one is defined, by including a <classloader> element with a </null> value:


<deployment>
    <classloader><inject bean="deploymentCL"/></classloader>

    <bean name="Bean1" ... >
        <classloader><inject bean="beanCL"/></classloader>
        ...
    </bean>

    <bean name="Bean2" ... >
        <classloader></null></classloader>
        ...
    </bean>

</deployment>

The classloader beans that you inject must be deployed in the runtime environment before bean classes can be loaded. Typically this is done by also declaring them in the XML deployment descriptor:


<deployment xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="urn:jboss:bean-deployer:2.0 bean-deployer_2_0.xsd"
            xmlns="urn:jboss:bean-deployer:2.0">

    <bean name="URL" class="java.net.URL">
        <constructor>
       <parameter>file:/Users/newtonm/jbossmc/microcontainer/trunk/docs/examples/User_Guide/gettingStarted/commandLineClient/target/client-cl.dir/otherLib/humanResourcesService-1.0.0.jar</parameter>
        </constructor>
    </bean>

    <bean name="customCL" class="java.net.URLClassLoader">
        <constructor>
            <parameter>
                <array>
                    <inject bean="URL"/>
                </array>
            </parameter>
        </constructor>
    </bean>

    <bean name="HRService" class="org.jboss.example.service.HRManager">
        <classloader><inject bean="customCL"/></classloader>
        ...
</bean>

If a classloader is not available for a bean when an attempt is made to deploy it then the bean will remain in a pre INSTANTIATED state. Once the classloader is deployed then the bean will automatically continue to deploy. This means that you can define classloaders in the deployment descriptor after beans that depend on them and everything will deploy as expected.

Note

User defined classloaders are detected by the microcontainer during the deployment process as the controller performs the Instantiate action. They are used instead of the default classloader by calling setContextClassLoader() on the current thread. In this way they can be subsequently retrieved by code that loads the bean class using Thread.currentThread().getContextClassLoader(). After the bean class has been loaded the default classloader is put back by calling setContexClassLoader() again.