Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.
EclipseLink/Development/Dynamic/Design DynamicClassCreation
Dynamic Persistence Design: Class Creation
This page describes the design in dynamic persistence for the creation of persistent classes without having source.
Background
EclipseLink requires a concrete Java class class with the following capabilities to be a persistent entity:
- Ability to uniquely identify by type: entity.gteClass() must be unique for a ClassDescriptor in a given session
- Ability to create instances of the persistent type durig reading, cloning, and new instance creation (UnitOfWork.newInstance())
- Ability to retrieve and store values from the persistent entity using the mapping's AttributeAccessor
- Ability to do type conversions to align the value coming from a data store with the value stored in the object. It uses the AttributeAccessor's classification type for this purpose.
Dynamic persistence allows an application to use EclipseLink's persistence capabilities without requiring a static java class definition (.class file) to exist on the classpath at runtime. It does this using ASM to generate the byte-codes for a 'dynamic' class at runtime and convert these byte-codes into a class that it uses as if it were a static class. The EclipseLink runtime requires some special configuration to deal with the above requirements but in general it treats these dynamic classes no differently then it would a static class.
Design
In order to create a Java class at runtime without Java source code, the use of a custom ClassLoader is required, along with a bytecode manipulation framework (such as ASM or some other library).
Java classloaders form an instance-hierarchy at run-time, with the system (Bootstrap, Extension and System) class loaders
strictly controlled by the JVM. Once an application is launched (via an Application loader), a new loader MyCustomClassLoader
can
be added to the chain.
The basic implementation pattern is as follows - in the constructor, the new instance of MyCustomClassLoader
is added to the
runtime instance-hierarchy by calling super
with the parent loader.
public class MyCustomClassLoader extends ClassLoader { public MyCustomClassLoader (ClassLoader parent) { super(parent); } @Override protected Class<?> findClass(String className) throws ClassNotFoundException { if (some_condition) { try { byte[] bytes = use_framework_to_generate_bytecode(); return defineClass(className, bytes, 0, bytes.length); } catch (ClassFormatError cfe) { throw new ClassNotFoundException(className, cfe); } } return super.findClass(className); } }
The findClass
method is overridden so that if some condition is met, the bytecode for the Class className
is
generated; otherwise, the call is delegated up the instance-hierarchy to search for the class. The implementation hierarchy is responsible
for maintaining a cache of classes, as well as any resources that have been loaded (XML descriptor files, image files, etc). Thus, an instance
of MyCustomClassLoader
behaves as a 'proper' class loader in all cases, with the additional capability that non-existent
classes can be built/found without their corresponding .class files being on the JVM's classpath.
NB: it is important to note that two separate instances of MyCustomClassLoader
can generate two classes
for className
. Even though the bytecode is identical, the classes are distinct. When running under a Java EE™ container
(or an OSGi environment), the class loader hierarchy may be much more complicated, but the point remains - a custom classloader is
required to build classes at runtime; the custom classloader is part of a chain of loaders and each dynamic class is distinct.
It is thus incumbent upon the designer to ensure that the correct loader is used throughout the lifecycle of the application.