1. java
  2. /spring

Master Spring Framework for Enterprise Java Development

Spring Framework

Spring Framework stands as the most transformative technology in Java enterprise development, fundamentally changing how developers approach building complex applications. What started as a response to the complexity of Java Enterprise Edition (J2EE) has evolved into a comprehensive platform that powers millions of applications worldwide.

The Spring Revolution in Java Development

Before Spring Framework emerged in 2003, Java enterprise development was plagued by complexity, boilerplate code, and tight coupling between components. Traditional enterprise Java applications required extensive XML configuration, complicated deployment descriptors, and heavy application servers. Spring introduced a paradigm shift that made Java development more accessible, testable, and maintainable.

Understanding the Problems Spring Solves

The Traditional Java EE Challenges:

Tight Coupling: In pre-Spring applications, classes directly instantiated their dependencies, creating rigid architectures that were difficult to test and modify. If you wanted to change how a service worked, you often had to modify multiple classes throughout the application.

Configuration Complexity: Enterprise applications required extensive XML configuration files, deployment descriptors, and complex setup procedures that made development slow and error-prone.

Testing Difficulties: Because objects created their own dependencies, unit testing required complex mock setups or required running the entire application container.

Vendor Lock-in: Traditional Java EE tied applications to specific application servers and implementations, making it difficult to switch platforms or test in different environments.

How Spring Transforms Java Development

Spring addresses these challenges through several revolutionary concepts:

Inversion of Control (IoC): Instead of objects creating their own dependencies, Spring creates and manages them. This fundamental shift makes applications more modular, testable, and flexible.

Declarative Programming: Rather than writing imperative code that describes how to do something, Spring allows you to declare what you want to happen, handling the implementation details automatically.

POJO-Based Development: Spring works with Plain Old Java Objects (POJOs) rather than requiring inheritance from framework-specific classes, keeping your code clean and portable.

Non-Invasive Framework: Your business logic remains free of Spring-specific code, making it easier to test and maintain.

What is Spring Framework?

At its core, Spring is an inversion of control (IoC) container and aspect-oriented programming (AOP) framework that provides a comprehensive programming and configuration model for modern Java-based enterprise applications.

Core Spring Capabilities:

  • Dependency Injection: Automatic management of object dependencies through constructor injection, setter injection, and field injection
  • Aspect-Oriented Programming: Modular approach to cross-cutting concerns like logging, security, and transaction management
  • Data Access: Simplified database operations with consistent exception handling and resource management
  • Web Development: Full-featured MVC framework for building web applications and RESTful APIs
  • Security: Comprehensive security framework with authentication, authorization, and protection against common vulnerabilities
  • Testing: Extensive testing support with dependency injection for test classes and integration testing capabilities

Core Spring Concepts

Understanding Spring's fundamental concepts is essential for effective application development. These concepts work together to create a flexible, maintainable architecture that scales with your application's complexity.

Inversion of Control (IoC): The Foundation of Spring

Inversion of Control is the cornerstone principle that makes Spring so powerful. Traditional programming follows a "pull" model where objects actively create or look up their dependencies. IoC reverses this relationship—dependencies are "pushed" into objects from an external source (the Spring container).

Why IoC Matters:

Reduced Coupling: Objects no longer need to know how to create their dependencies, only how to use them. This makes code more modular and easier to change.

Enhanced Testability: You can easily inject mock objects during testing, making unit tests faster and more isolated.

Configuration Flexibility: The same code can work with different implementations by simply changing the configuration.

Lifecycle Management: Spring handles complex object creation, initialization, and cleanup automatically.

Let's see how this transformation works in practice:

// Traditional approach (tight coupling)
public class UserService {
    private UserRepository userRepository = new UserRepositoryImpl(); // Hard dependency
    
    public User findById(Long id) {
        return userRepository.findById(id);
    }
}

// Spring IoC approach (loose coupling)
@Service
public class UserService {
    private final UserRepository userRepository;
    
    // Constructor injection - Spring provides the dependency
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    public User findById(Long id) {
        return userRepository.findById(id);
    }
}

Analyzing the Transformation:

Traditional Approach Problems:

  • UserService is tightly coupled to UserRepositoryImpl
  • Difficult to test because you can't substitute the repository
  • Changing the repository implementation requires modifying UserService
  • No control over when or how the repository is created

