不知道大家在开发中,有没有过下面这些疑问。有的话,今天就一次解答清楚。 如何使用javac命令编译一个项目? java或者javac的一些参数到底有什么用? idea或者maven是如何编译java项目的?(你可能猜测底层是javac,但是你没有证据)。 自己能不能写一个程序,去编译一个项目。 public  class  Hello  { public  static  void  main ( String [ ]  args)  { System . out. println ( "Hello World!" ) ; } 
} java Hello 上面的命令是通过javac命令编译一个java文件,它会生成一个Hello.class文件。然后使用java命令运行Hello程序。 package  com. maple ; public  class  Hello  { public  static  void  main ( String [ ]  args)  { System . out. println ( "Hello World!" ) ; } 
} 然后使用java com.maple.Hello运行。但是你会发现报错了。 java  Hello
错误: 找不到或无法加载主类 Hellojava  com.maple.Hello
错误: 找不到或无法加载主类 com.maple.Hello
需要将编译之后的Hello.class文件移动到目录com/maple中去,并在com的同级目录执行java com.maple.Hello即可正常执行。 – com   在com的上级目录执行。java命令会解析待执行的全类名,并在目标目录中选择class文件。 javac java文件路径.java java 需要执行的带有main方法的全限定类名 带包名的需要在顶级包目录执行。 使用全类名(带包路径 例如:com.maple.Hello)  运行的类文件从classpath中找。没有指定默认当前目录。 例如我现在在D:\test目录下有com\maple\Hello.那么我可以在test目录下正常执行java com.maple.Hello. 同时也可以在任意目录执行java -cp D:\test com.maple.Hello.指定classpath执行。 也可以先设置classpath $env:classpath="$env:classhatp;d:\test",然后再任意目录执行java com.maple.Hello  参考文档:https://docs.oracle.com/en/java/javase/11/tools/javac.html#GUID-AEEC9F07-CB49-4E96-8BC7-BCC2C7F725C9 用法: javac < options>  < source files> 
其中, 可能的选项包括:
-g                          生成所有调试信息
-g:none                     不生成任何调试信息
-g:{ lines,vars,source}      只生成某些调试信息
-nowarn                     不生成任何警告
-verbose                    输出有关编译器正在执行的操作的消息
-deprecation                输出使用已过时的 API 的源位置
-classpath  < 路径>             指定查找用户类文件和注释处理程序的位置
-cp  < 路径>                    指定查找用户类文件和注释处理程序的位置
-sourcepath  < 路径>            指定查找输入源文件的位置
-bootclasspath  < 路径>         覆盖引导类文件的位置
-extdirs  < 目录>               覆盖所安装扩展的位置
-endorseddirs  < 目录>          覆盖签名的标准路径的位置
-proc:{ none,only}           控制是否执行注释处理和/或编译。
-processor  < class1 >[ ,< class2 >< class3 >.. .]  要运行的注释处理程序的名称;  绕过默认的搜索进程
-processorpath  < 路径>         指定查找注释处理程序的位置
-parameters                 生成元数据以用于方法参数的反射
-d  < 目录>                     指定放置生成的类文件的位置
-s  < 目录>                     指定放置生成的源文件的位置
-h  < 目录>                     指定放置生成的本机标头文件的位置
-implicit:{ none,class}      指定是否为隐式引用文件生成类文件
-encoding  < 编码>              指定源文件使用的字符编码
-source  < 发行版>               提供与指定发行版的源兼容性
-target  < 发行版>               生成特定 VM 版本的类文件
-profile  < 配置文件>             请确保使用的 API 在指定的配置文件中可用
-version                    版本信息
-help                       输出标准选项的提要
-A关键字[ = 值]                   传递给注释处理程序的选项
-X                          输出非标准选项的提要
-J< 标记>                      直接将 < 标记>  传递给运行时系统
-Werror                     出现警告时终止编译
@< 文件名>                      从文件读取选项和文件名
例 javac -d target/ -cp lib/ -sourcepath lib1 Test.java javac -d 【class文件生成目录】 -cp 【使用到的class依赖位置】-sourcepath 【依赖的其他类源路径】【待编译的java文件】 实现一个简单的编译器,来编译自己的项目。 编译项目,文件都是多个呢,那么javac要如何编译多个java文件呢。第一种方式,可以直接在javac后跟上多个java文件路径,就能编译多个。或者使用@文件名,在文件中指定要编译的java文件。 然后还有一个就是依赖问题,如果项目引用了其他依赖,那么就需要在编译时通过-cp将依赖库添加的classpath中。 在一个就是文件编码,我们使用中文,需要使用-encoding指定utf-8. 通过-d参数指定class文件输出目录。 最终效果如下 
 
