Saturday, February 18, 2012

Autowiring and Component scans

In our last blog entry, we took the "Hello World" program from the Spring 2.5 Recipes book and incorporated it into a Spring template utility project.

One if the interesting facts about the generated template test is that it includes the service as an "@Autowired" annotation:


@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class ExampleConfigurationTests {

@Autowired
private Service service;

@Test
public void testSimpleProperties() throws Exception {
assertNotNull(service);
}

}



A component scan statement in the bean configuration file apparently tells the Spring container to search for the @Component annotation in the specified package and subpackages:


<context:component-scan base-package="com.apress.springrecipes.hello" />


It's specified in the ExampleService bean:


@Component
public class ExampleService implements Service {



So, although the test class that uses it doesn't specify "ExampleService", but rather just "Service", since ExampleService is the only component in the package that matches the @Autowired annotation (it's a Service), Spring selects that as the class to be instantiated.

Our experiment today, though, is to first just implement @Autowired in a regular (not used for testing) class. If that works out, we'll try to see if we can use the component scan to avoid coding the beans.

We'll base our logic on this tutorial:

http://www.mkyong.com/spring/spring-auto-wiring-beans-with-autowired-annotation/

So, first let's generate a spring utility project. We'll call it "SpringTestAutowireAnnotation", and give it the same package name as the tutorial: "com.mkyong.common".

We can download the source code, and drag and drop the java source files into that package in the generated project. Also, we'll copy these bean configuration statements from the configuration file into our own project's configuration file (src/main/resources/META-INF/spring/app-context.xml):

<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>

<bean id="customer" class="com.mkyong.common.Customer" >
<property name="action" value="buy" />
<property name="type" value="1" />
</bean>

<bean id="personA" class="com.mkyong.common.Person" >
<property name="name" value="mkyongA" />
</bean>



So, now here's our app-context.xml:


<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

<description>Example configuration to get you started.</description>

<context:component-scan base-package="com.mkyong.common" />

<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>

<bean id="customer" class="com.mkyong.common.Customer" >
<property name="action" value="buy" />
<property name="type" value="1" />
</bean>

<bean id="personA" class="com.mkyong.common.Person" >
<property name="name" value="mkyongA" />
</bean>

</beans>



We'll have to change the means of retrieving the application context in the "App" class from this:

ApplicationContext context =
new ClassPathXmlApplicationContext(new String[] {"SpringBeans.xml"});


to this:

ApplicationContext context =
new ClassPathXmlApplicationContext("app-context.xml");



And, also, add the classpath app-context to the new run configuration for the project:

To do this, right click on App.java, select "Run As", "Run Configuration", click the new icon, give it a name like "SpringAutoWiredTest", click on the "Classpath" tab, select "User Entries", click the "Advanced" button on the right, select "Add External Folder", then navigate to the current project and then down to "src/main/resources/META-INF/spring", and then press "Choose".

Apply the changes and run.

Here's the output:

Customer [person=Person [name=mkyongA], type=1, action=buy]



Ok, now the ultimate test. Can we get delete the beans from the configuration file, add the @Component annotation to the beans, and get the same results? Let's try it.

Here's the new app-context.xml, with the beans deleted:

<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

<description>Example configuration to get you started.</description>

<context:component-scan base-package="com.mkyong.common" />

<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>

</beans>



Let's add the @Component annotations:

import org.springframework.stereotype.Component;

@Component
public class Customer {


and

import org.springframework.stereotype.Component;

@Component
public class Person {



Here's the output:


Customer [person=Person [name=null], type=0, action=null]


The name and action are null, because these were specified in the beans which we deleted. That's something to keep in mind when considering when and how to use the @Component tag.

No comments:

Post a Comment