![]() |
|
News Coverage
|
Java EE 5 Evolutive Java Applications
Evolutive Java Applications
Jan. 1, 2002 12:00 AM
For the past few years I've participated in several projects to update existing Java applications. While working on those projects I often wanted to be able to add new functionality to a class without recompiling it. Some of the reasons for this were:
Finally, I created Dynamic Java Binder (DJBinder), a tool that enables you to dynamically attach interface implementations to a class without changing its source file. The dynamically attached interfaces are equivalent to the interfaces listed after the "implements" keywords. DJBinder uses the class loading mechanism of the Java 2 platform to create the link between the classes and the interfaces at runtime.
How DJBinder Works Listing 1 shows all the definitions used in this example. Note: These classes have not been designed with the intention of using any dynamic mechanism. First I'll present the Java code you need to write to solve this problem using DJBinder, then I'll explain how DJBinder handles the internal details. The following statements write the person "p" data to the standard output using the Print interface:
Person p = ... ;
The DJBinder method that implements the Print interface without changing the Person class is a new abstract class named DI_Person__ Print:
public abstract class
The main line of the toStandard- Output() method is:
Person p= (Person) (Object)this;
Writing the age is a little more difficult because the age member has a protected visibility that forbids access from the DI_Person__Print class. The solution proposed by DJBinder is to create a new class with all the protected and private members of the Person class that may be accessed from the DI_Person__* classes. The name of this class must be DA_Person. For example, the following class authorizes access to the age member:
abstract class DA_Person{
public abstract class
DA_Person pp = (DA_Person) The mechanism implemented by DJBinder is similar to the inner classes of Java. The DI_* classes are similar to inner classes. The most important difference is that each DI_* class is defined in a different source file and can belong to a different JAR file. The fact that each DI_* class can be extended from another class offers an elegant way to implement the multiple heritage in Java without the problems of other languages such as C++. The code shown in Listing 2 compiles well, but at runtime there would be several errors if DJBinder didn't use the class loading mechanism to change the bytecode on the fly. The class loading mechanism works in the following way: every time the Java Virtual Machine needs a new class, it requests the current class loader to return the corresponding bytecode. For example, the default class loader returns the content of a file named className.class. This file is searched in the directories listed in the CLASSPATH environment variable. The DJBinder class loader works in a different way. First, it gets the original bytecode using the class loader that's normally used by the application, changes it, then returns the modified bytecode to the virtual machine. The DJBinder tool requires that the JVM uses the DJBinder class loader. For the console programs you simply need to replace the traditional command:
virtualMachinePath javaParameters
with
virtualMachinePath javaParameters
The amslib.djbinder.Start class runs your application using the DJBinder class loader. For other kinds of Java programs (applets, servlets, JSPs, etc.) there's a similar procedure based on the runtime environment characteristics. For example, if you run your Java application server using the amslib.djbinder.Start class, the beans and servlets become DJBinder aware and can use any interface that's dynamically implemented. The DJBinder class loader changes the original bytecode in four places:
1.The cast operations with an interface of target type If the same cast is done twice for the same object, the previous DI_* object is returned instead of creating a new object. This allows you to keep some information within the DI_* objects. In my example this bytecode change prevents the ClassCastException that should be thrown by the following statement of the PrintAllPersons class (see Listing 2): Print d= (Print)e.nextElement(); The object returned by the expression e.nextElement() is an instance of Person; normally it can't be cast to the Print interface, but DJBinder creates the corresponding DI_* object, which becomes the result of the cast operation.
2. The cast operations that have a class as target type In my example this bytecode change prevents the exceptions that should be thrown by the statements:
Person p= (Person) (Object)this;
3. The references to a DA_* class In my example this bytecode change enables you to access the protected age member of the Person class.
4. The instanceof and == operations Let's assume that the classes X, DI_X__I, and DI_X__J exist and the "v" variable refers to an object of type X, therefore the cast operations:
(I) v
do not throw an exception, and the operations:
v instanceof I
These changes create the illusion that the main object and the dynamic interface implementation objects are a single object. This simple fact makes programming a lot easier. Figure 1 shows the software architecture allowed by DJBinder. A final requirement of DJBinder is that the DI_* and DA_* classes must belong to the same package of the main class or to a subpackage named djbinder. For example, if the Person class belongs to the acme.applis package, the DI_Person__Print and DA_Person classes must belong to the acme.applis package or the acme applis.djbinder package. The djbinder subpackage allows you to use the DJBinder mechanism for classes that belong to a sealed package - a package that can't be modified. What do software developers and application administrators gain from using DJBinder? For developers the modification unit is usually the source file. Each source file has a creation and modification date. In large software projects with several developers it's necessary to establish a reservation mechanism to prevent two developers from changing the same file simultaneously, otherwise one of the modifications would be lost. DJBinder allows you to distribute the tasks and responsibilities among the developers better, so each developer can work on a well-defined set of functionalities (grouped within an interface). This feature facilitates the parallel work of several developers (increasing the concurrent engineering), thus reducing the duration of the project. For customers and administrators the modification unit is the executable file. In a Java application the executable files are the JAR files. DJBinder allows you to build JAR files associated with each functional modification. New functionalities become available when the corresponding JAR file is added to the runtime environment. The JAR files already delivered with the previous versions of the application don't have to be modified. This feature facilitates the administration and reassures customers who are afraid of regressions. A feature that can be easily designed using DJBinder is virtual typing - using a type that's different from the Java class name to look for a dynamic interface implementation. For example, you could assign the virtual type "French" to an instance of the Person class. In that case the Print interface could be implemented by the DI_French__Print class and include some extra fields. Different instances of the same Person class could have different virtual types. This extra flexibility is very useful, especially to communicate with legacy applications or legacy databases.
Conclusion
For more information about the class loading mechanism see http://java.sun.com/products/jdk/1.2/docs/api/ Reader Feedback: Page 1 of 1
Latest AJAXWorld RIA Stories
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||