Java custom ClassLoader loads external classes

Foreword nonsense

  A few days ago, I downloaded an open source project on GitHub and wanted to run it. Starting this project will be associated with some tables in the database. Therefore, if you want to run it, you need to build the database table. But this project involves dozens of tables, and the author did not give the SQL statement to create the table. (Follow-up: In vomiting blood, the author gave sql statements in the project...)

  If you want to use this project, you have to build the tables yourself, but you can't fix dozens of tables by yourself after a while. With this time, it is better to find a new project and give up this project decisively. As a result, after searching around, I found that there are really few open source Java projects of this type. Therefore, there is no way to return to this project. But the thought of manually creating dozens of tables makes the scalp numb. Can SQL be automatically generated? Generate sql statements based on Java objects, the general idea is still simple.

  The key to realizing this idea is to obtain the Java class object, use reflection to obtain the class name, and field information to generate table-building SQL based on this information; but this code can only be placed in this project for execution; if you want to separate this code, it will not work. For example, if I put this code in a new project and use Class.forName("") to get the class in the open source project, I can't get it. Because the project's native classLoader cannot load external classes; if you want to load an externally specified class, you can only customize the class loader ClassLoader;

  I have a little understanding of the class loading process before, and I know some nouns such as the parent delegation model and the stages of class loading. But just based on these, I don't know how to customize ClassLoader? So Baidu searched the code and modified it to run, and the code is very short, simpler than imagined. The simplest custom ClassLoader only requires us to specify which classes to load, and the rest is handed over to the parent class to load. Because it is relatively simple to customize a simple ClassLoader, I have the motivation to understand this knowledge. The following will briefly introduce the class loading process and the parental delegation model. (The content is from Chapter 7 of "In-depth Understanding of Java Virtual Machine (Fourth Edition) - Zhou Zhiming": Virtual Machine Class Loading Mechanism)

class life cycle


   The whole process of class loading includes: [Loading], [Verification], [Preparation], [Analysis], [Initialization];

Loading

  In the [loading] phase, the virtual machine needs to complete three things:

  • Obtain the binary byte stream through the fully qualified class name of the class;
  • Convert the static storage structure represented by the byte stream into the runtime structure of the method area;
  • Generate a Class object in memory;

  To put it simply, in the [loading] stage, the binary file of the class is loaded into the memory through the fully qualified class name, and a Class object is generated;

Verification

  The main purpose of this stage is to ensure that the information in the loaded binary stream meets the requirements of the current virtual machine and will not cause harm to itself. Pure Java code, for example, cannot access data outside the bounds of an array, cannot cast it to an unimplemented class, and the compiler will refuse to compile if it does.

  Class files are not necessarily compiled from Java source code, and can be generated by any means, including using a hexadecimal compiler to write directly to generate class files. At the bytecode level, what the Java source code cannot do can be achieved, at least semantically. If the virtual machine does not check the input byte stream, it is likely to cause a system crash due to loading harmful byte streams, so verification is an important task for the virtual machine to protect itself. Verification is mainly divided into 4 stages: file format verification, metadata verification, bytecode verification, symbol reference verification.

【File Format Verification】

  • Whether to start with the magic number 0xCAFEBABE;
  • Whether the major and minor version numbers are within the processing range of the current virtual machine; (This error has been encountered before, if a lower version of the virtual machine processes a higher version of the class file, an error will be reported);
  • Is there an unsupported type in the constant pool

There are many more items, so I won't list them one by one. This stage of verification ensures that the format conforms to the requirements of the Java type information.

【Metadata Verification】

  • Does this class have a parent class (except Object, other classes have parent classes);
  • Whether the parent class of this class inherits the class that is not allowed to be inherited (class modified by final);
  • If this class is not an abstract class, whether it implements the methods required to be implemented in the parent class or interface.
  • 。。。。。。

At this stage, semantic verification is performed on the metadata information to ensure that there are no metadata information that does not conform to Java semantics

