How Stuff Works: Spring Component Scanning

Spring has an interesting feature of scanning its components defined and load it.So the configuration is tied to application ie the code, using annotations.Spring javaconfig also provides the capability to do convention over configuration.There are a lot of documents,references etc explaining how to do the spring configuration. I was looking into the under the hood flow of how the stuff works...

A minimal config to application context xml

<context:component-scan base-package="packageName"/>

will scan all the component classes in the package.The component classes in the classpath are detected and bean definitions are auto-registered for them.

As per the Schema URI and Schema XSD, the context namespace will be like this - Reference


There are stereotype annotations which are markers for any class that fulfills a role within an application.This is well showcased in SpringMVC.More about the annotations

For efficient configuration, we can have multiple context xmls for maintaining resources. The application can have one for DAOs, one for services and so on.The layers can be effectively scanned by the context loader with this usage. So MVC applications will have seperate xmls for @Repository (data access tier),@Service (service),@Controller (web tier) components.
So for the example in a simple java app, I used them in a single xml. But this is mot an mvc app.

A UserDAO Interface

package com.sample.data;

import java.util.List;

public interface UserDAO {

List<String> getUsers();
}


Its Implementation

package com.sample.data;

import java.util.ArrayList;
import java.util.List;

import org.springframework.stereotype.Repository;

@Repository("userDAO")
public class UserDAOImpl implements UserDAO {

@Override
public List<String> getUsers() {

List<String> l = new ArrayList<String>();
l.add("Roger Moore");
l.add("Pierce Brosnan");

return l;
}

}


Service Layer

package com.sample.service;

import java.util.List;

public interface UserService {
List<String> getUsers();
}

And its implementation

package com.sample.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.sample.data.UserDAO;


@Service("userService")
public class UserServiceImpl implements UserService{

@Autowired
private UserDAO userDAO;

@Override
public List<String> getUsers() {

return userDAO.getUsers();
}


}


the client

package com.sample.client;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.sample.service.UserService;

public class SpringClient {

private UserService service;

public SpringClient(){
ApplicationContext appContext = new ClassPathXmlApplicationContext("resource/applicationContext.xml");
service= (UserService) appContext.getBean("userService");
((ClassPathXmlApplicationContext)appContext).close();
}


public void showUsers() {
for (String s : service.getUsers()) {
System.out.println(s);
}
}

public static void main(String[] args) {
SpringClient spc = new SpringClient();
spc.showUsers();
}

}




applicationContext

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"
default-autowire="byName">

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


<context:component-scan base-package="com.sample.data">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Repository"/>
</context:component-scan>

<context:component-scan base-package="com.sample.service">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Service"/>
</context:component-scan>

</beans>



After bootstrapping, the set of application components and service that need to be created are identified. AbstractbeanDefinitionReader will read resource definitions. DefaultListableBeanFactory will be used as default bean factory based on bean definition objects. XmlBeanDefinitionReader.loadBeanDefinitions() load bean definitions from the specified XML file in which the BeanDefinitionParser will identify the context namespaces and parses the applicationContext xml. The resources are identified by the implementation of ResourcePatternResolver:, ie PathMatchingResourcePatternResolver in which the location patterns are found like an ant-style. Internally it uses ClassLoader.getResources(String name) method which returns an Enumeration containing URLs representing classpath resources. Then the ComponentScanBeanDefinitionParser will parse through the context defintion nodes. If annotation configuration is enabled, autowiring of components takes place as these "candidate patterns" can be set as autowired. A default AutowiredAnnotationBeanPostProcessor will be registered by the "context:annotation-config" and "context:component-scan" XML tags.If filters are added, then it will parse the type filters. In the example I have provided annotation is the type filter. So it will use AnnotationTypeFilter to load the annotation Repository which is provided as the DAO's annotation resolver.

And we know that spring classes are designed to be extended.I was going through the API docs and found that we can add and exclude filters programmatically too.

So I added showComponents to client code using a selected base package

public void showComponents(){

ClassPathScanningCandidateComponentProvider provider =
new ClassPathScanningCandidateComponentProvider(true);
String basePackage = "com/sample/data";
provider.addExcludeFilter(new AnnotationTypeFilter(Repository.class, true));
Set<BeanDefinition> filteredComponents = provider.findCandidateComponents(basePackage);
System.out.println("No of components :"+filteredComponents.size());

for (BeanDefinition component : filteredComponents) {
System.out.println("Component:"+ component.getBeanClassName());
}

provider.resetFilters(true);
provider.addIncludeFilter(new AnnotationTypeFilter(Repository.class, true));
filteredComponents = provider.findCandidateComponents(basePackage);
System.out.println("No of components :"+filteredComponents.size());

for (BeanDefinition component : filteredComponents) {
System.out.println("Component:"+ component.getBeanClassName());
}

}

So the output will be

No of components :0
No of components :1
Component: com.sample.data.UserDAOImpl



If code and configuration are static like beans, the scanner annotations are useful. For accessing resources, jndi or jdbc or anything dynamic like that better go for xml as it is easy to modify it without code change.Its widely used for request mappings and controllers in Spring MVC.The xml overrides the config.When classes are more scanning will be difficult, so we have to filter them based on the type required.


More Reading

Classpath scanning and managed components

1 comment:

  1. very nice... for more java examples, visit http://java2novice.com site

    ReplyDelete