I am trying to speed up the Integration tests in our environment. All our classes are autowired. In our applicationContext.xml file we have defined the following:
<context:annotation-config/>
<context:component-scan base-package="com.mycompany.framework"/>
<context:component-scan base-package="com.mycompany.service"/>
...additional directories
I have noticed that Spring is scanning all directories indicated above and then iterates over each bean and caches the properties of each one. (I went over the DEBUG messages from spring)
As a result, the following test takes about 14 seconds to run:
public class MyTest extends BaseSpringTest {
#Test
def void myTest(){
println "test"
}
}
Is there any way to lazy load the configuration? I tried adding default-lazy-init="true" but that didn't work.
Ideally, only the beans required for the test are instantiated.
thanks in advance.
Update: I should have stated this before, I do not want to have a context file for each test. I also do not think one context file for just the tests would work. (This test context file would end up including everything)
If you really want to speed up your application context, disable your <component-scan and performs the following routine before running any test
Resource resource = new ClassPathResource(<PUT_XML_PATH_RIGHT_HERE>); // source.xml, for instance
InputStream in = resource.getInputStream();
Document document = new SAXReader().read(in);
Element root = document.getRootElement();
/**
* remove component-scanning
*/
for ( Iterator i = root.elementIterator(); i.hasNext(); ) {
Element element = (Element) i.next();
if(element.getNamespacePrefix().equals("context") && element.getName().equals("component-scan"))
root.remove(element);
}
in.close();
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(true);
for (String source: new String[] {"com.mycompany.framework", "com.mycompany.service"}) {
for (BeanDefinition bd: scanner.findCandidateComponents(source)) {
root
.addElement("bean")
.addAttribute("class", bd.getBeanClassName());
}
}
//add attribute default-lazy-init = true
root.addAttribute("default-lazy-init","true");
/**
* creates a new xml file which will be used for testing
*/
XMLWriter output = new XMLWriter(new FileWriter(<SET_UP_DESTINATION_RIGHT_HERE>));
output.write(document);
output.close();
Besides that, enable <context:annotation-config/>
As you need to perform the routine above before running any test, you can create an abstract class where you can run the following
Set up a Java system property for testing environment as follows
-Doptimized-application-context=false
And
public abstract class Initializer {
#BeforeClass
public static void setUpOptimizedApplicationContextFile() {
if(System.getProperty("optimized-application-context").equals("false")) {
// do as shown above
// and
System.setProperty("optimized-application-context", "true");
}
}
}
Now, for each test class, just extends Initializer
One approach is to skip the auto detection completely and either load up a separate context (with the components required for the test) or redefine your beans at runtime (prior to the test running).
This thread discusses redefinition of beans and a custom test class for doing this:
Spring beans redefinition in unit test environment
This is the price you pay for auto-detection of components - it's slower. Even though your test only requires certain beans, your <context:component-scan> is much broader, and Spring will instantiate and initialise every bean it finds.
I suggest that you use a different beans file for your tests, one which only defines the beans necessary for the test itself, i.e. not using <context:component-scan>.
Probably what you need is to refactor your config to use less autowiring. My approach is almost always wire the beans by name, trying to be explicit with the design but, at the same time, not being too verbose either, using autowiring when is clear that you are using it in order to hide minor details.
Addendum:
If that is not enough and you are using junit, you may want to use a utility from the JUnit Addons project. The class DirectorySuiteBuilder dynamically builds up a test suite from a directory structure. So you can make something like
DirectorySuiteBuilder builder = new DirectorySuiteBuilder();
Test suite = builder.suite("project/tests");
Initializing the Spring context before this code, you can run all tests at once. However, if each test assume a "clean" Spring context, then you are probably lost.
In this kind of situation, you will need to find a balance.
On one hand, you would rightly want to run the tests in a shortest possible time to get the results quick. This is especially important when working in a team environment with continuous integration working.
On the other hand, you would also rightly want to keep the configuration of tests as simple as possible so the maintenance of test suite would not become too cumbersome to be useful.
But at the end of the day, you will need to find your own balance and make a decision.
I would recommend creating a few context configuration files for testing to group some tests so such a simple test would not take long time simply being configured by Spring, while keeping the number of configuration files to minimum you can manage.
Convention bean factory is designed to solve this problem and speeds up the whole process significantly, 3x or more.
Since none of the answers here solved this problem for me, I add my own experience.
My problem was that Spring, Hibernate and EhCache grouped up in the attempt of drowning my console with verbose DEBUG messages, resulting unreadable log and - far worse - unbearable low performance.
Configuring their log levels fixed all up:
Logger.getLogger("org.hibernate").setLevel(Level.INFO);
Logger.getLogger("net.sf.ehcache").setLevel(Level.INFO);
Logger.getLogger("org.springframework").setLevel(Level.INFO);
Related
What is the correct way to initialize some relatively big data, and share them (read only, so thread safe) across all JUnit5 tests?
I've looked this answer and others that are similar, but I always seem to have 1 or 2 more levels of assembly/instantiation than they deal with.
My testing setup is this:
I have a custom Repository data structure that needs to be initialized just once, read from multiple sources and assembled (about 100 - 200 mb) and then shared to all the tests.
Each test class instantiates an Engine in #BeforeAll, that needs the repository above and then goes on and executes the tests in series, calling engine.reset() between tests. Each test has it's own unique setup. Engine is semi-heavy, and impossible to have one for each test.
#TestInstance(TestInstance.Lifecycle.PER_CLASS) is used so we get only one instance per testing class, (and one engine per class).
Multithreading/Parallel testing is used, each test class is done in parallel, and methods within it are done in sequence. This means:
systemProperty("junit.jupiter.execution.parallel.enabled", true)
systemProperty("junit.jupiter.execution.parallel.mode.default", "same_thread")
systemProperty("junit.jupiter.execution.parallel.mode.classes.default", "concurrent")
systemProperty("junit.jupiter.execution.parallel.config.strategy","dynamic")
systemProperty("junit.jupiter.execution.parallel.config.dynamic.factor",1) // could be 2!
Since there is nothing before #BeforeAll, I had to improvise:
I ended up declaring the repository on the top level of a kotlin test class file, outside of the class and initialize it like this: (large irrelevant chunks are omitted for clarity)
TestSetAlpha.kt:
import org.junit.jupiter.api.*
val database:Repository = Repository().also{
it.setupData(Config(...))
it.someOtherInit()
blah blah
}
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class `Engine Test Set ALPHA` {
var eng = Engine()
#BeforeAll
fun initAll() {
// configure Engine
println("Configuring Engine ALPHA")
eng.setDatabase(database)
eng.configure {
....
....
}
}
#BeforeEach
fun init() {
// reset the engine
eng.reset()
}
#Test
fun `A simple test`() {
eng.add(...)
eng.add(...)
eng.execute()
// interrogate resulting state
assert(eng.property == ...)
...
}
On subsequent test class files, I can reuse the same database Repository, and it only realy initializes once on a project level (verified!). There are no changes and no mutability on the repository after it loads, and that is guaranteed by it's API. This means that on an 16 thread CPU, I can reuse the database and roughly run 16 test classes in parallel.
I'm not sure on the loading and instantiating semantics of that global val. With a lot of data, JUnit5 is waiting for the also closure to complete before continuing with any tests, probably because it can't proceed with the classes on the files? I've never gotten an error, but feel this will probably break with a future update or on another platform because it's not clean and looks like a hack.
I would like to specify and have a guarantee that the repository is instantiated and shared properly across all classes & files and then have the threads start. How do you go about doing that though? There isn't some kind of top level, global #BeforeBeforeAll, although it would be exactly what I require. Any feedback and refactoring is welcomed. I can't run the tests without parallelism of course.
Far simpler than I thought it would be!
On top scope or on another file, use a singleton object!
object DatabaseProvider {
val database: Repository by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
val r=Repository()
r.setupData(Config(...))
// Load and add everything into the database
return#lazy r
}
}
and then in each test class, you plug in the database as part of initialization:
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class `Engine Test Set ALPHA`{
var eng = Engine()
#BeforeAll
fun initAll() {
// configure Engine
eng.setupRepo(DatabaseProvider.database)
eng.configure= ....
println("Configuration of Engine 1 DONE!")
}
Note the lazy init mode set to synchronized.
The #BeforeAll methods will fire up before the database repository is loaded, but it will block on each test class until the initialization oof the repository is done, and then continue.
My unit tests are seeing org.hibernate.LazyInitializationException: could not initialize proxy [org.openapitools.entity.MenuItem#5] - no Session. I'm not sure why they expect a session in a unit test. I'm trying to write to an in-memory h2 database for the unit tests of my Controller classes that implement the RESTful APIs. I'm not using any mock objects for the test, because I want to test the actual database transactions. This worked fine when I was using Spring-Boot version 1.x, but broke when I moved to version 2. (I'm not sure if that's what caused the tests to break, since I made lots of other changes. My point is that my code has passed these tests already.)
My Repositories extend JPARepository, so I'm using a standard Hibernate interface.
There are many answers to this question on StackOverflow, but very few describe a solution that I could use with Spring-Data.
Addendum: Here's a look at the unit test:
#Test
public void testDeleteOption() throws ResponseException {
MenuItemDto menuItemDto = createPizzaMenuItem();
ResponseEntity<CreatedResponse> responseEntity
= adminApiController.addMenuItem(menuItemDto);
final CreatedResponse body = responseEntity.getBody();
assertNotNull(body);
Integer id = body.getId();
MenuItem item = menuItemApiController.getMenuItemTestOnly(id);
// Hibernate.initialize(item); // attempted fix blows up
List<String> nameList = new LinkedList<>();
for (MenuItemOption option : item.getAllowedOptions()) { // blows up here
nameList.add(option.getName());
}
assertThat(nameList, hasItems("pepperoni", "olives", "onions"));
// ... (more code)
}
My test application.properties has these settings
spring.datasource.url=jdbc:h2:mem:pizzaChallenge;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.username=pizza
spring.datasource.password=pizza
spring.jpa.show-sql=true
This is not standard Hibernate, but spring data. You have to understand that Hibernate uses lazy loading to avoid loading the whole object graph from the database. If you close the session or connection to the database e.g. by ending a transaction, Hibernate can't lazy load anymore and apparently, your code tries to access state that needs lazy loading.
You can use #EntityGraph on your repository to specify that an association should be fetched or you avoid accessing the state that isn't initialized outside of a transaction. Maybe you just need to enlarge the transaction scope by putting #Transactional on the method that calls the repository and accesses the state, so that lazy loading works.
I found a way around this. I'm not sure if this is the best approach, so if anyone has any better ideas, I'd appreciate hearing from them.
Here's what I did. First of all, before reading a value from the lazy-loaded entity, I call Hibernate.initialize(item);
This throws the same exception. But now I can add a property to the test version of application.properties that says
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
Now the initialize method will work.
P.S. I haven't been able to find a good reference for Spring properties like this one. If anyone knows where I can see the available properties, I'd love to hear about it. The folks at Spring don't do a very good job of documenting these properties. Even when they mention a specific property, they don't provide a link that might explain it more thoroughly.
I have a (web-)application that needs special configurations and/or extensions based on the customer using the application. I call these additions "plugins" and they are auto discovered by classpath scanning when the application starts. For extensions that is incredibly easy. Let's say I want to have a plugin which adds an API that prints "hello world" when the URL /myplugin/greet is called: I just create a #Controller annotated class with the according #RequestMapping, put this in a myplugin.jar, copy that on the classpath and that's it.
Problems come up when I want to change some defaults and especially if I want to do this multiple times. Let's say my core application has a config like this:
#Configuration
public class CoreConfiguration {
#Bean
public Set<String> availableModules() {
return Collections.singleton("core");
}
}
Now I have two plugins that don't know about each other (but they do know the CoreConfig), but they both want to add themselves to the list of available modules. How would I do that? If I only had a single plugin that wants to override the module list I could override the existing bean from CoreConfiguration, but with two plugins that becomes a problem. What I imagine is something like this:
#Configuration
public class FirstPluginConfiguration {
#Bean
public Set<String> availableModules(Set<String> availableModules) {
Set<String> extendedSet = new HashSet<>(availableModules);
extendedSet.add("FirstPlugin");
return extendedSet;
}
}
Of course a SecondPluginConfiguration would look nearly exactly like this, except that the Set is not extended by "FirstPlugin", but by "SecondPlugin". I tested it to check what would happen and spring will just never call the First/SecondPluginConfiguration "availableModules" methods but it does not show an error either.
Now of course in this case this could easily be solved by using a mutable Set in the CoreConfiguration and then autowiring and extending the set in the other configurations, but for example I also want to be able to add method interceptors to some beans. So for example I might have an interface CrashLogger which has a logCrash(Throwable t) method and in CoreConfiguration a ToFileCrashLogger is created that writes stack traces to files as the name suggests. Now a plugin could say that he also wants to get notified about crashes, for example the plugin wants to ADDITIONALLY send the stacktrace to someone by email. For that matter that plugin could wrap the CrashLogger configured by the CoreConfiguration and fire BOTH. A second plugin could wrap the wrapper again and do something totally different with the stacktrace and still call both of the other CrashLoggers.
The later does sound somewhat like AOP and if I'd just let ALL my beans be proxied (I did not test that) I could autowire them into my plugin configurations, cast them to org.springframework.aop.framework.Advised and then add advices that manipulate behaviour. However it does seem like a huge overkill to generate proxies for each and everyone of my beans just so that that plugin can potentially add one or two advices one one or two beans.
While creating new scenarios I only want to test the scenario I am currently working with. For this purpose I want to use the Meta: #skip tag before my scenarios. As I found out I have to use the embedder to configure the used meta tags, so I tried:
configuredEmbedder().useMetaFilters(Arrays.asList("-skip"));
but actually this still has no effect on my test scenarios. I used it in the constructor of my SerenityStories test suite definition. Here is the complete code of this class:
public class AcceptanceTestSuite extends SerenityStories {
#Managed
WebDriver driver;
public AcceptanceTestSuite() {
System.setProperty("webdriver.chrome.driver", "D:/files/chromedriver/chromedriver.exe");
System.setProperty("chrome.switches", "--lang=en");
System.setProperty("restart.browser.each.scenario", "true");
configuredEmbedder().useMetaFilters(Arrays.asList("-skip"));
runSerenity().withDriver("chrome");
}
#Override
public Configuration configuration() {
Configuration configuration = super.configuration();
Keywords keywords = new LocalizedKeywords(DEFAULTSTORYLANGUAGE);
Properties properties = configuration.storyReporterBuilder().viewResources();
properties.setProperty("encoding", "UTF-8");
configuration.useKeywords(keywords)
.useStoryParser(new RegexStoryParser(keywords, new ExamplesTableFactory(new LoadFromClasspath(this.getClass()))))
.useStoryLoader(new UTF8StoryLoader()).useStepCollector(new MarkUnmatchedStepsAsPending(keywords))
.useDefaultStoryReporter(new ConsoleOutput(keywords)).storyReporterBuilder().withKeywords(keywords).withViewResources(properties);
return configuration;
}
}
Is this the wrong place or have I missed something? Still all scenarios are executed.
EDIT:
I changed following classes and now I think that it "works"
public AcceptanceTestSuite() {
System.setProperty("webdriver.chrome.driver", "D:/files/chromedriver/chromedriver.exe");
System.setProperty("chrome.switches", "--lang=de");
System.setProperty("restart.browser.each.scenario", "true");
this.useEmbedder(configuredEmbedder());
runSerenity().withDriver("chrome");
}
#Override
public Embedder configuredEmbedder() {
final Embedder embedder = new Embedder();
embedder.embedderControls()
.useThreads(1)
.doGenerateViewAfterStories(true)
.doIgnoreFailureInStories(false)
.doIgnoreFailureInView(false)
.doVerboseFailures(true);
final Configuration configuration = configuration();
embedder.useConfiguration(configuration);
embedder.useStepsFactory(stepsFactory());
embedder.useMetaFilters(Arrays.asList("-skip"));
return embedder;
}
But now I get the message [pool-1-thread-1] INFO net.serenitybdd.core.Serenity - TEST IGNORED but the scenario is still executed. Only in the result page I get the info that this scenario is ignored (but still executed). Is there a way to SKIP the scenario so it won't run?
I could not make it run with using configuredEmbedder() but by adding -Dmetafilter="+working -finished" as goals in my mvn run configurations and using the tags #working for scenarios I'm working with and which I want to run and #finsihed for scenarios I don't want to execute. Still I have to change the run configuration if I want to change the meta tags so it is not very comfortable but still I get what I was looking for.
As long as you document it well (some doc in https://github.com/serenity-bdd/the-serenity-book would be brilliant), I think as a JBehave/Serenity user you are well enough placed to decide which option makes the most sense.
Investigation
I debugged the serenity-jbehave classes, trying to understand why setting
configuredEmbedder().useMetaFilters(Collections.singletonList("-skip"))
is not working in all the possible places I put it within my class extending the SerenityStories, I found the strategic code place where metaFilters in ExtendedEmbedder#embedder are overwritten with what we define in our class into settings from serenity-jbehave.
This method is SerenityReportingRunner#createPerformableTree:
private PerformableTree createPerformableTree(List<CandidateSteps> candidateSteps, List<String> storyPaths) {
ExtendedEmbedder configuredEmbedder = this.getConfiguredEmbedder();
configuredEmbedder.useMetaFilters(getMetaFilters());
BatchFailures failures = new BatchFailures(configuredEmbedder.embedderControls().verboseFailures());
PerformableTree performableTree = configuredEmbedder.performableTree();
RunContext context = performableTree.newRunContext(getConfiguration(), candidateSteps,
configuredEmbedder.embedderMonitor(), configuredEmbedder.metaFilter(), failures);
performableTree.addStories(context, configuredEmbedder.storyManager().storiesOfPaths(storyPaths));
return performableTree;
}
This line changes the set metaFilters:
configuredEmbedder.useMetaFilters(getMetaFilters());
It overrides the current metaFilters value.
Going further the call chain, we get to the logic that defines from where it gets metaFilters, i.e. where we can actually set it.
SerenityReportingRunner#createPerformableTree
↓
SerenityReportingRunner#getMetaFilters
↓
SerenityReportingRunner#getMetafilterSetting
This is the method we need!
private String getMetafilterSetting() {
Optional<String> environmentMetafilters = getEnvironmentMetafilters();
Optional<String> annotatedMetafilters = getAnnotatedMetafilters(testClass);
Optional<String> thucAnnotatedMetafilters = getThucAnnotatedMetafilters(testClass);
return environmentMetafilters.orElse(annotatedMetafilters.orElse(thucAnnotatedMetafilters.orElse("")));
}
As we see here, the metaFilters can be defined in three places, and they override each other. In the priority lowering order, they are:
Value of metafilter (exactly all lowercase!) VM property.
Value of on net.serenitybdd.jbehave.annotations.Metafilter annotation on our SerenityStories class.
Value of on net.thucydides.jbehave.annotations.Metafilter annotation on our SerenityStories class. This annotation is deprecated, but left in place for backwards-compatibility.
Solution that is working with the current serenity-jbehave version
I've tried/debugged all these three options, they work and override each other as described above.
1. Use environment metafilter property
Added this to my JVM run arguments:
-Dmetafilter=skip
2. Use the modern #Metafilter annotation
import net.serenitybdd.jbehave.SerenityStories;
import net.serenitybdd.jbehave.annotations.Metafilter;
#Metafilter("-skip")
public class Acceptance extends SerenityStories {
3. Use the deprecated #Metafilter annotation
import net.serenitybdd.jbehave.SerenityStories;
import net.thucydides.jbehave.annotations.Metafilter;
#Metafilter("-skip") // warned as deprecated
public class Acceptance extends SerenityStories {
Solution for my current project is to use the current #Metafilter("-skip") annotation on my test class, to not depend on/have to change VM properties of the particular Jenkins/local dev execution.
Possible pull request to make
https://github.com/serenity-bdd/serenity-core/issues/95 — here Serenity guys have suggested me to do a PR with this fix, since they are not concentrated on Serenity + JBehave now.
I understand where to make the changes (in the code chain described above), but I don't know what overriding logic should be:
— MetaFilters from configuredEmbedder override any of ENV/annotation MetaFilters.
OR
— Any ENV/annotation MetaFilters override Metafilters from configuredEmbedder
OR
— MetaFilters from configuredEmbedder are merged with ENV/annotation MetaFilters. This option required merging priority.
Any suggestions?
In any type of fix, I would prefer add the explicit logs about how the overriding is now working into SerenityReportingRunner#getMetafilterSetting, since the current behaviour is really non-obvious and took lots of time to investigate.
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
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.
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:
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..."
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.
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
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.