【Bytecode Verification】

  • Ensure that the data type of the operand stack and the instruction code sequence can work together; [People's words: there will be no such situation: an int type data is placed in the operation stack, but it is loaded into the local variable according to the long type when used]
  • Ensure that type conversions in method bodies are valid;
  • 。。。。。。。。

If a method does not pass the bytecode verification, there must be a problem; but it is not necessarily safe to pass the verification. For example, an infinite recursion can cause the stack memory to overflow; this stage is mainly to check the method body to ensure that the method will not make security events that endanger the virtual machine during runtime.

【Symbol Reference Verification】

  • Whether the fully qualified class name described by the string in the symbol reference can find the class;
  • Accessibility of classes, fields, and methods in symbolic references: whether private, protected, public, and default may be accessed by the current class;
  • 。。。。。

The purpose of symbol reference verification is to ensure that the following [parsing] actions can be executed normally. If the reference verification fails, an exception will be thrown, such as: NoSuchFieldError, NoSuchMethodError, IllegalAccessError.... For the class loading mechanism of the virtual machine, the verification phase is a very important phase, but it is not necessarily necessary. If the running code has been repeatedly used and verified, then you can consider using the -Xverify:none parameter to turn off most class verification measures when deploying the project, so as to shorten the virtual machine class loading time.

Preparation

  The [Preparation] stage is the stage of formally allocating memory for class variables and setting the initial value of class variables . The memory used is all in the method area (jdk1.8 is the metadata area). First of all, memory allocation at this stage only includes class variables (variables modified by static) and does not include instance variables. Instance variables are allocated to the heap when the object is instantiated. Second, the initial value is usually the zero value of the data type. For example: public static int val = 111;the initial value is 0, not 111; if it is modified by finalpublic static final int val = 111; , then the val value is 111;

Resolution

  The [parsing] stage is the process in which the virtual machine replaces the symbolic references in the constant pool with direct references; symbolic references use a set of symbols to describe the target of the lock reference, and symbols can be literals in any form, as long as they can accurately locate the referenced target when used. The so-called symbol reference is just a symbol, which just tells the jvm which calling methods are needed for this class, which classes are referenced or inherited, and so on. But when the JVM uses these resources, only these symbols are not enough, and the addresses of these resources must be known in detail in order to correctly call related resources. A direct reference is a type of pointer that points directly to the target. The parsing process is to complete the process of converting symbolic references into direct references to facilitate subsequent resource calls. [ Quoted from blog garden ]

Initialization

  Initialization is the last process of class loading, and it is the stage of executing the construction method. At this stage, a block of memory will be allocated in the heap memory to instantiate the object;

class loader

  The binary byte stream of the class is obtained through the fully qualified class name, and the class loader is used to implement this action; there are three types of class loaders: Bootstrap ClassLoader, Extension ClassLoader [ ], and Application ClassLoader [ ] sun.misc.Launcher$ExtClassLoader) sun.misc.Launcher$AppClassLoader.

Parental Delegation Model

  Loading mechanism: first check whether it has been loaded –> if it is not loaded, call the loader of the parent class. If the parent class is empty, the loader of the startup class is called. If the parent class fails to load, it will be loaded by its own loader.