Spring IoC Benefits:

  • UserService only depends on the UserRepository interface
  • Spring can inject any implementation of UserRepository
  • Easy to test by injecting mock repositories
  • The same service can work with database, file, or web service repositories
  • Spring manages the repository lifecycle and ensures proper initialization

Dependency Injection Types: Choosing the Right Approach

Spring supports three main types of dependency injection, each with distinct advantages and appropriate use cases. Understanding when and why to use each approach is crucial for building maintainable applications.

1. Constructor Injection (Recommended Approach)

Constructor injection is considered the best practice for most scenarios because it ensures immutable dependencies and makes required dependencies explicit.

@Service
public class OrderService {
    private final PaymentService paymentService;
    private final EmailService emailService;
    
    public OrderService(PaymentService paymentService, EmailService emailService) {
        this.paymentService = paymentService;
        this.emailService = emailService;
    }
}

Why Constructor Injection is Preferred:

Immutability: Dependencies are declared as final, preventing accidental modification after object creation.

Required Dependencies: The constructor clearly shows which dependencies are mandatory for the object to function properly.

Fail-Fast Behavior: If a required dependency is missing, the application fails to start rather than failing at runtime when the dependency is first used.

Testing Benefits: Easy to create instances in unit tests by simply calling the constructor with mock dependencies.

No Spring Dependency: Your classes don't need Spring-specific annotations to work, making them more portable.

2. Field Injection

Field injection uses the @Autowired annotation directly on fields, offering the most concise syntax but with several drawbacks.

@Service
public class OrderService {
    @Autowired
    private PaymentService paymentService;
    
    @Autowired
    private EmailService emailService;
}

Field Injection Characteristics:

Pros: Very concise and readable; minimal boilerplate code.

Cons:

  • Dependencies are mutable and can be changed after object creation
  • Makes unit testing more difficult (requires reflection or Spring test context)
  • Hides the dependencies from the class's public interface
  • Cannot be used with final fields
  • Harder to spot when a class has too many dependencies

When to Use: Primarily in test classes or configuration classes where simplicity is prioritized over best practices.

3. Setter Injection

Setter injection provides dependencies through setter methods, offering flexibility for optional dependencies.

@Service
public class OrderService {
    private PaymentService paymentService;
    
    @Autowired
    public void setPaymentService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }
}

Setter Injection Use Cases:

Optional Dependencies: When a dependency might not be available and the class can function without it.

Circular Dependencies: In rare cases where circular dependencies cannot be avoided (though this usually indicates a design problem).

Configuration Changes: When dependencies might need to be changed during the object's lifecycle.

Legacy Integration: When working with frameworks that require no-argument constructors.

Best Practice Recommendation: Use constructor injection for required dependencies and setter injection only for truly optional dependencies.

Spring Boot: Revolutionizing Spring Development

While Spring Framework solved many enterprise Java problems, setting up a Spring application still required extensive configuration, XML files, and complex project setup. Spring Boot emerged to eliminate this "configuration hell" and make Spring development as simple as possible while maintaining all of Spring's power and flexibility.

The Problem Spring Boot Solves

Configuration Complexity: Traditional Spring applications required numerous XML configuration files or Java configuration classes to set up basic functionality like web servers, database connections, and security.

Dependency Management: Developers spent significant time figuring out which versions of various libraries worked together, leading to "dependency hell" and compatibility issues.

Deployment Challenges: Applications needed to be deployed to external application servers, requiring server setup, configuration, and management.

Development Overhead: Getting a simple web application running could take hours or days of configuration before writing the first line of business logic.

How Spring Boot Transforms Development

Spring Boot addresses these challenges through several innovative approaches:

Auto-Configuration: Spring Boot examines your classpath and automatically configures beans based on what it finds. If you have a database driver on the classpath, it automatically configures a DataSource. If you have Spring MVC, it configures a web application.

Starter Dependencies: Instead of manually selecting compatible versions of multiple libraries, Spring Boot provides "starters" - curated dependency sets that are guaranteed to work together.

Embedded Servers: Applications include their own web server (Tomcat, Jetty, or Undertow), eliminating the need for external server setup and making deployment as simple as running a JAR file.

