转载

java – 包查找原理

快速上手一门新语言,我个人认为很重要的一点就是:理解它是如何引入包/模块的。

这对于我们组织项目结构,引入第三方依赖都是非常重要的,相信大家都能理解。

今天讲一下JAVA是如何进行包查找的,理解它对我们学习JAVA至关重要。

编写代码

创建项目目录

首先创建空的项目目录:

/Users/liangdong/eclipse-workspace/mine

创建源码目录

创建一个src目录保存项目的所有源码:

liangdongs-MacBook-Pro:mine liangdong$ pwd
/Users/liangdong/eclipse-workspace/mine
liangdongs-MacBook-Pro:mine liangdong$ ll
total 0
drwxr-xr-x  2 liangdong  staff  64  8  6 13:20 src

创建包目录

因为JAVA开源社区有很多很多包,每一个包里很多有class。

为了避免和开源包冲突,JAVA惯用法一般是用域名的反序作为包名,以避免冲突。

所以我创建一个包叫做:cc.yuerblog,对应目录必须是这样的:

liangdongs-MacBook-Pro:mine liangdong$ ll
total 0
drwxr-xr-x  2 liangdong  staff  64  8  6 13:20 bin
drwxr-xr-x  2 liangdong  staff  64  8  6 13:20 src
liangdongs-MacBook-Pro:mine liangdong$ mkdir -p src/cc/yuerblog

这种存放方式是JAVA的包查找机制决定的,必须这样做。

创建包内的Class

现在cc.yuerblog这个包里面创建一个class:

liangdongs-MacBook-Pro:mine liangdong$ cat src/cc/yuerblog/Demo.java
package cc.yuerblog;
 
public class Demo {
 
}

这里package定义了该class所处的包名,稍后我会演示如何在其他class中引入这个Demo类,以及Java是如何基于classpath查找到这个Demo类的,让我们带着疑问继续向后。

创建另外一个包

现在创建一个包存放程序的入口文件App类,它包含main函数:

liangdongs-MacBook-Pro:mine liangdong$ cat src/cc/yuerblog/entry/App.java
package cc.yuerblog.entry;
 
import cc.yuerblog.Demo;
 
public class App {
	public static void main(String[] args) {
		new Demo();
	}
}

引入Demo类是按照了包名引入的,其路径相对于项目目录。

编译源码

大家写JAVA上来就用IDE,貌似IDE很简单就可以运行程序了,但其实IDE背后是做了一些幕后工作的,下面我们手动来编译程序,以便理解JAVA包的查找方式。

我们再次看一下项目结构,现在处在项目根目录mine:

liangdongs-MacBook-Pro:mine liangdong$ find ./src
./src
./src/cc
./src/cc/yuerblog
./src/cc/yuerblog/entry
./src/cc/yuerblog/entry/App.java
./src/cc/yuerblog/Demo.java

源码都放在src目录下,每一个.java文件都需要编译为.class字节码文件。

我们可以先编译Demo.java,因为App.java依赖它:

liangdongs-MacBook-Pro:mine liangdong$ javac src/cc/yuerblog/Demo.java
liangdongs-MacBook-Pro:mine liangdong$ ll src/cc/yuerblog/
total 16
-rw-r--r--  1 liangdong  staff  194  8  6 14:41 Demo.class
-rw-r--r--  1 liangdong  staff   45  8  6 13:35 Demo.java
drwxr-xr-x  4 liangdong  staff  128  8  6 14:31 entry

.class文件默认生成在.java同目录。

接着编译App.java:

liangdongs-MacBook-Pro:mine liangdong$ javac ./src/cc/yuerblog/entry/App.java
./src/cc/yuerblog/entry/App.java:3: 错误: 找不到符号
import cc.yuerblog.Demo;
                  ^
  符号:   类 Demo
  位置: 程序包 cc.yuerblog
./src/cc/yuerblog/entry/App.java:7: 错误: 找不到符号
		new Demo();
		    ^
  符号:   类 Demo
  位置: 类 App
2 个错误

报错找不到Demo类。

怎么办呢?我们需要知道java是怎么查找包的:

JAVA是去CLASSPATH环境变量定义的各个目录下,查找包路径。

比如这里设置:CLASSPATH=/Users/liangdong/eclipse-workspace/mine/src的话,那么cc.yuerblog.Demo类就会在/Users/liangdong/eclipse-workspace/mine/src/cc/yuerblog/中查找Demo.java。

javac编译代码的时候,可以通过-cp参数指定CLASSPATH,这样javac就可以找到依赖的Demo.java文件,并将其也编译成Demo.class:

javac -cp ./src/ ./src/cc/yuerblog/entry/App.java

运行程序