同时如果有外部依赖,可以使用-cp指定依赖库地址。 简单代码如下: package  com. maple. compiler ; import  java. io.  * ; 
import  java. nio. file.  * ; 
import  java. nio. file. attribute.  BasicFileAttributes ; 
import  java. util.  ArrayList ; 
import  java. util.  Arrays ; 
import  java. util.  List ; 
import  java. util. stream.  Collectors ; 
public  class  MyJavaCompilerTool  { private  final  Path  projectPath; private  final  Path  outputPath; private  final  List < String > ; public  MyJavaCompilerTool ( String  projectPath,  String  outputPath,  List < String > )  { this . projectPath =  Paths . get ( projectPath) . toAbsolutePath ( ) ; this . outputPath =  outputPath !=  null  ?  Paths . get ( outputPath) . toAbsolutePath ( )  : this . projectPath. resolve ( "output" ) ; this . additionalJavacArgs =  additionalJavacArgs !=  null  ?  additionalJavacArgs :  new  ArrayList < > ( ) ; } public  void  compile ( )  throws  IOException ,  InterruptedException  { String  canCompile =  isProjectPath ( projectPath) ; if  ( ! canCompile. isEmpty ( ) )  { System . err. println ( canCompile) ; return ; } System . out. println ( "\n===========================【创建输出目录】=============================" ) ; System . out. println ( outputPath) ; Files . createDirectories ( outputPath) ; System . out. println ( "\n===========================【获取待编译文件】=============================" ) ; List < Path > =  findJavaFiles ( projectPath) ; System . out. println ( javaFiles) ; if  ( javaFiles. isEmpty ( ) )  { System . out. println ( "No Java files found in the project directory." ) ; return ; } List < String > =  new  ArrayList < > ( ) ; command. add ( "javac" ) ; command. add ( "-encoding" ) ; command. add ( "utf-8" ) ; command. addAll ( additionalJavacArgs) ; command. add ( "-d" ) ; command. add ( outputPath. toString ( ) ) ; command. addAll ( javaFiles. stream ( ) . map ( Path :: toString ) . collect ( Collectors . toList ( ) ) ) ; String  commandString =  command. stream ( ) . map ( arg ->  arg. contains ( " " )  ?  "\""  +  arg +  "\""  :  arg) . collect ( Collectors . joining ( " " ) ) ; System . out. println ( "\n===========================【开始编译】=============================" ) ; System . out. println ( "Executing command: "  +  commandString) ; ProcessBuilder  processBuilder =  new  ProcessBuilder ( command) ; processBuilder. redirectErrorStream ( true ) ; Process  process =  processBuilder. start ( ) ; try  ( BufferedReader  reader =  new  BufferedReader ( new  InputStreamReader ( process. getInputStream ( ) ) ) )  { String  line; while  ( ( line =  reader. readLine ( ) )  !=  null )  { System . out. println ( line) ; } } int  exitCode =  process. waitFor ( ) ; if  ( exitCode ==  0 )  { System . out. println ( "\n===========================【编译成功】=============================" ) ; System . out. println ( "Compilation successful. Output directory: "  +  outputPath) ; }  else  { System . err. println ( "\n===========================【编译失败】=============================" ) ; System . err. println ( "Compilation failed with exit code: "  +  exitCode) ; } } private  List < Path > findJavaFiles ( Path  directory)  throws  IOException  { List < Path > =  new  ArrayList < > ( ) ; Files . walkFileTree ( directory,  new  SimpleFileVisitor < Path > ( )  { @Override public  FileVisitResult  visitFile ( Path  file,  BasicFileAttributes  attrs)  { if  ( file. toString ( ) . endsWith ( ".java" ) )  { javaFiles. add ( file) ; } return  FileVisitResult . CONTINUE ; } } ) ; return  javaFiles; } private  static  String  isProjectPath ( Path  directory)  { File  file =  directory. toFile ( ) ; File [ ]  subFileList =  file. listFiles ( ) ; List < String > =  Arrays . asList ( "java" ,  "com" ,  "main" ,  "src" ) ; String  canCompile =  "项目路径【"  +  directory +  "】不支持编译 项目路径下请包含下列其中一种文件夹: "  +  String . join ( " " ,  supportFileList) ; if  ( subFileList !=  null )  { for  ( File  subFile :  subFileList)  { if  ( subFile. isDirectory ( )  &&  supportFileList. contains ( subFile. getName ( ) ) )  { canCompile =  "" ; break ; } } } return  canCompile; } public  static  void  main ( String [ ]  args)  { String  projectPath; if  ( args. length <  1 )  { System . out. println ( "Usage: java MyJavaCompilerTool [projectPath:缺省时为当前目录] [javac args...]" ) ; projectPath =  System . getProperty ( "user.dir" ) ; }  else  { projectPath =  args[ 0 ] ; } if  ( projectPath. startsWith ( "C:" ) )  { System . out. println ( "C盘路径,不支持编译" ) ; return ; } List < String > =  args. length >  1  ?  Arrays . asList ( args) . subList ( 1 ,  args. length)  :  new  ArrayList < > ( ) ; try  { System . out. println ( "\n===========================【开始执行】=============================" ) ; MyJavaCompilerTool  compiler =  new  MyJavaCompilerTool ( projectPath,  null ,  javacArgs) ; compiler. compile ( ) ; }  catch  ( IOException  |  InterruptedException  e)  { System . err. println ( e. getMessage ( )  +  " detail: "  +  e) ; } } 
} 
maven定义了一系列的生命周期,这些生命周期我们可以理解为是抽象的接口,实现具体功能是通过插件来实现的。对应的maven插件实现了某个生命周期的能力。 例如maven中的compile生命周期,它对应的默认实现就是maven-compiler-plugin这个插件。 参考:https://maven.apache.org/developers/mojo-api-specification.html 插件由一个或多个 Mojo 组成,每个 Mojo 都是插件目标之一的实现。Mojo 必须有一个名为 execute 的方法,该方法不声明任何参数,并且具有 void 返回类型。 简单理解插件的入口就是实现了Mojo类的execute方法。例如编译插件的入口就是CompilerMojo.exeute() 
 
