Spring Core in Java

Spring Core is the foundation of the Spring Framework, providing fundamental features like Dependency Injection (DI) and Inversion of Control (IoC) that are essential for building enterprise Java applications.

1. Introduction to Spring Core

Key Concepts

  • Inversion of Control (IoC): Objects don't create their dependencies, they get injected
  • Dependency Injection (DI): Mechanism for implementing IoC
  • Bean: Objects managed by Spring IoC container
  • ApplicationContext: Spring container that manages beans

Benefits

  • Loose coupling between components
  • Easier testing
  • Better code organization
  • Reduced boilerplate code

2. Setup and Configuration

Maven Dependencies

<!-- pom.xml -->
<properties>
<spring.version>6.1.0</spring.version>
</properties>
<dependencies>
<!-- Spring Core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Spring Core (transitive) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- For annotation configuration -->
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>
</dependencies>

3. Configuration Methods

1. XML Configuration (Traditional)

<!-- src/main/resources/applicationContext.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<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">
<!-- Enable component scanning -->
<context:component-scan base-package="com.example"/>
<!-- Bean definitions -->
<bean id="userService" class="com.example.service.UserServiceImpl">
<constructor-arg ref="userRepository"/>
</bean>
<bean id="userRepository" class="com.example.repository.UserRepositoryImpl"/>
<!-- Bean with properties -->
<bean id="dataSource" class="com.example.config.DataSource">
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="password"/>
<property name="maxConnections" value="10"/>
</bean>
</beans>

2. Java Configuration (Modern)

// AppConfig.java
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@ComponentScan(basePackages = "com.example")
@PropertySource("classpath:application.properties")
public class AppConfig {
@Bean
public UserService userService(UserRepository userRepository) {
return new UserServiceImpl(userRepository);
}
@Bean
public UserRepository userRepository() {
return new UserRepositoryImpl();
}
@Bean
public DataSource dataSource() {
DataSource dataSource = new DataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/mydb");
dataSource.setUsername("root");
dataSource.setPassword("password");
dataSource.setMaxConnections(10);
return dataSource;
}
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
}

3. Annotation Configuration (Most Common)

// AnnotationBasedConfig.java
package com.example.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = "com.example")
public class AnnotationBasedConfig {
// Beans will be automatically discovered via @Component, @Service, etc.
}

4. Core Annotations

Stereotype Annotations

package com.example.service;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
@Service  // Specialized @Component for service layer
public class UserService {
private final UserRepository userRepository;
@Autowired  // Constructor injection (recommended)
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
// Business methods...
}
package com.example.repository;
import org.springframework.stereotype.Repository;
@Repository  // Specialized @Component for persistence layer
public class UserRepositoryImpl implements UserRepository {
// Data access methods...
}
package com.example.controller;
import org.springframework.stereotype.Controller;
@Controller  // Specialized @Component for web layer
public class UserController {
// Web handling methods...
}
package com.example.component;
import org.springframework.stereotype.Component;
@Component  // Generic Spring-managed component
public class EmailService {
// Utility methods...
}

5. Dependency Injection Types

1. Constructor Injection (Recommended)

package com.example.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
private final PaymentService paymentService;
private final NotificationService notificationService;
private final InventoryService inventoryService;
// Constructor injection - dependencies are clearly defined and immutable
@Autowired
public OrderService(PaymentService paymentService,
NotificationService notificationService,
InventoryService inventoryService) {
this.paymentService = paymentService;
this.notificationService = notificationService;
this.inventoryService = inventoryService;
}
public void processOrder(Order order) {
inventoryService.checkAvailability(order);
paymentService.processPayment(order);
notificationService.sendConfirmation(order);
}
}

2. Setter Injection

package com.example.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class ProductService {
private ProductRepository productRepository;
private CategoryService categoryService;
// Setter injection - useful for optional dependencies
@Autowired
public void setProductRepository(ProductRepository productRepository) {
this.productRepository = productRepository;
}
@Autowired
public void setCategoryService(CategoryService categoryService) {
this.categoryService = categoryService;
}
public Product getProductById(Long id) {
Product product = productRepository.findById(id);
if (product != null) {
product.setCategory(categoryService.getCategory(product.getCategoryId()));
}
return product;
}
}

3. Field Injection

package com.example.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class ShoppingCartService {
@Autowired  // Field injection - concise but less testable
private ProductService productService;
@Autowired
private PricingService pricingService;
@Autowired
private DiscountService discountService;
public Cart calculateTotal(Cart cart) {
// Use injected dependencies
return pricingService.calculateTotal(cart);
}
}

6. Bean Scopes

Scope Annotations

