While XML-based configurations offer a robust way to manage Spring Beans, many developers find annotation-driven approaches more concise and intuitive, especially for larger applications. This method streamlines the process of defining components and injecting dependencies directly within the Java code.
Initial Setup for Annotation-Driven Configuration
Before utilizing annotations for Spring Bean management, a few prerequisites must be met:
- Required Dependencies: Ensure your project includes the necessary Spring core and Spring AOP dependencies. For Maven projects, add them to your
pom.xml. - Spring Configuration File: Create an XML configuration file (e.g.,
applicationContext.xml) to bootstrap the Spring IoC container and enable annotation processing. - Enable Component Scanning: Within your Spring XML configuration, you must explicitly enable component scanning to allow Spring to discover classes annotated as components.
Here's an example of a Spring configuration file (applicationContext.xml) that enables component scanning:
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- Instruct Spring to scan for components (classes with @Component, @Service, etc.)
starting from the specified base package. -->
<context:component-scan base-package="com.example.app.components"/>
</beans>
Defining Beans with Stereotype Annotations
Spring provides several stereotype annotations to mark classes as components managed by the IoC container. When component scanning is enabled, Spring automatically detects these classes and registers them as beans.
@Component: A generic stereotype for any Spring-managed component.@Controller: Indicates that an annotated class is a "Controller" in the MVC architecture.@Service: Indicates that an annotated class is a "Service" in the service layer.@Repository: Indicates that an annotated class is a "Repository" in the data access layer, often used with persistence technologies.
Functionally, these four annotations are identical for basic bean definition. Their distinct names are primarily for semantic clarity and can be targeted by specific aspects or other configurations.
Consider a simple service class that we want Spring to manage:
package com.example.app.components;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
/**
* Marks this class as a Spring component.
* The 'value' attribute specifies a custom bean name ("userManagementService").
* @Scope("prototype") indicates that a new instance should be created for each request.
*/
@Component("userManagementService")
@Scope("prototype")
public class UserService {
public void createUser() {
System.out.println("UserService: Executing user creation logic...");
}
}
To retrieve and use this bean, you would load the Spring context and request the bean by its name:
package com.example.app;
import com.example.app.components.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class ComponentScanDemo {
public static void main(String[] args) {
// Load the Spring application context from the XML configuration file
ApplicationContext springContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// Retrieve the UserService bean by its custom name
UserService userService = (UserService) springContext.getBean("userManagementService");
userService.createUser();
// Demonstrate prototype scope: retrieve again to show a new instance (if scope were singleton, it'd be the same)
UserService anotherUserService = (UserService) springContext.getBean("userManagementService");
System.out.println("Are userService and anotherUserService the same object? " + (userService == anotherUserService));
}
}
Dependency Injection with Annotations
Dependency injection (DI) allows Spring to provide collaborating objects (dependencies) to a bean. Annotations simplify this by eliminating the need for explicit XML wiring.
1. Injecting Dependencies with @Autowired
The @Autowired annotation, provided by Spring, is the primary mechanism for automatic dependency injection. It performs injection by type matching. Spring will scan the application context for a bean that matches the type of the target field, setter method, or constructor parameter.
First, let's define a data access component:
package com.example.app.data;
import org.springframework.stereotype.Repository;
@Repository("dataAccessComponent")
public class DataRepository {
public String fetchData() {
System.out.println("DataRepository: Accessing database to fetch data.");
return "Retrieved data record from the data layer.";
}
}
Now, let's create a service layer component that depends on DataRepository. We'll use @Autowired on a setter method, though it can also be applied to fields or constructors.
package com.example.app.service;
import com.example.app.data.DataRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service("businessProcessor")
public class BusinessLogicService {
private DataRepository repository;
// @Autowired on a setter method
@Autowired
public void setRepository(DataRepository repository) {
this.repository = repository;
}
public void processAndDisplay() {
System.out.println("BusinessLogicService: Starting business operation.");
String data = repository.fetchData(); // Calls the injected DataRepository
System.out.println("BusinessLogicService received: " + data);
}
}
2. Injecting Dependencies with @Resource
The @Resource annotation, part of the JSR-250 standard (Java Common Annotations), also facilitates dependency injection. Unlike @Autowired which prioritizes type, @Resource primarily performs injection by name. If a name is explicitly specified (e.g., @Resource(name="myBean")), it will look for a bean with that exact name. If no name is specified, it will use the field name as the bean name. If a bean is not found by name, it falls back to type matching.
Let's modify our BusinessLogicService to use @Resource:
package com.example.app.service;
import com.example.app.data.DataRepository;
import org.springframework.stereotype.Service;
import javax.annotation.Resource; // Don't forget this import
@Service("businessProcessor")
public class BusinessLogicService {
// @Resource on a field, using the custom bean name from DataRepository
@Resource(name = "dataAccessComponent")
private DataRepository dataStore; // Field name changed from 'repository' to 'dataStore'
public void processAndDisplay() {
System.out.println("BusinessLogicService: Starting business operation.");
String data = dataStore.fetchData(); // Calls the injected DataRepository
System.out.println("BusinessLogicService received: " + data);
}
}
The test class for these dependency injection examples remains similar:
package com.example.app;
import com.example.app.service.BusinessLogicService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class DependencyInjectionDemo {
public static void main(String[] args) {
ApplicationContext springContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// Retrieve the BusinessLogicService bean
BusinessLogicService processor = (BusinessLogicService) springContext.getBean("businessProcessor");
processor.processAndDisplay();
}
}
Comparison of @Autowired and @Resource
- Origin:
@Autowiredis a Spring-specific annotation.@Resourceis a standard Java annotation (JSR-250), making it potentially more portable (though still often used within Spring).
- Injection Strategy:
@Autowiredprimarily injects by type. If multiple beans of the same type exist, it can be refined with@Qualifier.@Resourceprimarily injects by name. If a name is provided, it attempts to match that bean name. If no name is provided, it defaults to matching the field/setter name as the bean name. If a match is still not found, it falls back to type matching.
- Placement: Both annotations can be applied to fields and setter methods.