【问题标题】:Spring startup performance issuesSpring启动性能问题
【发布时间】:2011-08-22 07:22:24
【问题描述】:

我正在尝试将 Spring 集成到一个包含数千个类的相当大的应用程序中,但由于组件扫描,我在启动容器时遇到了巨大的延迟。

我已经将“base-package”中指定的目录数量缩小到最少,以减少扫描无关目录所浪费的时间,但是初始化的class-path扫描部分仍然需要大约1-2分钟。

那么,有没有办法优化扫描过程?我曾考虑将候选类路径存储在一个文件中,然后创建容器然后从文件中获取它们,而不是每次启动时扫描类路径,但我真的不知道从哪里开始,或者这是否可能.

非常感谢任何建议。提前致谢。


Edit1:从自动生成的 xml 文件中加载 bean 定义,将 Spring 引导时间减少到 9~10 秒,这证实了 Spring 用于组件类路径扫描的反射 api 是主要的启动延迟的来源。
至于生成xml文件,这里是代码,因为它可能对有同样问题的人有帮助。

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.ArrayList;


public class ConfigurationWriter {

    public ArrayList<String> beanDefinitions = new ArrayList<String>();

    public ConfigurationWriter() {

        // the context loaded with old fashioned way (classpath scanning)
        ApplicationContext context = SpringContainerServiceImpl.getInstance().getContext();
        String[] tab = context.getBeanDefinitionNames();
        for (int i = 0; i < tab.length - 6; i++) {
            Class clazz = context.getType(tab[i]);
            String scope = context.isPrototype(tab[i]) ? "prototype" : "singleton";
            String s = "<bean id=\"" + tab[i] + "\" class=\"" + clazz.getName() + "\" scope=\"" + scope + "\"/>";
            beanDefinitions.add(s);
        }
        // Collections.addAll(beanDefinitions, tab);

    }

    @SuppressWarnings("restriction")
    public void generateConfiguration() throws FileNotFoundException {
        File xmlConfig = new File("D:\\dev\\svn\\...\\...\\src\\test\\resources\\springBoost.xml");
        PrintWriter printer = new PrintWriter(xmlConfig);

        generateHeader(printer);

        generateCorpse(printer);

        generateTail(printer);

        printer.checkError();

    }

    @SuppressWarnings("restriction")
    private void generateCorpse(PrintWriter printer) {

        for (String beanPath : beanDefinitions) {
            printer.println(beanPath);
        }

    }

    @SuppressWarnings("restriction")
    private void generateHeader(PrintWriter printer) {
        printer.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
        printer.println("<beans xmlns=\"http://www.springframework.org/schema/beans\"");
        printer.println("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
        printer.println("xmlns:context=\"http://www.springframework.org/schema/context\"");
        printer.println("xsi:schemaLocation=\"");
        printer.println("http://www.springframework.org/schema/mvc");
        printer.println("http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd");
        printer.println("http://www.springframework.org/schema/beans");
        printer.println("http://www.springframework.org/schema/beans/spring-beans-3.0.xsd");
        printer.println("http://www.springframework.org/schema/context");
        printer.println("http://www.springframework.org/schema/context/spring-context-3.0.xsd\"");
        printer.println("default-lazy-init=\"true\">");
    }

    @SuppressWarnings("restriction")
    private void generateTail(PrintWriter printer) {
        // printer.println("<bean class=\"com.xxx.frmwrk.spring.processors.xxxBeanFactoryPostProcessor\"/>");
        printer.println("<bean class=\"com.xxx.frmwrk.spring.processors.xxxPostProcessor\"/>");
        printer.println("</beans>");
    }

}

编辑 2: Spring 5 包含一组重要的优化以加速上下文初始化,它还带有一个有趣且方便的功能,可以在编译时生成候选组件的索引: Spring Context Indexer

【问题讨论】:

  • 目录中有多少(%)类是 Spring Bean?
  • 我不太确定(这是一个非常大的项目),但我所看到的我相信它大约是 90% 到 100%,因为 xml 和属性文件被隔离在不同的位置)
  • 呃,感觉你很痛苦。刚刚继承了一个具有 425 个配置 xml 的 3600 bean Spring 怪物。 9 分钟开机......在一个非常好的一天。谢谢你的帖子!手指交叉,将添加任何发现的金块。
  • 你如何解决@Autowire 的问题?!​​
  • 这再次证实了反射是一项非常昂贵的操作并且应该始终被缓存......或者它只是与 Spring 相关的反射?我们应该在哪里提交错误?甲骨文还是春天? ;-)

标签: java spring spring-mvc classpath bootstrapping


【解决方案1】:

除了减少要扫描的目录之外,我唯一想到的就是使用lazy bean initialization。 如果你有很多豆子,这可能会有所帮助

【讨论】:

  • 谢谢,你能告诉我如何配置容器以便对所有检测到的 bean 使用惰性初始化吗?
  • 您将 'default-lazy-init="true"' 添加到您的 标记中,尽管我不确定这是您想要的。
  • 这就是问题所在,我没有使用显式 bean 配置(我没有任何 ... 标签)我的 xml 文件只包含 和包含/排除过滤器。
  • 我说的是“beans tag”,而不是“bean tags” :) appcontext.xml 的根元素是什么?
  • 不,它仍然需要相同的时间,这证明延迟是因为类路径扫描,而不是加载的 bean 数量。