项目所有的.class文件都编译出来之后,就可以把它们运行起来。

运行过程还是从App.class开始作为入口,后续调用到了Demo.class,因此我们运行程序得时候依旧需要classpath:

liangdongs-MacBook-Pro:mine liangdong$ java -cp ./src cc.yuerblog.entry.App

运行程序得时候,需要指定入口程序得完整类名,即:cc.yuerblog.entry.App。

因为指定了-cp ./src,所以可以在src/cc/yuerblog/entry/中找到App.class。

没有classpath则肯定无法找到./src下面的类:

liangdongs-MacBook-Pro:mine liangdong$ java  cc.yuerblog.entry.App
错误: 找不到或无法加载主类 cc.yuerblog.entry.App

一般来说我们不会通过export CLASSPATH的方式设置包查找路径,因为会影响到其他java程序得包查找路径。

创建jar包

仅仅编译出那么多.class文件还不够,我们一般会将.class打包成一个jar包,这样其他项目可以引入我们的包并访问其中的类,或者直接运行我们包的main函数拉起程序。

jar包其实就是用zip压缩的一个包含了.class文件的目录。

我们稍微改造一下javac命令,把.class文件生成到一个独立的bin目录下:

liangdongs-MacBook-Pro:mine liangdong$ mkdir bin
liangdongs-MacBook-Pro:mine liangdong$ javac -d ./bin -cp ./src/ ./src/cc/yuerblog/Demo.java ./src/cc/yuerblog/entry/App.java
liangdongs-MacBook-Pro:mine liangdong$ find ./bin/
./bin/
./bin//cc
./bin//cc/yuerblog
./bin//cc/yuerblog/entry
./bin//cc/yuerblog/entry/App.class
./bin//cc/yuerblog/Demo.class

可见,.class按照包路径被放到了bin目录下。

如果我们写另外一个java类想要导入cc.yuerblog.Demo类,其实只需要在classpath中增加bin目录即可加载成功。

为了生成一个方便运输的Jar包,我们必须进入bin目录,此时相对路径才与包名一致,然后jar打包所有的.class文件:

jar -cvf mine.jar ./cc/yuerblog/Demo.class ./cc/yuerblog/entry/App.class
已添加清单
正在添加: cc/yuerblog/Demo.class(输入 = 194) (输出 = 166)(压缩了 14%)
正在添加: cc/yuerblog/entry/App.class(输入 = 308) (输出 = 236)(压缩了 23%)

我们可以解压一下jar文件看一下:

liangdongs-MacBook-Pro:bin liangdong$ unzip mine.jar -d ./x
Archive:  mine.jar
   creating: ./x/META-INF/
  inflating: ./x/META-INF/MANIFEST.MF
  inflating: ./x/cc/yuerblog/Demo.class
  inflating: ./x/cc/yuerblog/entry/App.class

会发现jar包里有META-INF的元信息描述,以及所有按包名存放的class文件。

运行jar包

因为App.class具备main方法,我们可以指定jar包的入口程序,这样jar包就是可执行的了(-e参数):

jar -cvfe mine.jar cc.yuerblog.entry.App  ./cc/yuerblog/Demo.class ./cc/yuerblog/entry/App.class

现在运行jar包会从cc.yuerblog.entry.App类执行:

liangdongs-MacBook-Pro:bin liangdong$ java -jar mine.jar

引用jar包

如果jar包是作为一个类库给其他人用,那么就不必指定入口类了。

假设我们新建一个项目,定义如下java类:

import cc.yuerblog.Demo;
 
public class Test {
	public static void main(String[] args) {
		new Demo();
	}
}

编译的时候,需要通过-cp指定classpath指向jar包(jar包就是一个压缩的目录),那么java自然会在jar包的cc/yuerblog/Demo.class找到类文件:

liangdongs-MacBook-Pro:mine liangdong$ javac -cp ./bin/mine.jar Test.java

这样生成Test.class后,我们运行的时候不仅需要指定classpath包含Test.class所在目录,还需要包含jar:

liangdongs-MacBook-Pro:mine liangdong$ java -cp ./bin/mine.jar:. Test
javac的参数是文件名,并通过-cp指定对依赖的查找路径。
java的参数是类名,并通过-cp指定对依赖的查找路径。

java程序运行需要确保所有的.class文件都可以通过classpath找到,因此-cp参数兼具了:

  • 查找当前项目的class
  • 查找依赖的外部class(比如jar包里的,或者其他某个目录下的)

如果文章帮到了你,请您乐于扫码捐赠1元钱,以便支持服务器运转。

java – 包查找原理
原文  https://yuerblog.cc/2019/08/06/java-包查找原理/
正文到此结束
Loading...