序言
因为本系列属于我自己独立书写,没有任何成系统的参考资料和教程,基本上可以说是生啃源码,肯定存在不足和纰漏,请多多谅解,本系列基于jdk9,如果和读者手上的版本不同,可以互相参考,原理是相通的,主要的说明我会放到代码的注释中,便于理解
首先,第一步,进入main方法中
import com.sun.tools.javac.Main;
public class Test{
public static void main(String[] args) throws Exception {
Main m=new Main();
m.main(args);
}
}
点开main方法,各种包装过程我就不赘述了,直接进入准备对应的代码
public static int compile(String[] args) {
com.sun.tools.javac.main.Main compiler =
new com.sun.tools.javac.main.Main("javac");
//设置为Javac指令
return compiler.compile(args).exitCode;
//返回值为最终结束状态码
}
public Result compile(String[] args) {
Context context = new Context();
//这是一个容器,用来存储运行过程中需要的各种参数和对象
//其实就是包装过的HashMap
JavacFileManager.preRegister(context);
//对context容器进行第一次设置
Result result = compile(args, context);
//主要方法
if (fileManager instanceof JavacFileManager) {
try {
// A fresh context was created above, so jfm must be a JavacFileManager
((JavacFileManager)fileManager).close();
} catch (IOException ex) {
bugMessage(ex);
}
}
return result;
}
进入JavacFileManager.preRegister(context);
public static void preRegister(Context context) {
//创建一个JavacFileManager对象放入context容器
//并通过构造方法继续对context容器进行设置
context.put(JavaFileManager.class,
(Factory<JavaFileManager>)c -> new JavacFileManager(c, true, null));
//Factory<JavaFileManager>用于延迟加载
}
进入JavacFileManager构造方法
public JavacFileManager(Context context, boolean register, Charset charset) {
super(charset);
if (register)
//将context中对应的元素进行覆盖,消除前面延迟加载的影响
context.put(JavaFileManager.class, this);
//对本类进行设置,这部分内容我留到后面再提,因为如果输入为空
//JavacFileManager类根本不会被赋值,也根本不会调用这个方法
//其次,我们需要先讲解后面的代码,才能讲清楚这一段
setContext(context);
}
回到上文,进入compile(args, context);
public Result compile(String[] args) {
Context context = new Context();
JavacFileManager.preRegister(context); // can't create it until Log has been set up
//进入这里
Result result = compile(args, context);
if (fileManager instanceof JavacFileManager) {
try {
// A fresh context was created above, so jfm must be a JavacFileManager
((JavacFileManager)fileManager).close();
} catch (IOException ex) {
bugMessage(ex);
}
}
return result;
}
开始准备阶段
public Result compile(String[] argv, Context context) {
//如果我们设置了初始输出流
if (stdOut != null) {
context.put(Log.outKey, stdOut);
}
//如果我们设置了异常输出流
if (stdErr != null) {
context.put(Log.errKey, stdErr);
}
//获取日志类,并做进一步的设置
log = Log.instance(context);
进入Log.instance(context)
public static Log instance(Context context) {
Log instance = context.get(logKey);
//如果我们已经设置了日志类
if (instance == null)
//在构造方法中对context进一步设置
instance = new Log(context);
return instance;
}
protected Log(Context context) {
//initWriters(context)是对日志类进一步设置的方法
this(context, initWriters(context));
}
private static Map<WriterKind, PrintWriter> initWriters(Context context) {
PrintWriter out = context.get(outKey);
PrintWriter err = context.get(errKey);
//若为设置默认输出流和错误输出流,设为系统默认流
if (out == null && err == null) {
out = new PrintWriter(System.out, true);
err = new PrintWriter(System.err, true);
return initWriters(out, err);
//进行健壮性判断
} else if (out == null || err == null) {
PrintWriter pw = (out != null) ? out : err;
return initWriters(pw, pw);
} else {
return initWriters(out, err);
}
}
private static Map<WriterKind, PrintWriter> initWriters(PrintWriter out, PrintWriter err) {
//主要是设置日志类内部集合中常量和流的对应关系,没什么好说的,
Map<WriterKind, PrintWriter> writers = new EnumMap<>(WriterKind.class);
writers.put(WriterKind.ERROR, err);
writers.put(WriterKind.WARNING, err);
writers.put(WriterKind.NOTICE, err);
writers.put(WriterKind.STDOUT, out);
writers.put(WriterKind.STDERR, err);
return writers;
}
回到this方法,其实没什么好讲的,无非是尝试到context取值填充到日志类中,后面遇到了我再详细讲解对应的值的含义
private Log(Context context, Map<WriterKind, PrintWriter> writers) {
super(JCDiagnostic.Factory.instance(context));
//将本类(日志类)放入context中
context.put(logKey, this);
//将前文设置的常量和流对应集合放入本类成员变量中
this.writers = writers;
@SuppressWarnings("unchecked") // FIXME
DiagnosticListener<? super JavaFileObject> dl =
context.get(DiagnosticListener.class);
this.diagListener = dl;
diagnosticHandler = new DefaultDiagnosticHandler();
//instance方法查询是否存在本对象,否则将本对象存入
messages = JavacMessages.instance(context);
messages.add(Main.javacBundleName);
//instance方法查询是否存在本对象,否则将本对象存入
final Options options = Options.instance(context);
//对命令行表进行初始化设置
initOptions(options);
//将命令行表设置的方法作为线程接口Runnable的run方法存入自身数组中
options.addListener(() -> initOptions(options));
}