【解决方案2】:

您对那里的性能无能为力,我猜您关心的不是生产环境中的启动,而是测试的启动时间*。 两个提示:

  • 查看您的 test-appcontext 仅使用应用的最低要求组件
  • 不要使用组件扫描指令列表,而是使用一个带有逗号分隔值的指令,如下所示:base-package="com.package.one,com.package.two..."

【讨论】:

  • 已经这样做了,我正在使用过滤器只保留我需要的组件。也就是说,我确实理解 2 分钟对于应用程序启动来说并不是什么大问题,但对于开发应用程序的开发人员来说,每次测试都等待 2 分钟到休眠和其他框架所需的时间真的很令人沮丧。
  • 您确定您在测试上下文中拥有的 所有 组件是必需的吗?您能否以一种可以减少测试所需组件的方式拆分您的测试,这样您也可以拆分您的测试上下文?
  • 你有一个正确的观点,但问题是延迟不是由加载的组件数量引起的,而是由类路径扫描过程引起的,即使我排除了一些组件。
  • 如果你能够减少“base-package”的数量,你也会减少你的启动时间。
  • 是的,但我已经将它降低到最低限度。感谢您输入@abalogh。
【解决方案3】:

问题:目录中有多少(%)类是 Spring Bean?

回答:我不太确定(这是一个非常大的项目),但据我所见,我相信大约 90% 到 100%,因为 xml 和属性文件被隔离在不同的位置)

如果问题真的是组件扫描而不是 bean 初始化过程本身(我非常怀疑),那么我能想到的唯一解决方案是使用 Spring XML 配置而不是组件扫描。 - (可以自动创建 XML 文件)。

但是如果你有很多类,并且其中 90% - 100% 是 Beans,那么,扫描文件的减少将有 10%-0% 的最大提升。

您应该尝试其他方法来加快初始化速度,可以使用延迟加载或任何延迟加载相关技术,或者(这不是开玩笑)使用更快的硬件(如果它不是独立的应用程序)。


生成 Spring XML 的一种简单方法是编写一个简单的 spring 应用程序,该应用程序使用类路径扫描,就像您的原始应用程序一样。在所有的 Bean 初始化之后,遍历 Spring Context 中的 Bean,检查 bean 是否属于重要包,并将该 bean 的 XML Config 写入文件。

【讨论】:

  • 对,自动创建一个 xml 配置文件将是完美的,因为解析它会比使用反射扫描整个类路径快得多,但这可能吗?如果是这样,你能解释一下如何实现它吗?
  • @Mehdi:查看我的扩展答案
  • 谢谢,我一定会试一试的。
  • 在从自动生成的 xml 文件加载定义时,我已经设法将 spring 引导时间减少到 9~10 秒,这是一个相当大的改进。但是我无法阻止我内心贪婪的自我询问您在回复中提到的其他惰性初始化技术,您能告诉我更多细节吗?
  • static.springsource.org/spring/docs/3.0.x/reference/… >默认情况下,ApplicationContext 实现会在初始化过程中急切地创建和配置所有单例 bean。通常,这种预实例化是可取的,因为配置或周围环境中的错误会立即发现,而不是几小时甚至几天之后。当这种行为不可取时,您可以使用 Layz 初始化。一个惰性初始化的 bean 告诉 Spring 在它第一次被请求时创建一个 bean 实例,而不是在启动时。
【解决方案4】:

您可以使用 Spring 的 Java-based container configuration 代替组件扫描。

与基于 XML 的配置相比,基于 Java 的容器配置是类型安全的。

但首先你应该检查你的组件扫描路径是否足够具体,以至于它们不包括第三方库的类。

【讨论】:

    【解决方案5】:

    注解类的自动发现目前需要扫描指定包中的所有类,并且可能需要很长时间,这是当前类加载机制的一个已知问题。

    Java 9 将在 Jigsaw 方面提供帮助。

    来自 Mark Reinold 的 Java 平台模块系统要求,http://openjdk.java.net/projects/jigsaw/spec/reqs/

    高效的注释检测—— 必须能够识别模块工件中存在特定注释的所有类文件,而无需实际读取所有类文件。在运行时,必须能够识别已加载模块中存在特定注释的所有类,而无需枚举模块中的所有类,只要该注释在运行时保留。为了提高效率,可能需要指定只有某些注释需要以这种方式检测。 一种可能的方法是使用模块中存在的注释索引以及每个注释适用的元素的指示来扩充模块的定义。为了限制索引的大小,只会包含本身使用新元注释(例如 @Indexed)进行注释的注释。

    【讨论】:

      【解决方案6】:

      我知道这是一个老问题,你会看到当时的情况有所不同,但希望它可以帮助其他人像我一样研究这个问题。

      根据这个对不同问题的回答,@ComponentScan 注释现在支持 lazyInit 标志,这应该有助于减少启动时间。

      https://stackoverflow.com/a/29832836/4266381

      注意:您的编辑听起来像是切换到 XML 本身就是魔法。然而,仔细查看代码,您有default-lazy-init="true"。我想知道这是否是真正的原因。

      【讨论】:

        猜你喜欢
        • 2018-12-28
        • 1970-01-01
        • 2011-07-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-06-29
        相关资源
        最近更新 更多