Understanding Spring Framework Architecture
Monolithic Architecture
A single project deployed as a WAR package running on a single Tomcat instance. This approach integrates all functionality into one cohesive unit, often referred to as "all in one."
Key technologies in monolithic architecture typically include:
- Spring Framework
- Spring MVC
- MyBatis
Distributed Architecture
A project organized into multiple modules, where each module runs on its own Tomcat server. Modules can communicate with each other while maintaining internal independence.
Key technologies in distributed architecture include:
- Spring Boot (with SSM components)
- Spring Cloud
- Various middleware solutions
Framework Concept and Benefits
A framework is a software system that integrates foundational components like basic structures, specifications, design patterns, programming languages, and libraries. It addresses common problems in specific domains, enabling developers to achieve software development goals more efficiently and stably.
Key advantages of frameworks:
- Enhanced Development Efficiency: Pre-built components and tools accelerate development compared to manual coding.
- Reduced Development Costs: Standardized code snippets eliminate redundant development while providing optimized systems.
- Improved Application Stability: Thoroughly tested components and design patterns minimize bugs and enhance reliability.
- Standardized Solutions: Domain-specific frameworks provide common language and conceptual foundations for better team communication.
However, frameworks also present challenges:
- Learning Curve: Specific languages and paradigms require significant time investment.
- Potential Limitations: Some specialized requirements may exceed framework capabilities.
- Version Compatibility Issues: Framework updates can cause compatibility problems requiring careful migration.
- Architectural Risks: Misunderstanding framwork concepts can lead to design flaws.
Spring Framework Fundamentals
Spring Framework Structure
The Spring Framework consists of several key modules:
| Module | Description |
|---|---|
| Core Container | Foundation of Spring applications; all functionality requires the IoC container. |
| AOP & Aspects | Provides aspect-oriented programming capabilities. |
| TX | Enables declarative transaction management. |
| Spring MVC | Offers integrated features for web applications. |
Spring Framework Advantages
- Rich Ecosystem: Extensive modules and libraries like Spring Boot, Security, and Cloud.
- Modular Design: Loosely coupled components enable reusability and maintainability.
- Simplified Java Development: Various tools and APIs reduce complexity.
- Continuous Innovation: Regular updates keep pace with emerging technologies.
From version 6.0.6 onwards, Spring requires Java 17+ and supports Groovy and Kotlin as alternative JVM languages.
Spring IoC Container
Components in Spring
In conventional three-tier architecture, applications consist of various components that Spring can manage completely. Spring handles:
- Component instantiation
- Property assignment
- Inter-component references
- Component lifecycle management
Developers only need to write metadata (configuration files) specifying which classes to manage and their relationships.
Spring IoC Container Interfaces and Implementations
The core interfaces are:
BeanFactory: Provides advanced configuration for managing any object type.ApplicationContext: Extends BeanFactory with additional enterprise features like AOP integration, message resource handling, and application-specific implementations.
Common ApplicationContext implementations:
| Implementation | Description |
|---|---|
| ClassPathXmlApplicationContext | Creates IoC container by reading XML configuration files from the classpath. |
| FileSystemXmlApplicationContext | Creates IoC container by reading XML configuration files from the filesystem. |
| AnnotationConfigApplicationContext | Creates IoC container by reading Java configuration classes. |
| WebApplicationContext | Specialized for web applications, storing objects in ServletContext. |
Spring Configuration Approaches
Spring offers three primary configuration methods:
- XML Configuration: The original approach defining beans and dependencies in XML files.
- Annotation Configuration: Introduced in Spring 2.5, using annotations like @Component to define beans.
- Java Configuration Class: Introduced in Spring 3.0, using @Configuration and @Bean annotations.
Inversion of Control (IoC) and Dependency Injection (DI)
IoC transfers object creation and management control from the application to the IoC container. When an application needs an object, it no longer creates it directly but relies on the container.
DI handles dependency relationships within the container, eliminating hard-coded dependencies between objects. Spring supports three DI forms:
- Constructor injection
- Setter method injection
- Interface injection
Spring IoC Implementation Examples
XML Configuration Approach
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.6</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.10.2</version>
</dependency>
<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 id="user1" class="com.example.entity.User"/>
<bean id="address1" class="com.example.entity.Address"/>
<!-- Static factory method -->
<bean id="addressStatic1" class="com.example.entity.AddressStatic"
factory-method="getInstance"/>
<!-- Instance factory method -->
<bean id="factory" class="com.example.entity.Factory"/>
<bean id="addressStatic2" factory-bean="factory" factory-method="getAddress"/>
<!-- Constructor injection -->
<bean id="user2" class="com.example.entity.User">
<constructor-arg name="name" value="John"/>
<constructor-arg name="age" value="25"/>
<constructor-arg name="address" ref="address1"/>
</bean>
<!-- Setter injection -->
<bean id="user3" class="com.example.entity.User">
<property name="name" value="Jane"/>
<property name="address" ref="address1"/>
<property name="age" value="30"/>
</bean>
</beans>
Annotation Configuration Approach
Spring provides several annotations to define beans:
| Annotation | Description |
|---|---|
| @Component | General-purpose stereotype annotation for any Spring-managed component. |
| @Repository | Stereotype for persistence layer components. |
| @Service | Stereotype for service layer components. |
| @Controller | Stereotype for Spring MVC controllers. |
// Configuration to enable component scanning
<context:component-scan base-package="com.example.service"/>
// Component example
@Component("dataService")
public class DataService {
// Business logic implementation
}
// Autowiring example
@Controller("userController")
public class UserController {
@Autowired
private UserService userService;
public void processRequest() {
userService.processData();
}
}
Java Configuration Class Approach
@Configuration
@ComponentScan(basePackages = {"com.example.components"})
@PropertySource("classpath:application.properties")
public class AppConfig {
@Bean
public DataSource createDataSource(
@Value("${db.username}") String username,
@Value("${db.password}") String password,
@Value("${db.url}") String url,
@Value("${db.driver}") String driverClassName) {
DataSource dataSource = new DataSource();
dataSource.setUsername(username);
dataSource.setPassword(password);
dataSource.setUrl(url);
dataSource.setDriverClassName(driverClassName);
return dataSource;
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
}
Bean Scopes and Lifecycle Methods
Bean scopes define the lifecycle and visibility of bean instances:
| Scope | Description | Creation Timing | Default |
|---|---|---|---|
| singleton | Single instance in the IoC container | During IoC container initialization | Yes |
| prototype | Multiple instances in the IoC container | When bean is requested | No |
| request | Instance per HTTP request (web apps) | Per request | No |
| session | Instance per HTTP session (web apps) | Per session | No |
Lifecycle methods can be specified using:
- XML:
init-methodanddestroy-methodattributes - Annotations:
@PostConstructand@PreDestroy
Spring AOP (Aspect-Oriented Programming)
AOP Concepts
AOP complements Object-Oriented Programming by addressing cross-cutting concerns that span multiple objects. It separates core business logic from auxiliary functionalities like logging, transaction management, and security.
Key AOP terminology:
- Cross-cutting Concerns: functionalities that affect multiple parts of the application (e.g., logging, security)
- Advice: action taken by an aspect at a particular join point (before, after, around)
- Join Point: point during the execution of a program (e.g., method execution)
- Pointcut: predicate that matches join points
- Aspect: module that encapsulates cross-cutting concerns
- Target: object being advised by one or more aspects
- Proxy: object created after applying advice to a target object
- Weaving: linking aspects with other application types or objects to create an advised object
AOP Implementation with Annotations
Dependencies
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>6.0.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.0.6</version>
</dependency>
Service Interface and Implementation
public interface MathService {
int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
int divide(int a, int b);
}
@Component
public class MathServiceImpl implements MathService {
@Override
public int add(int a, int b) {
return a + b;
}
@Override
public int subtract(int a, int b) {
return a - b;
}
@Override
public int multiply(int a, int b) {
return a * b;
}
@Override
public int divide(int a, int b) {
return a / b;
}
}
Aspect Implementation
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.MathServiceImpl.*(..))")
public void logBeforeMethod(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("[Before] Method " + methodName + " called with args: " + Arrays.toString(args));
}
@AfterReturning(pointcut = "execution(* com.example.service.MathServiceImpl.*(..))",
returning = "result")
public void logAfterMethod(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
System.out.println("[AfterReturning] Method " + methodName + " returned: " + result);
}
@AfterThrowing(pointcut = "execution(* com.example.service.MathServiceImpl.*(..))",
throwing = "exception")
public void logAfterException(JoinPoint joinPoint, Throwable exception) {
String methodName = joinPoint.getSignature().getName();
System.out.println("[AfterThrowing] Method " + methodName + " threw exception: " + exception);
}
@After("execution(* com.example.service.MathServiceImpl.*(..))")
public void logAfterFinally(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("[After] Method " + methodName + " execution completed");
}
@Around("execution(* com.example.service.MathServiceImpl.*(..))")
public Object logAroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("[Around] Before " + methodName + " with args: " + Arrays.toString(args));
try {
Object result = joinPoint.proceed();
System.out.println("[Around] After " + methodName + " with result: " + result);
return result;
} catch (Exception e) {
System.out.println("[Around] Exception in " + methodName + ": " + e);
throw e;
}
}
}
Configuration to Enable AOP
@Configuration
@ComponentScan(basePackages = "com.example")
@EnableAspectJAutoProxy
public class AppConfig {
// Configuration code
}
Pointcut Expressions
Pointcut expressions define which methods should be intercepted:
execution(* com.example.service.*.*(..)): All methods in the service packageexecution(public * com.example.service..*(..)): All public methods in service package and subpackagesexecution(* com.example.service.*.*(String,..)): Methods with first parameter of type String
Aspect Ordering
When multiple aspects apply to the same method, use @Order to control execution order:
@Aspect
@Component
@Order(1)
public class FirstAspect {
// Aspect implementation
}
@Aspect
@Component
@Order(2)
public class SecondAspect {
// Aspect implementation
}
Spring Transaction Management
Transaction Management Approaches
Spring offers two transaction management approaches:
- Programmatic Transaction Management: Explicit transaction control through code
- Declarative Transaction Management: Transaction control through configuration or annotations
Declarative Transaction Management
Dependencies
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>6.0.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>6.0.6</version>
</dependency>
Configuration
@Configuration
@ComponentScan(basePackages = "com.example.tx")
@EnableTransactionManagement
@PropertySource("classpath:jdbc.properties")
public class TransactionConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource() {
DataSource dataSource = new DataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
Service with Transactional Methods
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private InventoryService inventoryService;
@Transactional
public void placeOrder(Order order) {
// Create order
orderRepository.save(order);
// Update inventory
inventoryService.reduceInventory(order.getItems());
}
@Transactional(readOnly = true)
public Order getOrder(Long orderId) {
return orderRepository.findById(orderId);
}
@Transactional(timeout = 30)
public void processBatchOrders(List<Order> orders) {
for (Order order : orders) {
processOrder(order);
}
}
@Transactional(rollbackFor = Exception.class)
public void riskyOperation() throws Exception {
// Code that might throw exceptions
}
@Transactional(isolation = Isolation.READ_COMMITTED)
public void readCommittedOperation() {
// Operation with read committed isolation
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void newTransactionOperation() {
// Operation that always creates a new transaction
}
}
Transaction Attributes
The @Transactional annotation supports several atributes:
readOnly: Sets the transaction as read-onlytimeout: Transacsion timeout in secondsrollbackFor: Exceptions that trigger rollbacknoRollbackFor: Exceptions that don't trigger rollbackisolation: Transaction isolation levelpropagation: Transaction propagation behavior
Transaction Isolation Levels
| Level | Description |
|---|---|
| DEFAULT | Use default isolation level of the underlying database |
| READ_UNCOMMITTED | Allows reading uncommitted data |
| READ_COMMITTED | Only allows reading committed data |
| REPEATABLE_READ | Ensures repeated reads return the same data |
| SERIALIZABLE | Highest isolation level, prevents all concurrency issues |
Transaction Propagation Behaviors
| Behavior | Description |
|---|---|
| REQUIRED | Supports current transaction; creates new if none exists |
| REQUIRES_NEW | Creates new transaction, suspends current if exists |
| SUPPORTS | Supports current transaction; executes non-transactionally if none |
| NOT_SUPPORTED | Executes non-transactionally; suspends current if exists |
| MANDATORY | Must execute within existing transaction; throws exception if none |
| NEVER | Must execute non-transactionally; throws exception if transaction exists |
| NESTED | Executes within nested transaction if exists |