Understanding Classloaders: log4j in a J2EE Environment

A previous article of
mine explained the basics of log4j. log4j is an open source logging tool
developed under the Jakarta Apache project. The previous article demonstrated
how to use log4j in a strictly JSP/servlet environment, which forms half of the
whole J2EE world. The other half, EJBs, requires a subtler way of handling your
log4j code and configuration. This article will show you why this is the case
and how to go about it.

Brief Synopsis

log4j is a popular logging tool, as it provides flexible control over logging
and debugging requirements of a Java project. It is hierarchical in nature and
provides runtime control over all aspects of logging without having to change
the source code.

log4j controls logging with three main properties: loggers, appenders, and
layouts. A logger logs to an appender in a particular
layout (style). These can be specified using an external
configuration file, which is the best way of doing so. These configuration
properties are loaded during your application startup and can be changed at
runtime.

The steps involved in using log4j are:

1. Write a configuration file. In this file:

Specify the level of the root logger and attach an appender to it.

Specify the properties of the appender.

Specify a layout for the appender.

2. In your code, acquire a logger by class or name. Typically, this should be
the logger associated with the current class.

3. Start logging using any of the methods of the logger you acquired in step 2
(log.debug(), log.info(), log.warn(),
log.error(), log.fatal()).

Setup

The examples in this article are tested on BEA WebLogic 7.0 SP2 demo
version. The reason for using WebLogic instead of an open source equivalent
like JBoss is because WebLogic provides the hardest challenge in terms of
configuring log4j. It is also the most prevalent application server and the one
for which I received the most requests for help after my previous article.

This setup is by no means the recommended setup for WebLogic. It is only
intended as a testing environment towards configuring log4j in a J2EE
environment for this article.

WebLogic Setup

WebLogic 7.0 SP 2 can be downloaded from BEA's trial page. Select and download the installer with which you are most
comfortable. Note that you will need to register with BEA and that the
download size may be huge (approximately 150MB). Alternatively, you could ask
for a free demo CD to be sent to you.

Once you get hold of the installer, installing WebLogic is a simple process
of answering wizard-style questions. Choose the typical install for this
testing environment. When you reach the end of the install, you will be asked
if you want to configure a domain. Select "yes" to run the domain configuration
wizard. The first screen will ask you to select the domain type and name.
Select WLSDomain as the domain type and leave the name as
mydomain. In the next screen, leave the server type as single
server (standalone server). Use the default location in the next screen for
the domain location. On clicking "next," you will be taken to the standalone
server configuration. Leave all entries as they are and click "next." You will
now need to select a username and password for this domain. I used
admin as the username and password as the password
(Highly original!). Select "No" on the next screen for registering the service
as a Windows service (if you are running this on Windows). Finally, select "Yes"
to placing a shortcut on the Windows Start Menu. The final screen lets you
review everything that you have just done. Clicking the "create" button will
create this domain for you.

To run WebLogic for this domain, go to Start->All Programs->BEA
WebLogic Platform 7.0->User Projects->mydomain->Start Server.
This simply launches a Windows command file located in your BEA home directory
under user_projects\mydomain called startWebLogic.cmd. You will be asked for the username and password that you provided while running the domain configuration wizard.
You will know the server is running successfully when you see the message
"Server started in running mode." To exit the server, simply close this DOS
window.

Once your server is running successfully, fire up a browser window and
navigate to http://localhost:7001/console. This will bring up the console
window from where you can install and configure the applications to run on this
server. You will be asked for your username and password, as before. Once you
are in, you will see various administrative tasks that you can perform.

log4j Setup

As mentioned before, log4j can be downloaded from the log4j web site. Please refer to the previous article on how to download and install the log4j
binaries. We will leave the configuration of log4j until later in this
chapter.

Why is It Different? The Concept of Classloaders

Although a full and thorough discussion on classloaders is outside of the scope
of this article, I will try and explain what classloaders are and how they
impact our configuration of log4j in an application server.

Classloaders, as the name suggests, are responsible for loading classes
within the JVM. Before your class can be executed or accessed, it must become
available via a classloader. Given a class name, a classloader locates the
class and loads it into the JVM. Classloaders are Java classes themselves.
This brings the question: if classloaders are Java classes themselves, who or
what loads them?

When you execute a Java program (i.e., by typing java at a
command prompt), it executes and launches a native Java launcher. By native, I
mean native to your current platform and environment. This native Java
launcher contains a classloader called the bootstrap classloader. This
bootstrap classloader is native to your environment and is not written
in Java. The main function of the bootstrap classloader is to load the core
Java classes.

Figure 1; Classloader delegation hierarchy

The JVM implements two other classloaders by default. The bootstrap
classloader loads the extension and application classloaders into memory. Both
are written in Java. As mentioned before, the bootstrap classloader loads the
core Java classes (for example, classes from the java.util
package). The extension classloader loads classes that extend the core Java
classes (e.g., classes from the javax
packages, or the classes under the ext directory of your runtime).
The application classloader loads the classes that make up your
application.

