Introduction to Spring Framework
Definition
Spring is a comprehensive framework for building enterprise Java applications. It provides infrastructure support and simplifies the development of Java applications by promoting good design practices.
Key Features
- Lightweight: Minimizes overhead and allows for modular usage
- Inversion of Control (IoC): Container manages object creation and dependencies
- Aspect-Oriented Programming: Separates cross-cutting concerns
- Container: Manages the lifecycle of objects (beans)
- Integration: Integrates with various technologies (JDBC, JPA, JMS, etc.)
Spring Modules
| Module | Purpose |
|---|---|
| Spring Core | IoC and Dependency Injection |
| Spring AOP | Aspect-Oriented Programming support |
| Spring MVC | Web application framework |
| Spring Data | Data access abstraction |
| Spring Security | Authentication and authorization |
| Spring Boot | Rapid application development with auto-configuration |
Dependency Injection (DI)
Definition
Dependency Injection is a design pattern where an object receives its dependencies from an external source rather than creating them itself. The container "injects" the required dependencies into the object.
Benefits of DI
- Loose coupling between components
- Easier unit testing (can inject mock objects)
- Better code reusability
- Configuration flexibility
- Separation of concerns
Types of Dependency Injection
| Type | Description | Usage |
|---|---|---|
| Constructor Injection | Dependencies passed via constructor | Recommended for mandatory dependencies |
| Setter Injection | Dependencies set via setter methods | Optional dependencies |
| Field Injection | Dependencies injected directly into fields | Convenient but less testable |
// WITHOUT Dependency Injection (tight coupling)
class OrderService {
private EmailService emailService;
public OrderService() {
// Creates its own dependency - tightly coupled
this.emailService = new EmailService();
}
}
// WITH Dependency Injection (loose coupling)
class OrderService {
private EmailService emailService;
// Constructor Injection - dependency provided externally
public OrderService(EmailService emailService) {
this.emailService = emailService;
}
}
// 1. Constructor Injection (Recommended)
@Service
public class OrderService {
private final EmailService emailService;
@Autowired // Optional since Spring 4.3 for single constructor
public OrderService(EmailService emailService) {
this.emailService = emailService;
}
}
// 2. Setter Injection
@Service
public class OrderService {
private EmailService emailService;
@Autowired
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
}
// 3. Field Injection
@Service
public class OrderService {
@Autowired
private EmailService emailService;
}
Exam Tip
Constructor injection is preferred because: (1) dependencies are clearly visible, (2) objects can be immutable (final fields), (3) easier to test without Spring container.
Inversion of Control (IoC)
Definition
Inversion of Control is a design principle where the control of object creation and dependency management is transferred from the application code to a container or framework. The framework "calls" your code rather than your code calling the framework.
IoC Container
The Spring IoC container is responsible for:
- Creating objects (beans)
- Configuring objects
- Assembling dependencies
- Managing object lifecycle
Types of IoC Containers
| Container | Interface | Description |
|---|---|---|
| BeanFactory | org.springframework.beans.factory.BeanFactory | Basic container with lazy initialization |
| ApplicationContext | org.springframework.context.ApplicationContext | Advanced container with eager initialization, AOP, i18n support |
// Traditional approach (no IoC)
public class Application {
public static void main(String[] args) {
// Application creates and manages objects
EmailService emailService = new EmailService();
OrderService orderService = new OrderService(emailService);
orderService.processOrder();
}
}
// With IoC Container (Spring)
public class Application {
public static void main(String[] args) {
// Container creates and manages objects
ApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
// Get bean from container - dependencies already injected
OrderService orderService = context.getBean(OrderService.class);
orderService.processOrder();
}
}
Note
IoC is the principle; Dependency Injection is one implementation of IoC. Spring uses DI to achieve IoC.
Aspect-Oriented Programming (AOP)
Definition
AOP is a programming paradigm that allows separation of cross-cutting concerns (concerns that affect multiple parts of an application) from the main business logic. Examples include logging, security, transaction management.
AOP Terminology
| Term | Definition |
|---|---|
| Aspect | A module that encapsulates a cross-cutting concern |
| Join Point | A point during execution (method call, exception) |
| Advice | Action taken at a join point (before, after, around) |
| Pointcut | Expression that matches join points |
| Target Object | Object being advised (proxied) |
| Weaving | Linking aspects with target objects |
Types of Advice
| Advice Type | Annotation | When Executed |
|---|---|---|
| Before | @Before | Before the join point |
| After Returning | @AfterReturning | After successful completion |
| After Throwing | @AfterThrowing | After exception is thrown |
| After (Finally) | @After | After join point (regardless of outcome) |
| Around | @Around | Wraps the join point (before and after) |
// Aspect for logging
@Aspect
@Component
public class LoggingAspect {
// Pointcut - matches all methods in service package
@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("Completed: " + joinPoint.getSignature().getName());
}
@Around("execution(* com.example.service.*.*(..))")
public Object logAround(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
Object result = pjp.proceed(); // Execute actual method
long duration = System.currentTimeMillis() - start;
System.out.println("Execution time: " + duration + "ms");
return result;
}
}
Exam Tip
Common cross-cutting concerns suitable for AOP: Logging, Security, Transaction Management, Caching, Error Handling, Performance Monitoring.
Bean Scopes
Definition
Bean scope defines the lifecycle and visibility of a bean within the Spring container. It determines how many instances of a bean are created and how they are shared.
Types of Bean Scopes
| Scope | Description | Usage |
|---|---|---|
| singleton | Single instance per Spring container (default) | Stateless beans, services |
| prototype | New instance created for each request | Stateful beans |
| request | One instance per HTTP request (web only) | Request-specific data |
| session | One instance per HTTP session (web only) | User session data |
| application | One instance per ServletContext (web only) | Application-wide data |
// Singleton scope (default)
@Component
@Scope("singleton") // Optional - singleton is default
public class UserService {
// Same instance shared across all requests
}
// Prototype scope
@Component
@Scope("prototype")
public class ShoppingCart {
// New instance created each time bean is requested
}
// Request scope (web applications)
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestData {
// One instance per HTTP request
}
// Session scope (web applications)
@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserSession {
// One instance per HTTP session
}
Common Mistake
Injecting a prototype-scoped bean into a singleton bean results in only one instance of the prototype bean being created. Use @Lookup or ObjectFactory for proper prototype injection.
Autowiring
Definition
Autowiring is Spring's mechanism for automatically resolving and injecting dependencies. Spring can automatically discover beans and inject them into dependent beans.
Autowiring Modes
| Mode | Description |
|---|---|
| no | No autowiring (default in XML) |
| byName | Match by bean name |
| byType | Match by bean type |
| constructor | Match by constructor argument type |
// @Autowired - injects by type
@Service
public class OrderService {
@Autowired
private UserRepository userRepository; // Field injection
@Autowired
public OrderService(PaymentService paymentService) { // Constructor
this.paymentService = paymentService;
}
}
// @Qualifier - when multiple beans of same type exist
public interface PaymentService { }
@Service("creditCard")
public class CreditCardPayment implements PaymentService { }
@Service("paypal")
public class PayPalPayment implements PaymentService { }
@Service
public class OrderService {
@Autowired
@Qualifier("creditCard") // Specifies which implementation
private PaymentService paymentService;
}
// @Primary - default bean when multiple exist
@Service
@Primary
public class CreditCardPayment implements PaymentService { }
Spring Annotations Overview
Stereotype Annotations
| Annotation | Purpose |
|---|---|
| @Component | Generic Spring-managed component |
| @Service | Service layer class (business logic) |
| @Repository | Data access layer class (DAO) |
| @Controller | Web controller (MVC) |
| @RestController | REST API controller (@Controller + @ResponseBody) |
Configuration Annotations
| Annotation | Purpose |
|---|---|
| @Configuration | Marks class as source of bean definitions |
| @Bean | Declares a bean to be managed by Spring |
| @ComponentScan | Enables component scanning |
| @PropertySource | Specifies property file location |
| @Value | Injects values from properties |
@Configuration
@ComponentScan(basePackages = "com.example")
@PropertySource("classpath:application.properties")
public class AppConfig {
@Value("${app.name}")
private String appName;
@Bean
public DataSource dataSource() {
// Create and configure DataSource
return new DriverManagerDataSource();
}
@Bean
@Scope("prototype")
public ReportGenerator reportGenerator() {
return new ReportGenerator();
}
}
Bean Lifecycle
Lifecycle Stages
- Instantiation: Container creates bean instance
- Populate Properties: Dependencies are injected
- BeanNameAware: setBeanName() called
- BeanFactoryAware: setBeanFactory() called
- ApplicationContextAware: setApplicationContext() called
- Pre-Initialization: BeanPostProcessor.postProcessBeforeInitialization()
- InitializingBean: afterPropertiesSet() called
- Custom init-method: @PostConstruct or init-method
- Post-Initialization: BeanPostProcessor.postProcessAfterInitialization()
- Bean Ready for Use
- DisposableBean: destroy() called on shutdown
- Custom destroy-method: @PreDestroy or destroy-method
@Component
public class MyBean {
public MyBean() {
System.out.println("1. Constructor called");
}
@Autowired
public void setDependency(SomeDependency dep) {
System.out.println("2. Dependencies injected");
}
@PostConstruct
public void init() {
System.out.println("3. PostConstruct - initialization");
}
@PreDestroy
public void cleanup() {
System.out.println("4. PreDestroy - cleanup before destruction");
}
}
// Alternative using interfaces
@Component
public class AnotherBean implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() {
System.out.println("InitializingBean.afterPropertiesSet()");
}
@Override
public void destroy() {
System.out.println("DisposableBean.destroy()");
}
}
Exam Tip
Prefer @PostConstruct and @PreDestroy annotations over implementing interfaces. They are part of standard Java (JSR-250) and don't couple your code to Spring.
Spring Boot Overview
Definition
Spring Boot is an extension of the Spring framework that simplifies the creation of stand-alone, production-grade Spring applications with minimal configuration.
Key Features
- Auto-configuration: Automatically configures beans based on classpath
- Starter Dependencies: Pre-configured dependency descriptors
- Embedded Servers: Built-in Tomcat, Jetty, or Undertow
- Production-ready: Health checks, metrics, externalized configuration
- No XML Configuration: Java-based or properties-based configuration
Spring Boot Starters
| Starter | Purpose |
|---|---|
| spring-boot-starter-web | Web applications with Spring MVC |
| spring-boot-starter-data-jpa | JPA with Hibernate |
| spring-boot-starter-security | Spring Security |
| spring-boot-starter-test | Testing libraries |
// Main application class
@SpringBootApplication // Combines @Configuration, @EnableAutoConfiguration, @ComponentScan
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
// application.properties
// server.port=8080
// spring.datasource.url=jdbc:mysql://localhost:3306/mydb
// spring.datasource.username=root
// spring.datasource.password=password
Spring Boot Runners
// CommandLineRunner - receives arguments as String array
@Component
public class MyRunner implements CommandLineRunner {
@Override
public void run(String... args) {
System.out.println("Application started with args");
for (String arg : args) {
System.out.println(arg);
}
}
}
// ApplicationRunner - receives parsed ApplicationArguments
@Component
public class AnotherRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) {
System.out.println("Non-option args: " + args.getNonOptionArgs());
System.out.println("Option names: " + args.getOptionNames());
}
}
Logging in Spring Boot
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Service
public class OrderService {
private static final Logger logger = LoggerFactory.getLogger(OrderService.class);
public void processOrder(Order order) {
logger.debug("Processing order: {}", order.getId());
logger.info("Order processed successfully");
logger.warn("Low inventory warning");
logger.error("Failed to process order", exception);
}
}
// application.properties logging configuration
// logging.level.root=INFO
// logging.level.com.example=DEBUG
// logging.file.name=application.log
REST API Basics
Definition
REST (Representational State Transfer) is an architectural style for designing networked applications. It uses HTTP methods to perform CRUD operations on resources.
HTTP Methods and CRUD
| HTTP Method | CRUD Operation | Description |
|---|---|---|
| GET | Read | Retrieve resource(s) |
| POST | Create | Create new resource |
| PUT | Update | Update entire resource |
| PATCH | Update | Partial update |
| DELETE | Delete | Remove resource |
REST Controller Annotations
| Annotation | Purpose |
|---|---|
| @RestController | Marks class as REST controller |
| @RequestMapping | Maps HTTP requests to handler methods |
| @GetMapping | Maps GET requests |
| @PostMapping | Maps POST requests |
| @PutMapping | Maps PUT requests |
| @DeleteMapping | Maps DELETE requests |
| @PathVariable | Extracts value from URI path |
| @RequestBody | Binds request body to object |
| @RequestParam | Extracts query parameters |
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
// GET /api/users
@GetMapping
public List<User> getAllUsers() {
return userService.findAll();
}
// GET /api/users/5
@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
return userService.findById(id);
}
// POST /api/users
@PostMapping
public User createUser(@RequestBody User user) {
return userService.save(user);
}
// PUT /api/users/5
@PutMapping("/{id}")
public User updateUser(@PathVariable Long id, @RequestBody User user) {
return userService.update(id, user);
}
// DELETE /api/users/5
@DeleteMapping("/{id}")
public void deleteUser(@PathVariable Long id) {
userService.delete(id);
}
// GET /api/users/search?name=John
@GetMapping("/search")
public List<User> searchUsers(@RequestParam String name) {
return userService.findByName(name);
}
}
Exam Tip
@RestController = @Controller + @ResponseBody. It automatically serializes return objects to JSON/XML.