Opinionated Defaults: Spring Boot makes sensible choices for most configurations while still allowing customization when needed.

Spring Boot simplifies Spring application development by providing:

  • Auto-configuration: Intelligent automatic setup based on classpath dependencies and application context
  • Starter dependencies: Pre-configured, tested dependency sets that eliminate version compatibility issues
  • Embedded servers: Built-in web servers that eliminate deployment complexity and infrastructure requirements
  • Production-ready features: Built-in health checks, metrics collection, monitoring endpoints, and externalized configuration

Anatomy of a Spring Boot Application

Let's examine how Spring Boot creates a fully functional enterprise application with minimal configuration. This example demonstrates the power of Spring Boot's conventions and auto-configuration.

The Application Entry Point

// Main application class
@SpringBootApplication  // Combines @Configuration, @EnableAutoConfiguration, @ComponentScan
public class EcommerceApplication {
    public static void main(String[] args) {
        SpringApplication.run(EcommerceApplication.class, args);
    }
}

Understanding @SpringBootApplication:

This single annotation is actually a powerful combination of three essential Spring annotations:

@Configuration: Tells Spring that this class contains bean definitions and configuration.

@EnableAutoConfiguration: Enables Spring Boot's auto-configuration mechanism, which automatically configures beans based on classpath dependencies.

@ComponentScan: Automatically discovers and registers Spring components (services, controllers, repositories) in the current package and its sub-packages.

The main Method:

  • SpringApplication.run() bootstraps the entire Spring application
  • Creates the Spring application context
  • Starts the embedded web server (if web dependencies are present)
  • Configures all the auto-configuration
  • Makes the application ready to handle requests

This single class replaces what would traditionally require extensive XML configuration and deployment descriptor files.

Data Layer: JPA Entities

The entity class represents your data model and how it maps to database tables. Spring Boot automatically configures JPA (Java Persistence API) when it detects JPA dependencies on the classpath.

// Entity class
@Entity
@Table(name = "products")
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false)
    private String name;
    
    @Column(nullable = false)
    private BigDecimal price;
    
    @Enumerated(EnumType.STRING)
    private Category category;
    
    // Constructors, getters, setters
    public Product() {}
    
    public Product(String name, BigDecimal price, Category category) {
        this.name = name;
        this.price = price;
        this.category = category;
    }
    
    // Getters and setters...
}

JPA Annotations Explained:

@Entity: Marks this class as a JPA entity that should be mapped to a database table.

@Table(name = "products"): Specifies the exact table name in the database. Without this, JPA would use the class name as the table name.

@Id: Designates the primary key field.

@GeneratedValue(strategy = GenerationType.IDENTITY): Tells JPA that the database will automatically generate ID values (auto-increment).

@Column: Provides additional constraints and metadata for database columns. nullable = false creates a NOT NULL constraint.

@Enumerated(EnumType.STRING): Stores enum values as strings in the database rather than ordinal numbers, making the database more readable and maintainable.

Default Constructor: JPA requires a no-argument constructor for object instantiation from database rows.

Data Access Layer: Spring Data JPA Repositories

Spring Data JPA eliminates the need to write boilerplate database access code by automatically generating repository implementations based on interface definitions and method naming conventions.

// Repository interface (Spring Data JPA)
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
    List<Product> findByCategory(Category category);
    List<Product> findByPriceBetween(BigDecimal minPrice, BigDecimal maxPrice);
    
    @Query("SELECT p FROM Product p WHERE p.name LIKE %:name%")
    List<Product> findByNameContaining(@Param("name") String name);
}

The Magic of Spring Data JPA:

Interface-Only Approach: You only define the interface—Spring Data JPA automatically creates the implementation at runtime using proxy classes.

Method Name Conventions: Spring Data JPA parses method names to understand what query you want:

  • findByCategory(Category category) becomes SELECT * FROM products WHERE category = ?
  • findByPriceBetween(BigDecimal min, BigDecimal max) becomes SELECT * FROM products WHERE price BETWEEN ? AND ?

Built-in Methods from JpaRepository:

  • findAll(): Retrieves all entities
  • findById(Long id): Finds an entity by its primary key
  • save(Product product): Saves or updates an entity
  • deleteById(Long id): Deletes an entity by ID
  • count(): Returns the total number of entities

