电脑JAVA网址打不开_电脑JAVA网址打不开

1.类加载器简介

类加载器负责在运行时将Java类动态加载到JVM(Java虚拟机)。它是JRE(Java运行时环境)的一部分,由于类加载器的存在,JVM无需了解底层文件或文件系统即可运行Java程序。而且,这些Java类不会一次全部加载到内存中,而是在应用程序需要时加载。这是类加载器出现的地方。他们负责将类加载到内存中。在本教程中,我们将讨论不同类型的内置类加载器,它们如何工作以及对我们自己的自定义实现的介绍。

2.内置加载器类型

让我们从一个简单的例子开始学习如何使用各种类加载器加载不同的类:

public void printClassLoaders() throws ClassNotFoundException {     System.out.println("Classloader of this class:"        + PrintClassLoader.class.getClassLoader());     System.out.println("Classloader of Logging:"        + Logging.class.getClassLoader());     System.out.println("Classloader of ArrayList:"        + ArrayList.class.getClassLoader());}

执行以上方法时,将打印:

Class loader of this class:sun.misc.Launcher$AppClassLoader@18b4aac2Class loader of Logging:sun.misc.Launcher$ExtClassLoader@3caeaf62Class loader of ArrayList:null

如我们所见,这里有三种不同的类加载器:应用、扩展、启动类加载器。本文分别用app、ext、bootstrap来代替这三类加载器。

app加载包含示例方法的类,应用程序或系统类加载器将我们自己的文件加载到类路径中。

接下来,ext加载Logging类,扩展类加载器加载作为标准核心Java类扩展的类。

最后,bootstrap加载ArrayList类,bootstrap是所有其它加载器的父级。

但是,我们可以看到最后一个,对于ArrayList,它在输出中显示为null。这是因为引导类加载器是用本机代码而不是Java编写的,因此它不会显示为Java类。由于这个原因,bootstrap加载器的行为在不同平台 JVM之间会有所不同。

现在让我们详细讨论这些类加载器。

2.1.Bootstrap类加载器

Java类由java.lang.ClassLoader的实例加载。但是,类加载器本身就是类。因此,问题是,谁来加载java.lang.ClassLoader本身?

它主要负责加载JDK内部类,通常是rt.jar和$JAVA_HOME/jre/lib目录中的其他核心库。此外,Bootstrap类加载器还充当所有其他ClassLoader实例的父级。

该引导类加载器是核心JVM的一部分,并以本机代码编写,如上面的示例中指出的那样。不同的平台可能对此特定的类加载器具有不同的实现,通俗点讲就是操作系统进行加载。

2.2.ext类加载器

该ext加载器是Bootstrap类加载器的一个孩子,需要装载标准核心Java类的扩展类,以便它可以在平台上运行的所有应用程序。

ext加载器从JDK扩展目录(通常是$JAVA_HOME/lib/ext目录)或java.ext.dirs系统属性中提到的任何其他目录加载。

2.3.app类加载器

系统或应用程序类加载器负责将所有应用程序级类加载到JVM中。它加载在类路径环境变量-classpath或-cp命令行选项中找到的文件。另外,它是扩展类加载器的子级。

3.类加载器如何工作?

类加载器是Java运行时环境的一部分。当JVM请求一个类时,类加载器将尝试定位该类,并使用完全限定的类名称将类定义加载到运行时中。

所述java.lang.ClassLoader.loadClass()方法是负责加载类定义成运行时。它尝试基于完全限定的名称加载类。

如果尚未加载该类,则它将请求委托给父类加载器。此过程是递归发生的。

最终,如果父类加载器找不到该类,则子类将调用java.net.URLClassLoader.findClass()方法在文件系统本身中查找类。

如果最后一个子类加载器也无法加载该类,则它将抛出java.lang.NoClassDefFoundError或java.lang.ClassNotFoundException。

让我们看一个抛出ClassNotFoundException时的输出示例。

java.lang.ClassNotFoundException: com.baeldung.classloader.SampleClassLoader        at java.net.URLClassLoader.findClass(URLClassLoader.java:381)        at java.lang.ClassLoader.loadClass(ClassLoader.java:424)        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)        at java.lang.Class.forName0(Native Method)        at java.lang.Class.forName(Class.java:348)

