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 scopeattribute
- 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 @Valueannotations.
- 
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!
💡 Found a typo or mistake? Click "Edit this page" to suggest a correction. Your feedback is greatly appreciated!