package com.example.model;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.RequestScope;
import org.springframework.web.context.annotation.SessionScope;
@Component
@Scope("singleton")  // Default - one instance per Spring container
public class ApplicationConfig {
// Application-wide configuration
}
@Component
@Scope("prototype")  // New instance every time bean is requested
public class PrototypeBean {
// Stateful component that shouldn't be shared
}
@Component
@RequestScope  // Web-aware scope - one instance per HTTP request
public class RequestScopedBean {
// Request-specific data
}
@Component
@SessionScope  // Web-aware scope - one instance per HTTP session
public class UserSession {
private User currentUser;
public void setCurrentUser(User user) {
this.currentUser = user;
}
public User getCurrentUser() {
return currentUser;
}
}

7. Complete Example Application

Domain Models

package com.example.model;
public class User {
private Long id;
private String username;
private String email;
private String firstName;
private String lastName;
// Constructors
public User() {}
public User(String username, String email, String firstName, String lastName) {
this.username = username;
this.email = email;
this.firstName = firstName;
this.lastName = lastName;
}
// Getters and setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
@Override
public String toString() {
return "User{id=" + id + ", username='" + username + "', email='" + email + "'}";
}
}

Repository Layer

package com.example.repository;
import com.example.model.User;
import org.springframework.stereotype.Repository;
import java.util.*;
@Repository
public class UserRepository {
private final Map<Long, User> users = new HashMap<>();
private Long currentId = 1L;
public User save(User user) {
if (user.getId() == null) {
user.setId(currentId++);
}
users.put(user.getId(), user);
return user;
}
public Optional<User> findById(Long id) {
return Optional.ofNullable(users.get(id));
}
public List<User> findAll() {
return new ArrayList<>(users.values());
}
public Optional<User> findByUsername(String username) {
return users.values().stream()
.filter(user -> username.equals(user.getUsername()))
.findFirst();
}
public void deleteById(Long id) {
users.remove(id);
}
public boolean existsByUsername(String username) {
return users.values().stream()
.anyMatch(user -> username.equals(user.getUsername()));
}
}

Service Layer

package com.example.service;
import com.example.model.User;
import com.example.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User createUser(User user) {
if (userRepository.existsByUsername(user.getUsername())) {
throw new IllegalArgumentException("Username already exists: " + user.getUsername());
}
return userRepository.save(user);
}
public Optional<User> getUserById(Long id) {
return userRepository.findById(id);
}
public List<User> getAllUsers() {
return userRepository.findAll();
}
public Optional<User> updateUser(Long id, User userDetails) {
return userRepository.findById(id)
.map(existingUser -> {
existingUser.setEmail(userDetails.getEmail());
existingUser.setFirstName(userDetails.getFirstName());
existingUser.setLastName(userDetails.getLastName());
return userRepository.save(existingUser);
});
}
public boolean deleteUser(Long id) {
if (userRepository.findById(id).isPresent()) {
userRepository.deleteById(id);
return true;
}
return false;
}
public Optional<User> getUserByUsername(String username) {
return userRepository.findByUsername(username);
}
}

Additional Services with Dependencies

package com.example.service;
import com.example.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class NotificationService {
private final EmailService emailService;
private final SmsService smsService;
@Autowired
public NotificationService(EmailService emailService, SmsService smsService) {
this.emailService = emailService;
this.smsService = smsService;
}
public void sendWelcomeNotification(User user) {
emailService.sendWelcomeEmail(user);
smsService.sendWelcomeSms(user);
}
public void sendPasswordResetNotification(User user, String resetToken) {
emailService.sendPasswordResetEmail(user, resetToken);
}
}
@Service
class EmailService {
public void sendWelcomeEmail(User user) {
System.out.println("Sending welcome email to: " + user.getEmail());
// Actual email sending logic
}
public void sendPasswordResetEmail(User user, String resetToken) {
System.out.println("Sending password reset email to: " + user.getEmail());
// Actual email sending logic
}
}
@Service
class SmsService {
public void sendWelcomeSms(User user) {
System.out.println("Sending welcome SMS to user: " + user.getUsername());
// Actual SMS sending logic
}
}

Configuration Class

package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
@Configuration
@ComponentScan(basePackages = "com.example")
@PropertySource("classpath:application.properties")
public class AppConfig {
// Additional beans can be defined here if needed
@Bean
public String applicationName(Environment env) {
return env.getProperty("app.name", "Spring Core Demo Application");
}
@Bean
public Integer maxUsers(Environment env) {
return env.getProperty("app.max-users", Integer.class, 1000);
}
}

Application Properties

# src/main/resources/application.properties
app.name=Spring Core Demo
app.version=1.0.0
app.max-users=500
database.url=jdbc:mysql://localhost:3306/demo
database.username=admin
database.password=secret
logging.level.com.example=DEBUG

