英文:
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.jar
或java com.foo.MainClass
),会涉及两个类加载器。一个加载器负责加载系统相关的类,比如java.lang.String
。可执行文件本身已经知道如何做这件事(你不需要配置PATH
、CLASSPATH
或JAVA_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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论