相信有一些小伙伴会遇到这样的需求:我们希望针对一部分引用定制化jar包的java文件,通过后台代码的方式,将java源文件编译之后并打包成jar包,并最终返回jar包所在的目录,通过FileInputStream的方式,在配置前台就可以将此jar包导出!
在某些特定场景:比如能力开放平台访问SDK,或者定制化RPC框架的客户端SDK,如果我们可以将此客户端远程调用的相关配置文件(比如zk地址,或者token认证代码),一并以数据库的形式配置到前台界面,然后通过此sdk下载的模式,可以快速实现我平台系统和第三方厂家的快速接入使用!
话不多说,下面就上代码!
本组件主要包含三大部分,首先是主方法:
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.tools.*;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 将源码编译后生成jar包
* @author wenyn
* @since 2020-05-18
*/
public class BmgJarPackageUtil {
private static final Log log = LogFactory.getLog(BmgJarPackageUtil.class);
// 源码路径
private static String source = ApplicationConfig.getVal("bmg.compile.source");
// 打jar包的路径
private static String bin = ApplicationConfig.getVal("bmg.compile.bin");
// 依赖包路径
private static String lib = ApplicationConfig.getVal("bmg.compile.lib");
/**
* 判断字符串是否为空 有值为true 空为:false
*/
private static boolean isnull(String str) {
if (null == str) {
return false;
} else if ("".equals(str)) {
return false;
} else if (str.equals("null")) {
return false;
} else {
return true;
}
}
/**
* 编译java文件
*
* @param encoding 编译编码
* @param jars 需要加载的jar
* @param filePath 文件或者目录(若为目录,自动递归编译)
* @param sourceDir java源文件存放目录
* @param targetDir 编译后class类文件存放目录
* @param diagnostics 存放编译过程中的错误信息
* @return
* @throws Exception
*/
private static boolean compiler(String encoding, String jars, String filePath, String sourceDir, String targetDir, DiagnosticCollector<JavaFileObject> diagnostics, String sign)
throws Exception {
// 获取编译器实例
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
// 获取标准文件管理器实例
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
try {
if (!isnull(filePath) && !isnull(sourceDir) && !isnull(targetDir)) {
return false;
}
// 得到filePath目录下的所有java源文件
File sourceFile = new File(filePath);
List<File> sourceFileList = new ArrayList<File>();
getSourceFiles(sourceFile, sourceFileList, targetDir, sign);
// 没有java文件,直接返回
if (sourceFileList.size() == 0) {
System.out.println(filePath + "目录下查找不到任何java文件");
return false;
}
// 获取要编译的编译单元
Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(sourceFileList);
/**
* 编译选项,在编译java文件时,编译程序会自动的去寻找java文件引用的其他的java源文件或者class。 -sourcepath选项就是定义java源文件的查找目录, -classpath选项就是定义class文件的查找目录。
*/
Iterable<String> options = Arrays.asList("-encoding", encoding, "-classpath", jars, "-d", targetDir, "-sourcepath", sourceDir);
JavaCompiler.CompilationTask compilationTask = compiler.getTask(null, fileManager, diagnostics, options, null, compilationUnits);
// 运行编译任务
return compilationTask.call();
} finally {
fileManager.close();
}
}
/**
* 查找该目录下的所有的java文件
*
* @param sourceFile
* @param sourceFileList
* @throws Exception
*/
private static void getSourceFiles(File sourceFile, List<File> sourceFileList, String targetDir, String sign) throws Exception {
if (sourceFile.exists() && sourceFileList != null) {//文件或者目录必须存在
if (sourceFile.isDirectory()) {// 若file对象为目录
// 得到该目录下以.java结尾的文件或者目录
File[] childrenFiles = sourceFile.listFiles(new FileFilter() {
public boolean accept(File pathname) {
if (pathname.isDirectory()) {
try {
String cpSrc = pathname.getPath();
String newTarget = targetDir.substring(0, targetDir.lastIndexOf(sign)-1);
String cpTarget = newTarget + pathname.getPath().substring(pathname.getPath().indexOf("src") + 3, pathname.getPath().length());
new CopyDirectory().copyDirectiory(cpSrc, cpTarget);
} catch (IOException e) {
log.error("BmgJarPackageUtil.getSourceFiles--{}文件夹拷贝出现异常...", e);
return false;
}
return true;
} else {
String name = pathname.getName();
if (name.endsWith(".java") ? true : false) {
return true;
}
try {
String newTarget = targetDir.substring(0, targetDir.lastIndexOf(sign)-1);
String cpTarget = newTarget + pathname.getPath().substring(pathname.getPath().indexOf("src") + 3, pathname.getPath().length());
new CopyDirectory().copyFile(pathname, new File(cpTarget));
} catch (IOException e) {
log.error("BmgJarPackageUtil.getSourceFiles--{}文件拷贝出现异常...", e);
return false;
}
return false;
}
}
});
// 递归调用
for (File childFile : childrenFiles) {
getSourceFiles(childFile, sourceFileList, targetDir, sign);
}
} else {// 若file对象为文件
sourceFileList.add(sourceFile);
}
}
}
/**
* 查找该目录下的所有的jar文件
*
* @param jarPath
* @throws Exception
*/
private static String getJarFiles(String jarPath) throws Exception {
StringBuilder jarsBuilder = new StringBuilder();
File sourceFile = new File(jarPath);
if (sourceFile.exists()) {// 文件或者目录必须存在
if (sourceFile.isDirectory()) {// 若file对象为目录
// 得到该目录下以.java结尾的文件或者目录
sourceFile.listFiles(new FileFilter() {
public boolean accept(File pathname) {
if (pathname.isDirectory()) {
return true;
} else {
String name = pathname.getName();
if (name.endsWith(".jar") ? true : false) {
jarsBuilder.append(pathname.getPath()).append(";");
return true;
}
return false;
}
}
});
}
}
return jarsBuilder.toString();
}
/**
* 获取所有class文件,并将其变为Class对象
* @param jarFileCreator
* @param rootDir
* @param targetDir
*/
private static void getClass(JarFileCreator jarFileCreator, String rootDir, String targetDir){
File classDir = new File(targetDir);
if(classDir.isDirectory()){
File[] files = classDir.listFiles();
for(File file :files){
getClass(jarFileCreator, rootDir, file.getPath());
}
} else {
String name = classDir.getName();
// class文件才可以加入编译路径
if(name.endsWith(".class")){
ThirdClassLoader thirdClassLoader = new ThirdClassLoader(rootDir);
Class clazz = thirdClassLoader.findClass(targetDir);
jarFileCreator.addClass(clazz, targetDir);
}
}
}
/**
* 将class文件打包成jar包
* @param jarPath
* @param bin
* @throws Exception
*/
private static void writeNewJar(String jarPath, String bin) throws Exception{
JarFileCreator jarFileCreator = new JarFileCreator(new File(jarPath));
getClass(jarFileCreator, bin, bin);
jarFileCreator.createJarFile();
}
/**
* 将源码打包成jar
* @param sign
* @param jarName
* @throws Exception
*/
public static String packageJar(String sign, String jarName) throws Exception{
try {
String newSource = source + File.separator + sign;
String newBin = bin + File.separator + sign;
String newJarPath = newBin + File.separator + jarName;
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
boolean compilerResult = compiler("UTF-8", getJarFiles(lib), newSource, newSource, newBin, diagnostics, sign);
if (!compilerResult) {
log.error("BmgJarPackageUtil.compile--{}源码编译失败...");
for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
log.error("BmgJarPackageUtil.compile--{}" + diagnostic.getMessage(null));
}
} else {
log.info("BmgJarPackageUtil.compile--{}源码编译成功,准备打包..");
writeNewJar(newJarPath, newBin);
// 最终返回新生成的jar包路径
return newJarPath;
}
} catch (Exception e) {
log.error("BmgJarPackageUtil.compile--{}源码编译出现异常..", e);
}
return null;
}
public static void main(String[] args) {
try {
// 生成源码的时候,源码路径必须具备唯一性,防止多个请求同时过来,不同源码之间相互影响
String path = packageJar("222", "test333.jar");
System.out.println(path);
} catch (Exception e) {
e.printStackTrace();
}
}
}
此主类主要作用有三点:
1)获取依赖的Jar包--getJarFiles
2)获取所有源码类并复制到目标目录--getSourceFiles
3)在目标目录编译源码--compiler
4)在目标目录获取所有class文件--getClass
6)生成jar包--writeNewJar
网上大多数提供的打jar包,要么是依赖ant,要么就是依赖maven插件,少数几个通过java代码来编译的,也没结合打jar包过程,而且还仅仅只能打那种只有jdk依赖的工程,功能比较单一。
我在网上找了不少相关代码,做了整体的梳理和重新整理,并形成目前比较有效的工具代码,并在平台上分享出来,多谢浏览!
另外,整体代码,由于我今天刚写博客,还未审核通过,等审核通过,我会贴上链接地址!
文中用到的相关文献如下所示:
http://www.xwood.net/_site_domain_/_root/5870/5874/t_c274926.html
https://blog.csdn.net/xiaozaq/article/details/54350239
https://blog.csdn.net/qq_20641565/article/details/78744677
https://www.cnblogs.com/yang3wei/archive/2012/04/04/2739801.html