Spring启动性能问题
我正在尝试将 Spring 集成到一个具有数千个类的相当大的应用程序中,并且由于组件扫描,我在启动容器时遇到了巨大的延迟。
我已经将“base-package”中指定的目录数量缩小到最少,以减少扫描不相关目录所浪费的时间,但是初始化的类路径扫描部分仍然需要大约 1-2 分钟。
那么,有没有办法优化扫描过程呢?我想过将候选类路径存储在文件中,然后让容器从文件中获取它们,而不是每次启动时扫描类路径,但我真的不知道从哪里开始,或者是否可能。
非常感谢任何建议。提前致谢。
编辑1:从自动生成的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 上下文索引器
I'm trying to integrate Spring in a pretty large application with thousands of classes, and i'm experiencing huge delays starting my container because of component-scanning.
I have already narrowed the number of directories specified in the "base-package", to the minimum in order to reduce the time wasted in scanning irrelevant directories, but the class-path scanning part of initialization still takes about 1-2 mins.
So, is there a way to optimize the scanning process ? I've thought of storing the candidate classes path in a file and make the container then get them from the file instead of scanning the class-path with every startup, but i don't really know where to start or if that is even possible.
Any advice is much appreciated. Thanks in advance.
Edit1: Loading bean definitions form an autogenerated xml file, reduced the Spring bootstrap time to 9~10 secs which confirms that the reflection api used by Spring for the components class-path scanning is the major source of startup delays.
As for generating the xml file here is the code, since it might be helpful for someone with the same issues.
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>");
}
}
Edit 2: With Spring 5 including an important set of optimizations for speeding up the context initialization, It also comes with an interesting and handy feature that enables generating an index of candidate components at compile time : Spring Context Indexer
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
如果问题确实是组件扫描和不是 bean 初始化过程本身(我对此非常怀疑),那么我能想到的唯一解决方案是使用 Spring XML 配置而不是组件扫描。 -(可以自动创建XML文件)。
但如果你有很多类,并且其中90% - 100%都是Bean,那么,扫描文件的减少最大会提高10%-0%。
您应该尝试其他方法来加速初始化,可以使用延迟加载或任何延迟加载相关技术,或者(这不是开玩笑)使用更快的硬件(如果它不是独立的应用程序)。
生成 Spring XML 的一种简单方法是编写一个简单的 Spring 应用程序,该应用程序像原始应用程序一样使用类路径扫描。所有Bean初始化完成后,它会遍历Spring Context中的Bean,检查该Bean是否属于重要包,并将该Bean的XML Config写入文件中。
If the problem is really the component scan and not the bean initializing process itself (and I highly doubt that), then the only solution I can imagine is to use Spring XML configuration instead of component scan. - (May you can create the XML file automatically).
But if you have many classes and 90% - 100% of them are Beans, then, the reduction of scanned files will have a maximal improvement of 10%-0%.
You should try other ways to speed up your initialization, may using lazy loading or any lazy loading related techniques, or (and that is not a joke) use faster hardware (if it is not a stand alone application).
A easy way to generate the Spring XML is to write a simple spring application that uses the class path scanning like your original application. After all Beans are initialize, it iterates through the Beans in the Spring Context, check if the bean belongs to the important package and write the XML Config for this bean in a file.
自动发现带注释的类目前需要扫描指定包中的所有类,并且可能需要很长时间,这是当前类加载机制的已知问题。
Java 9 将通过 Jigsaw 提供帮助。
来自 Mark Reinold 的 Java 平台模块系统要求,http://openjdk.java.net /projects/jigsaw/spec/reqs/ :
高效的注释检测 —
必须能够识别模块工件中存在特定注释的所有类文件,而无需实际读取所有类文件。在运行时,必须能够识别已加载模块中存在特定注释的所有类,而无需枚举模块中的所有类,只要注释在运行时保留即可。为了提高效率,可能有必要指定仅某些注释需要以这种方式进行检测。
一种可能的方法是使用模块中存在的注释的索引以及每个注释所应用的元素的指示来增强模块的定义。为了限制索引的大小,仅包含本身用新元注释(例如@Indexed)注释的注释。
Auto discovery of annotated classes currently requires to scan all classes in the specified package(s) and can take a long time, a known problem of the current class loading mechanism.
Java 9 is going to help here with Jigsaw.
From the Java Platform Module System requirements by Mark Reinold, http://openjdk.java.net/projects/jigsaw/spec/reqs/ :
Efficient annotation detection —
It must be possible to identify all of the class files in a module artifact in which a particular annotation is present without actually reading all of the class files. At run time it must be possible to identify all of the classes in a loaded module in which a particular annotation is present without enumerating all of the classes in the module, so long as the annotation was retained for run time. For efficiency it may be necessary to specify that only certain annotations need to be detectable in this manner.
One potential approach is to augment a module’s definition with an index of the annotations that are present in the module, together with an indication of the elements to which each annotation applies. To limit the size of the index, only annotations which themselves are annotated with a new meta-annotation, say @Indexed, would be included.
您对那里的性能无能为力,我想您并不关心生产环境中的启动,而是关心测试的启动时间*。
两个提示:
Not much you can do about the performance there, I guess you aren't concerned about the startup in production environment, but the startup time of your tests*.
Two tips:
我知道这是一个老问题,而且你会看到当时的情况有所不同,但希望它可以帮助其他人像我一样研究这个问题。
根据这个对不同问题的回答,
@ComponentScan
注释现在支持lazyInit
标志,这应该有助于减少启动时间。https://stackoverflow.com/a/29832836/4266381
注意:您的编辑听起来像是单独切换到 XML这就是魔法。然而,仔细查看代码,您会发现
default-lazy-init="true"
。我想知道这是否是真正的原因。I know it is an old question, and as you will see the situation was different at that time, but hopefully it can help others researching this issue as I did.
According to this answer to a different question, The
@ComponentScan
annotation now supports alazyInit
flag, which should help in reducing start-up time.https://stackoverflow.com/a/29832836/4266381
Note: Your edit made it sound like switching to XML by itself was the magic. Yet, looking closer at the code, you had
default-lazy-init="true"
. I wonder if that was the true reason.除了减少要扫描的目录之外,我唯一想到的是使用 延迟 bean 初始化。
如果你有很多豆子,这可能会有所帮助
The only thing that comes in my mind, beside reducing the directories to be scanned, is the use of lazy bean initialization.
May this could help if you have a lot of beans
您可以使用Spring的 基于Java的容器配置而不是组件扫描。
与基于 XML 的配置相比,基于 Java 的容器配置是类型安全的。
但首先您应该检查您的组件扫描路径是否足够具体,以便它们不包含第三方库的类。
You could use Spring's Java-based container configuration instead of component scan.
In comparison to XML-based configuration the Java-based container configuration is type-safe.
But first of all you should check whether your component scan paths are specific enough so that they do not include classes of third party libraries.