Spring Configuration
Introduction
Spring Framework offers a powerful and flexible configuration system that allows developers to define how their application components (beans) are created, wired, and managed. For beginners, understanding Spring's configuration options is a fundamental step in mastering the framework.
In this guide, we'll explore the different approaches to Spring configuration:
- XML-based configuration
- Java-based configuration
- Annotation-based configuration
Each method has its strengths, and modern Spring applications often use a combination of these approaches. By the end of this tutorial, you'll understand how to configure Spring applications effectively and choose the right approach for your specific needs.
XML-Based Configuration
XML configuration was Spring's original configuration mechanism. Despite newer alternatives, many existing applications still use XML configuration, making it important to understand.
Basic XML Configuration
A Spring XML configuration file typically looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- Bean definitions go here -->
<bean id="customerService" class="com.example.services.CustomerServiceImpl">
<!-- Constructor-based dependency injection -->
<constructor-arg ref="customerRepository" />
</bean>
<bean id="customerRepository" class="com.example.repositories.JdbcCustomerRepository">
<!-- Property-based dependency injection -->
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mydb" />
<property name="username" value="root" />
<property name="password" value="password" />
</bean>
</beans>
Loading XML Configuration
To load and use an XML configuration file:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Application {
public static void main(String[] args) {
// Load the Spring XML configuration
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// Get a bean from the container
CustomerService customerService = context.getBean("customerService", CustomerService.class);
// Use the bean
customerService.findAllCustomers();
}
}
XML Configuration Features
XML configuration provides several features:
- Bean Definitions: Define beans with the
<bean>
element - Dependencies: Configure dependencies through constructor arguments (
<constructor-arg>
) or setters (<property>
) - Collections: Configure collection types like lists, maps, and sets
- Bean Scopes: Define bean scopes (singleton, prototype, etc.) using the
scope
attribute - Lifecycle Methods: Specify initialization and destruction methods
Example of collections and scopes:
<bean id="customerService" class="com.example.CustomerService" scope="singleton">
<property name="supportedRegions">
<list>
<value>North America</value>
<value>Europe</value>
<value>Asia</value>
</list>
</property>
<property name="regionManagers">
<map>
<entry key="North America" value-ref="naManager" />
<entry key="Europe" value-ref="euManager" />
</map>
</property>
</bean>
Java-Based Configuration
Java-based configuration uses Java classes and annotations to configure the Spring container. This approach offers type safety, better refactoring capabilities, and IDE assistance.
Basic Java Configuration
Here's a simple Java configuration class:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.apache.commons.dbcp2.BasicDataSource;
@Configuration
public class AppConfig {
@Bean
public CustomerService customerService() {
return new CustomerServiceImpl(customerRepository());
}
@Bean
public CustomerRepository customerRepository() {
JdbcCustomerRepository repository = new JdbcCustomerRepository();
repository.setDataSource(dataSource());
return repository;
}
@Bean(destroyMethod = "close")
public BasicDataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mydb");
dataSource.setUsername("root");
dataSource.setPassword("password");
return dataSource;
}
}
Loading Java Configuration
To load a Java configuration:
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Application {
public static void main(String[] args) {
// Load the Java configuration
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// Get a bean from the container
CustomerService customerService = context.getBean(CustomerService.class);
// Use the bean
customerService.findAllCustomers();
}
}
Java Configuration Features
Java configuration offers several features:
- @Configuration: Marks a class as a source of bean definitions
- @Bean: Defines a bean to be managed by Spring
- @Import: Combines multiple configuration classes
- @Profile: Enables beans conditionally based on active profiles
- @PropertySource: Integrates property files
Example with additional features:
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.PropertySource;
@Configuration
@Import({SecurityConfig.class, PersistenceConfig.class})
@PropertySource("classpath:application.properties")
public class AppConfig {
@Bean
@Profile("development")
public DataSource developmentDataSource() {
// Development database configuration
}
@Bean
@Profile("production")
public DataSource productionDataSource() {
// Production database configuration
}
}
Annotation-Based Configuration
Annotation-based configuration minimizes explicit configuration by using annotations directly on your component classes, enabling Spring to auto-detect and configure beans.
Component Scanning
To enable annotation-based configuration, you need to configure component scanning:
In XML:
<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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.example" />
</beans>
In Java:
@Configuration
@ComponentScan("com.example")
public class AppConfig {
// Configuration beans can still be defined here
}
Component Annotations
Spring provides several annotations to define components:
- @Component: Generic stereotype for any Spring-managed component
- @Service: Indicates the class is a service layer component
- @Repository: Indicates the class is a data access component
- @Controller: Indicates the class is a web controller
Example:
import org.springframework.stereotype.Service;
@Service
public class CustomerServiceImpl implements CustomerService {
private final CustomerRepository repository;
// Constructor injection
public CustomerServiceImpl(CustomerRepository repository) {
this.repository = repository;
}
@Override
public List<Customer> findAllCustomers() {
return repository.findAll();
}
}
import org.springframework.stereotype.Repository;
@Repository
public class JdbcCustomerRepository implements CustomerRepository {
private DataSource dataSource;
// Setter injection
@Autowired
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public List<Customer> findAll() {
// JDBC code to fetch customers
}
}
Dependency Injection Annotations
Spring provides several annotations for dependency injection:
- @Autowired: Injects a dependency (field, constructor, or setter)
- @Qualifier: Specifies which bean to inject when multiple candidates exist
- @Value: Injects values from properties files
Example:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
public class NotificationService {
private final MessageSender messageSender;
private final String appName;
@Autowired
public NotificationService(@Qualifier("emailSender") MessageSender messageSender,
@Value("${application.name}") String appName) {
this.messageSender = messageSender;
this.appName = appName;
}
public void sendNotification(String recipient, String message) {
String formattedMessage = String.format("[%s] %s", appName, message);
messageSender.sendMessage(recipient, formattedMessage);
}
}
Combining Configuration Approaches
Modern Spring applications often combine different configuration approaches. For example:
- Java configuration for application-level beans and infrastructure
- Annotation-based configuration for components
- Properties files for environment-specific values
Here's an example of a mixed configuration:
@Configuration
@ComponentScan("com.example")
@PropertySource("classpath:application.properties")
@ImportResource("classpath:legacy-config.xml")
public class AppConfig {
@Bean
public DataSource dataSource(@Value("${db.url}") String url,
@Value("${db.username}") String username,
@Value("${db.password}") String password) {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
In this example:
- Java configuration defines the overall structure
- Component scanning finds annotated components
- Property values are injected from a properties file
- Legacy XML configuration is imported with
@ImportResource
Real-World Example: Building a Simple Customer Management System
Let's put everything together in a simple customer management system example:
Domain Objects
public class Customer {
private Long id;
private String firstName;
private String lastName;
private String email;
// Getters, setters, constructors
}
Repository Layer
public interface CustomerRepository {
List<Customer> findAll();
Customer findById(Long id);
void save(Customer customer);
void delete(Long id);
}
@Repository
public class JdbcCustomerRepository implements CustomerRepository {
private final JdbcTemplate jdbcTemplate;
@Autowired
public JdbcCustomerRepository(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public List<Customer> findAll() {
return jdbcTemplate.query("SELECT id, first_name, last_name, email FROM customers",
(rs, rowNum) -> {
Customer customer = new Customer();
customer.setId(rs.getLong("id"));
customer.setFirstName(rs.getString("first_name"));
customer.setLastName(rs.getString("last_name"));
customer.setEmail(rs.getString("email"));
return customer;
});
}
// Other methods implementation
}
Service Layer
public interface CustomerService {
List<Customer> findAllCustomers();
Customer findCustomerById(Long id);
void saveCustomer(Customer customer);
void deleteCustomer(Long id);
}
@Service
public class CustomerServiceImpl implements CustomerService {
private final CustomerRepository customerRepository;
@Autowired
public CustomerServiceImpl(CustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}
@Override
public List<Customer> findAllCustomers() {
return customerRepository.findAll();
}
// Other methods implementation
}
Configuration
@Configuration
@ComponentScan(basePackages = "com.example")
@PropertySource("classpath:database.properties")
public class AppConfig {
@Bean
public DataSource dataSource(Environment env) {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(env.getProperty("db.driver"));
dataSource.setUrl(env.getProperty("db.url"));
dataSource.setUsername(env.getProperty("db.username"));
dataSource.setPassword(env.getProperty("db.password"));
return dataSource;
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
Main Application
public class CustomerManagementApp {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
CustomerService customerService = context.getBean(CustomerService.class);
// Display all customers
System.out.println("All Customers:");
List<Customer> customers = customerService.findAllCustomers();
for (Customer customer : customers) {
System.out.println(customer.getFirstName() + " " + customer.getLastName());
}
// Add a new customer
Customer newCustomer = new Customer();
newCustomer.setFirstName("John");
newCustomer.setLastName("Smith");
newCustomer.setEmail("[email protected]");
customerService.saveCustomer(newCustomer);
System.out.println("\nCustomer added successfully!");
}
}
Summary
Spring configuration is a fundamental aspect of developing applications with the Spring Framework. In this guide, we've covered:
- XML-based configuration: Traditional approach with explicit bean definitions
- Java-based configuration: Type-safe configuration using Java classes and annotations
- Annotation-based configuration: Lightweight approach using component scanning and annotations
- Combined approaches: How to effectively mix different configuration styles
Each configuration approach has its strengths:
- XML configuration provides clear separation between code and configuration
- Java configuration offers type safety and refactoring support
- Annotation-based configuration reduces boilerplate and promotes convention over configuration
Modern Spring applications typically use a combination of these approaches, with Java configuration and annotations being the most common choice for new projects.
Additional Resources and Exercises
Resources
Exercises
-
Basic Configuration: Create a Spring application with Java configuration that defines a simple service and repository.
-
Mixed Configuration: Extend the application to use component scanning for your services and repositories, while keeping explicit configuration for infrastructure components.
-
Profile-Based Configuration: Configure your application to use different data sources based on profiles (e.g., "development" vs. "production").
-
Property Externalization: Move all configurable properties to an external properties file and inject them using
@Value
annotations. -
Advanced Configuration: Create a more complex application with multiple modules, each with its own configuration class, and combine them using
@Import
.
By mastering Spring configuration, you'll build a solid foundation for developing robust and maintainable Spring applications!
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)