Custom Queries: When method naming isn't sufficient, use @Query to write custom JPQL (Java Persistence Query Language) or native SQL queries.

Parameter Binding: The @Param annotation explicitly binds method parameters to query parameters, useful for complex queries.

This approach eliminates hundreds of lines of boilerplate DAO (Data Access Object) code that you would traditionally write for database operations.

Business Logic Layer: Services

The service layer contains your application's business logic and acts as a bridge between the presentation layer (controllers) and the data access layer (repositories). This layer is where you implement business rules, validation, and complex operations.

// Service layer
@Service
@Transactional
public class ProductService {
    private final ProductRepository productRepository;
    
    public ProductService(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }
    
    public List<Product> getAllProducts() {
        return productRepository.findAll();
    }
    
    public Optional<Product> getProductById(Long id) {
        return productRepository.findById(id);
    }
    
    public Product saveProduct(Product product) {
        return productRepository.save(product);
    }
    
    public void deleteProduct(Long id) {
        productRepository.deleteById(id);
    }
    
    public List<Product> getProductsByCategory(Category category) {
        return productRepository.findByCategory(category);
    }
}

Service Layer Architecture Principles:

@Service Annotation: Marks this class as a Spring service component, making it eligible for dependency injection and component scanning. This is a specialization of @Component that indicates the class contains business logic.

@Transactional Annotation: Automatically manages database transactions for all methods in this class:

  • Starts a transaction when a method begins
  • Commits the transaction if the method completes successfully
  • Rolls back the transaction if an exception occurs
  • Handles connection management and cleanup automatically

Constructor Injection: Uses the recommended dependency injection pattern, ensuring the service has all required dependencies at creation time.

Business Logic Encapsulation: While this example shows simple CRUD operations, real services would contain:

  • Data validation and business rule enforcement
  • Complex business calculations
  • Coordination between multiple repositories
  • Integration with external services
  • Caching and performance optimizations

Return Types: Notice the use of Optional<Product> for the getProductById method, which is a modern Java approach to handling potentially null values safely.

Separation of Concerns: The service doesn't know about HTTP requests, JSON formatting, or database implementation details—it focuses purely on business logic.

// REST Controller @RestController @RequestMapping("/api/products") @CrossOrigin(origins = "*") public class ProductController { private final ProductService productService;

public ProductController(ProductService productService) { this.productService = productService; }

@GetMapping public ResponseEntity<List<Product>> getAllProducts() { List<Product> products = productService.getAllProducts(); return ResponseEntity.ok(products); }

@GetMapping("/{id}") public ResponseEntity<Product> getProductById(@PathVariable Long id) { return productService.getProductById(id) .map(product -> ResponseEntity.ok(product)) .orElse(ResponseEntity.notFound().build()); }

@PostMapping public ResponseEntity<Product> createProduct(@Valid @RequestBody Product product) { Product savedProduct = productService.saveProduct(product); return ResponseEntity.status(HttpStatus.CREATED).body(savedProduct); }

@PutMapping("/{id}") public ResponseEntity<Product> updateProduct(@PathVariable Long id, @Valid @RequestBody Product product) { return productService.getProductById(id) .map(existingProduct -> { product.setId(id); Product updatedProduct = productService.saveProduct(product); return ResponseEntity.ok(updatedProduct); }) .orElse(ResponseEntity.notFound().build()); }

@DeleteMapping("/{id}") public ResponseEntity<Void> deleteProduct(@PathVariable Long id) { if (productService.getProductById(id).isPresent()) { productService.deleteProduct(id); return ResponseEntity.noContent().build(); } return ResponseEntity.notFound().build(); }

@GetMapping("/category/{category}") public ResponseEntity<List<Product>> getProductsByCategory(@PathVariable Category category) { List<Product> products = productService.getProductsByCategory(category); return ResponseEntity.ok(products); } }


## Spring Security Integration

