Skip to main content

Spring Framework

Spring Framework is the most popular application development framework for enterprise Java. It provides comprehensive infrastructure support for developing Java applications.

Framework Overview

What is Spring?

Spring Framework is an open-source framework that makes Java/J2EE development easier. It provides a lightweight container that manages the lifecycle and configuration of application objects.

Core Modules

Core Container

  • Beans: Bean factory
  • Core: IoC and DI
  • Context: ApplicationContext
  • SpEL: Spring Expression Language

Data Access

  • JDBC: Database access
  • ORM: Hibernate, JPA integration
  • Transactions: Transaction management

Web

  • Web: Basic web support
  • WebMVC: MVC framework
  • WebFlux: Reactive web

AOP

  • Aspect-Oriented Programming
  • Cross-cutting concerns
  • Declarative transactions

Inversion of Control (IoC)

Understanding IoC

Inversion of Control is a principle where the control of object creation and dependency management is inverted from the application code to the framework.Traditional Approach:
public class UserService {
    private UserRepository userRepository = new UserRepository();
    // Tightly coupled
}
IoC Approach:
public class UserService {
    private UserRepository userRepository;
    
    // Dependency injected by Spring
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

Dependency Injection (DI)

Recommended approach - immutable dependencies
@Service
public class UserService {
    private final UserRepository userRepository;
    private final EmailService emailService;
    
    @Autowired  // Optional in Spring 4.3+
    public UserService(UserRepository userRepository, 
                      EmailService emailService) {
        this.userRepository = userRepository;
        this.emailService = emailService;
    }
}

Spring Beans

Bean Definition

@Component
public class UserService {
    // Automatically detected by component scanning
}

@Repository  // Specialized @Component for DAOs
public class UserRepository {}

@Service     // Specialized @Component for services
public class EmailService {}

@Controller  // Specialized @Component for web controllers
public class UserController {}

Bean Scopes

Default scope - One instance per Spring container
@Component
@Scope("singleton")  // Default, can be omitted
public class UserService {}

Bean Lifecycle

@Component
public class LifecycleBean {
    
    // 1. Constructor
    public LifecycleBean() {
        System.out.println("1. Constructor called");
    }
    
    // 2. Dependency Injection
    @Autowired
    public void setDependency(SomeDependency dependency) {
        System.out.println("2. Dependency injected");
    }
    
    // 3. Post-initialization
    @PostConstruct
    public void init() {
        System.out.println("3. @PostConstruct");
    }
    
    // 4. Custom init method
    @Bean(initMethod = "customInit")
    public void customInit() {
        System.out.println("4. Custom init");
    }
    
    // 5. Pre-destruction
    @PreDestroy
    public void cleanup() {
        System.out.println("5. @PreDestroy");
    }
}

Aspect-Oriented Programming (AOP)

AOP Concepts

AOP allows you to modularize cross-cutting concerns (logging, security, transactions) that would otherwise be scattered across multiple classes.Key Concepts:
  • Aspect: Module containing cross-cutting logic
  • Join Point: Point in program execution
  • Advice: Action taken at join point
  • Pointcut: Expression that matches join points
  • @Before: Before method execution
  • @After: After method execution (finally)
  • @AfterReturning: After successful execution
  • @AfterThrowing: After exception
  • @Around: Before and after execution

AOP Examples

@Aspect
@Component
public class LoggingAspect {
    
    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("Executing: " + 
            joinPoint.getSignature().getName());
    }
    
    @AfterReturning(
        pointcut = "execution(* com.example.service.*.*(..))",
        returning = "result"
    )
    public void logAfterReturning(JoinPoint joinPoint, Object result) {
        System.out.println("Method returned: " + result);
    }
    
    @AfterThrowing(
        pointcut = "execution(* com.example.service.*.*(..))",
        throwing = "error"
    )
    public void logAfterThrowing(JoinPoint joinPoint, Throwable error) {
        System.out.println("Exception: " + error.getMessage());
    }
}

