- Java custom ClassLoader – loading classes from an InputStream
- Saved searches
- Use saved searches to filter your results more quickly
- Amarnath510/CustomClassLoader
- Name already in use
- Sign In Required
- Launching GitHub Desktop
- Launching GitHub Desktop
- Launching Xcode
- Launching Visual Studio Code
- Latest commit
- Git stats
- Files
- README.md
- Создание собственного ClassLoader в Java
- Как загрузить Java класс на лету
- How to use custom ClassLoader to load jars in runtime
Java custom ClassLoader – loading classes from an InputStream
A ClassLoader in Java helps in dynamically loading Java classes into the JVM. Java comes with a many different types of ClassLoader(s). However, many a times we are required to develop our own custom ClassLoader. Let us check how we can do that. In the following example, we will develop a ClassLoader to load classes from an in-memory Stream without writing it to a file and using a URLClassLoader.
First step would be to actually create a jar say calculations-1.0.jar. We will load it in a stream and then load all its classes. The jar file will have the following 2 classes.
//Addition.java package com.techitmore.calculations; public class Addition < public static int add(int a, int b) < return a + b; >>
//Subtraction.java package com.techitmore.calculations; public class Subtraction < public static int subtract(int a, int b) < return a - b; >>
Let us now switch to the main program where we will load this jar and its classes. Since our aim is to write a custom ClassLoader for loading classes from an in-memory stream, we should first load this jar in a JarInputStream. In this example, we are using a FileInputStream to create a JarInputStream. However, we could have used any InputStream where we are getting the jar file contents from.
//Startup.java package com.techitmore.jarloaderexample; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.jar.JarInputStream; public class Startup < public static void main(String[] args) throws FileNotFoundException, IOException < String path = "../calculations/target/calculations-1.0.jar"; FileInputStream fileInputStream = new FileInputStream(path); JarInputStream jarInputStream = new JarInputStream(fileInputStream); >>
Next step is to focus on our custom ClassLoader say StreamClassLoader. The constructor of the ClassLoader will take a JarInputStream, loads its data and then create a map of the class names in the jar and its corresponding data as byte[]. This way it can load the data of any class contained in the stream.
//StreamClassLoader.java package com.techitmore.jarloaderexample; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; public class StreamClassLoader extends ClassLoader < private final MapclassData; public StreamClassLoader(JarInputStream jarInputStream) throws IOException < classData = new HashMap(); JarEntry jarEntry = null; while ((jarEntry = jarInputStream.getNextJarEntry()) != null) < String entryName = jarEntry.getName(); int entrySize = (int) jarEntry.getSize(); byte[] entryData = new byte[entrySize]; jarInputStream.read(entryData, 0, entrySize); if (entryName.endsWith(".class")) < String className = entryName.replace("/", ".").replace(".class", ""); classData.put(className, entryData); >> > >
Override the following 2 functions to load a class. So the trick is to use the defineClass function and pass it the class data that we have stored in our map corresponding to the class name asked for.
@Override public Class loadClass(String name) throws ClassNotFoundException < // note that it is required to first try loading the class using parent loader try < return super.loadClass(name); >catch (ClassNotFoundException e) < return findClass(name); >> @Override public Class findClass(String name) throws ClassNotFoundException < Class loadedClass = null; byte[] data = classData.getOrDefault(name, new byte[0]); if (data.length == 0) < throw new ClassNotFoundException(); >loadedClass = defineClass(name, data, 0, data.length, null); return loadedClass; >
Thus the complete StreamClassLoader would look like:
//StreamClassLoader.java package com.techitmore.jarloaderexample; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; public class StreamClassLoader extends ClassLoader < private final MapclassData; public StreamClassLoader(JarInputStream jarInputStream) throws IOException < classData = new HashMap(); JarEntry jarEntry = null; while ((jarEntry = jarInputStream.getNextJarEntry()) != null) < String entryName = jarEntry.getName(); int entrySize = (int) jarEntry.getSize(); byte[] entryData = new byte[entrySize]; jarInputStream.read(entryData, 0, entrySize); if (entryName.endsWith(".class")) < String className = entryName.replace("/", ".").replace(".class", ""); classData.put(className, entryData); >> > public String[] getAllClassNames() < Setkeyset = classData.keySet(); return keyset.toArray(new StringJava custom jar classloader); > @Override public Class loadClass(String name) throws ClassNotFoundException < // note that it is required to first try loading the class using parent loader try < return super.loadClass(name); >catch (ClassNotFoundException e) < return findClass(name); >> @Override public Class findClass(String name) throws ClassNotFoundException < Class loadedClass = null; byte[] data = classData.getOrDefault(name, new byte[0]); if (data.length == 0) < throw new ClassNotFoundException(); >loadedClass = defineClass(name, data, 0, data.length, null); return loadedClass; > >
The ClassLoader can be tested from our Startup class. The complete code for the Startup class is:
//Startup.java package com.techitmore.jarloaderexample; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.jar.JarInputStream; public class Startup < public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException < String path = "../calculations/target/calculations-1.0.jar"; FileInputStream fileInputStream = new FileInputStream(path); JarInputStream jarInputStream = new JarInputStream(fileInputStream); StreamClassLoader scl = new StreamClassLoader(jarInputStream); jarInputStream.close(); fileInputStream.close(); String[] classes = scl.getAllClassNames(); for (String cl : classes) < Class c = scl.loadClass(cl); if (c != null) < System.out.println("Successfully loaded " + cl); >> > >
Saved searches
Use saved searches to filter your results more quickly
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session. You switched accounts on another tab or window. Reload to refresh your session.
Custom Class Loader in Java
Amarnath510/CustomClassLoader
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Sign In Required
Please sign in to use Codespaces.
Launching GitHub Desktop
If nothing happens, download GitHub Desktop and try again.
Launching GitHub Desktop
If nothing happens, download GitHub Desktop and try again.
Launching Xcode
If nothing happens, download Xcode and try again.
Launching Visual Studio Code
Your codespace will open once ready.
There was a problem preparing your codespace, please try again.
Latest commit
Git stats
Files
Failed to load latest commit information.
README.md
Custom Class Loader Tutorial
Why do we need to write a Custom Class Loader?
- Default ClassLoaders in Java (Bootstrap, Extension, Application) can load files from local file system that is good enough for most of the cases.
- But if you are expecting a class at the runtime or from FTP server or via third party web service at the time of loading the class then you have to extend the existing class loader.
Custom Class Loader Implementation:
We need to override two methods
- getClass(String): Loads the class which we needed for our application by calling loadClassFileData(..) method.
public Class getClass(String name) throws ClassNotFoundException < String file = name.replace('.', File.separatorChar) + ".class"; byte[] buffer = null; try < // This will load the byte code data from file. buffer = loadClassFileData(file); Class c = defineClass(name, buffer, 0, buffer.length); resolveClass(c); return c; > catch (IOException ioe) < ioe.printStackTrace(); > return null; >
@Override public Class loadClass(String name) throws ClassNotFoundException < if (name.startsWith("com.cldemo")) < System.out.println("*** Loading Class with CustomClassLoader ***: " + name); return getClass(name); > System.out.println("*** Loading by using default class loaders. ***: " + name); return super.loadClass(name); >
Create two simple classes to test the Custom class loader.
public class Foo < static public void main(String args[]) throws Exception < System.out.println("Foo Constructor >>> " + args[0] + " " + args[1]); Bar bar = new Bar(args[0], args[1]); bar.printCL(); > public static void printCL() < System.out.println("Foo ClassLoader: "+Foo.class.getClassLoader()); > >
public class Bar < public Bar(String a, String b) < System.out.println("Bar Constructor >>> " + a + " " + b); > public void printCL() < System.out.println("Bar ClassLoader: "+Bar.class.getClassLoader()); > >
CustomClassLoader (Application start class)
public class CustomClassLoader < public static void main(String[] args) < try < String progClass = args[0]; String[] proArgs = new String[args.length - 1]; System.arraycopy(args, 1, proArgs, 0, proArgs.length); CCLoader ccl = new CCLoader(CustomClassLoader.class.getClassLoader()); Class clas = ccl.loadClass(progClass); Class[] mainArgsType = < (new String[0]).getClass() >; Method mainMethod = clas.getMethod("main", mainArgsType); Object[] argsArray = proArgs>; mainMethod.invoke(null, argsArray); > catch (Exception e) < e.printStackTrace(); > > >
Open Command Prompt under project root folder adn run the following commands,
> javac -cp . com/cldemo/Foo.java > javac -cp . com/cldemo/Bar.java > javac -cp . com/cldemo/CCLoader.java > javac -cp . com/cldemo/CustomClassLoader.java > java com/cldemo/CustomClassLoader com.cldemo.Foo 1212 1313
- Only classes in com.cldemo are loaded using our Custom class loader and remaining are loaded using default class loaders.
*** Loading Class with CustomClassLoader ***: com.cldemo.Foo *** Loading by using default class loaders ***: java.lang.Object *** Loading by using default class loaders ***: java.lang.String *** Loading by using default class loaders ***: java.lang.Exception *** Loading by using default class loaders ***: java.lang.System *** Loading by using default class loaders ***: java.lang.StringBuilder *** Loading by using default class loaders ***: java.io.PrintStream Foo Constructor >>> 1212 1313 *** Loading Class with CustomClassLoader ***: com.cldemo.Bar Bar Constructor >>> 1212 1313 *** Loading from default class loaders. ***: java.lang.Class Bar ClassLoader: com.cldemo.CCLoader@7852e922
Создание собственного ClassLoader в Java
Собственные загрузчики классов нужны для реализации рефлексии в Java.
Мы загружаем файл-класс «на лету» и исполняем его методы.
Как загрузить Java класс на лету
Чтобы загрузить Java класс нам нужен подготовленный файл с байт-кодом, имеющий расширение .class, однако в приведенном ниже примере никаких заготовок я не использую, и создаю класс так же — «на лету», используя Java компилятор (javac) и класс Runtime в коде программы.
import java.io.File; import java.io.FileNotFoundException; import java.lang.reflect.Method; import java.util.Properties; public class Main < public static void main(String[] args) throws Exception < //Получаем доступ ко всем Properties Properties p = System.getProperties(); //Получаем разделитель, используемый в операционной системе String sep = p.getProperty("file.separator"); //Получаем путь к папке JRE String jrePath = p.getProperty("java.home"); //Выполняем необходимые манипуляции для получения пути к файла javac (в моем случае javac.exe) int lastIndex = jrePath.lastIndexOf(sep); String javac = jrePath.substring(0, lastIndex) + sep + "bin" + sep + "javac"; if(p.getProperty("sun.desktop").equals("windows")) javac+=".exe"; else javac+=".sh"; //Проверяем, существует ли такой файл (javac.exe) File file = new File(javac); if(!file.isFile()) throw new FileNotFoundException("Компилятор по адресу "+ file.getAbsolutePath() +" недоступен "); System.out.println("Компилируем " + file.getAbsolutePath() + " " + file.getAbsolutePath()); //Запускаем компилятор javac, чтобы получить байт код внешнего класса Process p1 = Runtime.getRuntime().exec(javac+" "+file.getAbsolutePath()); //Если javac завершился с ошибкой, выбрасываем Exception (здесь он самописный) //т.к. мне необходимо было проверять синтаксис класса, который подключался. //Таким образом, если возникала ошибка компиляции, то процесс p1 мог вернуть текст //ошибки (поток байт) с помощью функции getErrorStream() if(p1.waitFor()!=0) try < throw new MyClassCompilationException("Ошибка компиляции", p1); >catch (MyClassCompilationException e) < e.printStackTrace(); return; >//Здесь мы уже имеем созданный файл с байт-кодом System.out.println("Компиляция завершена"); //Формируем абсолютный путь к файлу с байт-кодом int pointIndex = file.getAbsolutePath().lastIndexOf("."); String absulutePatch = file.getAbsolutePath().substring(0, pointIndex); //Объявляем MyClassLoader. Класс ClassLoader является абстрактным //поэтому необходимо его переопределить (каким образом, будет показано ниже) MyClassLoader loader = new MyClassLoader(); //Объявляем переменную типа Class. Class cl = loader.findClass(absulutePatch); System.out.println(cl); //Получаем метод m1 из загруженного класса Method method = cl.getMethod("m1", new Class[] ); System.out.println(method); //Выполняем метод m1. Нельзя забывать про метод newInstance(), если метод динамический. method.invoke(cl.newInstance(), new Object[]); //Выполняем метод m2. Он статический, поэтому newInstance() в методе invoke писать не надо Method method2 = cl.getMethod("m2", new Class[]); method2.invoke(cl, "QWERRTY"); > >
Загружаемый класс выглядит таким образом:
public class Hello < public void m1(String text, int c) < for(int i = 0; i < c; i++) < System.out.println(text + " Hi"+i); >> public static void m2(String text) < System.out.println(text + " from static method"); >>
Класс MyClassLoader будет загружать созданный класс в статический контекст, чтобы мы могли обращаться к его методам.
MyClassLoader имеет следующий вид:
import java.io.*; public class MyClassLoader extends ClassLoader < //Переопределяем метод findClass, которому надо передать путь к файлу с расширением .class @Override protected ClassfindClass(String name) throws ClassNotFoundException < //Проверяем, существует ли такой файл File f = new File(name+".class"); if(!f.isFile()) throw new ClassNotFoundException("Нет такого класса " + name); InputStream ins = null; try< //С помощью потока считываем файл в массив байт ins = new BufferedInputStream(new FileInputStream(f)); byte[]b = new byte[(int)f.length()]; ins.read(b); //С помощью функции defineClass загружаем класс Class c = defineClass("Hello", b, 0, b.length); return c; >catch (Exception e) < e.printStackTrace(); throw new ClassNotFoundException("Проблемы с байт кодом"); >finally < try < ins.close(); >catch (IOException e) < e.printStackTrace(); >> > >
С первом блоке кода, начиная со строки 54 мы начинаем работать с загруженным классом.
Результат работы приведенного выше кода выглядит следующим образом:
Здесь приведена загрузка простого класса с простыми методами, однако ничего не мешает загрузить туда более емкие классы.
Если будет нужно, опубликую, как взаимодействовать с внешним процессом так, чтобы получать от него сообщение об ошибке в виде потока байт.
В данном случае этим процессом является компилятор javac, который возвращает ошибку, если компилируемый код написан неправильно.
How to use custom ClassLoader to load jars in runtime
To load calsses in runtime java uses ClassLoader mechanism which is based on next core principles:
- delegation — by default uses parent-first delegation , — child ClassLoader will be used if parent is not able to find or load class. This behavior can be changed to child-first by overwriting ClassLoader.loadClass(. ) ;
- visibility — child ClassLoader is able to see all the classes loaded by parent but vice-versa is not true;
- uniqueness — allows to load a class exactly once, which is basically achieved by delegation and ensures that child ClassLoader doesn’t reload the class already loaded by parent;
The main scenarios to use custom ClassLoader is:
- Class Instrumentation — modifying classes at runtime. For example, to unit testing, debugging or monitoring;
- Isolation of executions — isolate several execution environments within a single process by making visible only a subset of classes for a particular thread, like it does in EE environments;
So, let’s see how using of custom ClassLoader looks from source code perspective:
List jars = Arrays.asList(new File("/tmp/jars").listFiles()); URL[] urls = new URL[files.size()]; for (int i = 0; i < jars.size(); i++) < try < urls[i] = jars.get(i).toURI().toURL(); >catch (Exception e) < e.printStackTrace(); >> URLClassLoader childClassLoader = new URLClassLoader(urls, ClassLoader.getSystemClassLoader());
Then load class with custom ClassLoader:
Class.forName("org.kostenko.examples.core.classloader.ClassLoaderTest", true , childClassLoader);
Note! If your loaded libraries uses some resources like properties or something else, you need to provide context class loader:
Thread.currentThread().setContextClassLoader(childClassLoader);
Also, you can use custom ClassLoaders to load services with Java Service Provider Interface(SPI)
ServiceLoader serviceLoader = ServiceLoader.load(MyProvider.class, childClassLoader); .