We know that when loading a class, we will delegate up level by level to determine whether it has been loaded. Start the class loader from user-defined class loader - application class loader - extended class loader - start the class loader. If the class is not loaded in the end, go back and load your own class.
Parental entrustment has one drawback:
Can't delegate down, can't delegate without
How to break the parental delegation mechanism: (i.e. can delegate downward or not)
Custom class loader (no delegation)
spi mechanism (delegation down)
Using spi to break parental delegation
What is SPI?
SPI, the full name of Service Provider Interface, is a set of interfaces provided by Java to be implemented or extended by third parties. It can be used to enable framework extension and replace components. The role of SPI is to find service implementations for these extended API s.
Scenarios used by SPI
API (Application Programming Interface) in most cases, the implementer formulates the interface and completes the implementation of the interface. The caller only depends on the interface call and has no right to choose different implementations. In terms of users, API is directly used by application developers.
SPI (Service Provider Interface) is an interface specification formulated by the caller and provided to the external implementation. When calling, the caller selects the external implementation he needs. In terms of users, SPI is used by framework extension personnel.
Because in some cases, the parent class loader needs to entrust the child class loader to load the class file. Due to the limitation of loading range, the parent loader cannot load the required file.
Take the Driver interface as an example. The Driver manager is loaded through Bootstrap ClassLoader, while com.mysql.jdbc.Driver is loaded through Application ClassLoader. Due to the parent delegation model, the parent loader cannot get the class loaded by the child loader. At this time, you need to start the class loader to delegate subclasses to load the Driver implementation, thus breaking the parental delegation.
Write an SPI case
Define an interface HelloSpi
package com.shendu.spi; public interface HelloSpi { public String getName(); }
Define two implementation classes, HelloSpiImpl01 and HelloSpiImpl02
public class HelloSpiImpl01 implements HelloSpi { @Override public String getName() { return "helloSpiImp01"; } } public class HelloSpiImpl02 implements HelloSpi { @Override public String getName() { return "helloSpiImp02"; } }
Define the full path file of the interface in META-INF\services \ as com.shendu.spi.HelloSpi, and write the full path of the implementation class in the file
com.shendu.spi.HelloSpiImpl01 com.shendu.spi.HelloSpiImpl02
Define test class as
public class SPITest { public static void main(String[] args) { ServiceLoader<HelloSpi> helloSpis = ServiceLoader.load(HelloSpi.class); for (HelloSpi hello : helloSpis) { System.out.println(hello.getName()); } } }
Printout:
helloSpiImp01 helloSpiImp02 Process finished with exit code 0
Here is just a small case to experience the charm of SPI. It allows the parent class to entrust the child class to load classes that are not in its own directory, thus breaking the conventional parent delegation mechanism.
The famous dubbo is enhanced based on SPI native. In the future, the author will give a source level SPI explanation. Please look forward to it.
Override the loadClass() method to break the parental delegation
Let's continue to review the source code of the loader method
//Working process source code of parent delegation model protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{ // First, check if the class has already been loaded Class c = findLoadedClass(name); if (c == null) { try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { //The parent class loader could not complete the class load request } if (c == null) { //Class loading by child loader c = findClass(name); } } if (resolve) { //Judge whether the link process is required, and pass in the parameters resolveClass(c); } return c; }
This method is the core method of parent delegation. We only need to override this method and add our own loading logic to break the parent delegation.
Case of overriding loadClass method
1 define custom loader
package com.shendu; import sun.misc.PerfCounter; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.channels.WritableByteChannel; public class MyClassLoader02 extends ClassLoader { private String classPath; public MyClassLoader02(String classPath) { this.classPath = classPath; } protected Class<?> findClass(String name) throws ClassNotFoundException { File file = new File(classPath); try { byte[] bytes = getClassBytes(file); //The defineClass method can convert a file composed of binary stream bytes into a java.lang.Class Class<?> c = this.defineClass(name, bytes, 0, bytes.length); return c; } catch (Exception e) { e.printStackTrace(); } return super.findClass(name); } private byte[] getClassBytes(File file) throws IOException { FileInputStream fis = new FileInputStream(file); FileChannel fc = fis.getChannel(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); WritableByteChannel wbc = Channels.newChannel(baos); ByteBuffer by = ByteBuffer.allocate(1024); while (true) { int i = fc.read(by); if (i == 0 || i == -1) break; by.flip(); wbc.write(by); by.clear(); } fis.close(); return baos.toByteArray(); } @Override public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { //Key point: This is to add my own logic. As long as the classes under com.shendu are loaded through my custom loader if (name.startsWith("com.shendu")) { c = findClass(name); } else { c = this.getParent().loadClass(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats PerfCounter.getParentDelegationTime().addTime(t1 - t0); PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } } }
Define user class
package com.shendu; public class User { private String name ; private Integer age ; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
Define test class
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { //Instantiate custom loader MyClassLoader02 myClassLoader = new MyClassLoader02("D:\\tcl_ouyang\\demo_coding\\jvmclassload\\target\\classes\\com\\shendu\\User.class"); //Load user class Class<?> user = myClassLoader.loadClass("com.shendu.User"); //Assign a value to the user attribute through reflection Method setName = user.getMethod("setName", String.class); Method setAge = user.getMethod("setAge", Integer.class); Object o = user.newInstance(); setName.invoke(o, "shendu"); setAge.invoke(o, 18); System.out.println(o); System.out.println(o.getClass().getClassLoader()); }
Print as follows:
User{name='shendu', age=18} com.shendu.MyClassLoader02@4554617c
From the results, it is indeed loaded with our custom class loader.