类路径
# 类路径
早期我们项目中需要连接数据库时会编写如下代码:
public class DbTest {
public static void main(String[] args) {
try {
Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection(url, username, password);
} catch (Exception e) {
e.printStackTrace();
}
}
}
2
3
4
5
6
7
8
9
10
之前我一直有个疑惑,为啥需要Class.forName("com.mysql.jdbc.Driver");
,我直接 new 一个不就行了吗,
教材中会直接告诉你不行,原因是为了解耦;最近,我在重温 maven 打包作用域的时,了解到 java包含三种类路径: 编译时类路径、测试时类路径、运行时类路径。
- 编译时类路径:项目编译时需要引入的路径
- 测试时类路径:
- 运行时类路径: 项目运行需要引入的包路径
看似解释了,其实啥也没解释,为了方便理解,下面,我将用代码的方式简单解释一下:
项目中有个如下类:
package com.king.im.client;
public class IMClient {
public static void main(String[] args) {
System.out.println("hello, world!");
}
}
2
3
4
5
6
7
通过如下命令进行编译:
# src\main\java\com\king\im\client\IMClient.java 需要编译的java源文件路径位置
# 编译后类路径生成位置
javac src\main\java\com\king\im\client\IMClient.java -d dist
2
3
从图中我们可以确定由javac编译后产生的类文件路径为源文件的包路径, 因此 dist 目录即为我们源代码的类路径
现在,我们可以通过命令行运行该类文件, 首先需要确保进入类路径中,否则执行会报错
java com.king.im.client.IMClient
现在我们进行一些改造,定义一个服务类接口和对应的服务类实现,然后编写如下代码
public interface DemoService {
void demo();
}
2
3
4
public class DemoServiceImpl implements DemoService {
@Override
public void demo() {
System.out.println("this is a demo class impl");
}
}
2
3
4
5
6
7
package com.king.im.client;
public class IMClient {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Class<?> clz = Class.forName("com.king.im.client.DemoServiceImpl");
DemoService demoService = (DemoService) clz.newInstance();
demoService.demo();
System.out.println("hello, world!");
}
}
2
3
4
5
6
7
8
9
10
进行编译后,会发现报错了。报错内容如下:
PS F:\Project\java\own\mini-project\mini-im-client> javac src\main\java\com\king\im\client\IMClient.java -d dist
src\main\java\com\king\im\client\IMClient.java:6: 错误: 找不到符号
DemoService demoService = (DemoService) clz.newInstance();
^
符号: 类 DemoService
位置: 类 IMClient
src\main\java\com\king\im\client\IMClient.java:6: 错误: 找不到符号
DemoService demoService = (DemoService) clz.newInstance();
^
符号: 类 DemoService
位置: 类 IMClient
2 个错误
2
3
4
5
6
7
8
9
10
11
12
13
找不到类错误,看下目录结构:
发现是在同一个包下面的,找不到类,由此可以断点我们源代码没有包含在类路径中,我们在编译时,需要添加编译时类路径的编译项目:
# -cp 指定额外类路径,默认包含了我们在安装java环境时已有的类路径:JAVA_HOME的配置
javac -cp "F:\Project\java\mini-im-client\src\main\java;" src\main\java\com\king\im\client\IMClient.java -d dist
2
编译通过!现在让我们立即运行该类文件,发现依然报错,内容如下:
PS F:\Project\java\own\mini-project\mini-im-client\dist> java com.king.im.client.IMClient
Exception in thread "main" java.lang.ClassNotFoundException: com.king.im.client.DemoServiceImpl
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:338)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:264)
at com.king.im.client.IMClient.main(IMClient.java:5)
2
3
4
5
6
7
8
9
找不到实现类,从编译后类路径中发现,实现类确实没有打包进来,并且我们代码中也并没有显示的引入该类进来,因此对应的实现类不会打包进我们项目的类路径中。
如果我们原代码中通过 new 方式创建实现类,实现类最终是会包含到源代码类路径的打包结果中。
DemoService demoService = new DemoServiceImpl();
流程走到此处,不知道大家是否注意到,之前教科书上解释说的 “解耦” 是否可以理解了,我们编写驱动接口只是面向接口编程,不注重具体的实现,各个厂商负责 具体实现细节,当我们需要使用某个厂商的实现时,只需要导入厂商提供实现代码,而不需要修改接口的驱动代码,这就是解耦的真正含义。
现在让我们自己当一回“软件厂商”,来实现这么一个服务
我们将 DemoServiceImpl
进行单独的编译,并打包到另外一个类路径中
javac -cp "F:\Project\java\mini-im-client\src\main\java;" src\main\java\com\king\im\client\DemoServiceImpl.java -d dist1
然后我们运行时,指定我们的实现类类路径:
java -cp "F:\Project\java\mini-im-client\dist1;" com.king.im.client.IMClient
运行成功!现在我们在来回顾一下加载流程:我们在代码中有使用到DemoService
,但并没有引入他的实现,而是程序运行时,我们通过添加 加载的运行时类路径
的参数,通过反射方式拿到实现类的实例,然后进行具体操作。
javac 进行编译后的类路径中会包含非当前模块的其他类,如果不想在当前模块中包含其他的类,推荐使用包管理工具进行编译打包,比如常用的maven。