```java
// Security configuration
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers(HttpMethod.GET, "/api/products/**").permitAll()
                .requestMatchers(HttpMethod.POST, "/api/products/**").hasRole("ADMIN")
                .requestMatchers(HttpMethod.PUT, "/api/products/**").hasRole("ADMIN")
                .requestMatchers(HttpMethod.DELETE, "/api/products/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .httpBasic()
            .and()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        
        return http.build();
    }
}

// User entity
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(unique = true, nullable = false)
    private String username;
    
    @Column(nullable = false)
    private String password;
    
    @Enumerated(EnumType.STRING)
    private Role role;
    
    // Constructors, getters, setters...
}

// Custom UserDetailsService
@Service
public class CustomUserDetailsService implements UserDetailsService {
    private final UserRepository userRepository;
    
    public CustomUserDetailsService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username)
            .orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
        
        return org.springframework.security.core.userdetails.User.builder()
            .username(user.getUsername())
            .password(user.getPassword())
            .authorities("ROLE_" + user.getRole().name())
            .build();
    }
}

Spring Data JPA Features

Custom Repository Methods

@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
    // Query methods (Spring generates implementation)
    List<Product> findByCategory(Category category);
    List<Product> findByPriceLessThan(BigDecimal price);
    List<Product> findByNameContainingIgnoreCase(String name);
    
    // Custom JPQL queries
    @Query("SELECT p FROM Product p WHERE p.price BETWEEN :minPrice AND :maxPrice ORDER BY p.price")
    List<Product> findByPriceRange(@Param("minPrice") BigDecimal minPrice, 
                                  @Param("maxPrice") BigDecimal maxPrice);
    
    // Native SQL queries
    @Query(value = "SELECT * FROM products WHERE MATCH(name) AGAINST(?1 IN NATURAL LANGUAGE MODE)", 
           nativeQuery = true)
    List<Product> findByFullTextSearch(String searchTerm);
    
    // Modifying queries
    @Modifying
    @Query("UPDATE Product p SET p.price = p.price * :multiplier WHERE p.category = :category")
    int updatePricesByCategory(@Param("category") Category category, 
                              @Param("multiplier") BigDecimal multiplier);
}

Pagination and Sorting

@RestController
@RequestMapping("/api/products")
public class ProductController {
    
    @GetMapping("/paged")
    public ResponseEntity<Page<Product>> getProductsPaged(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "10") int size,
            @RequestParam(defaultValue = "id") String sortBy,
            @RequestParam(defaultValue = "asc") String sortDir) {
        
        Sort sort = sortDir.equalsIgnoreCase("desc") ? 
            Sort.by(sortBy).descending() : 
            Sort.by(sortBy).ascending();
        
        Pageable pageable = PageRequest.of(page, size, sort);
        Page<Product> products = productService.getProductsPaged(pageable);
        
        return ResponseEntity.ok(products);
    }
}

Configuration Management

Application Properties

# application.properties

# Database configuration
spring.datasource.url=jdbc:mysql://localhost:3306/ecommerce
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# JPA/Hibernate configuration
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

# Server configuration
server.port=8080
server.servlet.context-path=/api

# Logging configuration
logging.level.com.example=DEBUG
logging.level.org.springframework.web=INFO

# Security configuration
spring.security.user.name=admin
spring.security.user.password=admin123
spring.security.user.roles=ADMIN

Configuration Classes

@Configuration
@ConfigurationProperties(prefix = "app")
public class AppConfig {
    private String name;
    private String version;
    private Database database = new Database();
    
    // Getters and setters...
    
    public static class Database {
        private int maxConnections = 10;
        private int timeout = 30;
        
        // Getters and setters...
    }
}

// Usage in service
@Service
public class AppInfoService {
    private final AppConfig appConfig;
    
    public AppInfoService(AppConfig appConfig) {
        this.appConfig = appConfig;
    }
    
    public Map<String, Object> getAppInfo() {
        Map<String, Object> info = new HashMap<>();
        info.put("name", appConfig.getName());
        info.put("version", appConfig.getVersion());
        info.put("maxConnections", appConfig.getDatabase().getMaxConnections());
        return info;
    }
}

Testing with Spring

Integration Testing

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestPropertySource(locations = "classpath:test.properties")
@Transactional
@Rollback
class ProductServiceIntegrationTest {
    
    @Autowired
    private ProductService productService;
    
    @Autowired
    private ProductRepository productRepository;
    
    @Test
    void shouldSaveAndRetrieveProduct() {
        // Given
        Product product = new Product("Test Product", new BigDecimal("99.99"), Category.ELECTRONICS);
        
        // When
        Product savedProduct = productService.saveProduct(product);
        Optional<Product> retrievedProduct = productService.getProductById(savedProduct.getId());
        
        // Then
        assertThat(retrievedProduct).isPresent();
        assertThat(retrievedProduct.get().getName()).isEqualTo("Test Product");
    }
    
    @Test
    void shouldFindProductsByCategory() {
        // Given
        productRepository.save(new Product("Laptop", new BigDecimal("999.99"), Category.ELECTRONICS));
        productRepository.save(new Product("Phone", new BigDecimal("599.99"), Category.ELECTRONICS));
        productRepository.save(new Product("Book", new BigDecimal("29.99"), Category.BOOKS));
        
        // When
        List<Product> electronics = productService.getProductsByCategory(Category.ELECTRONICS);
        
        // Then
        assertThat(electronics).hasSize(2);
        assertThat(electronics).allMatch(p -> p.getCategory() == Category.ELECTRONICS);
    }
}

Web Layer Testing

@WebMvcTest(ProductController.class)
class ProductControllerTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @MockBean
    private ProductService productService;
    
    @Test
    void shouldReturnProductsWhenGetAllProducts() throws Exception {
        // Given
        List<Product> products = Arrays.asList(
            new Product("Product 1", new BigDecimal("99.99"), Category.ELECTRONICS),
            new Product("Product 2", new BigDecimal("149.99"), Category.BOOKS)
        );
        when(productService.getAllProducts()).thenReturn(products);
        
        // When & Then
        mockMvc.perform(get("/api/products"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$", hasSize(2)))
            .andExpect(jsonPath("$[0].name", is("Product 1")))
            .andExpect(jsonPath("$[1].name", is("Product 2")));
    }
    
    @Test
    void shouldCreateProductWhenValidInput() throws Exception {
        // Given
        Product product = new Product("New Product", new BigDecimal("199.99"), Category.ELECTRONICS);
        when(productService.saveProduct(any(Product.class))).thenReturn(product);
        
        // When & Then
        mockMvc.perform(post("/api/products")
                .contentType(MediaType.APPLICATION_JSON)
                .content("{\"name\":\"New Product\",\"price\":199.99,\"category\":\"ELECTRONICS\"}"))
            .andExpect(status().isCreated())
            .andExpect(jsonPath("$.name", is("New Product")))
            .andExpect(jsonPath("$.price", is(199.99)));
    }
}

Spring Framework Modules

Core Container

  • Spring Core: IoC container, dependency injection
  • Spring Beans: Bean factory, bean lifecycle management
  • Spring Context: Application context, event handling
  • Spring Expression: SpEL (Spring Expression Language)

Data Access

  • Spring JDBC: Simplified JDBC operations
  • Spring ORM: Integration with ORM frameworks
  • Spring Data: Repository pattern implementation
  • Spring Transaction: Declarative transaction management

Web Layer

  • Spring Web: Web application support
  • Spring WebMVC: MVC framework for web applications
  • Spring WebFlux: Reactive web framework

Additional Modules

  • Spring Security: Authentication and authorization
  • Spring Test: Testing support
  • Spring AOP: Aspect-oriented programming
  • Spring Integration: Enterprise integration patterns

Spring Boot Starters

Common starters for different use cases:

<!-- Web applications -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Data JPA -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<!-- Security -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!-- Testing -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

<!-- Validation -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

Best Practices

Dependency Injection

  • Prefer constructor injection over field injection
  • Use @Autowired sparingly - constructor injection is explicit
  • Avoid circular dependencies - redesign your architecture

Configuration

  • Use @ConfigurationProperties for external configuration
  • Separate configuration by environment (dev, test, prod)
  • Validate configuration using Bean Validation

Service Layer

  • Keep services stateless
  • Use @Transactional appropriately
  • Handle exceptions gracefully

Testing

  • Write integration tests for complete workflows
  • Use @MockBean for isolating units under test
  • Test security configurations

Next Steps

Explore specific Spring topics in detail:

Spring Framework provides the foundation for building enterprise-grade Java applications with clean architecture, testability, and maintainability.