zoukankan      html  css  js  c++  java
  • java(web安全)--Classloader

    什么是ClassLoader?

    我们知道Java程序在Java虚拟机(JVM)上运行。当我们编译Java类时,JVM将创建字节码,该字节码与平台和机器无关。字节码存储在.class文件中。当我们尝试使用一个类时,ClassLoader将其加载到内存中。
    

    内置的ClassLoader类型

    Java内置了三种类型的内置ClassLoader。

    1.Bootstrap Class Loader 加载JDK内部类。它加载rt.jar和其他核心类,例如java.lang。*包类。
    2.Extensions Class Loader 它从JDK扩展目录(通常为$ JAVA_HOME / lib / ext目录)中加载类。
    3.System Class Loader该类加载器从当前类路径加载类。我们可以在使用-cp或-classpath命令行选项调用程序时设置classpath。
    

     ClassLoader层次结构

    ClassLoader在将类加载到内存中是分层的。每当提出加载类的请求时,它都会将其委托给父类加载器。这就是在运行时环境中保持唯一性的方式。如果父类加载器找不到该类,则该类加载器本身将尝试加载该类。
    让我们通过执行以下java程序来了解这一点。
    public class ClassLoaderTest {
    
        public static void main(String[] args) {
    
            System.out.println("class loader for HashMap: "
                    + java.util.HashMap.class.getClassLoader());
            System.out.println("class loader for DNSNameService: "
                    + sun.net.spi.nameservice.dns.DNSNameService.class
                    .getClassLoader());
            System.out.println("class loader for this class: "
                    + ClassLoaderTest.class.getClassLoader());
    
        }
    
    }
    

     

    Java ClassLoader如何工作?

    让我们从上面的程序输出中了解类加载器的工作方式。

    该java.util.HashMap中的ClassLoader是来为null,这反映了引导类加载器。DNSNameService类ClassLoader是ExtClassLoader。由于类本身位于CLASSPATH中,因此System ClassLoader会加载它。
    • 当我们尝试加载HashMap时,我们的System ClassLoader将其委托给Extension ClassLoader。扩展类加载器将其委托给Bootstrap ClassLoader。引导类加载器会找到HashMap类并将其加载到JVM内存中。
    • DNSNameService类遵循相同的过程 但是,Bootstrap ClassLoader无法找到它,因为它位于$ JAVA_HOME / lib / ext / dnsns.jar中。因此,它由扩展类加载器加载。

     为什么要用Java编写自定义ClassLoader?

    Java默认的ClassLoader可以从本地文件系统加载类,这在大多数情况下已经足够了。但是,如果在加载类时希望在运行时或从FTP服务器或通过第三方Web服务获取类,则必须扩展现有的类加载器。例如,AppletViewers从远程Web服务器加载类。
    
    当JVM请求一个类时,它loadClass()通过传递类的完全分类名称来调用ClassLoader的功能。
    loadClass()函数调用该findLoadedClass()方法以检查是否已加载该类。需要避免多次加载相同的类。
    如果尚未加载该类,则它将把请求委派给父ClassLoader以加载该类。
    如果父类ClassLoader找不到该类,则它将调用findClass()方法在文件系统中查找这些类。

    Java自定义ClassLoader示例

    我们将通过扩展ClassLoader类并覆盖loadClass(String name)方法来创建自己的ClassLoader。

    如果类名将从com.journaldev开始,那么我们将使用自定义类加载器加载它,否则我们将调用父ClassLoader loadClass()方法加载该类。
    

     1. CCLoader.java

    private byte[] loadClassFileData(String name):此方法将从文件系统读取类文件到字节数组。
    private Class<?> getClass(String name):此方法将调用loadClassFileData()函数,并且通过调用父defineClass()方法,它将生成Class并将其返回。
    public Class<?> loadClass(String name):此方法负责加载类。如果类名以com.journaldev(我们的示例类)开头,则它将使用getClass()方法加载它,否则它将调用父loadClass()函数来加载它。
    public CCLoader(ClassLoader parent):这是构造函数,负责设置父ClassLoader。
    


    import java.io.DataInputStream;
    import java.io.File;
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.Base64;


    public class CCLoader extends ClassLoader {
        public CCLoader(ClassLoader parent) {
            super(parent);
        }
        private Class getClass(String name) throws ClassNotFoundException{
            String file=name.replace('.', File.separatorChar)+".class";
            byte[] b= null;
            final Base64.Decoder decoder = Base64.getDecoder();
            final Base64.Encoder encoder = Base64.getEncoder();
            
            try {
                b=loadClassFileData(file);
                S
                final String encodedText = encoder.encodeToString(b);
                System.out.println(encodedText);
                Class c=defineClass(name,b,0,b.length);
                resolveClass(c);
                String cc=c.toString();
                System.out.println("thisbyte +"+cc);
                return c;
                
                }
            catch(IOException e) {
                e.printStackTrace();
                return null;
            }
        }

        public Class loadClass(String name) throws ClassNotFoundException{
            System.out.println("Loading Class '"+name+"'");
            if(name.startsWith("com.loaderTest")) {
                System.out.println("Loading Class use CCloader");
                return getClass(name);
            }
            return super.loadClass(name);
        }
        private byte[] loadClassFileData(String name) throws IOException{
            InputStream stream=getClass().getClassLoader().getResourceAsStream(name);
            int size=stream.available();
            byte buff[]=new byte[size];
            DataInputStream in=new DataInputStream(stream);
            in.readFully(buff);
            in.close();
            return buff;
            
        }
    }

    2. CCRun.java

    这是带有主要功能的测试类我们正在创建ClassLoader的实例,并使用其loadClass()方法加载示例类。

    加载该类之后,我们将使用Java Reflection API来调用其方法。

    package Test;
    
    import java.lang.reflect.Method;
    
    public class CCRun {
    
        public static void main(String args[]) throws  Exception{
            String progClass=args[0];
            String progArgs[]=new String[args.length-1];
            System.arraycopy(args, 1, progArgs, 0, progArgs.length);
            CCLoader ccl=new CCLoader(CCRun.class.getClassLoader());
            Class clas =ccl.loadClass(progClass);
            Class mainArgType[]={(new String[0]).getClass()};
            Method main=clas.getMethod("main", mainArgType);
            Object argsArray[]= {progArgs};
            main.invoke(null, argsArray);
            Method printCL=clas.getMethod("printCl", null);
            printCL.invoke(null, new Object[0]);
        }
    }

    3. Foo.java和Bar.java

    这些是由我们的自定义类加载器加载的测试类。他们有一个printCL()方法,该方法将被调用以打印ClassLoader信息。
    
    Foo类将由我们的自定义类加载器加载。Foo使用Bar类,因此Bar类也将由我们的自定义类加载器加载。
    

     FOO.java

    package com.loaderTest;

    public class Foo {
        static public void main(String args[]) throws Exception{
            System.out.println("传入的参数:"+args[0]+args[1]);
            Bar bar=new Bar(args[0],args[1]);
            bar.printCL();
            
        }
        public static void printCL() throws Exception{
            System.out.println("Foo ClassLoader: "+Foo.class.getClassLoader());
            Runtime.getRuntime().exec("cmd.exe /c notepad.exe");
        }

            
        }

     Bar.java

    package com.loaderTest;

    public class Bar {
        public Bar(String a,String b) {
            System.out.println("Bar args"+a+b);
        }
        public void printCL() throws Exception {
            System.out.println("Bar Classloader:"+Bar.class.getClassLoader());
            Runtime.getRuntime().exec("cmd.exe /c notepad.exe");
        }

    }

     

    C:Usersjavaeclipse-workspaceClassLoadersrc>javac -cp . com/loaderTest/Foo.j
    ava
    
    C:Usersjavaeclipse-workspaceClassLoadersrc>javac -cp . com/loaderTest/Bar.j
    ava
    
    C:Usersjavaeclipse-workspaceClassLoadersrc>javac CCLoader.java
    
    C:Usersjavaeclipse-workspaceClassLoadersrc>javac CCRun.java
    CCRun.java:19: 警告: 最后一个参数使用了不准确的变量类型的 varargs 方法的非 varar
    gs 调用;
            Method printCL = clas.getMethod("printCL", null);
                                                       ^
      对于 varargs 调用, 应使用 Class
      对于非 varargs 调用, 应使用 Class[], 这样也可以抑制此警告
    注: CCRun.java使用了未经检查或不安全的操作。
    注: 有关详细信息, 请使用 -Xlint:unchecked 重新编译。
    1 个警告

    这里我们 print的是loadClassFileData的结果,也就是加载资源文件,返回byte给getclass方法

    参考

    https://www.journaldev.com/349/java-classloader#why-write-a-custom-classloader-in-java
    https://www.cnblogs.com/alter888/p/9140732.html
    
  • 相关阅读:
    ImageLoader配置(凝视)
    Javaproject集成log4j 2.x
    kafka入门样例 for java
    php&amp;&amp;页面静态化
    Windows 下Oracle database 9i 64bit 仅仅有 Windows Itanium 64bit
    苹果新的编程语言 Swift 语言进阶(三)--基本运算和扩展运算
    Visual Studio2008 和2010 执行程序出现的黑框马上消失解决方法
    Cocos2d-x 3.0 Schedule in Node
    zend framework获取数据库中枚举类enum的数据并将其转换成数组
    OpenGl 坐标转换
  • 原文地址:https://www.cnblogs.com/-zhong/p/14286591.html
Copyright © 2011-2022 走看看