Spring Configuration

Application Configuration

@Configuration
@ComponentScan(basePackages = "com.example")
@EnableTransactionManagement
@EnableAspectJAutoProxy
public class AppConfig {
    
    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/mydb");
        dataSource.setUsername("root");
        dataSource.setPassword("password");
        return dataSource;
    }
    
    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

Profiles

@Configuration
@Profile("dev")
public class DevConfig {
    @Bean
    public DataSource devDataSource() {
        // Development database
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .build();
    }
}

@Configuration
@Profile("prod")
public class ProdConfig {
    @Bean
    public DataSource prodDataSource() {
        // Production database
        return new DriverManagerDataSource("jdbc:mysql://prod-server/db");
    }
}
Activate profile:
# Command line
java -Dspring.profiles.active=prod -jar myapp.jar

# application.properties
spring.profiles.active=dev

Data Access

JDBC Template

@Repository
public class UserRepositoryImpl implements UserRepository {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    @Override
    public User findById(Long id) {
        String sql = "SELECT * FROM users WHERE id = ?";
        return jdbcTemplate.queryForObject(sql, new Object[]{id}, 
            (rs, rowNum) -> {
                User user = new User();
                user.setId(rs.getLong("id"));
                user.setName(rs.getString("name"));
                user.setEmail(rs.getString("email"));
                return user;
            });
    }
    
    @Override
    public List<User> findAll() {
        String sql = "SELECT * FROM users";
        return jdbcTemplate.query(sql, 
            (rs, rowNum) -> {
                User user = new User();
                user.setId(rs.getLong("id"));
                user.setName(rs.getString("name"));
                return user;
            });
    }
    
    @Override
    public int save(User user) {
        String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
        return jdbcTemplate.update(sql, user.getName(), user.getEmail());
    }
}

Transaction Management

Always use transactions for operations that modify data to ensure data consistency.
@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    @Transactional
    public void createUser(User user) {
        userRepository.save(user);
        // If exception occurs, transaction will rollback
    }
    
    @Transactional(readOnly = true)
    public User getUser(Long id) {
        return userRepository.findById(id);
    }
    
    @Transactional(
        propagation = Propagation.REQUIRED,
        isolation = Isolation.READ_COMMITTED,
        timeout = 30,
        rollbackFor = Exception.class
    )
    public void complexOperation() {
        // Complex transactional operation
    }
}

Testing

Unit Testing

@ExtendWith(MockitoExtension.class)
public class UserServiceTest {
    
    @Mock
    private UserRepository userRepository;
    
    @InjectMocks
    private UserService userService;
    
    @Test
    public void testFindUser() {
        User expectedUser = new User(1L, "John");
        when(userRepository.findById(1L)).thenReturn(expectedUser);
        
        User actualUser = userService.findUser(1L);
        
        assertEquals(expectedUser, actualUser);
        verify(userRepository).findById(1L);
    }
}

Integration Testing

@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerIntegrationTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @Autowired
    private UserRepository userRepository;
    
    @Test
    public void testGetUser() throws Exception {
        User user = new User(1L, "John");
        userRepository.save(user);
        
        mockMvc.perform(get("/users/1"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.name").value("John"));
    }
}

Best Practices

1

Use Constructor Injection

  • Promotes immutability
  • Makes dependencies explicit
  • Easier to test
2

Favor Composition Over Inheritance

  • More flexible design
  • Easier to maintain
  • Better testability
3

Keep Beans Stateless

  • Especially for singleton beans
  • Thread-safe by design
  • Easier to scale
4

Use Appropriate Bean Scopes

  • Singleton for stateless beans
  • Prototype for stateful beans
  • Request/Session for web-specific data
5

Leverage Spring Boot

  • Auto-configuration
  • Embedded servers
  • Production-ready features

Spring Boot

Rapid application development

Java Fundamentals

Java basics

JVM Internals

Understanding JVM

Build docs developers (and LLMs) love