Get ahead
VMware offers training and certification to turbo-charge your progress.
Learn moreSpring 2.0 introduced annotation support and annotation-aware configuration options that can be leveraged by Spring users who are developing with Java 5 (or later versions):
@Transactional | for demarcating and configuring transaction definitions |
---|---|
@Aspect (AspectJ) | for defining aspects along with @Pointcut definitions and advice (@Before, @After, @Around) |
@Repository | for indicating a class that is operating as a repository (a.k.a. Data Access Object or DAO) |
@Required | for enforcing annotated bean properties are provided a value |
With Spring 2.1, this theme of annotation-driven configuration has been significantly extended and will continue to evolve as we progress toward the RC1 release. In fact, it is now possible to drive Spring's dependency injection via annotations. Furthermore, Spring can discover beans that need to be configured within an application context.
This blog entry will serve as a tutorial-style introduction to the basic features in 10 easy-to-follow steps. I will follow up later in the week with information on some more advanced features and customization options. If you are interested in alternative configuration options, you should also check out the Spring Java Configuration project and this blog.
This tutorial requires at least Java 5, and Java 6 is recommended (otherwise there is a single requirement at the end of step 1).
Grab spring-framework-2.1-m1-with-dependencies.zip. After extracting the archive, you will find the spring.jar and spring-mock.jar in the 'dist' directory. Add them to your CLASSPATH as well as the following (paths shown are relative to the 'lib' directory of the extracted 2.1-m1 archive):
public interface GreetingService {
String greet(String name);
}
Then, a simple implementation:
public class GreetingServiceImpl implements GreetingService {
private MessageRepository messageRepository;
public void setMessageRepository(MessageRepository messageRepository) {
this.messageRepository = messageRepository;
}
public String greet(String name) {
Locale locale = Locale.getDefault();
String message = messageRepository.getMessage(locale.getDisplayLanguage());
return message + " " + name;
}
}
Since the service depends upon a MessageRepository, define that interface next:
public interface MessageRepository {
String getMessage(String language);
}
And for now, a stub implementation:
public class StubMessageRepository implements MessageRepository {
Map<String,String> messages = new HashMap<String,String>();
public void initialize() {
messages.put("English", "Welcome");
messages.put("Deutsch", "Willkommen");
}
public String getMessage(String language) {
return messages.get(language);
}
}
Define the beans for a Spring application context. Notice, that I am including a new 'context' namespace (NOTE: the 'aop' namespace is also included here and will be used in the final step):
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.1.xsd">
<bean class="blog.GreetingServiceImpl"/>
<bean class="blog.StubMessageRepository"/>
</beans>
Obviously this configuration looks a little sparse. As you can probably guess, the 'context' namespace will soon play a role.
public class GreetingServiceImplTests extends AbstractDependencyInjectionSpringContextTests {
private GreetingService greetingService;
public void setGreetingService(GreetingService greetingService) {
this.greetingService = greetingService;
}
@Override
protected String[] getConfigLocations() {
return new String[] { "/blog/applicationContext.xml" };
}
public void testEnglishWelcome() {
Locale.setDefault(Locale.ENGLISH);
String name = "Spring Community";
String greeting = greetingService.greet(name);
assertEquals("Welcome " + name, greeting);
}
public void testGermanWelcome() {
Locale.setDefault(Locale.GERMAN);
String name = "Spring Community";
String greeting = greetingService.greet(name);
assertEquals("Willkommen " + name, greeting);
}
}
Try running the tests and notice that they fail with a NullPointerException. This is to be expected since the GreetingServiceImpl has not been provided a MessageRepository. In the next two steps, you will add annotations to drive the dependency injection and initialization respectively.
@Autowired
public void setMessageRepository(MessageRepository messageRepository) {
this.messageRepository = messageRepository;
}
Then, add the 'annotation-config' element (from the new 'context' namespace) to your configuration:
<beans ... >
<context:annotation-config/>
<bean class="blog.GreetingServiceImpl"/>
<bean class="blog.StubMessageRepository"/>
</beans>
Rerun the tests. They will still fail, but if you look closely it's a new problem. The assertions fail, because the messages being returned are null. That means that the 'messageRepository' property has been set on the greeting service! Now, the StubMessageRepository simply needs to be initialized.
@PostConstruct
public void initialize() {
messages.put("English", "Welcome");
messages.put("Deutsch", "Willkommen");
}
Rerun the tests. This time they should pass!
The @Autowired annotation can also be used for constructor-based injection. If you'd like to experiment with that option, remove the setter method from the GreetingServiceImpl and add this constructor instead (then rerun the tests):
@Autowired
public GreetingServiceImpl(MessageRepository messageRepository) {
this.messageRepository = messageRepository;
}
If preferred, you can even use field-injection. Remove the constructor, add the annotation directly to the field, and rerun the tests. The code should look like this:
@Autowired
private MessageRepository messageRepository;
Add a JDBC-based repository implementation of the MessageRepository:
public class JdbcMessageRepository implements MessageRepository {
private SimpleJdbcTemplate jdbcTemplate;
@PostConstruct
public void setUpDatabase() {
jdbcTemplate.update("create table messages (language varchar(20), message varchar(100))");
jdbcTemplate.update("insert into messages (language, message) values ('English', 'Welcome')");
jdbcTemplate.update("insert into messages (language, message) values ('Deutsch', 'Willkommen')");
}
@PreDestroy
public void tearDownDatabase() {
jdbcTemplate.update("drop table messages");
}
public String getMessage(String language) {
return jdbcTemplate.queryForObject("select message from messages where language = ?", String.class, language);
}
}
Notice that in addition to @PostConstruct for initialization, this is using @PreDestroy to mark a method to be called on destruction. One thing is unclear from this implementation: how will the SimpleJdbcTemplate be provided? One option would be to provide a bean definition for the template. Another option would be to somehow provide a DataSource implementation to the template's constructor. Add the following (annotated) method:
@Autowired
public void createTemplate(DataSource dataSource) {
this.jdbcTemplate = new SimpleJdbcTemplate(dataSource);
}
This demonstrates dependency injection working with an arbitrary method (not a traditional 'setter'). This will be tested in the course of the next step.
In Spring 2.1, "candidate" beans can even be discovered rather than provided explicitly in the XML as above. Certain annotations are recognized by default. This includes the @Repository annotation as well as a new @Component annotation. Add those two annotations to JdbcMessageRepository and GreetingServiceImpl respectively:
@Repository
public class JdbcMessageRepository implements MessageRepository { ... }
@Component
public class GreetingServiceImpl implements GreetingService { ... }
Then modify the XML file by removing the existing explicit bean definitions and simply adding a component-scan tag:
<beans ... >
<context:component-scan base-package="blog"/>
</beans>
Then, add just the DataSource bean definition and the new tag for configuring property placeholders:
<beans ... >
<context:component-scan base-package="blog"/>
<context:property-placeholder location="classpath:blog/jdbc.properties"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
... and the jdbc.properties file itself:
jdbc.driver=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:mem:blog
jdbc.username=sa
jdbc.password=
Rerun the tests, and you should see the green bar even though only the data source has been defined in XML.
Finally, add an aspect (the @Aspect annotations are also automatically detected by default):
@Aspect
public class ServiceInvocationLogger {
private int invocationCount;
@Pointcut("execution(* blog.*Service+.*(..))")
public void serviceInvocation() {}
@Before("serviceInvocation()")
public void log() {
invocationCount++;
System.out.println("service invocation #" + invocationCount);
}
}
And to activate automatic proxy generation, simply add the following tag to the xml:
<aop:aspectj-autoproxy/>
Rerun the tests, and you should see the log messages!
NOTE: the scanning and configuration process can be initiated without any XML and can be customized (e.g. detect your own annotations and/or types). I will discuss those features and more in the next post.
In the mean time, I hope this post will serve its purpose well - providing hands-on experience with these new Spring 2.1 features. As always, we are looking forward to feedback from the community, so please feel free to leave comments!