如果我们从调用java.lang.Class.forName()开始经历事件序列,我们可以理解,它首先尝试通过父类加载器加载该类,然后尝试通过java.net.URLClassLoader.findClass()查找class本身。

当仍然找不到该类时,它将引发ClassNotFoundException。

类装入器具有三个重要功能。

3.1.委托模型

类加载器遵循委托模型,在该模型中,根据请求查找类或资源,ClassLoader实例会将对类或资源的搜索委托给父类加载器。

假设我们有一个将应用程序类加载到JVM中的请求。app加载器首先将该类的加载委托给其父ext加载器,而父ext加载器又将其委托给bootstrap加载器。

仅当bootstrap和ext类加载器未能成功加载类时,app类加载器才会尝试加载类本身。

3.2.唯一性

作为委托模型的结果,很容易确保唯一的类,因为我们总是尝试向上委托。

如果父类加载器无法找到该类,则只有当前实例自己会尝试这样做。

3.3.可见度

子类加载器对其父类加载器加载的类可见。

例如,app加载的类对ext和Bootstrap类加载器加载的类具有可见性,反过来是不允许的。

为了说明这一点,如果类A由应用程序类加载器加载,而类B由扩展类加载器加载,则就应用程序类加载器加载的其它加载器A和B而言都是可见的。

但是,就扩展类加载器加载的其他类而言,类B是唯一可见的类。

4.自定义ClassLoader

在大多数情况下,如果文件已经在文件系统中,则内置的类加载器就足够了。

但是,在需要从本地硬盘驱动器或网络中加载类的情况下,我们可能需要使用自定义类加载器。

在本节中,我们将介绍自定义类加载器的其他一些用例,并演示如何创建一个。

4.1.定制类装载机用例

自定义类加载器不仅对在运行时加载类有帮助,还包括一些用例:

帮助修改现有的字节码,例如编织代理 动态创建适合用户需求的类。例如在JDBC中,通过动态类加载完成不同驱动程序实现之间的切换。在为具有相同名称和程序包的类加载不同的字节码时实现类版本控制机制。这可以通过URL类加载器(通过URL加载jar)或自定义类加载器来完成。在更具体的示例中,自定义类加载器可能会派上用场。

例如,浏览器使用自定义类加载器从网站加载可执行内容。浏览器可以使用单独的类加载器从不同的网页加载applet。用于运行小程序的小程序查看器包含一个ClassLoader,该类加载器可访问远程服务器上的网站,而无需查看本地文件系统。

然后通过HTTP加载原始字节码文件,并将其转换为JVM中的类。即使这些小程序具有相同的名称,但如果由不同的类加载器加载,它们也被视为不同的组件。

现在,我们了解了为什么自定义类加载器是相关的,让我们实现ClassLoader的子类来扩展和总结JVM如何加载类的功能。

4.2.创建我们的自定义类加载器

出于说明目的,假设我们需要使用自定义类加载器从文件中加载类。我们需要扩展ClassLoader类并重写findClass()方法:

public class CustomClassLoader extends ClassLoader {     @Override    public Class findClass(String name) throws ClassNotFoundException {        byte[] b = loadClassFromFile(name);        return defineClass(name, b, 0, b.length);    }     private byte[] loadClassFromFile(String fileName)  {        InputStream inputStream = getClass().getClassLoader().getResourceAsStream(                fileName.replace('.', File.separatorChar) + ".class");        byte[] buffer;        ByteArrayOutputStream byteStream = new ByteArrayOutputStream();        int nextValue = 0;        try {            while ( (nextValue = inputStream.read()) != -1 ) {                byteStream.write(nextValue);            }        } catch (IOException e) {            e.printStackTrace();        }        buffer = byteStream.toByteArray();        return buffer;    }}

在上面的示例中,我们定义了一个自定义类加载器,该类加载器扩展了默认类加载器并从指定文件加载字节数组。

5.了解java.lang.ClassLoader

让我们讨论java.lang.ClassLoader类中的一些基本方法,以更清楚地了解其工作方式。

5.1.loadClass()方法

public Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {

此方法负责加载给定名称参数的类,name参数引用完全限定的类名称。

Java虚拟机调用loadClass()方法来解决将class resolve设置为true的类引用。但是,不一定总是要解析一个类。如果仅需要确定该类是否存在,则将resolve参数设置为false。

此方法用作类加载器的入口点。

我们可以尝试从java.lang.ClassLoader的源代码了解loadClass()方法的内部工作:

protected 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 {                    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                }                 if (c == null) {                    // If still not found, then invoke findClass in order                    // to find the class.                    c = findClass(name);                }            }            if (resolve) {                resolveClass(c);            }            return c;        }    }

该方法的默认实现按以下顺序搜索类:

调用findLoadedClass(String)方法以查看是否已加载该类。在父类加载器上调用loadClass(String)方法。调用findClass(String)方法以查找类。

5.2.defineClass()方法

protected final Class> defineClass(  String name, byte[] b, int off, int len) throws ClassFormatError

此方法负责将字节数组转换为类的实例。在使用该类之前,我们需要解决它。

如果数据不包含有效的类,则会抛出ClassFormatError。

另外,由于此方法被标记为final,因此无法覆盖它。

5.3.findClass()方法

protected Class> findClass(  String name) throws ClassNotFoundException

此方法查找以标准名称作为参数的类。我们需要在遵循委托模型来加载类的自定义类加载器实现中重写此方法。

另外,如果父类加载器找不到请求的类,则loadClass()会调用此方法。

如果没有任何类加载器的父类找到该类,则默认实现将引发ClassNotFoundException

5.4.getParent()方法

public final ClassLoader getParent()

此方法返回父类加载器进行委派。

一些实现(如第2节中之前看到的实现)使用null来表示引导类加载器

5.5.getResource()方法

public URL getResource(String name)

此方法尝试查找具有给定名称的资源。

它将首先委托给资源的父类加载器。如果父级为null,则搜索虚拟机内置的类加载器的路径。

如果失败,则该方法将调用findResource(String)来查找资源。指定为输入的资源名称可以相对于或相对于绝对路径。

它返回用于读取资源的URL对象;如果找不到资源或调用者没有足够的特权来返回资源,则返回null。

重要的是要注意,Java从类路径加载资源。

最后,Java中的资源加载被认为是与位置无关的,因为只要设置环境来查找资源,代码在何处运行都无关紧要。

6.上下文类加载器

通常,上下文类加载器为J2SE中引入的类加载委托方案提供了一种替代方法。

如我们之前所了解的,JVM中的类加载器遵循分层模型,因此每个类加载器都有一个单独的父级,而bootstrap类加载器除外。

但是,有时当JVM核心类需要动态加载应用程序开发人员提供的类或资源时,我们可能会遇到问题。

例如,在JNDI中,核心功能由rt.jar中的引导程序类实现。但是,这些JNDI类可能会加载由独立供应商实现的JNDI提供程序(部署在应用程序类路径中)。这种情况要求bootstrap类加载器(父类加载器)加载对应用程序加载器(子类加载器)可见的类。

J2SE委托在这里不起作用,为了解决这个问题,我们需要找到替代的类加载方式。并且可以使用线程上下文加载器来实现。

所述的java.lang.Thread类有一个方法getContextClassLoader() ,返回ContextClassLoader对于该特定线程。该ContextClassLoader由线程的创建者加载资源和类时提供。

如果未设置该值,则默认为父线程的类加载器上下文。

7.结论

类加载器对于执行Java程序至关重要。作为本文的一部分,我们提供了很好的介绍。

我们讨论了不同类型的类加载器,即Bootstrap,扩展和系统类加载器。Bootstrap充当所有它们的父级,并负责加载JDK内部类。另一方面,扩展和系统分别从Java扩展目录和类路径加载类。

然后,我们讨论了类加载器的工作方式,并讨论了一些功能,例如委托,可见性和唯一性,然后简要说明了如何创建自定义的。最后,我们介绍了Context类加载器。

本文翻译自网站https://www.baeldung.com/java-classloaders翻译或者理解有问题地方请关注公众号、加我微信、一起进群讨论