Main Application Class

package com.example;
import com.example.config.AppConfig;
import com.example.model.User;
import com.example.service.NotificationService;
import com.example.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.Scanner;
public class SpringCoreApplication {
public static void main(String[] args) {
// Create Spring application context
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// Get beans from context
UserService userService = context.getBean(UserService.class);
NotificationService notificationService = context.getBean(NotificationService.class);
// Demo application
runUserManagementDemo(userService, notificationService);
// Close context
((AnnotationConfigApplicationContext) context).close();
}
private static void runUserManagementDemo(UserService userService, NotificationService notificationService) {
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("\n=== User Management System ===");
System.out.println("1. Create User");
System.out.println("2. List All Users");
System.out.println("3. Find User by ID");
System.out.println("4. Exit");
System.out.print("Choose option: ");
int choice = scanner.nextInt();
scanner.nextLine(); // consume newline
switch (choice) {
case 1:
createUserDemo(userService, notificationService, scanner);
break;
case 2:
listAllUsersDemo(userService);
break;
case 3:
findUserByIdDemo(userService, scanner);
break;
case 4:
System.out.println("Exiting...");
return;
default:
System.out.println("Invalid option!");
}
}
}
private static void createUserDemo(UserService userService, NotificationService notificationService, Scanner scanner) {
System.out.print("Enter username: ");
String username = scanner.nextLine();
System.out.print("Enter email: ");
String email = scanner.nextLine();
System.out.print("Enter first name: ");
String firstName = scanner.nextLine();
System.out.print("Enter last name: ");
String lastName = scanner.nextLine();
try {
User user = new User(username, email, firstName, lastName);
User createdUser = userService.createUser(user);
System.out.println("User created: " + createdUser);
// Send welcome notification
notificationService.sendWelcomeNotification(createdUser);
} catch (Exception e) {
System.out.println("Error creating user: " + e.getMessage());
}
}
private static void listAllUsersDemo(UserService userService) {
System.out.println("\n=== All Users ===");
userService.getAllUsers().forEach(System.out::println);
}
private static void findUserByIdDemo(UserService userService, Scanner scanner) {
System.out.print("Enter user ID: ");
Long id = scanner.nextLong();
userService.getUserById(id).ifPresentOrElse(
user -> System.out.println("Found user: " + user),
() -> System.out.println("User not found with ID: " + id)
);
}
}

8. Advanced Spring Core Features

Bean Lifecycle Callbacks

package com.example.component;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
@Component
public class DatabaseConnectionPool implements InitializingBean, DisposableBean {
private boolean connected = false;
// 1. @PostConstruct annotation (recommended)
@PostConstruct
public void init() {
System.out.println("Initializing database connection pool...");
connect();
connected = true;
System.out.println("Database connection pool initialized.");
}
// 2. InitializingBean interface
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("afterPropertiesSet() called");
// This runs after @PostConstruct
}
// 3. @PreDestroy annotation (recommended)
@PreDestroy
public void cleanup() {
System.out.println("Cleaning up database connection pool...");
disconnect();
connected = false;
System.out.println("Database connection pool cleaned up.");
}
// 4. DisposableBean interface
@Override
public void destroy() throws Exception {
System.out.println("destroy() called");
// This runs after @PreDestroy
}
private void connect() {
// Actual connection logic
System.out.println("Connected to database");
}
private void disconnect() {
// Actual disconnection logic
System.out.println("Disconnected from database");
}
public boolean isConnected() {
return connected;
}
}

Conditional Bean Configuration

package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@Configuration
public class FeatureConfig {
@Bean
@Profile("dev")  // Only created when 'dev' profile is active
public DevelopmentService developmentService() {
return new DevelopmentService();
}
@Bean
@Profile("prod")  // Only created when 'prod' profile is active
public ProductionService productionService() {
return new ProductionService();
}
@Bean
@Conditional(WindowsCondition.class)  // Conditional based on custom condition
public WindowsService windowsService() {
return new WindowsService();
}
@Bean
@Conditional(LinuxCondition.class)
public LinuxService linuxService() {
return new LinuxService();
}
}
// Custom condition for Windows
class WindowsCondition implements org.springframework.context.annotation.Condition {
@Override
public boolean matches(org.springframework.context.annotation.ConditionContext context, 
org.springframework.core.type.AnnotatedTypeMetadata metadata) {
return System.getProperty("os.name").toLowerCase().contains("windows");
}
}
// Custom condition for Linux
class LinuxCondition implements org.springframework.context.annotation.Condition {
@Override
public boolean matches(org.springframework.context.annotation.ConditionContext context, 
org.springframework.core.type.AnnotatedTypeMetadata metadata) {
return System.getProperty("os.name").toLowerCase().contains("linux");
}
}
class DevelopmentService {
public String getEnvironment() { return "Development"; }
}
class ProductionService {
public String getEnvironment() { return "Production"; }
}
class WindowsService {
public String getOS() { return "Windows"; }
}
class LinuxService {
public String getOS() { return "Linux"; }
}

