WebLogics Classloading Framework
WebLogic s Classloading Framework
A classloader is used by the JVM to locate and load Java classes into memory at runtime. Java classloaders define a hierarchy, a tree-like collection of parent and child classloaders. The root of this classloader hierarchy is the bootstrap classloader, which is created by the JVM for loading its internal classes and the java.* packages included within the JVM. An extensions classloader, which is a child of the bootstrap classloader, is used to load any JARs placed in the extensions directory of the JDK. This means that any JAR in the JDK's extensions directory may refer only to other classes in that directory, or to the JDK classes. Finally, the system classpath classloader, which is a child of the extensions classloader, is responsible for loading classes from the JVM's classpath. Any custom classloader created by an application, including WebLogic's classloaders, are all descendants of this system classpath classloader.
This classloader hierarchy plays an important role when locating and loading classes into memory. In general, classloaders try to reuse a preloaded version of a class. The hierarchy of classloaders intrinsically determines the scope from where the preloaded version of the class may be fetched. By default, child classloaders and classes loaded by these classloaders have direct access to their parent classloader and any classes loaded by it. This means that any classes loaded by a classloader are visible directly or indirectly, to all its descendants. Conversely, a parent classloader cannot see any class loaded by any of its child classloaders. In the same way, a classloader cannot access any classes loaded by a sibling classloader.
Given these visibility constraints, we can infer the general sequence of actions that occur when a classloader needs to load a particular class. First, the classloader checks to see whether the class isn't loaded already. If so, the in-memory version of the class is used. If the class hasn't been loaded already, the classloader delegates the request to its parent classloader. The parent classloader performs the same checks. If it hasn't loaded the class already, the request again is forwarded to its parent. In this way, each classloader may potentially delegate the request to its parent until it reaches the root of the hierarchy.
Only when a parent fails to load the class does the child classloader attempt to do so. This is an important point to remember: a child classloader loads a class only if its parent fails to load it. This means that even if both parent and child classloaders have access to a particular class, and the child classloader receives a request to load the class, it is the parent that actually loads the class. The application that triggered the request to load a class receives a ClassNotFoundException if neither the classloader nor any of its ancestors can locate the class. For this reason, you could say classloaders follow the delegation model when loading classes.
Let's reinforce this classloading mechanism with an example from WebLogic. Suppose you've included a library JAR in WebLogic's classpath, and also bundled it within a deployed EAR. WebLogic provides a separate classloader for the EAR, which is a child of the system classpath classloader. This means that the library JAR and all of the Java classes bundled with it are visible to both the system classpath classloader and the EAR's classloader. Now if a Java class within the EAR attempts to create an instance of the class located within the JAR (or invokes a static method on the class), then the class is loaded by its parent classloader i.e., the system classpath classloader, and not the EAR's classloader. Even though the class is visible to the EAR's classloader, it is the parent's version of the class that is always used. If you remove the library JAR from the system classpath, the parent classloader will fail to load the particular class, and in this case, the EAR classloader will locate and load the class.
Remember, we've just described the general behavior for Java classloaders. You can build custom Java classloaders that deviate from this default classloading scheme.
12.4.1 The Need for Custom Classloaders
In fact, WebLogic relies on a number of classloaders to support auto-deployment, dynamic class updates, and application redeployment. WebLogic needs to depend on custom classloaders because Java classloaders don't support any standard mechanism for unloading or replacing a set of classes. This makes it a little more awkward to build an application server that also must support dynamic updates of compiled classes, drop-in replacements for EJBs, and other modules, without redeploying the entire application server! The only way to get around this problem is to throw away the entire classloader and effectively dispense with all the Java classes it has loaded.
For this reason, WebLogic creates a number of classloaders for effective deployment. For instance, WebLogic creates a child classloader for each EAR that is deployed to the server. By doing this, it needs only to throw away the EAR's classloader when it has to reload and update the application classes in the EAR. In fact, depending on the structure of the enterprise application, WebLogic establishes a hierarchy of classloaders for each EAR. WebLogic can then prune (and rebuild) an entire branch of the classloader hierarchy rooted at the EAR's classloader when it needs to redeploy the EAR or parts of it. Of course, the EAR's redeployment doesn't affect any of the sibling classloaders of other deployed applications.
Let's now put all of this theory into practice and see how WebLogic implements its classloader hierarchy.
12.4.2 WebLogic's Classloaders
WebLogic's standard classloading framework needs to support two objectives:
- It must be able to isolate multiple applications deployed on a single server. This means that classes used by application A must never come in conflict with any classes used by application B. Furthermore, redeploying application A must have no effect on classes used by application B.
- Within an application, it must allow you to redeploy web applications without having to redeploy the EJBs.
WebLogic achieves these goals by creating a separate classloader hierarchy for each application deployed to the server. The parent of this hierarchy is the system classpath classloader, which is used to load the classes specified in WebLogic's classpath. An application generally is packaged in an EAR file, and everything within the EAR (servlets, JSPs, EJBs, etc.) is treated as being part of the same application. However, you also can deploy web applications (WARs), EJB modules (EJB JARs), and resource adapters (RARs) directly to WebLogic. In this case, if you deploy a web application (WAR) and EJB module (EJB JAR) independently, they both are considered to be two separate applications, so WebLogic establishes separate classloaders for both the WAR and the EJB module. If you deploy two EARs independently, again WebLogic creates separate classloaders for both applications.
By creating a separate classloader hierarchy for each application, classloaders associated with one application cannot see the classloaders or classes of another application, and because sibling classloaders are isolated from each other, this also isolates the applications.
WebLogic constructs a hierarchy of classloaders for each deployed application (EAR). The base application classloader loads all of the EJB JARs within the EAR. In addition, a child classloader is created for each web application (WAR) within the EAR. Figure 12-1 illustrates this classloading hierarchy.
Figure 12-1. WebLogic's classloader hierarchy
Thus, the base application classloader is a child of the system classloader. All EJBs within an EAR share the same classloader, whereas each web application acquires its own classloader. Moreover, the EJB classloader is the parent for all web application classloaders. This classloader hierarchy has the following consequences:
- Any classes and libraries referenced in WebLogic's classpath at startup are loaded by the system classpath classloader, so you cannot unload (or refresh) any of the classes referenced in WebLogic's system classpath while the server is running. For this reason, redeploying an application or any of its modules has no effect on any class referenced in WebLogic's classpath.
- Within the same EAR, each web application is isolated from every other web application. This allows you to redeploy a web application without affecting other web applications. In addition, you can redeploy a web application without affecting any of the deployed EJBs.
- All EJB modules share the same classloader, so in order to refresh a particular EJB module, you must redeploy all EJB JARs within the application. This means that WebLogic must effectively redeploy the whole EAR.
- JSP pages and servlets can access the EJB interfaces in their parent classloader. Because the EJB classloader is the parent for all web classloaders, you need not package EJB interfaces in any of the web applications. The delegation model ensures that if the class in the web application needs to invoke an EJB method, it can load the required EJB interfaces by passing the call to the parent EJB classloader.
- Each web application creates a separate classloader for each servlet and JSP page (actually, the compiled implementation class for the JSP). This servlet-specific classloader is a child of the web application classloader. This allows you to reload individual servlets and JSPs easily, without the need for redeploying the web application or affecting any of the EJBs.
We've just looked at the standard classloading scheme supported by WebLogic. Later, we shall see how you can override this and set up your own classloading hierarchy.
12.4.2.1 Overriding the parent delegation model
There is another twist in this tale. As explained in Chapter 2, you can configure a web application classloader so that it doesn't use the default parent delegation scheme by setting the prefer-web-inf-classes element to true in the weblogic.xml descriptor file. When this flag is enabled, the web application classloader instead tries to load classes from its WEB-INF/ directory before asking its parent classloader. For example, if you need to ensure that your web application uses its own version of an XML parser, you can package the parser classes in the WAR and enable the prefer-web-inf-classes option. In this case, any code within your web application will always use the version of the XML parser in your WAR.
You must be careful, though. If you try to pass an XML Document object, say, to an EJB, a ClassCastException will be thrown because the EJB classloader will load a different version of the Document class.
12.4.2.2 EJB redeployment
WebLogic 8.1 provides an additional redeployment capability for EJBs, which can be useful during development. If you set the enable-bean-class-redeploy element to true in the weblogic-ejb-jar.xml descriptor file, the bean implementation class is loaded in a child classloader of the EJB classloader. In other words, the EJB classloader loads all the classes (such as the interface files) except for the implementation class, which is loaded by a new child classloader. As a result, you will be able to redeploy the bean's implementation without redeploying the entire bean. For example, to refresh the implementation class, you need only execute something like the following:
weblogic.Deployer -name appname -redeploy com/oreilly/ejb/EJB.class
12.4.2.3 Deploying EJB modules and web applications as separate applications
If your application includes servlets and JSPs that make use of EJBs, we recommend deploying the web application and EJB components as part of the same application (EAR). If you instead deploy your web applications and EJB modules independently (i.e., not as part of an EAR), then they are treated as separate applications. This means that all web applications and EJB applications are assigned their own classloaders. As a result, your EJB classes are not visible to the servlets and JSPs because they are bound within the scope of their own classloaders. In this instance, if you want to provide servlets and JSPs with access to the EJBs, you need to package the EJB interfaces in the web application (WAR).
Earlier we saw how WebLogic optimizes the performance of method calls to "remote" objects if the objects are located in the same JVM, using pass-by-reference instead of the marshalling and unmarshalling that usually would occur. This same difference in the behavior of method calls is observed when packaging web applications and EJBs in the same EAR, as well as when deploying web applications and EJBs separately as independent applications. In the former case, if a servlet invokes an EJB method, WebLogic uses pass-by-reference semantics just like an ordinary Java method call. In the latter case, WebLogic uses traditional RMI semantics even though the web application and the EJB component live in the same JVM. The same behavior occurs when an EJB invokes a method on another EJB deployed as a separate application.
Thus, WebLogic's pass-by-reference optimization occurs only if the caller and callee belong to the same application. If WebLogic were to support pass-by-reference semantics, a ClassCastException would be generated when a parameter bound to a version of the class loaded in one classloader is assigned to another version of the same class in another classloader. To avoid this problem, WebLogic must use the traditional call-by-value semantics when making RMI calls between applications. For this reason, WebLogic cannot support pass-by-reference semantics when web applications and EJB components are deployed as separate applications.
|
12.4.2.4 Customizing the classloader hierarchy
WebLogic 8.1 lets you customize the default classloading scheme. You can determine your own classloading hierarchy, arranging the classloaders for the EJB and web application modules in any way you like. A custom classloading scheme gives you better control over which modules are reloadable and which classes are visible between modules. However, this flexibility is useful in limited cases for the following reasons:
- Any custom classloading scheme may not have more than three levels of nesting. This includes the base application classloader.
- Only web applications and EJB modules may participate in this custom classloading scheme.
- Servlet reloading is disabled for web application modules participating in a custom classloading scheme.
- If you set up a classloader for each EJB module, you lose WebLogic's call-by-reference RMI optimization, and the call-by-value semantics are used instead.
- Other application servers may not support custom classloader hierarchies. Thus, your application may break compatibility with other J2EE application servers by choosing to avoid the default classloading scheme.
- You may need to package your application in such a way that it can take advantage of the new classloading scheme.
Given these caveats, let's look at how you can create your own classloader hierarchy. The key lies in specifying a collection of nestable XML elements within the weblogic-application.xml descriptor file. These elements are, of course, optional, and if you don't specify them, the default classloader scheme is used. As an example, let's create the classloader hierarchy depicted in Figure 12-2.
Figure 12-2. A custom classloader hierarchy
As the figure illustrates, the top-level application classloader loads an EJB module and has two child classloaders. The first child classloader loads an EJB module, while the second classloader loads an EJB and web application module. This non-standard hierarchy for the enterprise application can be accomplished with the following XML definition:
EJB1.jar EJB2.jar EJB3.jar WAR1.war
This classloader structure has several implications, all of which follow from the earlier description of classloading:
- Because EJB2 is assigned its own classloader, you can redeploy EJB2 without redeploying the entire enterprise application. Ordinarily, this is prohibited because all EJBs are loaded by the application classloader. If you need to redeploy EJB1, you will have to redeploy the entire application.
- Because EJB2 and EJB3 are assigned their own classloaders, the two modules within the application are isolated from each other. The only way EJB2 can invoke EJB3 is if the home and remote interfaces of EJB3 are packaged within EJB2 (or EJB1). Likewise, WAR1 can invoke EJB2 only if the home and remote interfaces of EJB2 are available to the WAR classloader.
You can use standard techniques to overcome these visibility problems. For instance, our sample classloader hierarchy ensures that WAR1 can, by default, see and make use of EJB1. If WAR1 must be able to use EJB2, you must make its home and remote interfaces available to WAR1. An easy way to achieve this is to place the home and remote interfaces of the EJB within the WEB-INF/classes directory in WAR2 itself.