VIP创新项目1课程总结2021-2022学年第1学期
1. Java基础
1.1 Java代码是如何执行的
Java执行过程如下图:
- 在指定目录下,编写Java代码,Hello.java源代码如下:
class Hello{ public static void main(String[] agrs){ System.out.println("hello"); } }
编译java源代码
当java源程序编码结束后,就需要编译器编译。
安装好jdk后,我们打开jdk目录,有两个.exe文件,即javac.exe(编译源代码,xxx.java文件) 和 java.exe(执行字节码,xxx.class文件).
用命令行进入到该目录下,编译这个文件javac Hello.java
F:\你的工程所在路径>javac Hello.java
- 将编译后的Hello.class文件打成jar包
jar -cvf hello.jar Hello.class
c表示要创建一个新的jar包,v表示创建的过程中在控制台输出创建过程的一些信息,f表示给生成的jar包命名
F:\你的工程所在路径> jar -cvf hello.jar Hello.class 已添加清单 正在添加: Hello.class(输入 = 409) (输出 = 280)(压缩了 31%)
- 运行jar包
java -jar hello.jar 这时会报如下错误 hello.jar中没有主清单属性
F:\你的工程所在路径>java -jar hello.jar hello.jar中没有主清单属性
- 解决办法1:java -classpath hello.jar Hello
- 解决办法2:添加Main-Class属性
最后一样一定要回车,空一行,不然无法识别最后一行的配置。
Manifest-Version、Main-Class和Class-Path后面跟着一个英文的冒号,冒号后面必须跟着一个空格,然后才是版本号、类和ClassPath。
Class-Path中的各项应使用空格分隔,不是逗号或分号。
Class-Path中如果有很多项,写成一行打包的时候会报错line too long,这时需要把Class-Path分多行写。注意:从第二行开始,必须以两个空格开头,三个以上我没试过,不过不用空格开头和一个空格开头都是不行的,我已经试过了。
Class-Path写完之后最后一定要有一个空行。
jar包内有些配置文件想放在jar包外面,比如文件config.properties:如果这个文件是以路径方式载入的,比如new file("./config/config.properties"),那么将config.properties放在jar包相同目录下的config目录下即可,也就是说“./”路径等价于jar包所在目录;如果这个文件是以ClassPath下的文件这种方式载入的,比如在Spring中载入classpath:config.properties,则在MF文件的配置文件的ClassPath中添加“./”,然后将这个配置文件与jar包放在同一个目录即可,当然也可以在MF文件的配置文件的ClassPath中添加“./config/”,然后把配置文件都放在jar包相同目录下的config目录下。
用压缩软件打开hello.jar,会发现里面多了一个META-INF文件夹,里面有一个MENIFEST.MF的文件,用记事本打开
Manifest-Version: 1.0 Created-By: 1.8.0_121 (Oracle Corporation) Main-Class: Hello
在第三行的位置写入 Main-Class: Hello (注意冒号后面有一个空格,整个文件最后有一行空行),保存
再次运行
java -jar hello.jar
此时成功在控制台看到 hello ,成功
备注:
.jar分2种,有一种只是当作调用包;另外一种是可以直接执行,类似.exe。
但是一般可直接执行的*.jar文件,用winrar等解压软件打开会发现都有一个META-INF的文件夹,这个文件夹中必须有个MANIFEST.MF文件,这个文件主要是用来描述可执行的*.jar的执行入口文件(通常是含有main()方法的类文件),格式大体如下:
Manifest-Version: 1.0 Created-By: 1.4.2_08 (Sun Microsystems Inc.) Main-Class: Main
这边Main既是运行类,含有main()方法的一个类文件,名字为Main.class。
1.2Java打包案例
1.带package的jar包——直接输出hello
方法步骤
(0)创建目录F:\Example_Project
(1)创建edu.hncj.vip目录,用记事本写一个Hello.java的文件
package edu.hncj.vip; class Hello{ public static void main(String[] agrs){ System.out.println("hello"); } }
(2)用命令行进入到该目录下,编译这个文件javac -d . Hello.java
F:\Example_Project\edu\hncj\vip>javac -d . Hello.java
javac -d . 中 -d参数:编译时,同时由系统自动生成package目录 .参数:指定Hello.java类的包名 将要生成在哪个目录下,.表示当前目录,可以任意指定
(3)将编译后的Hello.class文件打成jar包
jar -cvf hello.jar edu\hncj\vip\ F:\Example_Project\edu\hncj\vip>jar -cvf hello.jar edu\hncj\vip\ 已添加清单 正在添加: edu/hncj/vip/(输入 = 0) (输出 = 0)(存储了 0%) 正在添加: edu/hncj/vip/Hello.class(输入 = 422) (输出 = 292)(压缩了 30%)
c表示要创建一个新的jar包,v表示创建的过程中在控制台输出创建过程的一些信息,f表示给生成的jar包命名
(4)运行jar包
java -jar hello.jar
这时会报如下错误 hello.jar中没有主清单属性
用压缩软件打开hello.jar,会发现里面多了一个META-INF文件夹,里面有一个MENIFEST.MF的文件,用记事本打开
添加Main-Class属性
Manifest-Version: 1.0 Created-By: 1.8.0_181 (Oracle Corporation) Main-Class: edu.hncj.vip.Hello
在第三行的位置写入 Main-Class: Hello (注意冒号后面有一个空格,整个文件最后有一行空行),保存
再次运行 java -jar hello.jar ,此时成功在控制台看到 hello ,成功
2.含有两个类的jar包——通过调用输出hello
(0)创建目录F:\Example_Project\两个类的jar>
(1)用记事本写一个Hello.java和一个Student.java的文件
目的是让Hello调用Student的speak方法
class Hello{ public static void main(String[] agrs){ //System.out.println("hello"); Student.speak(); } } class Student{ public static void speak(){ System.out.println("hello,I'm 20级学生 "); } }
(2)编译: javac -encoding UTF-8 Hello.java
此时Hello.java和Student.java同时被编译,因为Hello中调用了Student,在编译Hello的过程中发现还需要编译Student
F:\Example_Project\两个类的jar>javac -encoding UTF-8 Hello.java
(3)打jar包
在F:\Example_Project\两个类的jar>下创建META-INF文件夹
F:\Example_Project\两个类的jar>mkdir META-INF
在META-INF下创建MANIFEST.MF
F:\Example_Project\两个类的jar>type nul>META-INF\MANIFEST.MF
这次我们换一种方式直接定义Main-Class。
Manifest-Version: 1.0 Created-By: 1.8.0_121 (Oracle Corporation) Main-Class: Hello
事先准备好上述的MANIFEST.MF文件,并存放在META-INF文件夹下,此时打jar包的命令如下
F:\Example_Project\两个类的jar> jar -cvfm hello.jar META-INF\MANIFEST.MF Hello.class Student.class 已添加清单 正在添加: Hello.class(输入 = 293) (输出 = 223)(压缩了 23%) 正在添加: Student.class(输入 = 408) (输出 = 308)(压缩了 24%)
jar -cvfm hello.jar META-INF\MANIFEST.MF Hello.class Student.class命令解释如下:
表示用第一个文件当做MENIFEST.MF文件,hello.jar作为名称,将Hello.class和Student.class打成jar包。其中多了一个参数m,表示要定义MANIFEST文件
(4)运行 java -jar hello.jar ,此时成功在控制台看到 hello ,成功
F:\Example_Project\两个类的jar> java -jar hello.jar hello,I'm 20级学生
3.有目录结构的jar包——通过引包并调用输出hello
(0)创建文件夹
F:\Example_Project\有目录结构的两个类jar(1)我们将上一个稍稍变化一下,将Student这个类放在edu.hncj.vip包下,Hello放在com包下
修改Hello.java文件
package com; import edu.hncj.vip.Student; class Hello{ public static void main(String[] agrs){ //System.out.println("hello"); Student.speak(); } }
修改Student.java文件,注意需要修改为public公共类
package edu.hncj.vip; public class Student{ public static void speak(){ System.out.println("hello,I'm 20级学生 "); } }
查看当前目录结构
F:\Example_Project\有目录结构的两个类jar>tree . /f /a 卷 工作 的文件夹 PATH 列表 卷序列号为 00000057 5296:88B3 F:\EXAMPLE_PROJECT\有目录结构的两个类JAR +---com | Hello.java | \---edu \---hncj \---vip Student.java
方法步骤
(1)编译Hello.java
javac -encoding UTF-8 -d . com\Hello.java
编译Hello.java时,自动编译Student.java文件
(2)打jar包,同样准备好MANIFEST文件
在F:\Example_Project\两个类的jar>下创建META-INF文件夹
F:\Example_Project\有目录结构的两个类jar>mkdir META-INF
在META-INF下创建MANIFEST.MF
F:\Example_Project\有目录结构的两个类jar>type nul>META-INF\MANIFEST.MF
这次我们换一种方式直接定义Main-Class。注意后面的空行
Manifest-Version: 1.0 Created-By: 1.8.0_121 (Oracle Corporation) Main-Class: com.Hello 此处有空行
事先准备好上述的MANIFEST.MF文件,并存放在META-INF文件夹下,此时打jar包的命令如下
jar -cvfm hello.jar META-INF\MANIFEST.MF com\Hello.class edu\hncj\vip F:\Example_Project\有目录结构的两个类jar>jar -cvfm hello.jar META-INF\MENIFEST.MF com\Hello.class edu\hncj\vip 已添加清单 正在添加: com/Hello.class(输入 = 310) (输出 = 238)(压缩了 23%) 正在添加: edu/hncj/vip/(输入 = 0) (输出 = 0)(存储了 0%) 正在添加: edu/hncj/vip/Student.class(输入 = 421) (输出 = 319)(压缩了 24%) 正在添加: edu/hncj/vip/Student.java(输入 = 146) (输出 = 130)(压缩了 10%)
输出信息表示已经把哪些文件打包在hello.jar中
(3)运行 java -jar hello.jar ,此时成功在控制台看到 hello ,成功
4.制作含有jar文件的jar包
我们将场景稍稍变得复杂一点,看看jar包中需要引入其他jar包的场景
1、两个jar包间相互调用——调用jar外的jar输出hello
最终生成的jar包结构
hello.jar
tom.jar
方法步骤
(0)生成student.jar
准备:创建Student.java文件
public class Student{ public static void speak(){ System.out.println("hello,I'm 20级学生 "); } }
在Student.java同级目录下打开cmd
执行如下命令编译java文件
javac -encoding UTF-8 Student.java
执行jar -cvf student.jar Student.class 命令生成jar包
F:\Example_Project\两个jar相互调 用>javac -encoding UTF-8 Student.java F:\Example_Project\两个jar相互调 用>jar -cvf student.jar Student.class 已添加清单 正在添加: Student.class(输入 = 408) (输出 = 307)(压缩了 24%)
(1)编写一个Hello.java并将其编译成Hello.class,注意,由于Hello里面引用了Student类的speak方法,因此在打jar包时应使用-cp参数,将student.jar包引入
Hello.java文件内容: class Hello{ public static void main(String[] agrs){ //System.out.println("hello"); Student.speak(); } }
执行 javac命令编译Hello.java
如果直接编译Hello.java会提示如下错误
F:\Example_Project\两个jar相互调用>javac Hello.java Hello.java:5: 错误: 找不到符号 Student.speak(); ^ 符号: 变量 Student 位置: 类 Hello 1 个错误
可以在编译Hello.java的同时,把依赖的jar放在classpath下
F:\Example_Project\两个jar相互调 用>javac -cp student.jar Hello.java
这里的 -cp 表示 -classpath,指的是把student.jar加入classpath路径下
(2)将hello.class达成jar包
创建META-INF目录,创建MANIFEST.MF 文件,注意末尾的空行
Manifest-Version: 1.0 Created-By: 1.8.0_181 (Oracle Corporation) Main-Class: Hello 这里有空行
达成jar包,执行jar -cvfm hello.jar META-INF/MANIFEST.MF hello.class
F:\Example_Project\两个jar相互调 用>jar -cvfm hello.jar META-INF/MANIFEST.MF Hello.class 已添加清单 正在添加: Hello.class(输入 = 293) (输出 = 223)(压缩了 23%)
目前可以顺利的达成jar包,但运行时会出错,如下。
(3)此时运行 java -jar 发现报错 ClassNotFoundException:Student
F:\Example_Project\两个jar相互调 用>java -jar hello.jar Exception in thread "main" java.lang.NoClassDefFoundError: Student at Hello.main(Hello.java:5) Caused by: java.lang.ClassNotFoundException: Student at java.net.URLClassLoader.findClass(URLClassLoader.java:381) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ... 1 more
原因很简单,引入jar包需要在MENIFEST.MF文件中配置一个新属性:Class-Path,路径指向你需要的所有jar包
现在打开hello.jar中MENIFEST.MF文件,添加Class-Path。应该变成
Manifest-Version: 1.0 Created-By: 1.8.0_121 (Oracle Corporation) Main-Class: Hello Class-Path: student.jar
(4)好了,修改这个文件,再次运行,发现成功在控制台输出 hello
tips:引入多个jar包,中间用空格隔开
至此,我们可以总结出,命令变化如下
javac -cp xxx.jar 要编译的文件 -d 目标位置
jar -cvfm 命名 MENIFEST文件 要打包的文件1 要打包的文件2
5.jar包中含有jar包——调用jar内的jar输出hello
当项目中我们把所需要的第三方jar包也打进了我们自己的jar包中时,如果仍然按照上述操作方式,会报找不到Class异常。原因就是jar引用不到放在自己内部的jar包。
这种情况的具体实现细节比较复杂,可以先参考这篇文章:
http://www.cnblogs.com/adolfmc/archive/2012/10/07/2713562.html
引用方法1:使用AppClassloader来加载
引用方法2:也可以把需要加载的jar都扔到%JRE_HOME%/lib/ext下面,这个目录下的jar包会在Bootstrap Classloader工作完后由Extension Classloader来加载。非常方便,非常省心。
(1)本部分使用AppClassloader加载
在F:\Example_Project\jar包内含有jar包 的目录下创建edu.hncj.vip.Student.java
package edu.hncj.vip; public class Student{ public static void speak(){ System.out.println("hello,I'm 20级学生 "); } }
(2)生成student.jar文件
编译Student.java文件,执行 javac -d . -encoding UTF-8 edu/hncj/vip/Student.java命令
F:\Example_Project\jar包内含有jar包>javac -d . -encoding UTF-8 edu/hncj/vip/Student.java
生成student.jar包,jar -cvf student.jar edu\hncj\vip\
F:\Example_Project\jar包内含有jar包>jar -cvf student.jar edu\hncj\vip\ 已添加清单 正在添加: edu/hncj/vip/(输入 = 0) (输出 = 0)(存储了 0%) 正在添加: edu/hncj/vip/Student.class(输入 = 421) (输出 = 319)(压缩了 24%) 正在添加: edu/hncj/vip/Student.java(输入 = 146) (输出 = 130)(压缩了 10%)
在 F:\Example_Project\jar包内含有jar包 目录下创建lib目录,把student放置在lib目录下