Maven如何使用ClassLoader加载类

huangapple go评论83阅读模式
英文:

How Maven loads classes using ClassLoader

问题

我对Java中的ClassLoader的了解目前还有些模糊。这是因为我尚未找到任何针对初学者的ClassLoader的良好文档。而我现在正在寻找与Maven相关的内容。在声明了免责声明之后,让我进入我的问题。

我正在编写一个Spring MVC应用程序,并且我决定深入了解使用ClassLoader加载依赖项 - JAR文件和类。根据ClassLoader的文档,我发现JAR中的类是从CLASSPATH加载的,并且我可以在.m2/repository目录下看到它们,但CLASSPATH没有产生任何结果,它几乎是空的。

请问有人可以解释一下,如果CLASSPATH是空的,使用Maven时类是如何通过ClassLoader加载到JVM内存中的吗?

谢谢

英文:

My knowledge of ClassLoader in Java at the moment, is a little obscure. That's because I have not found any good documentation geared towards beginner of CLassLoader, thus far. And what I am looking for exactly is in relation to Maven. With disclaimer stated, let me get into my question.

I am writing a Spring MVC application, and I decided to look into how the Dependencies - Jars and classes are loaded using ClassLoader. And what I found is according to the documentation of ClassLoader, classes in Jar are loaded from CLASSPATH, and I can see them under the .m2/repository directory, but CLASSPATH does not yield anything, it's practically empty.

Can somebody please explain to me, how the classes from JAR are loaded into JVM Memory using ClassLoader using Maven, if the CLASSPATH is empty.

Thanks

答案1

得分: 3

你对几个概念有些混淆。

类加载器

类加载器是一个运行时概念,而Maven是一个编译时概念。因此,它们两者之间没有任何关联。Maven和类加载器没有交互关系。

当你启动一个基本的Java应用程序(java -jar foo.jarjava com.foo.MainClass),会涉及两个类加载器。一个加载器负责加载系统相关的类,比如java.lang.String。可执行文件本身已经知道如何做这件事(你不需要配置PATHCLASSPATHJAVA_HOME——它可以正常工作)。在Java 8之前,它会自动找到rt.jar,其中包含了String.class和其他核心类。从Java 11开始,它会在你的Java发行版中找到jmod文件。

然后,一旦虚拟机启动完成,虚拟机会创建另一个类加载器,这个加载器也是基于虚拟机内置的一些内容,称为应用程序类加载器。

这个加载器使用类路径。类路径的来源取决于你如何运行Java应用:

  • java -jar somejar.jar

在这种情况下,类路径的来源是JAR文件清单中的Class-Path:行(位于META-INF/MANIFEST.MF文件中)。没有其他来源——CLASSPATH环境变量以及任何-cp-classpath选项都会被完全忽略。

  • java -cp a.jar:b.jar:. com.foo.ClassName

注意,-cp等同于-classpath(它们表示同样的意思):在这里,类路径是由列出的所有文件和目录组成的(在Windows中,使用;作为分隔符),类会从这些地方加载,包括com.foo.ClassName本身。

  • java com.foo.ClassName

如果你没有显式地指定-cp参数,那么将使用环境变量CLASSPATH。但通常不建议这样做,最好始终指定类路径。

这是运行时的内容,与Maven没有任何关系。

自定义类加载器

你可以创建自己的类加载器,这个抽象的概念只需要能够将资源名称转换为字节流。就是这样。你可以创建一个类加载器(确实如此!public class MyLoader extends java.lang.ClassLoader { ... }),它可以从网络加载数据,或者动态生成数据,或者从加密数据存储中获取数据,随你喜欢。

像这样使用自定义类加载器是一种在奇怪的地方(不是JAR文件或目录)查找类的解决方案,同时也是一种机制,允许Java在开发过程中动态重新加载类,比如在开发Web应用程序时,没有使用像Eclipse那样的热代码替换调试器。类加载器是一种使得Web服务器具有以下功能的机制:“我从预配置的特定目录加载JAR或WAR文件.. 如果你替换了JAR文件,我会察觉到并开始使用新的JAR文件”。

编写自己的类加载器几乎可以看作是火箭科学,通常只有在编写应用服务器等特殊情况下才需要,而这并不是一项常见的工作。

Maven

