更多内容请关注微信公众号【Java技术江湖】
这是一位阿里 Java 工程师的技术小站,作者黄小斜,专注 Java 相关技术:SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程,偶尔讲点Docker、ELK,同时也分享技术干货和学习经验,致力于Java全栈开发!(关注公众号后回复”资料“即可领取 3T 免费技术学习资源以及我我原创的程序员校招指南、Java学习指南等资源)
**
前言:本文教你怎么用javac和java命令,讲解了classpath的原理,以及如何利用脚本(shell或bat)进行项目部署,离开ide,还原最本质的Java编译运行过程,并用简单的实例展示这些用法。
具体代码在我的GitHub中可以找到
喜欢的点一下星哈谢谢。
文章首发于我的个人博客:
更多关于Java后端学习的内容请到我的CSDN博客上查看:
javac命令初窥
注:以下红色标记的参数在下文中有所讲解。
本部分参考https://www.cnblogs.com/xiazdong/p/3216220.html
用法: javac
其中, 可能的选项包括:
在详细介绍javac命令之前,先看看这个classpath是什么
classpath是什么
在dos下编译java程序,就要用到classpath这个概念,尤其是在没有设置环境变量的时候。classpath就是存放.class等编译后文件的路径。
javac:如果当前你要编译的java文件中引用了其它的类(比如说:继承),但该引用类的.class文件不在当前目录下,这种情况下就需要在javac命令后面加上-classpath参数,通过使用以下三种类型的方法 来指导编译器在编译的时候去指定的路径下查找引用类。
IDE中的classpath
对于一个普通的Javaweb项目,一般有这样的配置:
总结:
(1).何时需要使用-classpath:当你要编译或执行的类引用了其它的类,但被引用类的.class文件不在当前目录下时,就需要通过-classpath来引入类
(2).何时需要指定路径:当你要编译的类所在的目录和你执行javac命令的目录不是同一个目录时,就需要指定源文件的路径(CLASSPATH是用来指定.class路径的,不是用来指定.java文件的路径的)
Java项目和Java web项目的本质区别
(看清IDE及classpath本质)
这里展示一个web项目的.classpath
-g、-g:none、-g:{lines,vars,source}
-bootclasspath、-extdirs
[类文件的搜索路径: C:\Java\jdk1.7.0_25\jre\lib\resources.jar,C:\Java\jdk1.7.0_25 \jre\lib\rt.jar,C:\Java\jdk1.7.0_25\jre\lib\sunrsasign.jar,C:\Java\jdk1.7.0_25\j re\lib\jsse.jar,C:\Java\jdk1.7.0_25\jre\lib\jce.jar,C:\Java\jdk1.7.0_25\jre\lib\ charsets.jar,C:\Java\jdk1.7.0_25\jre\lib\jfr.jar,C:\Java\jdk1.7.0_25\jre\classes ,C:\Java\jdk1.7.0_25\jre\lib\ext\access-bridge-32.jar,C:\Java\jdk1.7.0_25\jre\li b\ext\dnsns.jar,C:\Java\jdk1.7.0_25\jre\lib\ext\jaccess.jar,C:\Java\jdk1.7.0_25\ jre\lib\ext\localedata.jar,C:\Java\jdk1.7.0_25\jre\lib\ext\sunec.jar,C:\Java\jdk 1.7.0_25\jre\lib\ext\sunjce_provider.jar,C:\Java\jdk1.7.0_25\jre\lib\ext\sunmsca pi.jar,C:\Java\jdk1.7.0_25\jre\lib\ext\sunpkcs11.jar,C:\Java\jdk1.7.0_25\jre\lib \ext\zipfs.jar,..\bin]
如果利用 -bootclasspath 重新定义: javac -bootclasspath src Xxx.java,则会出现下面错误:
致命错误: 在类路径或引导类路径中找不到程序包 java.lang
-sourcepath和-classpath(-cp)
?-classpath(-cp)指定你依赖的类的class文件的查找位置。在Linux中,用“:”分隔classpath,而在windows中,用“;”分隔。
?-sourcepath指定你依赖的类的java文件的查找位置。
举个例子,
public class A { public static void main(String[] args) { B b = new B(); b.print(); } } public class B { public void print() { System.out.println("old"); } }
目录结构如下:
sourcepath //此处为当前目录
|-src |-com |- B.java |- A.java |-bin |- B.class //是 B.java
编译后的类文件
如果要编译 A.java,则必须要让编译器找到类B的位置,你可以指定B.class的位置,也可以是B.java的位置,也可以同时都存在。
javac -classpath bin src/A.java //查找到B.class javac -sourcepath src/com src/A.java //查找到B.java javac -sourcepath src/com -classpath bin src/A.java //同时查找到B.class和B.java
如果同时找到了B.class和B.java,则:
?如果B.class和B.java内容一致,则遵循B.class。
?如果B.class和B.java内容不一致,则遵循B.java,并编译B.java。
以上规则可以通过 -verbose选项看出。
-d
?d就是 destination,用于指定.class文件的生成目录,在eclipse中,源文件都在src中,编译的class文件都是在bin目录中。
这里我用来实现一下这个功能,假设项目名称为project,此目录为当前目录,且在src/com目录中有一个Main.java文件。‘
package com; public class Main { public static void main(String[] args) { System.out.println("Hello"); } } javac -d bin src/com/Main.java
上面的语句将Main.class生成在bin/com目录下。
-implicit:{none,class}
?如果有文件为A.java(其中有类A),且在类A中使用了类B,类B在B.java中,则编译A.java时,默认会自动编译B.java,且生成B.class。
?implicit:none:不自动生成隐式引用的类文件。
?implicit:class(默认):自动生成隐式引用的类文件。
public class A { public static void main(String[] args) { B b = new B(); } } public class B { } 如果使用: javac -implicit:none A.java
则不会生成 B.class。
-source和-target
?-source:使用指定版本的JDK编译,比如:-source 1.4表示用JDK1.4的标准编译,如果在源文件中使用了泛型,则用JDK1.4是不能编译通过的。
?-target:指定生成的class文件要运行在哪个JVM版本,以后实际运行的JVM版本必须要高于这个指定的版本。
javac -source 1.4 Xxx.java
javac -target 1.4 Xxx.java
-encoding
默认会使用系统环境的编码,比如我们一般用的中文windows就是GBK编码,所以直接javac时会用GBK编码,而Java文件一般要使用utf-8,如果用GBK就会出现乱码。
?指定源文件的编码格式,如果源文件是UTF-8编码的,而-encoding GBK,则源文件就变成了乱码(特别是有中文时)。
javac -encoding UTF-8 Xxx.java
-verbose
输出详细的编译信息,包括:classpath、加载的类文件信息。
比如,我写了一个最简单的HelloWorld程序,在命令行中输入:
D:\Java>javac -verbose -encoding UTF-8 HelloWorld01.java
输出:
[语法分析开始时间 RegularFileObject[HelloWorld01.java]] [语法分析已完成, 用时 21 毫秒] [源文件的搜索路径: .,D:\大三下\编译原理\cup\java-cup-11a.jar,E:\java\jflex\lib\J //-sourcepath Flex.jar] [类文件的搜索路径: C:\Java\jdk1.7.0_25\jre\lib\resources.jar,C:\Java\jdk1.7.0_25 //-classpath、-bootclasspath、-extdirs 省略............................................ [正在加载ZipFileIndexFileObject[C:\Java\jdk1.7.0_25\lib\ct.sym(META-INF/sym/rt.j ar/java/lang/Object.class)]] [正在加载ZipFileIndexFileObject[C:\Java\jdk1.7.0_25\lib\ct.sym(META-INF/sym/rt.j ar/java/lang/String.class)]] [正在检查Demo] 省略............................................ [已写入RegularFileObject[Demo.class]] [共 447 毫秒]
编写一个程序时,比如写了一句:System.out.println(“hello”),实际上还需要加载:Object、PrintStream、String等类文件,而上面就显示了加载的全部类文件。
其他命令
-J
?传递一些信息给 Java Launcher.
javac -J-Xms48m Xxx.java //set the startup memory to 48M.
-@
则使用下面的命令:
javac @sourcefiles.txt
编译这三个源文件。
使用javac构建项目
一个简单的javac编译
新建两个文件夹,src和 build
src/com/yp/test/HelloWorld.java
build/
├─build └─src └─com └─yp └─test HelloWorld.java
java文件非常简单
package com.yp.test; public class HelloWorld { public static void main(String[] args) { System.out.println("helloWorld"); } }
编译:
javac src/com/yp/test/HelloWorld.java -d build
-d 表示编译到 build文件夹下
查看build文件夹 ├─build │ └─com │ └─yp │ └─test │ HelloWorld.class │ └─src └─com └─yp └─test HelloWorld.java
运行文件
如果引用到多个其他的类,应该怎么做呢 ?
怎么打成jar包?
生成可以运行的jar包
需要指定jar包的应用程序入口点,用-e选项:
E:\codeplace\n_learn\java\javacmd\build> jar cvfe h.jar com.yp.test.HelloWorld * 已添加清单 正在添加: com/(输入 = 0) (输出 = 0)(存储了 0%) 正在添加: com/yp/(输入 = 0) (输出 = 0)(存储了 0%) 正在添加: com/yp/test/(输入 = 0) (输出 = 0)(存储了 0%) 正在添加: com/yp/test/entity/(输入 = 0) (输出 = 0)(存储了 0%) 正在添加: com/yp/test/entity/Cat.class(输入 = 545) (输出 = 319)(压缩了 41%) 正在添加: com/yp/test/HelloWorld.class(输入 = 844) (输出 = 487)(压缩了 42%)
直接运行
java -jar h.jar 额外发现 指定了Main类后,jar包里面的 META-INF/MANIFEST.MF 是这样的, 比原来多了一行Main-Class…. Manifest-Version: 1.0 Created-By: 1.8.0 (Oracle Corporation) Main-Class: com.yp.test.HelloWorld
如果类里有引用jar包呢?
先下一个jar包 这里直接下 log4j
* main函数改成 import com.yp.test.entity.Cat; import org.apache.log4j.Logger; public class HelloWorld { static Logger log = Logger.getLogger(HelloWorld.class); public static void main(String[] args) { Cat c = new Cat("keyboard"); log.info("这是log4j"); System.out.println("hello," + c.getName()); } }
现的文件是这样的
├─build ├─lib │ log4j-1.2.17.jar │ └─src └─com └─yp └─test │ HelloWorld.java │ └─entity Cat.java
这个时候 javac命令要接上 -cp ./lib/*.jar E:\codeplace\n_learn\java\javacmd>javac -encoding "utf8" src/com/yp/test/HelloWorld.java -sourcepath src -d build -g -cp ./lib/*.jar 运行要加上-cp, -cp 选项貌似会把工作目录给换了, 所以要加上 ;../build E:\codeplace\n_learn\java\javacmd\build>java -cp ../lib/log4j-1.2.17.jar;../build com.yp.test.HelloWorld
结果:
log4j:WARN No appenders could be found for logger(com.yp.test.HelloWorld). log4j:WARN Please initialize the log4j system properly. log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info. hello,keyboard
由于没有 log4j的配置文件,所以提示上面的问题,往 build 里面加上 log4j.
再运行
E:\codeplace\n_learn\java\javacmd>java -cp lib/log4j-1.2.17.jar;build com.yp.tes t.HelloWorld 15:19:57,359 INFO [HelloWorld] 这是log4j hello,keyboard
说明:
这个log4j配置文件,习惯的做法是放在src目录下, 在编译过程中 copy到build中的,但根据ant的做法,不是用javac的,而是用来处理,我猜测javac是不能copy的,如果想在命令行直接 使用,应该是用cp命令主动去执行 copy操作
ok 一个简单的java 工程就运行完了
但是 貌似有些繁琐, 需要手动键入 java文件 以及相应的jar包 很是麻烦,
so 可以用 shell 来脚本来简化相关操作
shell 文件整理如下:
#!/bin/bash echo "build start" JAR_PATH=libs BIN_PATH=bin SRC_PATH=src # java文件列表目录 SRC_FILE_LIST_PATH=src/sources.list #生所有的java文件列表 放入列表文件中 rm -f $SRC_PATH/sources find $SRC_PATH/ -name *.java > $SRC_FILE_LIST_PATH #删除旧的编译文件 生成bin目录 rm -rf $BIN_PATH/ mkdir $BIN_PATH/ #生成依赖jar包 列表 for file in ${JAR_PATH}/*.jar; do jarfile=${jarfile}:${file} done echo "jarfile = "$jarfile #编译 通过-cp指定所有的引用jar包,将src下的所有java文件进行编译 javac -d $BIN_PATH/ -cp $jarfile @$SRC_FILE_LIST_PATH #运行 通过-cp指定所有的引用jar包,指定入口函数运行 java -cp $BIN_PATH$jarfile com.zuiapps.danmaku.server.Main
编译 : 1. 需要编译所有的java文件 2. 依赖的java 包都需要加入到 classpath 中去 3. 最后设置 编译后的 class 文件存放目录 即 -d bin/ 4. java文件过多是可以使用 @$SRC_FILE_LIST_PATH 把他们放到一个文件中去 运行: 1.需要吧 编译时设置的bin目录和 所有jar包加入到 classpath 中去
javap
import java.awt.*; import java.applet.*; public class DocFooter extends Applet { String date; String email; public void init() { resize(500,100); date = getParameter("LAST_UPDATED"); email = getParameter("EMAIL"); } }
在命令行上键入javap DocFooter后,输出结果如下
Compiled from “DocFooter.java”
public class DocFooter extends java.applet.Applet { java.lang.String date; java.lang.String email; public DocFooter(); public void init(); }
如果加入了-c,即javap -c DocFooter,那么输出结果如下
Compiled from “DocFooter.java”
public class DocFooter extends java.applet.Applet { java.lang.String date; java.lang.String email; public DocFooter(); Code: 0: aload_0 1: invokespecial #1 // Method java/applet/Applet."":()V 4: return public void init(); Code: 0: aload_0 1: sipush 500 4: bipush 100 6: invokevirtual #2 // Method resize:(II)V 9: aload_0 10: aload_0 11: ldc #3 // String LAST_UPDATED 13: invokevirtual #4 // Method getParameter:(Ljava/lang/String;)Ljava/lang/String; 16: putfield #5 // Field date:Ljava/lang/String; 19: aload_0 20: aload_0 21: ldc #6 // String EMAIL 23: invokevirtual #4 // Method getParameter:(Ljava/lang/String;)Ljava/lang/String; 26: putfield #7 // Field email:Ljava/lang/String; 29: return }
上面输出的内容就是字节码。
用法摘要
-help 帮助
-l 输出行和变量的表
-public 只输出public方法和域
-protected 只输出public和protected类和成员
-package 只输出包,public和protected类和成员,这是默认的
-p -private 输出所有类和成员
-s 输出内部类型签名
-c 输出分解后的代码,例如,类中每一个方法内,包含java字节码的指令,
-verbose 输出栈大小,方法参数的个数
-constants 输出静态final常量
总结
javap可以用于反编译和查看编译器编译后的字节码。平时一般用javap -c比较多,该命令用于列出每个方法所执行的JVM指令,并显示每个方法的字节码的实际作用。可以通过字节码和源代码的对比,深入分析java的编译原理,了解和解决各种Java原理级别的问题。