当程序有多个启动入口时,需要根据不同的启动类来决定方法是否执行,此时就需要获取启动类。
首先根据系统参数 sun.java.command 来获取启动类,如果以jar包方式启动,则获取到的就是jar包名称,此时需要从线程栈中获取main方法或者从jar包的元数据中获取。
java代码如下:
java
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
@Slf4j
public class MainClassUtil {
private static String MAIN_CLASS ;
/**
* 忽略的第三方引导类启动
*/
private static final Set<String> IGNORE_MAIN_CLASS = new HashSet<>(Arrays.asList("org.springframework.boot.loader.JarLauncher"
, "org.springframework.boot.loader.WarLauncher", "org.springframework.boot.loader.PropertiesLauncher"));
/**
* 查找启动类
* @return 找到返回具体的类名称,否则返回空
*/
public static String getStartMainClass() {
if (MAIN_CLASS != null && !MAIN_CLASS.isEmpty()) {
return MAIN_CLASS;
}
// 系统参数
String clazz = System.getProperty("sun.java.command");
if (clazz.contains(".jar")) {
// 通过堆栈跟踪
clazz = fromStackTrace();
// 通过 MANIFEST.MF 查找
if(clazz == null || clazz.isEmpty()){
clazz = fromJarFile();
}
}
MAIN_CLASS = clazz;
log.info("start class :{}",MAIN_CLASS);
return clazz;
}
/**
* 从jar文件 META-INF/MANIFEST.MF 获取
* @return 启动类,未找到返回空
*/
private static String fromJarFile() {
String javaCmd = System.getProperty("sun.java.command");
String jarName = javaCmd.substring(0,javaCmd.indexOf(".jar") + 4);
String userDir = System.getProperty("user.dir");
// 拼接全路径
if(!jarName.contains("/") && !jarName.contains("\\")){
jarName = userDir + File.separator + jarName;
}
String clazz = null;
log.info("jarName-------{}",jarName);
try(JarFile jarFile = new JarFile(jarName)){
Manifest manifest = jarFile.getManifest();
Attributes attributes = manifest.getMainAttributes();
clazz = attributes.getValue("Main-Class");
if(IGNORE_MAIN_CLASS.contains(clazz)){
clazz = attributes.getValue("Start-Class");
}
}catch (Exception e){
log.error("jar包解析异常",e);
}
return clazz;
}
/**
* 栈内信息查找
* @return 启动类,未找到返回空
*/
private static String fromStackTrace(){
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
List<String> mainMethod = new ArrayList<>();
String methodName, cl;
for (StackTraceElement stack : stackTrace) {
methodName = stack.getMethodName();
cl = stack.getClassName();
if ("main".equals(methodName) && !IGNORE_MAIN_CLASS.contains(cl)) {
try {
if (hasStandardMain(cl)) {
mainMethod.add(cl);
}
} catch (Exception ignore) {
}
}
}
log.info("包含main方法的类:{}",mainMethod);
return mainMethod.isEmpty() ? null : mainMethod.get(mainMethod.size() - 1);
}
/**
* 判断是否有main方法
* @param clazz 类名
* @return true 有 false 没有
*/
private static boolean hasStandardMain(String clazz) throws Exception {
Class<?> aClass = Class.forName(clazz);
Method main = aClass.getDeclaredMethod("main", String[].class);
Class<?> returnType = main.getReturnType();
int modifiers = main.getModifiers();
return Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && (returnType.equals(void.class) || returnType.equals(Void.class)) ;
}
}