为了编译源代码,编译器必须知道你引用的所有类型的方法和字段等信息。如果编译器不知道String的内容,你是不能编译"Hello".toLowerCase()这样的代码的。

因此,编译器同样也有一个需要找到类的概念。但是这不被称为类加载,值得注意的是,Maven从不加载任何类。如果加载了类,任何类中的静态初始化程序都会运行,从而破坏你的编译过程。Maven只是检查类文件,从不让虚拟机实际加载它,以了解提供了什么类型的方法和字段等。

java.lang.ClassLoader在这一过程中没有任何作用

javac本身也有-classpath选项。实际上,Maven也有这个选项。

Maven会自动构建类路径,通过将已经为你编译的内容(例如,在编译src/test/java中的内容时,src/main/java中编译的内容将位于类路径上),以及所有的依赖项放在一起。具体是如何做到的呢?无论如何,Maven会构建一个包含许多目录和JAR文件的大型列表,并通过-classpath参数将其传递给javac

英文:

You're confusing a few things.

The ClassLoader

The ClassLoader is a runtime concept. Maven is a compile time concept. Therefore, one has nothing whatsoever to do with the other. Maven and ClassLoaders do not interact. At all.

When you start a basic java app (java -jar foo.jar or java com.foo.MainClass), you get 2 classloaders. One loader will load system stuff: java.lang.String, for example. the executable itself 'just knows' how to do this (you don't need to configure PATH, CLASSPATH or JAVA_HOME - it just works); up to java 8, it finds rt.jar automatically, which contains String.class and other core classes. Starting from java 11, it finds the jmod files in your java distro.

Then, once the VM has 'booted up', the VM makes another classloader, also based on its built-in stuff: The app classloader.

This one uses 'the classpath'. The source of this depends on how you ran your java app:

  • java -jar somejar.jar

The source of the classpath in this case is the Class-Path: line in the jar's manifest (the file at META-INF/MANIFEST.MF). And nothing else - the CLASSPATH environment variable, and any -cp or -classpath options are entirely ignored.

  • java -cp a.jar:b.jar:. com.foo.ClassName

Note that -cp is short for -classpath (they mean the same thing): Here, the classpath is taken to be all the files and directories listed (on windows, use ; as separator instead), and classes are loaded from there. Including com.foo.ClassName itself.

  • java com.foo.ClassName

If you don't explicitly specify a -cp param, then the environment var CLASSPATH is used. You don't want this though. Always specify classpath.

That's runtime - maven has nothing whatsoever to do with this.

Make your own

You can make your own ClassLoader; the abstraction is such that all it needs to be able to do, is turn a resource name into bytes. That's it. You can make a ClassLoader (literally! public class MyLoader extends java.lang.ClassLoader { ... }) that.. loads data from a network, or generates it on the fly, or fetches it from an encrypted data store. Whatever you like.

Using custom classloaders like this is a solution for finding classes in 'weird' places (not jar files or directories), as well as a mechanism to allow java to 'reload classes' on the fly - very useful when developing, say, web apps, without the use of a hot-code-replacing debugger like eclipse has. ClassLoaders is a mechanism whereby a web server can have the following feature: "I load jars or wars from a certain preconfigured directory.. and if you replace the jar, I will see it and start using the new one".

Writing your own ClassLoader is bordering on rocket science and not usually required unless you're, say, writing an app server. Not a common job.

Maven

To compile source code, the compiler must know the methods and fields and such of all the types you refer. You can't compile "Hello".toLowerCase(); if the compiler doesn't know what String contains.

Thus, compilers also have this notion of 'I need to find classes'. But This is not called class loading, and notably, maven never loads any classes. If it did, any static initializers in any class would run, and mess up your compile. Maven instead just inspects the class file, never letting the VM actually load it, to know what kind of methods and fields and the like are on offer.

java.lang.ClassLoader plays no part in this.

javac itself has a -classpath option as well. So does maven, really.

Maven constructs the classpath automatically, by putting the stuff it already compiled for you (e.g. when compiling the stuff in src/test/java, the compiled stuff from src/main/java is on the classpath), as well as all the dependencies. How? Well, does it matter? Maven does. It constructs a large list of dirs and jars and passes it to javac via the -classpath parameter.

huangapple
  • 本文由 发表于 2020年7月23日 20:05:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/63053836.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定