diff --git a/solon/src/main/java/org/noear/solon/core/AppContext.java b/solon/src/main/java/org/noear/solon/core/AppContext.java index 8023896af829b581e3565400d05198b9f954e263..f50c9cd9a91caec4a6505a83641eccfccf7d7f0c 100644 --- a/solon/src/main/java/org/noear/solon/core/AppContext.java +++ b/solon/src/main/java/org/noear/solon/core/AppContext.java @@ -732,56 +732,67 @@ public class AppContext extends BeanContainer { return; } + // 如果在AOT编译时,生成类索引文件,并加载bean if (NativeDetector.isAotRuntime()) { - //(aot 运行时) - String dir = basePackage.replace('.', '/'); - Set clzNames = ScanUtil.scan(classLoader, dir, n -> n.endsWith(".class")); - - // 如果在AOT编译时,生成类索引文件 - List clzNames2 = new ArrayList<>(); - - for (String name : clzNames) { - String clzName = name.substring(0, name.length() - 6).replace('/', '.'); - - Class clz = ClassUtil.loadClass(classLoader, clzName); - if (clz != null) { - if (tryBuildBeanOfClass(clz) > build_bean_ofclass_state0) { - clzNames2.add(clzName); - } - } - } + Set classNames = ClassIndexUtil.scanClassGenerateIndex(classLoader, basePackage); + //按照配置类优先,其他类次之,两阶段加载 + doMakeBean(classNames, classLoader); + return; + } - if (clzNames2.size() > 0) { - // 排序,确保索引文件内容稳定 - Collections.sort(clzNames2); - // 写入索引文件 - ClassIndexUtil.writeIndexFile(basePackage, clzNames2); - } - } else { - //(非 aot 运行时) 优先使用类索引文件(如果存在) - Collection clzNames = ClassIndexUtil.loadClassIndex(basePackage); - if (clzNames != null) { - for (String clzName : clzNames) { - Class clz = ClassUtil.loadClass(classLoader, clzName); - if (clz != null) { - tryBuildBeanOfClass(clz); - } - } + // 优先使用类索引文件(如果存在) + if (ClassIndexUtil.hasClassIndex(basePackage)) { + // 使用索引文件进行扫描 + Set classNames = ClassIndexUtil.loadClassIndex(basePackage); + if (classNames != null) { + //按照配置类优先,其他类次之,两阶段加载 + doMakeBean(classNames, classLoader); return; } + } + + //默认加载策略 (没有类索引文件,也不是aot编译阶段) + String dir = basePackage.replace('.', '/'); - String dir = basePackage.replace('.', '/'); - clzNames = ScanUtil.scan(classLoader, dir, n -> n.endsWith(".class")); + //扫描类文件并分析(采用两段式加载,先处理配置类,再处理其他类) + Set clzNames = ScanUtil.scan(classLoader, dir, n -> n.endsWith(".class")); - for (String name : clzNames) { - String clzName = name.substring(0, name.length() - 6).replace('/', '.'); + //按照配置类优先,其他类次之,两阶段加载 + doMakeBean(clzNames, classLoader); + + } - Class clz = ClassUtil.loadClass(classLoader, clzName); - if (clz != null) { - tryBuildBeanOfClass(clz); + //两阶段加载 + private void doMakeBean(Set clzNames, ClassLoader classLoader) { + // 创建两个集合:配置类集合和其他类集合 + List> configClasses = new ArrayList<>(); + List> otherClasses = new ArrayList<>(); + + // 先分析所有类,分类存储 + for (String name : clzNames) { + String clzName = name.substring(0, name.length() - 6); + clzName = clzName.replace('/', '.'); + + Class clz = ClassUtil.loadClass(classLoader, clzName); + if (clz != null) { + // 检查是否为配置类(带有@Configuration注解) + if (clz.isAnnotationPresent(org.noear.solon.annotation.Configuration.class)) { + configClasses.add(clz); + } else { + otherClasses.add(clz); } } } + + // 第一阶段:优先处理配置类 + for (Class clz : configClasses) { + tryBuildBeanOfClass(clz); + } + + // 第二阶段:处理其他类 + for (Class clz : otherClasses) { + tryBuildBeanOfClass(clz); + } } /** diff --git a/solon/src/main/java/org/noear/solon/core/runtime/ClassIndexUtil.java b/solon/src/main/java/org/noear/solon/core/runtime/ClassIndexUtil.java index 8234e5f7f7d972a4fe83102c180bc7f47e65aa06..eb9cdcd2d603fb9f7e15ef7c31d294d44480953f 100644 --- a/solon/src/main/java/org/noear/solon/core/runtime/ClassIndexUtil.java +++ b/solon/src/main/java/org/noear/solon/core/runtime/ClassIndexUtil.java @@ -15,15 +15,19 @@ */ package org.noear.solon.core.runtime; +import org.noear.solon.Utils; +import org.noear.solon.core.runtime.NativeDetector; import org.noear.solon.core.util.ResourceUtil; -import org.noear.solon.lang.Internal; +import org.noear.solon.core.util.ScanUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.*; import java.net.URL; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.util.*; +import java.util.function.Predicate; /** * 类索引工具类 @@ -32,17 +36,66 @@ import java.util.*; * 启动时:查找包对应的类索引文件。如果有,使用类索引文件替代ScanUtil.scan机制 * * @author noear - * @since 3.7 + * @since 3.6 */ -@Internal public class ClassIndexUtil { private static final Logger log = LoggerFactory.getLogger(ClassIndexUtil.class); + + /** + * 索引文件后缀名 + */ + private static final String INDEX_FILE_SUFFIX = ".solonindex"; - //索引文件后缀名 - private static final String INDEX_FILE_SUFFIX = ".index"; - - //索引文件存储路径 + /** + * 索引文件存储路径 + */ private static final String INDEX_FILE_DIR = "META-INF/solon-index/"; + + /** + * 文件名中允许的字符正则表达式 + */ + private static final String SAFE_FILENAME_PATTERN = "^[a-zA-Z0-9._-]+$"; + + /** + * 生成类索引文件 + * + * @param classLoader 类加载器 + * @param basePackage 基础包名 + * @return 扫描的所有java类文件 + */ + public static Set scanClassGenerateIndex(ClassLoader classLoader, String basePackage) { + if (classLoader == null) { + throw new IllegalArgumentException("classLoader cannot be null"); + } + + if (Utils.isEmpty(basePackage)) { + throw new IllegalArgumentException("basePackage cannot be null or empty"); + } + + String dir = basePackage.replace('.', '/'); + + // 扫描包下的所有类文件 + Set classNames = ScanUtil.scan(classLoader, dir, n -> n.endsWith(".class")); + + if (classNames.isEmpty()) { + return classNames; + } + + // 生成索引文件内容 + List indexContent = new ArrayList<>(); + for (String className : classNames) { + String fullClassName = className.substring(0, className.length() - 6).replace('/', '.'); + indexContent.add(fullClassName); + } + + // 排序,确保索引文件内容稳定 + Collections.sort(indexContent); + + // 写入索引文件 + writeIndexFile(basePackage, indexContent); + + return classNames; + } /** * 加载类索引文件 @@ -50,31 +103,45 @@ public class ClassIndexUtil { * @param basePackage 基础包名 * @return 类名列表,如果不存在索引文件则返回null */ - public static List loadClassIndex(String basePackage) { + public static Set loadClassIndex(String basePackage) { String indexFileName = getIndexFileName(basePackage); - + try { URL resourceUrl = ResourceUtil.getResource(INDEX_FILE_DIR + indexFileName); if (resourceUrl == null) { return null; } - List classNames = new ArrayList<>(); + Set classNames = new HashSet<>(); try (InputStream inputStream = resourceUrl.openStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { String line; while ((line = reader.readLine()) != null) { - classNames.add(line.trim()); + if (!line.trim().isEmpty()) { + classNames.add(line.trim()); + } } } - + return classNames; } catch (IOException e) { // 索引文件读取失败,返回null + log.warn("Failed to read class index file: " + indexFileName, e); return null; } } + /** + * 检查是否存在类索引文件 + * + * @param basePackage 基础包名 + * @return 是否存在索引文件 + */ + public static boolean hasClassIndex(String basePackage) { + String indexFileName = getIndexFileName(basePackage); + return ResourceUtil.getResource(INDEX_FILE_DIR + indexFileName) != null; + } + /** * 获取索引文件名 */ @@ -82,38 +149,39 @@ public class ClassIndexUtil { if (basePackage == null || basePackage.isEmpty()) { throw new IllegalArgumentException("basePackage cannot be null or empty"); } - + // 防止路径遍历攻击 if (basePackage.contains("..") || basePackage.contains("/") || basePackage.contains("\\")) { throw new IllegalArgumentException("Invalid basePackage: contains path traversal characters"); } - + + // 验证包名格式 + if (!basePackage.matches(SAFE_FILENAME_PATTERN)) { + throw new IllegalArgumentException("Invalid basePackage: contains unsafe characters"); + } + return basePackage.replace('.', '-') + INDEX_FILE_SUFFIX; } /** * 写入索引文件 */ - public static boolean writeIndexFile(String basePackage, List classNames) { + private static void writeIndexFile(String basePackage, List classNames) { String indexFileName = getIndexFileName(basePackage); - - File indexFile = RuntimeService.global().createClassOutputFile(INDEX_FILE_DIR + indexFileName); + File indexFile = new File("target/classes/" + INDEX_FILE_DIR + indexFileName); try { // 确保目录存在 indexFile.getParentFile().mkdirs(); - try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(indexFile), StandardCharsets.UTF_8))) { + try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(indexFile.toPath()), StandardCharsets.UTF_8))) { for (String className : classNames) { writer.write(className); writer.newLine(); } } - - return true; } catch (IOException e) { log.warn("Failed to write class index file for package: {}", basePackage, e); - return false; } } } \ No newline at end of file