37

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

Ben
  • 740
  • 1
  • 14
  • 29
  • How many (in %) of the classes in the directories are Spring Beans? – Ralph May 10 '11 at 09:22
  • I'm not really sure (it's a really big project) , but what i saw i believe it's arround 90 to 100%, since xml and properties files are isolated in separate locations) – Ben May 10 '11 at 09:30
  • 2
    Ugh, feel you pain. Just inherited a 3600 bean Spring monster with 425 config xml's. 9min to turn on... on a really good day. Thanks for your posts! Fingers crossed and will add any nuggets discovered. – Joseph Lust Feb 13 '13 at 18:13
  • how do you solve this for @Autowire stuff?! – Rafael Sanches Jul 13 '13 at 01:43
  • Which again confirms that reflection is a really expensive operation and should always be cached.... or is it just Spring-related reflection? Where should we file a bug? Oracle or Spring? ;-) – user1050755 Mar 22 '14 at 13:51

6 Answers6

9

Question: How many (in %) of the classes in the directories are Spring Beans?

Answer: I'm not really sure (it's a really big project) , but from what i saw i believe it's arround 90 to 100%, since xml and properties files are isolated in separate locations)

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.

Community
  • 1
  • 1
Ralph
  • 118,862
  • 56
  • 287
  • 383
  • right, automatically creating an xml configuration file would be perfect since parsing it, will be way faster than scanning the entire class-path with reflection, but is that possible ? and if so, can you explain how to achieve it ? – Ben May 10 '11 at 10:10
  • @Mehdi: see my extended answer – Ralph May 10 '11 at 10:23
  • Thank you, i'll definitely give it a try. – Ben May 10 '11 at 10:33
  • i've managed to reduce the spring bootstrapping time to 9~10 secs when loading definitions from an auto-generated xml file, and that's a pretty huge improvement. but i can't keep my inner greedy self from asking about the other lazy-init techniques you mentioned in your response, can you give me more specifics ? – Ben May 12 '11 at 09:23
  • 1
    http://static.springsource.org/spring/docs/3.0.x/reference/beans.html#beans-factory-lazy-init >By default, ApplicationContext implementations eagerly create and configure all singleton beans as part of the initialization process. Generally, this pre-instantiation is desirable, because errors in the configuration or surrounding environment are discovered immediately, as opposed to hours or even days later. When this behavior is not desirable, you can use Layz-initialization. A lazy-initialized bean tells Spring to create a bean instance when it is first requested, rather than at startup. – Ralph May 12 '11 at 10:21
4

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.

4

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:

  • Review that your test-appcontext only uses the minimally required components of your app
  • instead of having a list of component-scan directives, use one, with a comma-separated value like this: base-package="com.package.one,com.package.two..."
Captain Man
  • 6,997
  • 6
  • 48
  • 74
abalogh
  • 8,239
  • 2
  • 34
  • 49
  • Already done that, and i'm using filter to keep only the components i'm going to need. That said, i do understand that 2 mins is not a big deal for application startup, but for the developers working on the app it becomes really frustrating to wait 2mins on top to the time needed by hibernate and other frameworks, with every test. – Ben May 10 '11 at 09:23
  • Are you sure _all_ components you have in your test context is required? can you split your tests in a way that you can reduce the components needed for one, that way you could split your test contexts as well? – abalogh May 10 '11 at 09:25
  • You have a valid point, but the problem is that the delays are not caused by the number of components loaded, but rather, by the class-path scanning process which will still take the same time even if i excluded some components. – Ben May 10 '11 at 09:37
  • If you are able to reduce the number of "base-package"s, you will reduce your startup time as well. – abalogh May 10 '11 at 09:39
  • true, but i've already reduced it to the minimum. Thanks for you input @abalogh . – Ben May 10 '11 at 09:53
3

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 a lazyInit 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.

Xiangming Hu
  • 185
  • 2
  • 7
2

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.

rwitzel
  • 1,694
  • 17
  • 21
2

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

Hons
  • 3,804
  • 3
  • 32
  • 50
  • thanks, can you point me out how to configure the container in order to use the lazy-init for all the detected beans ? – Ben May 10 '11 at 09:17
  • you add 'default-lazy-init="true"' to your tag, though i'm not sure this is something you want. – abalogh May 10 '11 at 09:23
  • Well that's the issue here, i'm not using explicit bean configuration (i don't have any ... tags) my xml file cantains only the and the include/exclude filters. – Ben May 10 '11 at 09:27
  • 1
    I said "beans tag", not "bean tags" :) What is the root element of your appcontext.xml? – abalogh May 10 '11 at 09:35
  • My bad, you're correct, i'll try the lazy-init and keep you posted on the results. – Ben May 10 '11 at 09:40
  • 1
    nope, it still takes the same time, which proves that the latencies are because of the class-path scanning, not the number of beans loaded. – Ben May 10 '11 at 09:55