mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-09-23 09:49:01 +00:00
Merge branch '3.5.x'
Closes gh-47310
This commit is contained in:
+26
-3
@@ -62,6 +62,8 @@ public abstract class MainClassFinder {
|
||||
|
||||
private static final Type MAIN_METHOD_TYPE = Type.getMethodType(Type.VOID_TYPE, STRING_ARRAY_TYPE);
|
||||
|
||||
private static final Type PARAMETERLESS_MAIN_METHOD_TYPE = Type.getMethodType(Type.VOID_TYPE);
|
||||
|
||||
private static final String MAIN_METHOD_NAME = "main";
|
||||
|
||||
private static final FileFilter CLASS_FILE_FILTER = MainClassFinder::isClassFile;
|
||||
@@ -292,10 +294,20 @@ public abstract class MainClassFinder {
|
||||
|
||||
private boolean mainMethodFound;
|
||||
|
||||
private boolean java25OrLater = false;
|
||||
|
||||
ClassDescriptor() {
|
||||
super(SpringAsmInfo.ASM_VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(int version, int access, String name, String signature, String superName,
|
||||
String[] interfaces) {
|
||||
if (version >= 69) {
|
||||
this.java25OrLater = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable AnnotationVisitor visitAnnotation(String desc, boolean visible) {
|
||||
this.annotationNames.add(Type.getType(desc).getClassName());
|
||||
@@ -305,13 +317,24 @@ public abstract class MainClassFinder {
|
||||
@Override
|
||||
public @Nullable MethodVisitor visitMethod(int access, String name, String desc, String signature,
|
||||
String[] exceptions) {
|
||||
if (isAccess(access, Opcodes.ACC_PUBLIC, Opcodes.ACC_STATIC) && MAIN_METHOD_NAME.equals(name)
|
||||
&& MAIN_METHOD_TYPE.getDescriptor().equals(desc)) {
|
||||
this.mainMethodFound = true;
|
||||
if (hasRequiredAccess(access) && MAIN_METHOD_NAME.equals(name)) {
|
||||
if (MAIN_METHOD_TYPE.getDescriptor().equals(desc)
|
||||
|| (this.java25OrLater && PARAMETERLESS_MAIN_METHOD_TYPE.getDescriptor().equals(desc))) {
|
||||
this.mainMethodFound = true;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean hasRequiredAccess(int access) {
|
||||
if (this.java25OrLater) {
|
||||
return !isAccess(access, Opcodes.ACC_PRIVATE) && isAccess(access, Opcodes.ACC_STATIC);
|
||||
}
|
||||
else {
|
||||
return isAccess(access, Opcodes.ACC_PUBLIC, Opcodes.ACC_STATIC);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAccess(int access, int... requiredOpsCodes) {
|
||||
for (int requiredOpsCode : requiredOpsCodes) {
|
||||
if ((access & requiredOpsCode) == 0) {
|
||||
|
||||
+92
@@ -16,11 +16,21 @@
|
||||
|
||||
package org.springframework.boot.loader.tools;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.ClassFileVersion;
|
||||
import net.bytebuddy.description.method.MethodDescription;
|
||||
import net.bytebuddy.dynamic.scaffold.InstrumentedType;
|
||||
import net.bytebuddy.implementation.Implementation;
|
||||
import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
|
||||
import net.bytebuddy.jar.asm.MethodVisitor;
|
||||
import net.bytebuddy.jar.asm.Opcodes;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
@@ -179,6 +189,88 @@ class MainClassFinderTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void packagePrivateMainMethod() throws Exception {
|
||||
this.testJarFile.addFile("a/b/c/D.class", packagePrivateMainMethod(ClassFileVersion.JAVA_V25));
|
||||
ClassNameCollector callback = new ClassNameCollector();
|
||||
try (JarFile jarFile = this.testJarFile.getJarFile()) {
|
||||
MainClassFinder.doWithMainClasses(jarFile, null, callback);
|
||||
assertThat(callback.getClassNames()).hasToString("[a.b.c.D]");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void packagePrivateMainMethodBeforeJava25() throws Exception {
|
||||
this.testJarFile.addFile("a/b/c/D.class", packagePrivateMainMethod(ClassFileVersion.JAVA_V24));
|
||||
ClassNameCollector callback = new ClassNameCollector();
|
||||
try (JarFile jarFile = this.testJarFile.getJarFile()) {
|
||||
MainClassFinder.doWithMainClasses(jarFile, null, callback);
|
||||
assertThat(callback.getClassNames()).isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void parameterlessMainMethod() throws Exception {
|
||||
this.testJarFile.addFile("a/b/c/D.class", parameterlessMainMethod(ClassFileVersion.JAVA_V25));
|
||||
ClassNameCollector callback = new ClassNameCollector();
|
||||
try (JarFile jarFile = this.testJarFile.getJarFile()) {
|
||||
MainClassFinder.doWithMainClasses(jarFile, null, callback);
|
||||
assertThat(callback.getClassNames()).hasToString("[a.b.c.D]");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void parameterlessMainMethodBeforeJava25() throws Exception {
|
||||
this.testJarFile.addFile("a/b/c/D.class", parameterlessMainMethod(ClassFileVersion.JAVA_V24));
|
||||
ClassNameCollector callback = new ClassNameCollector();
|
||||
try (JarFile jarFile = this.testJarFile.getJarFile()) {
|
||||
MainClassFinder.doWithMainClasses(jarFile, null, callback);
|
||||
assertThat(callback.getClassNames()).isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
private ByteArrayInputStream packagePrivateMainMethod(ClassFileVersion classFileVersion) {
|
||||
byte[] bytecode = new ByteBuddy(classFileVersion).subclass(Object.class)
|
||||
.defineMethod("main", void.class, Modifier.STATIC)
|
||||
.withParameter(String[].class)
|
||||
.intercept(new EmptyBodyImplementation())
|
||||
.make()
|
||||
.getBytes();
|
||||
return new ByteArrayInputStream(bytecode);
|
||||
}
|
||||
|
||||
private ByteArrayInputStream parameterlessMainMethod(ClassFileVersion classFileVersion) {
|
||||
byte[] bytecode = new ByteBuddy(classFileVersion).subclass(Object.class)
|
||||
.defineMethod("main", void.class, Modifier.STATIC | Modifier.PUBLIC)
|
||||
.intercept(new EmptyBodyImplementation())
|
||||
.make()
|
||||
.getBytes();
|
||||
return new ByteArrayInputStream(bytecode);
|
||||
}
|
||||
|
||||
static class EmptyBodyImplementation implements Implementation {
|
||||
|
||||
@Override
|
||||
public InstrumentedType prepare(InstrumentedType instrumentedType) {
|
||||
return instrumentedType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteCodeAppender appender(Target implementationTarget) {
|
||||
return new ByteCodeAppender() {
|
||||
|
||||
@Override
|
||||
public Size apply(MethodVisitor methodVisitor, Context implementationContext,
|
||||
MethodDescription instrumentedMethod) {
|
||||
methodVisitor.visitInsn(Opcodes.RETURN);
|
||||
return Size.ZERO;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class ClassNameCollector implements MainClassCallback<Object> {
|
||||
|
||||
private final List<String> classNames = new ArrayList<>();
|
||||
|
||||
Reference in New Issue
Block a user