如果你也有一个老旧的Java Web应用,因为各种原因,代码库中的代码是不完整的,所以每次上线只能增量部署,或者研发规范里就要求增量部署,在这种情况下如何实现自动化的编译和部署呢?下面给出一个可行的方案:
1、部署前,将需要部署的代码合并到deliver分支
2、比较deliver分支和master分支的差异,得到差异列表之后,用于后续的编译
3、编译差异文件时,需要将应用所依赖的jar,已经部署的class文件等加入到CLASSPATH
4、如果各测试环境也使用这一套自动部署的方案,可能还需要考虑剥离环境相关的配置到配置文件中,并根据环境分目录存放,具体可以参考maven的profile机制
核心代码(代码结构请自行优化 :D):
1、比较两个分支的差异(忽略whitespace带来的差异):
try (Git git = Git.open(gitRepoFile);
ByteArrayOutputStream out = new ByteArrayOutputStream();
DiffFormatter df = new DiffFormatter(out);) {
Repository repository = git.getRepository();
ObjectReader reader = repository.newObjectReader();
String branchName = repository.getBranch();
ObjectId masterId = repository.resolve("remotes/origin/master^{tree}");
ObjectId branchId = repository.resolve(branchName + "^{tree}");
CanonicalTreeParser masterTreeParser = new CanonicalTreeParser();
masterTreeParser.reset(reader, masterId);
CanonicalTreeParser branchTreeParser = new CanonicalTreeParser();
branchTreeParser.reset(reader, branchId);
List<DiffEntry> diffs = git.diff()
.setNewTree(branchTreeParser)
.setOldTree(masterTreeParser)
.call();
Map<String, List<String>> diffFileMap = new HashMap<>();
List<String> changeFileList = new ArrayList<>();
List<String> deleteFileList = new ArrayList<>();
df.setDiffComparator(RawTextComparator.WS_IGNORE_ALL);
df.setRepository(git.getRepository());
for (DiffEntry diffEntry : diffs) {
df.format(diffEntry);
FileHeader fileHeader = df.toFileHeader(diffEntry);
@SuppressWarnings("unchecked")
List<HunkHeader> hunks = (List<HunkHeader>) fileHeader.getHunks();
int changedSize = 0;
for (HunkHeader hunkHeader : hunks) {
EditList editList = hunkHeader.toEditList();
for (Edit edit : editList) {
changedSize += edit.getLengthA() + edit.getLengthB();
}
}
if (changedSize > 0) {
ChangeType changeType = diffEntry.getChangeType();
if (ChangeType.DELETE.equals(changeType)) {
String oldFilePath = diffEntry.getOldPath();
log.info("{}|{}", changeType.name(), oldFilePath);
deleteFileList.add(oldFilePath);
} else {
String newFilePath = diffEntry.getNewPath();
log.info("{}|{}", changeType.name(), newFilePath);
changeFileList.add(newFilePath);
}
}
}
diffFileMap.put("change", changeFileList);
diffFileMap.put("delete", deleteFileList);
return diffFileMap;
}
2、编译:
Iterable<String> options = Arrays.asList(
"-classpath", classpath,
"-encoding", encoding,
"-source", jdkVersion,
"-target", jdkVersion,
"-d", targetPath);
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
log.info("classpath:{}", classpath);
log.info("targetPath:{}", targetPath);
try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);) {
Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(fileList);
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
CompilationTask compilationTask = compiler
.getTask(null, fileManager, diagnostics, options, null, compilationUnits);
if (!compilationTask.call()) {
log.error("JavaCompiler Build failed:");
for (Diagnostic<?> diagnostic : diagnostics.getDiagnostics()) {
long line = diagnostic.getLineNumber();
String source = diagnostic.getSource() != null ? diagnostic.getSource().toString() : "";
String kind = diagnostic.getKind().name();
String message = diagnostic.getMessage(null);
log.info("{} on line:{} in {}.", kind, line, source);
log.info("message:{}", message);
}
return false;
}
}
要实现完整的功能,还需要根据应用的具体情况完善,比如:从运行环境同步删除代码库中删除的文件;从代码库拉取文件时,用fetch,reset的方式;还可以借助Marathon、Mesos、Docker运行应用等。