Just How Does Spring Do Its Classpath Component Scanning Magic?

Posted on June 23, 2008 by Scott Leberknight

One really cool feature in Spring 2.5+ is classpath component scanning. For example, instead of manually defining and wiring up all the beans comprising your Spring-based application, you simply add a few "driver" snippets of XML to your application context configuration, and then annotate your component classes with @Component (or any specialization such as @Controller and @Service). I am calling the XML snippets a "driver" because all they do is enable a specific feature, such as classpath scanning or component autowiring:

<!-- Enable autowiring via @Autowire annotations -->
<context:annotation-config/>

<!-- Scan for components in a package (and its subpackages)  -->
<context:component-scan base-package="org.springframework.samples.petclinic.web"/>

After seeing this was pretty cool, I wanted to know how exactly they did the classpath scanning. It boils down to doing some gymnastics with class loaders and resource path matching and using the ClassLoader.getResources(String name) method which returns an Enumeration containing URLs representing classpath resources. Those resources (Java classes) are then checked to see if they contain the @Component annotation (or a specialization of it) and if so, are considered a "candidate" component. Other filtering can take place but by default those components are then defined programmatically as Spring beans. When annotation configuration is enabled, autowiring of components also takes place, so I can have Spring scan the classpath for all my web controllers, and then automatically inject dependencies such as services or data access objects. Cool!

The actual classes that perform this magic are the ClassPathScanningCandidateComponentProvider which, by default, uses a PathMatchingResourcePatternResolver to find matching classpath resources using ClassLoader.getResources(). As a quick example, you can see this in action by writing a simple script, like so:

ClassPathScanningCandidateComponentProvider provider =
    new ClassPathScanningCandidateComponentProvider(true);
String basePackage = "org/springframework/samples/petclinic";
Set<BeanDefinition> components = provider.findCandidateComponents(basePackage);
for (BeanDefinition component : components) {
    System.out.printf("Component: %s\n", component.getBeanClassName());
}

Running this code (using the PetClinic sample application shipped with Spring), you get the following output:

Component: org.springframework.samples.petclinic.hibernate.HibernateClinic
Component: org.springframework.samples.petclinic.jdbc.SimpleJdbcClinic
Component: org.springframework.samples.petclinic.jpa.EntityManagerClinic
Component: org.springframework.samples.petclinic.web.AddOwnerForm
Component: org.springframework.samples.petclinic.web.AddPetForm
Component: org.springframework.samples.petclinic.web.AddVisitForm
Component: org.springframework.samples.petclinic.web.ClinicController
Component: org.springframework.samples.petclinic.web.EditOwnerForm
Component: org.springframework.samples.petclinic.web.EditPetForm
Component: org.springframework.samples.petclinic.web.FindOwnersForm

For a more fun experiment, I tried to scan using "org" as the base package...

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at org.springframework.asm.ClassReader.a(Unknown Source)
	at org.springframework.asm.ClassReader.(Unknown Source)
	at org.springframework.core.type.classreading.SimpleMetadataReaderFactory.getMetadataReader(SimpleMetadataReaderFactory.java:76)
	at org.springframework.core.type.classreading.CachingMetadataReaderFactory.getMetadataReader(CachingMetadataReaderFactory.java:68)
	at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.findCandidateComponents(ClassPathScanningCandidateComponentProvider.java:181)
	at org.springframework.samples.petclinic.ClassPathScannerTester.main(ClassPathScannerTester.java:17)

Ok, so you need to restrict the scan space that Spring will use or else you could run out of memory scanning every possible class in your system! Regardless, with classpath scanning of components and autowiring of dependencies, you can cut down the amount of XML in Spring-based apps a lot.



Post a Comment:
Comments are closed for this entry.