All three default classloaders follow the delegation model. Before a child
classloader tries to locate a class, it delegates that task to a parent. When
your application requests a particular class, the application classloader
delegates this request to the extension classloader, which in turn delegates it
to the bootstrap classloader. If the class that you requested is a Java core
class, the bootstrap classloader will make the class available to you. However,
if it cannot find the class, the request returns to the extension classloader, and from
there to the application classloader itself. The idea is that each classloader
first looks up to its parent for a particular class. Only if the parent does
not have the class does the child classloader try to look it up itself.

In application servers, each separately-deployed web application and EJB
gets its own classloader (normally; this is certainly the case in WebLogic).
This classloader is derived from the application classloader and is responsible
for that particular EJB or web application. (Note that if an application is
deployed as an EAR file--a combination of EJB and webapps--it gets one
classloader, no more). This new classloader loads all classes that the webapp
or EJB require that are not already part the Java core classes or the
extension packages. It is also responsible for loading and unloading of
classes, a feature missing from the default classloaders. This feature helps
in hot deploy of applications.

When WebLogic starts up, it uses the Java-supplied application classloader
to load the classes that make up its runtime. It then launches individual
classloaders, derived from the Java application classloader, which load the
classes for individual applications. The individual classloaders are invisible
to the classloaders of the other applications; hence, classes loaded for one
particular application will not be seen by another application.

Figure 2: Individual application classloaders

What if you want to make a single class available to all applications?
Load it in a top-level classloader. This could be in the classpath of
WebLogic. When WebLogic starts, it will automatically load this class in
memory using the Java-supplied application classloader. All sub-application
classloaders get access to it. However, the negatives of this approach are
clear too. First, you lose the capability of hot deploy for this particular
class in all individual applications. Second, any change in this class means
that the server needs to be restarted, as there is no mechanism for a Java
application classloader to reload classes. You will need to weigh in the pros
and cons before you take this approach.

log4j is an external library to your J2EE application. Where do you store
this library? As mentioned in the previous paragraph, one option is in the
WebLogic startup classpath. However, this seems to be an easy way out, and is
not recommended for the reasons stated earlier. Configuration of log4j in a
J2EE environment is different because EJBs don't see the classes loaded by a
relevant webapp--EJBs are loaded by a different classloader! This is the
general case, unless you package your application to use the same classloader for loading your EJB as well as the webapp. I will illustrate this concept with
some examples.

We will start with deploying the original webapp from the previous article
along with an EJB and a JSP that exercises this EJB in our newly-configured
WebLogic server. The EJB is a very simple EJB, containing an even simpler method
that returns "Hello World" when invoked.

Example: Why Doesn't It Work?

Deploy the updated .war file and then the EJB .jar file for this example using the WebLogic console. When you try to install the EJB .jar file, you will get an error stating
that the org.apache.log4j.Logger classes cannot be found.

The EJB .jar file has no knowledge about the log4j classes in the
WEB-INF directory of the corresponding webapp, as the EJB and webapp use different classloaders. Even if you combine these two into an .ear file and try to deploy it, you will still get
the same error message. Why? Even though there is only one classloader for the
.ear file and it theoretically should be loaded when you deploy, we have not
told our EJB where to access the log4j.jar files.

Example: Why It Will Work

Here is an updated .ear file with a working example. Let me list the changes to make this example work:

I moved the log4j.jar file from the webapp's lib
directory into a top-level directory. This gives the .jar file the EJB's
visibility.

I modified the Manifest.mf for the EJB .jar to contain an entry
for Classpath, pointing to log4j.jar.

These changes are enough to make this .ear file deployable in WebLogic. When
you deploy this application using the console, you will not get any errors,
because our EJB now has access to the log4j classes. There is only one
classloader for the Log4JDemoEAR2 application. It is responsible for loading
the libraries and classes for this application as a whole. The
Classpath entry for the EJB is used to resolve dependencies and
load log4j.jar. The webapp still has access to this library, even
after moving it out of lib, as we use the same classloader!

Strategy For a J2EE Environment

Depending on your requirements, I suggest the following strategy. You will
realize that it is not just relevant for log4j, but to any external utility
classes that your application might require.

If the utility class is required only by your web application, keep it
under the WEB-INF/lib directory.

If it is required by both your web application and your EJB(s), make an
entry in your manifest file for the EJB and specify all of the utility classes on
the Classpath entry. Keep this utility file either at the top
level or under a top-level directory in your .ear file. This way, all of your EJBs
and .war files will have visibility to this utility class without you needing to
duplicate it.

Conclusion

J2EE packaging can be quite tiresome if you are not clear on how
classloading works. A good understanding of this "under the hood"
mechanism is critical to deploying your J2EE applications successfully. It
helps to have a deployment engineer specifically geared towards this task.

log4j works seamlessly in both aspects of a J2EE application. It just has to
be configured correctly. I hope this article has shown you the correct way.