Download Managing Class Names in Java Component Systems with Dynamic

Survey
yes no Was this document useful for you?
   Thank you for your participation!

* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project

Document related concepts
no text concepts found
Transcript
Managing Class Names in Java Component
Systems with Dynamic Update
Petr Hnětynka, Petr Tůma
Charles University
Department of Software Engineering
Distributed System Research Group
Malostranské náměstí 25,
118 00 Prague 1, Czech Republic
[email protected]
[email protected]
Abstract: This paper deals with class and interface name clashes in Java component
systems that occur because of evolutionary changes during the lifecycle of a
component application. We show that the standard facilities of the Java type system
do not provide a satisfactory way to deal with the name clashes, and present a solution
based on administering the names of classes and interfaces with a version identifier
using a byte code manipulation tool. The solution is demonstrated on a proof of
concept implementation.
Keywords: Components, Java, versioning, classes, interfaces, name clashes.
1
Introduction
The introduction of components into the software engineering process brings the
benefits of easier reuse and dynamic integration. Being elements of reuse and
integration, the components follow the typical lifecycle of applications [9] and are
therefore subject to evolutionary changes that may change both their internals and
their interfaces.
Because of features such as platform independence, type safety, garbage
collection, dynamic loading and others, both the component platforms and the
components themselves are often developed in Java [8]. The components are typically
sets of Java classes and interfaces, evolutionary changes to the internals and interfaces
of components thus require changes of the corresponding Java classes and interfaces.
Some evolutionary changes lead to a situation that requires different versions of a
class or an interface to coexist in one application. This is difficult to do in Java, where
two classes or interfaces with the same name cannot coexist unless loaded by
unrelated classloaders. In this paper, we describe how to deal with such a situation
without resorting to an elaborate naming scheme or an elaborate classloader
hierarchy, which we show to be worse than our solution.
2
Petr Hnětynka, Petr Tůma
In section 2, we detail the context in which the name clashes appear and explain why
some of the apparently straightforward solutions such as naming schemes or
classloader hierarchies do not work well. In section 3, we outline our approach to
avoiding the name clashes and illustrate it on a proof of concept implementation. In
section 4, we show how our approach differs from that of other Java component
systems. Section 5 concludes the paper.
2
2.1
Java Component Systems
Components, Classes, Interfaces
A software component is an independent unit in a sense that its interface, which
includes both the offered services and the context dependencies, is specified
explicitly. This facilitates an easy reuse of components without knowledge of their
internals. In contemporary Java component systems [2,6,13,15], the offered services
are represented by a set of interfaces that a component provides, and the context
dependencies are represented by a set of interfaces that a component requires. Given
the provided and required interfaces, a component is represented by a set of classes
that implement the provided interfaces and call the required interfaces.
Besides being implemented directly as a set of Java classes, a component can also
be composed from other components. We term these components composed, as
opposed to primitive. A composed component is created by connecting other
components through their provided and required interfaces. The connections can be
described in an architecture description language, which will then provide information
necessary for generating parts of the composed component implementation such as
component bindings and factories. The composition of components forms a
hierarchical structure that can be represented as a tree or, in a component system that
allows sharing of components, an acyclic graph.
2.2
Sources of Name Clashes
By a name clash, we understand a situation where a class or an interface cannot be
loaded by the Java virtual machine because its name is the same as a name of another
class or interface that has already been loaded. The name clashes that are of interest to
us are related to the evolutionary changes that can occur during the lifecycle of a
component application, namely changes to classes that make up the component and
changes to interfaces that make up the component.1
The first source of name clashes is a situation where the classes that make up a
component are replaced by a new version while the component application that uses
1
For sake of brevity, we omit the discussion of the likelihood that the situations described here
as sources of name clashes will occur in practice. For readers that will not find the
practicality of the situations evident, good arguments can be found in [1,9,11,14].
Managing Class Names in Java Component Systems with Dynamic Update
3
the component is running. This can lead to a name clash between the old and the new
version of the classes. As an example, consider a component with a class
LoggerImpl that is to be updated. 2 An attempt to replace this class by a new
version will cause a name clash because the Java virtual machine will refuse to load
the new LoggerImpl class after the old one has been loaded.
The second source of name clashes is a situation where the interfaces that make
up the component are replaced by a new version but the component application that
uses the component does not change and an adapter is used to bridge the mismatch.
This can lead to a name clash inside the adapter, which needs to access both the old
and the new version of the interfaces. As an example, consider a component with an
interface Log that is to be updated. An attempt to create an adapter between the old
and the new version will cause a name clash because both versions of the interface
will use the same name.
The third source of name clashes is a situation where a single Java virtual
machine is used to run several component applications to avoid redundant loading of
shared code. This can lead to a name clash when the component applications use
different versions of the same component.
2.3
Straightforward Solutions Do Not Work Well
Unique names: Probably the most straightforward solution that helps to avoid the
name clashes is to use unique names for each version of the classes and interfaces that
make up a component. The obvious disadvantage of this approach is the need to
change the names with each new version of the class and the interface. This
disadvantage is further aggravated when the change is not confined to the component
itself, but also needs to be made in the component application that uses the
component. The use of unique names also leads to using longer names with prefixes
or suffixes, which inconveniences the programmer.
Different classloaders: Another straightforward solution is to use different
classloaders to load classes and interfaces of different components. Each classloader
is associated with a namespace and can therefore load a class or an interface even
when another class or interface with the same name was already loaded by a different
classloader. Using a distinct classloader for each component will therefore avoid
name clashes because the names of classes and interfaces that make up each
component will belong to a different namespace. Unfortunately, this solution makes
the Java virtual machine see the interfaces of different components as incompatible
even when they have the same definition. This is a problem when the components are
to be connected to each other, because it is impossible to connect the incompatible
interfaces directly through Java references.
Java reflection: One way to avoid the problem of connecting seemingly incompatible
interfaces is to use the Java reflection API. The references used to connect the
interfaces can be passed as of the java.lang.Object type rather than their most
2
For more details on the components considered in the examples, see section 3.2.
4
Petr Hnětynka, Petr Tůma
derived type, and the Java reflection API can be used to invoke methods on the
interfaces. To invoke a method through the Java reflection API, an instance of the
java.lang.reflect.Method class must be obtained to represent the method,
and all arguments of the method that are of primitive types must be stored in
corresponding wrapper classes. Although the code to do this can be generated from
the interface description and therefore be no burden to the programmer, it slows down
the invocations considerably. Table 1 illustrates this by comparing the times of a
direct method invocation with the times of a method invocation through the Java
reflection API on a dual CPU 360MHz UltraSparc machine with Java 1.4.
no arguments
Direct invocation
Java reflection
11.22µs
872.4µs
single String
argument
11.22µs
803.3µs
five int
arguments
22.35µs
1643µs
Table 1. Times of method calls
Hierarchy of classloaders: Another way to avoid the problem of connecting
seemingly incompatible interfaces is to use a hierarchy of classloaders. A classloader
has exactly one parent, which is set during its creation and cannot be changed.
Typically, a classloader delegates requests to load a class or an interface to its parent
and only if the parent cannot load the class or the interface, the classloader tries to
load it by itself. Classloaders that share a parent therefore also share parts of their
associated namespaces.
In addition to using different classloaders to load classes and interfaces of
different components, the classloaders can be set up so that whenever two
components are to be connected to each other, the loading of their interfaces will be
delegated to the same parent classloader. The resulting hierarchy of classloaders will
mirror the hierarchy formed by the composition of components. This solution makes
the Java virtual machine see the interfaces of components that are to be connected to
each other as compatible, because they were loaded by the shared parent classloader.
Unfortunately, even this solution fails when the interfaces that make up a
component are replaced by a new version and an adapter is used to bridge the
mismatch between the component and the component application that uses the
component. The interfaces that make up the adapter necessarily belong to a single
namespace and because the adapter needs to access both the old and the new version
of the interfaces, a name clash will occur.
Modified Java virtual machine: Albeit not so straightforward, the third solution to be
mentioned is to modify the Java virtual machine. This solution is used by some
systems described in section 4. Without going into detail, we just note that this
solution requires the component application to run on a nonstandard Java virtual
machine and may interfere with mechanisms such as just in time compilation, which
makes it generally less flexible.
Managing Class Names in Java Component Systems with Dynamic Update
3
3.1
5
Removing Name Clashes in Byte Code
Byte Code Manipulation
The Unique names solution from section 2.3, which rested in using unique names for
each version of the classes and interfaces that make up a component, is attractive in
that it does not require changes to the Java virtual machine, does not incur
performance penalty, and works for all the sources of name clashes considered in
section 2.2. The problem rests with the convenience of its usage. Ideally, the
programmer should be able to use the same names for different versions of the classes
and interfaces, but the executable code of the component application should use
unique names for each version. A solution to this problem rests with byte code
manipulation of the executable code of the component application.
Several tools for byte code manipulation are available [3,4,5,17]. Usually, they
are used for performance optimizations, for extending functionality by intercepting
method invocations, or for generating code in adaptable systems. In our solution, the
byte code manipulation is used to augment the names of classes and interfaces that
make up a component by adding a unique version identifier, and to rename the
references to these classes and interfaces to use the augmented names. That way, the
programmer can use the same names for different versions of the classes and
interfaces in a convenient way, without running into name clashes.
With this solution, an associated problem appears when the standard classloader
of the Java virtual machine is used to load the classes or interfaces whose names were
augmented by the byte code manipulation. The standard classloader expects the files
that contain the classes and interfaces to be named the same as the classes and
interfaces themselves, which means that renaming and repackaging of the files is also
required. Alternatively, remote loading of classes from a version aware class server
can be used.
3.2
Proof of Concept
As a proof of concept, we have implemented the approach to avoid name clashes
through byte code manipulation in SOFA [15,7]. SOFA is a project of our research
group that provides a platform for component applications that supports a
construction of hierarchic components connected by potentially complex connectors.
The components are described in a component description language (CDL), which
can include a description of the component behavior by protocols [16]. The
components are fully versioned [12] and can be updated while the component
application that uses them is running.
In SOFA, the components are stored in a template repository together with the
necessary metadata that describe their classes and interfaces, including versions.
When running, the components reside inside a deployment dock, which provides the
necessary deployment and execution facilities. A single deployment dock can run
6
Petr Hnětynka, Petr Tůma
several component applications. Implemented in Java, it is therefore a platform where
all the sources of name clashes that were outlined in section 2.2 can occur.
When a component application is being launched in SOFA, the classes and
interfaces are loaded by the deployment dock using a single classloader. To obtain the
byte code of the classes and interfaces, the classloader contacts the template
repository, which acts as a class server for the deployment dock. The template
repository looks up the classes and interfaces by the names used in CDL. The names
of the classes and interfaces in the byte code are then augmented using the ASM tool
[3] and the augmented version is sent to the deployment dock. The augmentation is
done using the ASM tool [3]. The only exception to this mechanism is the case of
components that serve as adapters and therefore use several versions of the same
interface. In this case, the programmer has to use different names for different
versions of the interface simply to be able to write the adapter. The template
repository therefore requires an additional translation table that maps the names used
by the programmer to the augmented names.
The ASM tool uses the visitor pattern for byte code manipulation. The template
repository implements a class visitor with the visit, visitField,
visitMethod and visitInnerClass methods. The visit method is called
once for each visited class and augments the name of the class and the references to
the parent class and the implemented interfaces. The visitField, visitMethod
and visitInnerClass methods are called once for each field, method and inner
class of the visited class. The visitMethod method instantiates a method visitor to
modify the byte code of the visited methods.
The method visitor iterates through all instructions of a method and augments the
type names in all instructions that refer to type names. Similar to the class visitor, the
code visitor has a separate method for visiting related groups of instructions. The
instructions that need to be augmented are handled by the visitFieldInsn
(instructions for loading or storing a field of an object), visitLocalVariable (a
local variable declaration), visitMethodInsn (an instruction that invokes a
method), visitMultiANewArrayInsn (the multianewarray instruction),
visitTryCatchBlock (an exception handling block) and visitTypeInsn
(instructions that take a type descriptor as an argument) methods.
As an example, consider a primitive component of type Logger with a single
provided interface of type Log for logging events. The component has the following
CDL description:
struct Event { … };
interface Log {
void log (Event e);
};
frame Logger {
provides:
Managing Class Names in Java Component Systems with Dynamic Update
7
Log log;
};
architecture CUNI Logger implements Logger primitive;
As the CDL description is compiled, the symbols in it are assigned a unique version
identifier. In SOFA, a version identifier is globally unique, constructed from the name
of the SOFA node and a sequence number. In our example, let us presume that the
particular version identifier assigned to the symbols is nenya!0.
As a next step of the compilation, a Java mapping for the described types and a
component factory is generated. In our example, the generated interface is Log and
the generated classes are Event and LoggerBuilder.
When the CDL description is compiled and the mapping generated, the
programmer provides the implementation of the component. In our example, it is the
LoggerImpl class, which implements the Log interface. When writing the class,
the programmer uses the same names as in the CDL description. Here is a fragment of
the code of LoggerImpl:
public class LoggerImpl implements Log {
public void log (Event e) {
...
}
}
When the component is launched, the template repository augments the names of all
the classes and interfaces that make up the component by the version identifier, in this
example nenya_E_0. 3 If the programmer had to achieve the same result without
byte code manipulation, the fragment of the code of LoggerImpl above would have
to look like this:
public class LoggerImpl_nenya_E_0
implements Log_nenya_E_0 {
public void log (Event_nenya_E_0 e) {
...
}
}
To extend our example further, let us presume that during the lifecycle of the
component application that uses the Logger component, another method will be
added to the Log interface. The symbols in the CDL description of the new version
will be assigned a new version identifier nenya!1. When providing the new
implementation of the component, the programmer will simply add the new method to
the old LoggerImpl class, without modifying the names of the classes and
3
The exclamation mark in nenya!0 is replaced because it is not allowed in class names.
8
Petr Hnětynka, Petr Tůma
interfaces. When the new component will be launched, the template repository will
augment the names of all the classes and interfaces by the version identifier
nenya_E_1, which will make them different from the names used by the old
component and therefore avoid name clashes.
4
Related Work
Related to our work are the systems that address the need for coexistence of several
versions of a class or an interface in a single Java virtual machine, or the need to
dynamically update a Java application. One such system is described in [14], where
the authors provide a dynamic environment for distributed Java applications that
supports hosting multiple applications within a single JVM. The environment
manages classes and objects in distinct class and object spaces, which allows hosting
multiple applications without running into name clashes. The disadvantage of the
system is that the applications must communicate with each other using only the types
from the core of the system.
The Java Distributed Runtime Updating Management System (JDRUMS) [1]
allows updating Java classes at runtime. The name clashes are removed by using a
modified Java virtual machine that can load a new version of a class with the same
name as the old version. The first version of JDRUMS allows only limited changes of
classes and objects. The second version, JDRUMS 2, provides more options for
dynamic changes, but also brings security problems as during the update, it is possible
to retrieve secret information that is not available during normal program execution.
JDRUMS also disables the just in time compilation.
The Dynamic Java Classes (DJC) [11] also allow updating Java classes at
runtime. Again, the name clashes are removed by using a modified Java virtual
machine with a dynamic classloader that allows a class to be defined multiple times.
DJC also extend the Java security mechanism to cope with dynamic updates, and
disable the just in time compilation.
Many other Java component systems that exist today simply do not support the
dynamic updates or coexistence of several versions of a class or an interface with the
same name. One such system is the Enterprise Java Beans (EJB) framework from Sun
[6]. The components in EJB are called enterprise beans or beans for short. There is no
support for dynamic updates of beans in EJB.
Fractal [2] from ObjectWeb is a general software composition framework that
supports components. Fractal supports an explicit definition of provisions and
requirements of a component, composed components with a formal architecture
description, component bindings, component sharing and other features. Fractal itself
is an abstract framework and serves as a base for a reference implementation called
Julia. Fractal and Julia support dynamic updates through reconfiguration, which can
add new components, change bindings and remove old components, but cannot
handle dynamic updates that involve different versions of the same classes or
interfaces.
Managing Class Names in Java Component Systems with Dynamic Update
5
9
Conclusion
In this paper, we have pointed out the problem of name clashes that occur in Java
component systems because of evolutionary changes during the lifecycle of a
component application, and explained the sources of these name clashes. We have
shown that the standard facilities of the Java type system do not provide for a
satisfactory solution, and explained how solutions such as naming schemes or
classloader hierarchies may lead to performance penalties and inconvenience the
programmer.
We have proposed a solution to the problem of name clashes based on
administering the names of classes and interfaces with a version identifier using a
byte code manipulation tool. Through a proof of concept implementation, we have
also demonstrated that our solution integrates smoothly with a Java component
system. We have shown that our solution differs from and is superior to the solutions
used in contemporary Java component systems.
Acknowledgements
The authors would like to thank František Plášil, Vladimír Mencl and Jiří Adámek for
valuable comments. This work was partially supported by the Grant Agency of the
Czech Republic in grants number 102/03/0672 and 201/03/0911.
References
1.
Andersson, J.: A Deployment System for Pervasive Computing, Proceedings of the
International Conference on Software Maintenance (ICSM'00), 2000
2. Bruneton, E., Coupaye, T., Stefani, J. B.: The Fractal Composition Framework,
http://www.objectweb.org/fractal/current/Fractal1.0-0.pdf
3. Bruneton, E., Lenglet, R., Coupaye, T.: ASM: A code manipulation tool to
implement adaptable systems, http://www.objectweb.org/asm/current/asm-eng.pdf
4. Cohen, G. A., Chase, J. S., Kaminsky, D. L.: Automatic program transformation with
JOIE, USENIX 1998 Annual Technical Conference, New Orleans, Louisiana, USA,
1998
5. Dahm, M.: Byte Code Engineering, Proceedings JIT'99, Springer, 1999
6. DeMichiel, L. G., Yalcinalp, L. U., Krishnan, S.: Enterprise JavaBeans Specification,
Version 2.0, http://java.sun.com/products/ejb/docs.html
7. Distributed Systems Research Group: SOFA implementation in Java http://sofa.debian-sf.objectweb.org
8. Gosling, J., Joy, B., Steele, G., Bracha, G.: The Java Language Specification, Second
Edition, http://java.sun.com/docs/books/jls
9. Lehman, M. M., Ramil, J. F.: Software Evolution in the Age of Component Based
Software Engineering, IEE Proceedings Software, Special Issue on Component Based
Software Eng., v. 147, n. 6, Dec. 2000, pp. 249-255
10. Lindholm, T., Yellin, F.: The Java Virtual Machine Specification, Second Edition,
http://java.sun.com/docs/books/vmspec/index.html
10
Petr Hnětynka, Petr Tůma
11. Malabarba, S., Pandey, R., Gragg, J., Barr, E., Barnes, J. F.: Runtime support for
type-safe dynamic Java classes, Proceedings of the European Conference on ObjectOriented Programming, June 2000
12. Mencl, V., Hnětynka, P.: Managing Evolution of Component Specifications using a
Federation of Repositories, Tech. Report No. 2001/2, Dep. of SW Engineering,
Charles University, Prague, Jun 2001
13. Object Management Group: CORBA Components, v 3.0, OMG document formal/0206-65
14. Paal, S., Kammüller, R., Freisleben, B.: Customizable Deployment, Composition, and
Hosting of Distributed Java Applications, Proceedings of the International
Symposium on Distributed Objects and Applications (DOA 2002), Springer, 2002
15. Plášil, F., Bálek, D., Janeček, R.: SOFA/DCUP: Architecture for Component Trading
and Dynamic Updating, Proceedings of ICCDS'98, Annapolis, Maryland, USA, IEEE
CS Press, May 1998
16. Plášil, F., Višňovský, S.: Behavior Protocols for Software Components, IEEE
Transactions on Software Engineering, vol. 28, no. 11, Nov 2002
17. White, A.: Serp, http://serp.sourceforge.net