// ClassLoader类的loadClass

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
    
    
        synchronized (getClassLoadingLock(name)) {
    
    
            // 检查类是否被加载了,被加载了就直接返回class对象
            Class<?> c = findLoadedClass(name);
            if (c == null) {
    
    //没有被加载
                long t0 = System.nanoTime();
                try {
    
    
                    if (parent != null) {
    
    //父类加载器不为空,调用父类的加载器
                        c = parent.loadClass(name, false);
                    } else {
    
    //父类加载器为空,调用启动类的加载器
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
    
    
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

		//c == null ,说明父类没有加载成功,应该是抛异常了;调用自己的加载器;
                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
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
    
    
                resolveClass(c);
            }
            return c;
        }
    }

Two ways to customize ClassLoader

  What are the stages of the class loading process mentioned earlier? What are these stages doing? It can be seen that the class loading process is very complicated. There are two ways to customize ClassLoader. When customizing ClassLoader, you can decide whether to destroy the parental delegation model;

  • Inherit
      the extension class loader ExtClassLoader mentioned above from URLClassLoader, and the application class loader AppClassLoader is an inherited URLClassLoader; our custom ClassLoader can also inherit URLClassLoader, and then call the loadClass(String name) method of URLClassLoader to load the class. Specify the location of the class file when calling the loadClass method, and the loadClass method of URLClassLoader will complete the whole process of class loading, which will not destroy the parental delegation model. Because URLClassLoader does not have a loadClass method, this method inherits the loadClass method of ClassLoader; therefore, the model for loading classes is still the parent delegation model.
public class MyClassLoader  extends URLClassLoader {
    
    
    public MyClassLoader(URL[] urls){
    
    
        super(urls,getSystemClassLoader());
    }
}

Test class:

    @Test
    public void test() throws Exception {
    
    
        //指定加载的class文件路径;
        /**
         * 比如一个class文件的位置:
         * E:/ASD/SDF/GRY/WE/RT/TYU/com/org/entry/Account.class
         * Account类的类路径:com.org.entry.Account
         * 那么root = E:/ASD/SDF/GRY/WE/RT/TYU
         */
        String root  = "/E:/Project/IDEA/2022/javaSEtest/target/classes";
       //创建一个类加载器
        MyClassLoader classLoader = new MyClassLoader(new URL[]{
    
    new File("file:"+root).toURI().toURL()});
       //根据类名加载类
        Class bean = classLoader.loadClass("ioc.bean.BeanDefinition");//类全限定名;

    }

  Custom classloader code: GitHub

  • Inherit ClassLoader

  Inherit ClassLoader and rewrite the findClass method; use the defineClass method of ClassLoader to parse the binary bytecode into a Class object; in this way, when generating a Class object, the loadClass method of ClassLoader will be called first (you can debug in loadClass), so the parent delegation mechanism will not be destroyed. (If you want to destroy the parental delegation mechanism, rewrite the loadClass method, remove the logic of finding the parent class loader, and directly use the findClass method below to generate the Class object.)



public class MyClassLoader  extends  ClassLoader{
    
    


   private String classpath;
   public  MyClassLoader(String classpath){
    
    
            this.classpath = classpath;
    }

    @Override
    protected Class<?> findClass(String name)  {
    
    
        String path = name.replaceAll("\\.","/");
        try{
    
    
            byte[] classBytes = getClassBytes(classpath+"/"+path+".class");
            return defineClass(name,classBytes,0,classBytes.length);
        }catch (Throwable e){
    
    
            throw new RuntimeException("load class error:"+name);
        }
    }


    private byte[] getClassBytes(String path){
    
    
        try(InputStream fis = new FileInputStream(path); ByteArrayOutputStream classBytes = new ByteArrayOutputStream()){
    
    
            byte[] buffer = new byte[1024];
            int len = 0;
            while ((len = fis.read(buffer)) != -1) {
    
    
                classBytes.write(buffer, 0, len);
            }
            return classBytes.toByteArray();
        } catch (FileNotFoundException e) {
    
    
            e.printStackTrace();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
        return null;
    }
}

test

    @Test
    public void test() throws Exception{
    
    
       
        String root = "E:/Project/IDEA/spring-mini-0819/ioc-aop/target/classes";
        MyClassLoader  classLoader = new MyClassLoader (root);
        //要加载的类名
        Class<?> beanClass = classLoader.findClass("ioc.bean.BeanDefinition");
        Object o = beanClass.newInstance();
        System.out.println(o.getClass().getClassLoader());
    }


Guess you like

Origin blog.csdn.net/m0_37550986/article/details/127624209