首先确保maven使用的插件和你现在的源代码项目版本一致 首先查看maven插件的版本 去github下载对应版本的插件(https://github.com/apache/maven-compiler-plugin/tags)  进入一个普通项目,作为maven调试的入口。在pom同级目录下执行mvnDebug compile,然后maven会输出一个端口,复制这个端口号。  打开maven-compiler-plugin源代码项目,添加一个远程jvm调试,端口号使用刚刚的端口号。 
 
在CompilerMojo类上的execute方法第一行打断点,然后调试启动。断点就进来了。 
 
你可能也注意到右键maven的生命周期,会有一个调试的选项,但是点击调试是没有效果的。 
 
这是因为你的项目中没有maven的源代码,无法添加断点,只要将maven插件的依赖引入即可。 直接将maven-compiler-plugin依赖添加到dependencies中。 < dependencies> < dependency> < groupId> </ groupId> < artifactId> </ artifactId> < version> </ version> </ dependency> </ dependencies> 然后搜索CompilerMojo类,在方法开始处打上断点。 
 
再调试启动maven的生命周期,断点就进来了,如果你不是源代码,而是class文件,可以点击右上角下载源代码。 
 
前面有一些判断增量编译,是否需要编译的逻辑啥的,不是这次的重点,就略过了。最终走到编译器对象的执行编译方法。 
 
然后到构建的构建编译命令这里buildCompilerArguments.可以看到这里的args就是javac 命令后面的各种参数。 
 
最终通过javac执行参数+待编译的文件构建了一个命令行任务,执行编译,就是调用的javac。 
 
如果执行不到编译步骤,记得先clean下。 idea是如何编译的呢,今天就来一探究竟。使用内部模式点击编译按钮,看看他关联的Action类。 按住ctrl + alt点击构建项目小锤子。可以看到他关联的是CompileDirtyAction类。逻辑也很简单,就是获取一个项目任务管理器,然后执行了一个构建所有模块的方法。 
 
它会通过一个线程池提交任务,然后走到com.intellij.compiler.impl.CompileDriver#startup方法,然后执行到在外部程序中编译。 
 
 
 
可以看到运行的实际是一个java程序,我们看看这个java程序到底是什么,我把其他不重要的参数去掉,看看命令到底长啥样 "D:\Program Dev Kit\JDK\jdk8u412-b08\b in\java.exe" org.jetbrains.jps.cmdline.BuildMain 127.0 .0.1 59075  25329350 -c1e6-4849-9492-63e97408f1b2 D:/idea-source-code/intellij-community-idea-222.3345.118/system/idea/compile-server
可以看到这里实际执行了BuildMain这个类。由于这里是另外一个进程了,我们不能直接打断点到这里,代码是走不进来的。就需要使用构建调试和远程jvm调试了。 调试构建过程是idea提供的一种机制,可以使我们在运行构建时去调试代码。如何知道有这个扩展点呢。 在构建OSProcessHandler系统命令行处理程序之前,有一段代码,判断myBuildProcessDebuggingEnabled,也就是是否开启调试构建过程。 
 
现在知道了有这样一个判断,那要如何开启呢,我们就知道这个变量的赋值处,从下面的图片可以看到这个变量来源于这个一个action。找到关键key DebugBuildProcess 
 
在随处搜索中搜索操作(Action),输入DebugBuildProcess,然后打开调试构建过程开关(需要调试的idea中)。 
 
可以看到监听的端口是从idea的注册表中获取的,如果没有,就会随机分配一个。   复制获取端口的key compiler.process.debug.port,按ctrl+shift+alt+/打开idea的注册表(被调试的idea),在其中搜索,然后修改端口号。 
 
新增一个远程jvm调试,将端口号改成刚刚的端口号。 
 
在被调试的idea中点击小锤子,开始构建项目。等待idea执行到在外呼程序构建时,也就是使用java命令运行BuildMain之后,在被调试的idea的构建详情中就会显示等待调试,监听xxx端口。 
 
然后在有源代码的idea中运行远程Jvm调试程序。运行之后就可以看到进入了程序。 
 
继续执行,会运行到org.jetbrains.jps.incremental.IncProjectBuilder#runBuild方法。 
 
最终运行到org.jetbrains.jps.incremental.java.JavaBuilder#compileJava,这里可以看到之中调用了javac来进行编译。 
 
这里并不是直接使用javac命令,而是使用了tools包下的javaCompiler,但其实他和javac是一样的。这里同样有编译java文件最重要的几个元素,执行javac的相关参数,以及要编译的类。