JBoss.org Community Documentation

4.5. Dynamic classloading

So far we have been using the extension and application classloaders to load all of the classes in our application. The application classpath was setup by the run.sh script using the -cp flag to include the current directory and the client-1.0.0.jar.

java -Djava.ext.dirs=`pwd`/lib -cp .:client-1.0.0.jar org.jboss.example.client.Client $1

For convenience the JARs in the lib directory were added to the extenion classloader's classpath using the java.ext.dirs system property as this prevents us from having to list the full path to each of the JARs after the -cp flag. Since the extension classloader is the parent of the application classloader our client classes can find all of the microcontainer classes together with the Human Resources service classes at runtime.

Note

If you are using Java 6+ then you can use a wildcard to include all JARs in a directory with the -cp flag.

java -cp `pwd`/lib/*:.:client-1.0.0.jar org.jboss.example.client.Client $1

This means that all of the classes in our application will be added to the application classloader's classpath and the extension classloader's classpath will retain its default value.

This is all well and good but what happens if we now want to deploy an additional service at runtime? If the new service is packaged in a JAR file then it needs to be visible to a classloader before any of its classes can be loaded. The trouble is we have already setup the classpath for the application classloader (and extension classloader) on startup so we cannot easily add the url of the JAR. The same situation applies if the service classes are contained in a directory structure. Unless the top-level directory is located in the current directory (which is on the application classpath) then the classes will not be found by the application classloader.

It's also possible that we may wish to redeploy an existing service with changes to some of its classes. Since it is forbidden for an existing classloader to reload classes due to security constraints how can this be done?

What we need is the ability to create a new classloader that knows the location of the new service's classes, or that can load new versions of an existing service's classes, so that we can deploy the service's beans. JBoss Microcontainer allows us to do this using the <classloader> element in the deployment descriptor.

The client-cl distribution in the commandLineClient/target/client-cl.dir directory contains the following files that demonstrate how this works:

run.sh
client-1.0.0.jar
jboss-beans.xml
lib/concurrent-1.3.4.jar
   /jboss-common-core-2.0.4.GA.jar
   /jboss-common-core-2.2.1.GA.jar
   /jboss-common-logging-log4j-2.0.4.GA.jar
   /jboss-common-logging-spi-2.0.4.GA.jar
   /jboss-container-2.0.0.Beta6.jar
   /jboss-dependency-2.0.0.Beta6.jar
   /jboss-kernel-2.0.0.Beta6.jar
   /jbossxb-2.0.0.CR4.jar
   /log4j-1.2.14.jar
   /xercesImpl-2.7.1.jar
otherLib/humanResourcesService-1.0.0.jar

As you can see the humanResourcesService.jar file has been moved to a new subdirectory called otherLib. In this location it is no longer available to either the extension or application classloaders whose classpaths are setup in the run.sh script:

java -Djava.ext.dirs=`pwd`/lib -cp .:client-1.0.0.jar org.jboss.example.client.Client $1

We must therefore create a new classloader during the deployment of the service so that we can load in the service classes and create instances of the beans. You can see how this is done by looking at the contents of the jboss-beans.xml file:


<?xml version="1.0" encoding="UTF-8"?>

<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>
    <!-- <property name="hiringFreeze">true</property>
    <property name="salaryStrategy"><inject bean="AgeBasedSalary"/></property> -->
  </bean>

   <!-- <bean name="AgeBasedSalary" class="org.jboss.example.service.util.AgeBasedSalaryStrategy">
    <property name="minSalary">1000</property>
    <property name="maxSalary">80000</property>
  </bean>

  <bean name="LocationBasedSalary" class="org.jboss.example.service.util.LocationBasedSalaryStrategy">
    <property name="minSalary">2000</property>
    <property name="maxSalary">90000</property>
  </bean> -->

</deployment>

First of all we create an instance of java.net.URL called URL using parameter injection in the constructor to specify the location of the humanResourcesService.jar file on our local filesystem. Then we create an instance of a URLClassLoader by injecting the URL bean into the constructor as the only element in an array. Once this is done then we simply include a <classloader> element in our HRService bean definition and inject the customCL bean. This specifies that the HRManager class needs to be loaded by the customCL classloader.

But how do we know which classloader to use for the other beans in the deployment?

The answer is that all beans in the deployment use the current thread's context classloader. In our case the thread that handles deployment is the main thread of the application which has its context classloader set to the application classloader on startup. If you wish, you can choose to specify a different classloader for the entire deployment using a <classloader> element as follows:


<?xml version="1.0" encoding="UTF-8"?>

<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">

  <classloader><inject bean="customCL"/></classloader>

  <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>

  ...

</deployment>

This would be necessary for example if you wished to reconfigure the service by uncommenting the AgeBasedSalary or LocationBasedSalary beans. As you might expect, classloaders specified at the bean level override the deployment level classloader and if you wish to ignore the deployment level classloader altogether, and use the default classloader for a bean, then you can use the <null/> value as follows:


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

Warning

If you decide to create a new classloader for your service using the deployment descriptor then be aware that you may not be able to access classes loaded by it from the application classloader anymore. In our example this means that the client will no longer be able to cache a direct reference to the bean instance when using the microcontainer controller. You can see this for yourself by starting the client using the run.sh command and then trying to deploy the service. You should see that a java.lang.NoClassDefFoundError exception is thrown and the application will then exit.

You must therefore use the bus to access the service indirectly and provide access to any classes shared by the client in the application classpath. In our example this means the Address, Employee, and SalaryStrategy classes.