📜 ⬆️ ⬇️

Loading classes in java. Practice

This article is a continuation of the article Loading Classes in Java. Theory

The article describes the implementation of the framework of the application with plug-in modular architecture. The application engine will use the custom class loader that will load additional application plugins.

Application code does not pretend to originality, but only explains the approaches and principles of writing custom class loaders and methods for invoking dynamic code.

Motivation


')
Often, the architecture of complex systems involves the use of the mechanism of dynamic code loading. This is necessary when it is not known in advance which code will be executed in runtime. For example, the well-known game for programmers Robocode uses its own class loader to load custom tanks into the game. You can consider a separate tank as a module, developed in isolation from the application for a given interface. A similar situation is considered in the article, only at the most simplified level.

In addition, you can give a few obvious examples of the use of the mechanism of dynamic code loading. Suppose bytecode classes are stored in the database. Obviously, to load such classes you need a special loader, whose duties will also include the selection of the class code from the database.

Classes may need to be downloaded over the network / via the Internet. For such purposes, you need a bootloader capable of receiving bytecode using one of the network protocols. You can also select an existing URLClassLoader in the Java Class Library that can load classes at the specified path in the URL.

Training



The application implemented in the framework of the article will be a framework for dynamic code loading in the JRE and its execution. Each module will be a single Java class that implements the Module interface. A common interface for all modules is required for their invoking. Here, it is important to understand that there is another way to execute dynamic code - the Java Reflection API. However, for greater clarity and simplicity, a model with a common interface will be used.

When implementing custom loaders, it is important to remember the following:
1) any loader must explicitly or implicitly extend the java.lang.ClassLoader class;
2) any loader must support the delegation delegation model, forming a hierarchy;
3) the java.lang.ClassLoader class already implements the direct loading method - defineClass (...), which the byte code converts to java.lang.Class, validating it;
4) the recurrent search mechanism is also implemented in the java.lang.ClassLoader class and there is no need to take care of it;
5) for the correct implementation of the loader, it is enough just to override the findClass () method of the java.lang.ClassLoader class.

Let us consider in detail the behavior of the class loader when calling the loadClass () method to explain the last item in the above list.

The default implementation implies the following sequence of actions:
1) call findLoadedClass () to find the class being loaded in the cache;
2) if there is no class in the cache, the getParent (). LoadClass () call is made to delegate the loading right to the parent loader;
3) if the hierarchy of parent loaders could not load the class, a call to findClass () is called to directly load the class.

Therefore, in order to properly implement loaders, it is recommended to stick to the specified scenario - overriding the findClass () method.

Implementation



We define the interface modules. Let the module be loaded first (load), then executed (run), returning the result and then unloaded. This code is an API for developing modules. It can be compiled separately and packaged in * .jar for delivery separately from the main application.

public interface Module {

public static final int EXIT_SUCCESS = 0;
public static final int EXIT_FAILURE = 1;

public void load();
public int run();
public void unload();

}

* This source code was highlighted with Source Code Highlighter .


Consider the implementation of a module loader. This loader loads the code of classes from a specific directory, the path to which is specified in the pathtobin variable.
import java.io. File ;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class ModuleLoader extends ClassLoader {

/**
* .
*/
private String pathtobin;

public ModuleLoader( String pathtobin, ClassLoader parent) {
super(parent);
this .pathtobin = pathtobin;
}

@Override
public Class<?> findClass( String className) throws ClassNotFoundException {
try {
/**
* -
*/
byte b[] = fetchClassFromFS(pathtobin + className + ".class" );
return defineClass(className, b, 0, b.length);
} catch (FileNotFoundException ex) {
return super.findClass(className);
} catch (IOException ex) {
return super.findClass(className);
}

}

/**
* www.java-tips.org/java-se-tips/java.io/reading-a-file-into-a-byte-array.html
*/
private byte [] fetchClassFromFS( String path) throws FileNotFoundException, IOException {
InputStream is = new FileInputStream( new File (path));

// Get the size of the file
long length = new File (path).length();

if (length > Integer.MAX_VALUE) {
// File is too large
}

// Create the byte array to hold the data
byte [] bytes = new byte [( int )length];

// Read in the bytes
int offset = 0;
int numRead = 0;
while (offset < bytes.length
&& (numRead= is .read(bytes, offset, bytes.length-offset)) >= 0) {
offset += numRead;
}

// Ensure all the bytes have been read in
if (offset < bytes.length) {
throw new IOException( "Could not completely read file " +path);
}

// Close the input stream and return bytes
is .close();
return bytes;

}
}

* This source code was highlighted with Source Code Highlighter .


Now consider the implementation of the module loading engine. The directory with the modules (.class files) is specified as a parameter to the application.

import java.io. File ;

public class ModuleEngine {

public static void main( String args[]) {
String modulePath = args[0];
/**
* .
*/
ModuleLoader loader = new ModuleLoader(modulePath, ClassLoader.getSystemClassLoader());

/**
* .
*/
File dir = new File (modulePath);
String [] modules = dir.list();

/**
* .
*/
for ( String module: modules) {
try {
String moduleName = module.split( ".class" )[0];
Class clazz = loader.loadClass(moduleName);
Module execute = (Module) clazz.newInstance();

execute.load();
execute.run();
execute.unload();

} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}


}

}

* This source code was highlighted with Source Code Highlighter .


We implement the simplest module that simply prints to the standard output information about the stages of its execution. This can be done in a separate application by adding the path to the CLASSPATH to the compiled .jar file with the Module class (API).

public class ModulePrinter implements Module {

@Override
public void load() {
System. out .println( "Module " + this .getClass() + " loading ..." );
}

@Override
public int run() {
System. out .println( "Module " + this .getClass() + " running ..." );
return Module.EXIT_SUCCESS;
}

@Override
public void unload() {
System. out .println( "Module " + this .getClass() + " inloading ..." );
}
}

* This source code was highlighted with Source Code Highlighter .


By compiling this code, the result as a single class file can be copied to a separate directory, the path to which must be specified as a parameter of the main application.

Bit of irony


Dynamic code loading, an excellent and legal way to betray the responsibility of expanding the system to the user;)

Source: https://habr.com/ru/post/104229/


All Articles