What is Spring Core?
Spring Core is the foundation of the Spring Framework, providing the fundamental features of Inversion of Control (IoC) and Dependency Injection (DI). It manages the lifecycle and configuration of application objects (beans).
- IoC Container - Manages object creation and lifecycle
- Dependency Injection - Objects receive their dependencies instead of creating them
- Beans - Objects managed by the Spring IoC container
- Application Context - The IoC container implementation
Inversion of Control (IoC)
IoC is a design principle where the control of object creation and lifecycle is inverted from the application code to a container or framework.
Traditional Approach (Without IoC)
public class UserService {
private UserRepository userRepository;
public UserService() {
// Application code controls object creation
this.userRepository = new UserRepositoryImpl();
}
public User getUserById(int id) {
return userRepository.findById(id);
}
}
IoC Approach (With Spring)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private final UserRepository userRepository;
// Spring container controls object creation and injection
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUserById(int id) {
return userRepository.findById(id);
}
}
- Loose coupling between classes
- Easier testing (can inject mocks)
- Better separation of concerns
- Centralized configuration
Dependency Injection (DI)
DI is the implementation of IoC where dependencies are "injected" into objects by the container instead of objects creating their own dependencies.
Types of Dependency Injection
1. Constructor Injection (Recommended)
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final PaymentService paymentService;
// Constructor injection - dependencies are injected via constructor
@Autowired // @Autowired is optional if there's only one constructor
public OrderService(OrderRepository orderRepository,
PaymentService paymentService) {
this.orderRepository = orderRepository;
this.paymentService = paymentService;
}
public void processOrder(Order order) {
orderRepository.save(order);
paymentService.processPayment(order);
}
}
2. Setter Injection
@Service
public class EmailService {
private EmailProvider emailProvider;
// Setter injection - dependency injected via setter method
@Autowired
public void setEmailProvider(EmailProvider emailProvider) {
this.emailProvider = emailProvider;
}
public void sendEmail(String to, String message) {
emailProvider.send(to, message);
}
}
3. Field Injection (Not Recommended)
@Service
public class NotificationService {
// Field injection - less preferred
@Autowired
private EmailService emailService;
@Autowired
private SmsService smsService;
public void notifyUser(User user) {
emailService.sendEmail(user.getEmail(), "Hello!");
smsService.sendSms(user.getPhone(), "Hello!");
}
}
- Promotes immutability (final fields)
- Makes dependencies explicit and required
- Easier to test (can instantiate without Spring)
- Prevents NullPointerExceptions
Spring Beans
A bean is an object that is instantiated, assembled, and managed by the Spring IoC container.
Declaring Beans with Annotations
Component Scanning
// Spring automatically detects and registers these as beans
@Component // Generic stereotype
public class UtilityComponent {
// Component logic
}
@Service // Service layer stereotype
public class UserService {
// Business logic
}
@Repository // Data access layer stereotype
public class UserRepository {
// Database operations
}
@Controller // Presentation layer stereotype
public class UserController {
// HTTP request handling
}
Java Configuration
import org.springframework.context.annotation.*;
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
return new UserService(userRepository());
}
@Bean
public UserRepository userRepository() {
return new UserRepositoryImpl();
}
// Bean with custom name
@Bean(name = "emailSender")
public EmailService emailService() {
return new EmailServiceImpl();
}
}
Bean Scopes
Bean scope defines the lifecycle and visibility of bean instances.
Common Scopes
Singleton (Default)
@Component
@Scope("singleton") // This is the default, can be omitted
public class ConfigService {
// One instance per Spring container
// Shared across the entire application
}
Prototype
@Component
@Scope("prototype")
public class TaskProcessor {
// New instance created every time it's requested
// Useful for stateful objects
}
Web Scopes (Spring Web)
// Request scope - one instance per HTTP request
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST,
proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestContext {
// New instance for each HTTP request
}
// Session scope - one instance per HTTP session
@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION,
proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ShoppingCart {
// Same instance throughout user's session
}
- singleton - One instance per container (default)
- prototype - New instance every time
- request - One per HTTP request (web apps)
- session - One per HTTP session (web apps)
- application - One per ServletContext (web apps)
Application Context
The ApplicationContext is the central interface for providing configuration to a Spring application.
Creating Application Context
With Spring Boot (Recommended)
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication // Combines @Configuration, @EnableAutoConfiguration, @ComponentScan
public class Application {
public static void main(String[] args) {
// Spring Boot creates and manages the ApplicationContext
SpringApplication.run(Application.class, args);
}
}
Programmatic Creation (Without Spring Boot)
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
// Create context from Java configuration
ApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
// Retrieve beans
UserService userService = context.getBean(UserService.class);
userService.doSomething();
// Close context (important!)
((AnnotationConfigApplicationContext) context).close();
}
}
Complete Example
Here's a complete example showing IoC and DI in action:
1. Repository Layer
import org.springframework.stereotype.Repository;
import java.util.*;
@Repository
public class UserRepository {
private Map<Long, User> database = new HashMap<>();
public User findById(Long id) {
return database.get(id);
}
public void save(User user) {
database.put(user.getId(), user);
}
public List<User> findAll() {
return new ArrayList<>(database.values());
}
}
2. Service Layer
import org.springframework.stereotype.Service;
import java.util.*;
@Service
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
// Constructor injection
public UserService(UserRepository userRepository,
EmailService emailService) {
this.userRepository = userRepository;
this.emailService = emailService;
}
public User createUser(User user) {
userRepository.save(user);
emailService.sendWelcomeEmail(user.getEmail());
return user;
}
public User getUserById(Long id) {
return userRepository.findById(id);
}
public List<User> getAllUsers() {
return userRepository.findAll();
}
}
3. Supporting Service
import org.springframework.stereotype.Service;
@Service
public class EmailService {
public void sendWelcomeEmail(String email) {
System.out.println("Sending welcome email to: " + email);
// Email sending logic
}
}
4. Controller (Optional)
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping
public List<User> getAllUsers() {
return userService.getAllUsers();
}
@PostMapping
public User createUser(@RequestBody User user) {
return userService.createUser(user);
}
}
Why Spring Core Matters
- Loose Coupling - Classes depend on abstractions, not concrete implementations
- Testability - Easy to inject mocks for unit testing
- Maintainability - Changes in one class don't cascade to others
- Flexibility - Easy to swap implementations
- Centralized Configuration - All bean definitions in one place
Common Pitfalls
@Service
public class A {
private final B b;
public A(B b) { this.b = b; }
}
@Service
public class B {
private final A a;
public B(A a) { this.a = a; } // CIRCULAR DEPENDENCY!
}
// Solution: Redesign your classes or use @Lazy annotation
Use constructor injection instead of field injection for better testability and to make dependencies explicit.