Factory Beans

package com.example.factory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.stereotype.Component;
@Component
public class ConnectionFactoryBean implements FactoryBean<DatabaseConnection> {
private String url;
private String username;
private String password;
@Override
public DatabaseConnection getObject() throws Exception {
DatabaseConnection connection = new DatabaseConnection();
connection.setUrl(this.url);
connection.setUsername(this.username);
connection.setPassword(this.password);
connection.connect();
return connection;
}
@Override
public Class<?> getObjectType() {
return DatabaseConnection.class;
}
@Override
public boolean isSingleton() {
return true; // Return same instance every time
}
// Setters for configuration
public void setUrl(String url) { this.url = url; }
public void setUsername(String username) { this.username = username; }
public void setPassword(String password) { this.password = password; }
}
class DatabaseConnection {
private String url;
private String username;
private String password;
private boolean connected;
public void connect() {
System.out.println("Connecting to: " + url);
this.connected = true;
}
public void disconnect() {
this.connected = false;
}
// Getters and setters
public void setUrl(String url) { this.url = url; }
public void setUsername(String username) { this.username = username; }
public void setPassword(String password) { this.password = password; }
public boolean isConnected() { return connected; }
}

9. Testing Spring Applications

Spring Test Configuration

package com.example.service;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import com.example.config.TestConfig;
import com.example.model.User;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = TestConfig.class)
class UserServiceTest {
@Autowired
private UserService userService;
@Test
void testCreateUser() {
User user = new User("testuser", "[email protected]", "Test", "User");
User created = userService.createUser(user);
assertNotNull(created.getId());
assertEquals("testuser", created.getUsername());
assertEquals("[email protected]", created.getEmail());
}
@Test
void testFindUserById() {
User user = new User("finduser", "[email protected]", "Find", "User");
User created = userService.createUser(user);
var found = userService.getUserById(created.getId());
assertTrue(found.isPresent());
assertEquals(created.getId(), found.get().getId());
}
}
// Test configuration
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
class TestConfig {
@Bean
public UserService userService() {
return new UserService(userRepository());
}
@Bean
public UserRepository userRepository() {
return new UserRepository();
}
}

10. Best Practices

Configuration Best Practices

package com.example.bestpractices;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class BestPracticesExample {
/*
SPRING CORE BEST PRACTICES:
1. Use constructor injection for mandatory dependencies
2. Use @ComponentScan with specific packages
3. Prefer Java configuration over XML
4. Use @Profile for environment-specific configuration
5. Make beans immutable when possible
6. Use interface-based programming
7. Avoid circular dependencies
8. Use @Configuration for configuration classes
9. Prefer @PostConstruct/@PreDestroy over InitializingBean/DisposableBean
10. Use property files for external configuration
*/
// Good practice: Constructor injection with final fields
@Bean
public ServiceWithDependencies serviceWithDependencies(Repository repository, 
Helper helper) {
return new ServiceWithDependencies(repository, helper);
}
// Good practice: Configuration in properties files
@Bean
public DataSource dataSource() {
// Configuration should come from application.properties
return new DataSource();
}
}
// Good practice: Interface-based design
interface UserService {
User createUser(User user);
Optional<User> getUserById(Long id);
}
// Good practice: Immutable dependencies
class ServiceWithDependencies {
private final Repository repository;
private final Helper helper;
// Constructor injection makes dependencies clear and immutable
public ServiceWithDependencies(Repository repository, Helper helper) {
this.repository = repository;
this.helper = helper;
}
}

Summary

Key Spring Core Concepts:

  • IoC Container: Manages object creation and dependencies
  • Dependency Injection: Three types (Constructor, Setter, Field)
  • Bean: Objects managed by Spring container
  • ApplicationContext: Central interface for configuration

Common Annotations:

  • @Component, @Service, @Repository, @Controller
  • @Autowired for dependency injection
  • @Configuration for configuration classes
  • @Bean for method-level bean definitions
  • @Profile for environment-specific beans

Configuration Methods:

  1. XML Configuration: Traditional, verbose
  2. Java Configuration: Type-safe, modern
  3. Annotation Configuration: Concise, most popular

Spring Core provides the foundation for building modular, testable, and maintainable Java applications through its powerful IoC container and dependency injection features.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper