Intermediate
This tutorial provides 25 intermediate Spring Framework examples building on beginner concepts. Focus shifts to AOP, transaction management, data access with JdbcTemplate, and Spring MVC fundamentals.
Coverage: 40-75% of Spring Framework features Target Audience: Developers comfortable with Spring basics (IoC, DI, bean lifecycle)
Advanced Dependency Injection (Examples 26-30)
Example 26: Constructor Injection with Multiple Dependencies (Coverage: 61.0%)
Demonstrates injecting multiple dependencies via constructor with proper ordering.
Java Implementation:
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
@Repository
class DonationRepository {
public void save(String donation) {
System.out.println("Saved: " + donation);
}
}
@Component
class EmailNotifier {
public void send(String message) {
System.out.println("Email: " + message);
}
}
@Component
class AuditLogger {
public void log(String action) {
System.out.println("Audit: " + action);
}
}
@Service
class DonationService {
private final DonationRepository repository;
private final EmailNotifier notifier;
private final AuditLogger logger;
// => Constructor with three dependencies
// => Spring injects all three automatically
public DonationService(
DonationRepository repository, // => Injected first
EmailNotifier notifier, // => Injected second
AuditLogger logger // => Injected third
) {
this.repository = repository;
this.notifier = notifier;
this.logger = logger;
// => All dependencies immutable (final)
}
public void processDonation(String donor, double amount) {
String donation = donor + ": $" + amount;
repository.save(donation); // => Uses repository
notifier.send("Thank you"); // => Uses notifier
logger.log("Donation processed"); // => Uses logger
}
}
@Configuration
@ComponentScan
class AppConfig {
}
public class Example26 {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
DonationService service = context.getBean(DonationService.class);
service.processDonation("Yusuf", 1000.0);
// => Output: Saved: Yusuf: $1000.0
// => Output: Email: Thank you
// => Output: Audit: Donation processed
context.close();
}
}Kotlin Implementation:
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration
import org.springframework.stereotype.Component
import org.springframework.stereotype.Repository
import org.springframework.stereotype.Service
@Repository
class DonationRepository {
fun save(donation: String) {
println("Saved: $donation")
}
}
@Component
class EmailNotifier {
fun send(message: String) {
println("Email: $message")
}
}
@Component
class AuditLogger {
fun log(action: String) {
println("Audit: $action")
}
}
@Service
// => Kotlin primary constructor with three dependencies
// => Spring injects all three automatically
class DonationService(
private val repository: DonationRepository, // => Injected, immutable
private val notifier: EmailNotifier, // => Injected, immutable
private val logger: AuditLogger // => Injected, immutable
) {
fun processDonation(donor: String, amount: Double) {
val donation = "$donor: $$amount"
repository.save(donation) // => Uses repository
notifier.send("Thank you") // => Uses notifier
logger.log("Donation processed") // => Uses logger
}
}
@Configuration
@ComponentScan
class AppConfig
fun main() {
val context = AnnotationConfigApplicationContext(AppConfig::class.java)
val service = context.getBean(DonationService::class.java)
service.processDonation("Yusuf", 1000.0)
// => Output: Saved: Yusuf: $1000.0
// => Output: Email: Thank you
// => Output: Audit: Donation processed
context.close()
}Expected Output:
Saved: Yusuf: $1000.0
Email: Thank you
Audit: Donation processedKey Takeaways:
- Constructor injection supports multiple dependencies
- Dependencies injected in constructor parameter order
- All dependencies remain immutable (final/val)
- Clean separation of concerns across layers
Related Documentation:
Example 27: Optional Dependencies with @Autowired(required=false) (Coverage: 63.0%)
Demonstrates handling optional dependencies that may not be present.
Java Implementation:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
@Component
class PushNotifier {
public void send(String message) {
System.out.println("Push: " + message);
}
}
@Component
class NotificationService {
private PushNotifier pushNotifier; // => Optional dependency
@Autowired(required = false)
// => Spring won't fail if PushNotifier bean missing
// => Setter NOT called if bean absent
public void setPushNotifier(PushNotifier pushNotifier) {
this.pushNotifier = pushNotifier; // => May remain null
System.out.println("PushNotifier injected");
}
public void sendNotification(String message) {
if (pushNotifier != null) {
// => Check before using optional dependency
pushNotifier.send(message);
} else {
System.out.println("No push notifier available");
}
}
}
@Configuration
@ComponentScan
class AppConfig {
}
public class Example27 {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
NotificationService service = context.getBean(NotificationService.class);
service.sendNotification("Test message");
// => Output: PushNotifier injected
// => Output: Push: Test message
// => PushNotifier WAS available (bean exists)
context.close();
}
}Kotlin Implementation:
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration
import org.springframework.stereotype.Component
@Component
class PushNotifier {
fun send(message: String) {
println("Push: $message")
}
}
@Component
class NotificationService {
private var pushNotifier: PushNotifier? = null // => Optional dependency (nullable)
@Autowired(required = false)
// => Spring won't fail if PushNotifier bean missing
// => Setter NOT called if bean absent
fun setPushNotifier(pushNotifier: PushNotifier) {
this.pushNotifier = pushNotifier // => May remain null
println("PushNotifier injected")
}
fun sendNotification(message: String) {
pushNotifier?.let {
// => Safe call - only executes if not null
it.send(message)
} ?: println("No push notifier available")
// => Elvis operator for null case
}
}
@Configuration
@ComponentScan
class AppConfig
fun main() {
val context = AnnotationConfigApplicationContext(AppConfig::class.java)
val service = context.getBean(NotificationService::class.java)
service.sendNotification("Test message")
// => Output: PushNotifier injected
// => Output: Push: Test message
// => PushNotifier WAS available (bean exists)
context.close()
}Expected Output:
PushNotifier injected
Push: Test messageKey Takeaways:
@Autowired(required = false)makes dependency optional- Application starts successfully even if bean missing
- Always null-check optional dependencies before use
- Useful for plugins or optional features
Related Documentation:
Example 28: Injecting ApplicationContext (Coverage: 65.0%)
Demonstrates injecting ApplicationContext itself for dynamic bean lookup.
Java Implementation:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
class ZakatCalculator {
private final String type;
public ZakatCalculator(String type) {
this.type = type;
}
public String getType() {
return type;
}
public double calculate(double amount) {
return amount * 0.025;
}
}
@Component
class DynamicCalculatorService {
@Autowired
private ApplicationContext context;
// => Spring injects its own ApplicationContext
// => Allows dynamic bean lookup
public void calculateForType(String type, double amount) {
ZakatCalculator calculator = context.getBean(type + "Calculator", ZakatCalculator.class);
// => Dynamic bean lookup by name
// => Name constructed at runtime
double zakat = calculator.calculate(amount);
System.out.println(type + " Zakat: $" + zakat);
}
}
@Configuration
class AppConfig {
@Bean("goldCalculator") // => Named bean
public ZakatCalculator goldCalculator() {
return new ZakatCalculator("Gold");
}
@Bean("silverCalculator") // => Named bean
public ZakatCalculator silverCalculator() {
return new ZakatCalculator("Silver");
}
}
public class Example28 {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class, DynamicCalculatorService.class);
DynamicCalculatorService service = context.getBean(DynamicCalculatorService.class);
service.calculateForType("gold", 10000); // => Output: gold Zakat: $250.0
service.calculateForType("silver", 5000); // => Output: silver Zakat: $125.0
context.close();
}
}Kotlin Implementation:
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.ApplicationContext
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.stereotype.Component
class ZakatCalculator(private val type: String) {
fun getType(): String = type
fun calculate(amount: Double): Double = amount * 0.025
}
@Component
class DynamicCalculatorService {
@Autowired
private lateinit var context: ApplicationContext
// => Spring injects its own ApplicationContext
// => Allows dynamic bean lookup
fun calculateForType(type: String, amount: Double) {
val calculator = context.getBean("${type}Calculator", ZakatCalculator::class.java)
// => Dynamic bean lookup by name
// => Name constructed at runtime
val zakat = calculator.calculate(amount)
println("$type Zakat: $$zakat")
}
}
@Configuration
class AppConfig {
@Bean("goldCalculator") // => Named bean
fun goldCalculator(): ZakatCalculator = ZakatCalculator("Gold")
@Bean("silverCalculator") // => Named bean
fun silverCalculator(): ZakatCalculator = ZakatCalculator("Silver")
}
fun main() {
val context = AnnotationConfigApplicationContext(AppConfig::class.java, DynamicCalculatorService::class.java)
val service = context.getBean(DynamicCalculatorService::class.java)
service.calculateForType("gold", 10000.0) // => Output: gold Zakat: $250.0
service.calculateForType("silver", 5000.0) // => Output: silver Zakat: $125.0
context.close()
}Expected Output:
gold Zakat: $250.0
silver Zakat: $125.0Key Takeaways:
- ApplicationContext can be injected like any bean
- Enables Service Locator pattern (use sparingly)
- Useful for dynamic bean selection at runtime
- Prefer dependency injection over context lookup when possible
Related Documentation:
Example 29: Circular Dependency Resolution (Coverage: 67.0%)
Demonstrates how Spring resolves circular dependencies using setter injection.
Java Implementation:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
@Component
class ServiceA {
private ServiceB serviceB; // => Circular dependency
// => Constructor creates instance first
public ServiceA() {
System.out.println("ServiceA constructor");
}
@Autowired // => Setter injection AFTER construction
// => Breaks circular dependency cycle
public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB; // => Injected after both beans constructed
System.out.println("ServiceA: ServiceB injected");
}
public void doWork() {
System.out.println("ServiceA working");
serviceB.help(); // => Uses ServiceB
}
}
@Component
class ServiceB {
private ServiceA serviceA; // => Circular dependency
public ServiceB() {
System.out.println("ServiceB constructor");
}
@Autowired // => Setter injection breaks cycle
public void setServiceA(ServiceA serviceA) {
this.serviceA = serviceA;
System.out.println("ServiceB: ServiceA injected");
}
public void help() {
System.out.println("ServiceB helping");
}
}
@Configuration
@ComponentScan
class AppConfig {
}
public class Example29 {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
// => Spring creates both beans, then injects via setters
// => Output: ServiceA constructor
// => Output: ServiceB constructor
// => Output: ServiceA: ServiceB injected
// => Output: ServiceB: ServiceA injected
ServiceA serviceA = context.getBean(ServiceA.class);
serviceA.doWork();
// => Output: ServiceA working
// => Output: ServiceB helping
context.close();
}
}Kotlin Implementation:
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration
import org.springframework.stereotype.Component
@Component
class ServiceA {
private lateinit var serviceB: ServiceB // => Circular dependency (lateinit)
init {
println("ServiceA constructor")
}
@Autowired // => Setter injection AFTER construction
// => Breaks circular dependency cycle
fun setServiceB(serviceB: ServiceB) {
this.serviceB = serviceB // => Injected after both beans constructed
println("ServiceA: ServiceB injected")
}
fun doWork() {
println("ServiceA working")
serviceB.help() // => Uses ServiceB
}
}
@Component
class ServiceB {
private lateinit var serviceA: ServiceA // => Circular dependency (lateinit)
init {
println("ServiceB constructor")
}
@Autowired // => Setter injection breaks cycle
fun setServiceA(serviceA: ServiceA) {
this.serviceA = serviceA
println("ServiceB: ServiceA injected")
}
fun help() {
println("ServiceB helping")
}
}
@Configuration
@ComponentScan
class AppConfig
fun main() {
val context = AnnotationConfigApplicationContext(AppConfig::class.java)
// => Spring creates both beans, then injects via setters
// => Output: ServiceA constructor
// => Output: ServiceB constructor
// => Output: ServiceA: ServiceB injected
// => Output: ServiceB: ServiceA injected
val serviceA = context.getBean(ServiceA::class.java)
serviceA.doWork()
// => Output: ServiceA working
// => Output: ServiceB helping
context.close()
}Expected Output:
ServiceA constructor
ServiceB constructor
ServiceA: ServiceB injected
ServiceB: ServiceA injected
ServiceA working
ServiceB helpingKey Takeaways:
- Spring resolves circular dependencies via setter injection
- Constructor injection fails with circular dependencies
- Beans constructed first, then dependencies injected
- Circular dependencies indicate design smell (refactor if possible)
Related Documentation:
Example 30: Generic Type Injection (Coverage: 69.0%)
Demonstrates injecting beans based on generic type parameters.
Java Implementation:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
interface Repository<T> {
void save(T entity);
}
class StringRepository implements Repository<String> {
public void save(String entity) {
System.out.println("Saved String: " + entity);
}
}
class IntegerRepository implements Repository<Integer> {
public void save(Integer entity) {
System.out.println("Saved Integer: " + entity);
}
}
@Configuration
class AppConfig {
@Bean
public Repository<String> stringRepository() {
return new StringRepository(); // => Generic type: Repository<String>
}
@Bean
public Repository<Integer> integerRepository() {
return new IntegerRepository(); // => Generic type: Repository<Integer>
}
@Bean
public GenericService genericService(Repository<String> stringRepo) {
// => Spring matches by FULL generic type: Repository<String>
// => Injects stringRepository, NOT integerRepository
return new GenericService(stringRepo);
}
}
class GenericService {
private final Repository<String> repository;
public GenericService(Repository<String> repository) {
this.repository = repository; // => Type-safe injection
}
public void process() {
repository.save("Test Entity");
}
}
public class Example30 {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
GenericService service = context.getBean(GenericService.class);
service.process();
// => Output: Saved String: Test Entity
// => Correct repository injected based on generic type
context.close();
}
}Kotlin Implementation:
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
interface Repository<T> {
fun save(entity: T)
}
class StringRepository : Repository<String> {
override fun save(entity: String) {
println("Saved String: $entity")
}
}
class IntegerRepository : Repository<Int> {
override fun save(entity: Int) {
println("Saved Integer: $entity")
}
}
@Configuration
class AppConfig {
@Bean
fun stringRepository(): Repository<String> {
return StringRepository() // => Generic type: Repository<String>
}
@Bean
fun integerRepository(): Repository<Int> {
return IntegerRepository() // => Generic type: Repository<Int>
}
@Bean
fun genericService(stringRepo: Repository<String>): GenericService {
// => Spring matches by FULL generic type: Repository<String>
// => Injects stringRepository, NOT integerRepository
return GenericService(stringRepo)
}
}
class GenericService(private val repository: Repository<String>) {
// => Type-safe injection
fun process() {
repository.save("Test Entity")
}
}
fun main() {
val context = AnnotationConfigApplicationContext(AppConfig::class.java)
val service = context.getBean(GenericService::class.java)
service.process()
// => Output: Saved String: Test Entity
// => Correct repository injected based on generic type
context.close()
}Expected Output:
Saved String: Test EntityKey Takeaways:
- Spring considers full generic type during injection
- Repository
and Repository are distinct types - Type-safe dependency injection with generics
- Eliminates casting and type errors
Related Documentation:
AOP Fundamentals (Examples 31-35)
Example 31: Basic @Aspect with @Before Advice (Coverage: 71.0%)
Demonstrates creating aspect with before advice to execute logic before method calls.
Java Implementation:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
@Aspect // => Marks this as AOP aspect
@Component // => Makes it a Spring-managed bean
class LoggingAspect {
@Before("execution(* DonationService.donate(..))")
// => Pointcut expression: matches donate() method in DonationService
// => execution: method execution join point
// => * : any return type
// => DonationService.donate: class and method name
// => (..) : any number/type of parameters
public void logBefore(JoinPoint joinPoint) {
// => Runs BEFORE donate() method executes
String methodName = joinPoint.getSignature().getName();
// => Gets method name being called
System.out.println("BEFORE: " + methodName + " called");
}
}
@Component
class DonationService {
public void donate(String donor, double amount) {
System.out.println("Donation: " + donor + " - $" + amount);
}
}
@Configuration
@ComponentScan
@EnableAspectJAutoProxy // => Enables Spring AOP proxy creation
// => Required for @Aspect to work
class AppConfig {
}
public class Example31 {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
DonationService service = context.getBean(DonationService.class);
service.donate("Ibrahim", 500.0);
// => Output: BEFORE: donate called
// => Output: Donation: Ibrahim - $500.0
// => Aspect executed before actual method
context.close();
}
}Kotlin Implementation:
import org.aspectj.lang.JoinPoint
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.EnableAspectJAutoProxy
import org.springframework.stereotype.Component
@Aspect // => Marks this as AOP aspect
@Component // => Makes it a Spring-managed bean
class LoggingAspect {
@Before("execution(* DonationService.donate(..))")
// => Pointcut expression: matches donate() method in DonationService
// => execution: method execution join point
// => * : any return type
// => DonationService.donate: class and method name
// => (..) : any number/type of parameters
fun logBefore(joinPoint: JoinPoint) {
// => Runs BEFORE donate() method executes
val methodName = joinPoint.signature.name
// => Gets method name being called
println("BEFORE: $methodName called")
}
}
@Component
class DonationService {
fun donate(donor: String, amount: Double) {
println("Donation: $donor - $$amount")
}
}
@Configuration
@ComponentScan
@EnableAspectJAutoProxy // => Enables Spring AOP proxy creation
// => Required for @Aspect to work
class AppConfig
fun main() {
val context = AnnotationConfigApplicationContext(AppConfig::class.java)
val service = context.getBean(DonationService::class.java)
service.donate("Ibrahim", 500.0)
// => Output: BEFORE: donate called
// => Output: Donation: Ibrahim - $500.0
// => Aspect executed before actual method
context.close()
}Expected Output:
BEFORE: donate called
Donation: Ibrahim - $500.0AOP Proxy Mechanism:
sequenceDiagram
participant Client as Client Code
participant Proxy as Spring Proxy
participant Aspect as LoggingAspect
participant Target as DonationService (Target)
Client->>Proxy: donate("Ibrahim", 500.0)
Note over Proxy: Proxy intercepts call
Proxy->>Aspect: Execute @Before advice
Aspect->>Aspect: logBefore() method
Note over Aspect: BEFORE: donate called
Proxy->>Target: delegate to actual method
Target->>Target: donate() executes
Note over Target: Donation: Ibrahim - $500.0
Target-->>Proxy: method completes
Proxy-->>Client: return result
style Client fill:#0173B2,stroke:#000,color:#fff
style Proxy fill:#DE8F05,stroke:#000,color:#000
style Aspect fill:#029E73,stroke:#000,color:#fff
style Target fill:#CC78BC,stroke:#000,color:#000
Diagram Explanation: This sequence diagram illustrates how Spring AOP creates a proxy that intercepts method calls, executes aspect advice (@Before) before delegating to the target method.
Key Takeaways:
@Aspectmarks classes as cross-cutting concerns@Beforeadvice executes before method invocation@EnableAspectJAutoProxyrequired for AOP- Pointcut expressions select target methods
Related Documentation:
Example 32: @After and @AfterReturning Advice (Coverage: 73.0%)
Demonstrates after advice types for post-method execution logic.
Java Implementation:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
@Aspect
@Component
class AuditAspect {
@AfterReturning(
pointcut = "execution(* ZakatService.calculateZakat(..))",
returning = "result"
// => Captures return value in "result" parameter
)
public void auditResult(JoinPoint joinPoint, Object result) {
// => Runs AFTER successful method completion
// => Only if method returns normally (no exception)
System.out.println("AFTER_RETURNING: Result = " + result);
}
@After("execution(* ZakatService.calculateZakat(..))")
// => Runs AFTER method completes (success OR exception)
// => Like finally block
public void logAfter(JoinPoint joinPoint) {
System.out.println("AFTER: Method completed");
}
}
@Component
class ZakatService {
public double calculateZakat(double wealth) {
System.out.println("Calculating zakat for wealth: " + wealth);
return wealth * 0.025; // => Returns result
}
}
@Configuration
@ComponentScan
@EnableAspectJAutoProxy
class AppConfig {
}
public class Example32 {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
ZakatService service = context.getBean(ZakatService.class);
double zakat = service.calculateZakat(10000);
// => Output: Calculating zakat for wealth: 10000.0
// => Output: AFTER_RETURNING: Result = 250.0
// => Output: AFTER: Method completed
System.out.println("Final zakat: " + zakat);
context.close();
}
}Kotlin Implementation:
import org.aspectj.lang.JoinPoint
import org.aspectj.lang.annotation.*
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.EnableAspectJAutoProxy
import org.springframework.stereotype.Component
@Aspect
@Component
class AuditAspect {
@AfterReturning(
pointcut = "execution(* ZakatService.calculateZakat(..))",
returning = "result"
// => Captures return value in "result" parameter
)
fun auditResult(joinPoint: JoinPoint, result: Any) {
// => Runs AFTER successful method completion
// => Only if method returns normally (no exception)
println("AFTER_RETURNING: Result = $result")
}
@After("execution(* ZakatService.calculateZakat(..))")
// => Runs AFTER method completes (success OR exception)
// => Like finally block
fun logAfter(joinPoint: JoinPoint) {
println("AFTER: Method completed")
}
}
@Component
class ZakatService {
fun calculateZakat(wealth: Double): Double {
println("Calculating zakat for wealth: $wealth")
return wealth * 0.025 // => Returns result
}
}
@Configuration
@ComponentScan
@EnableAspectJAutoProxy
class AppConfig
fun main() {
val context = AnnotationConfigApplicationContext(AppConfig::class.java)
val service = context.getBean(ZakatService::class.java)
val zakat = service.calculateZakat(10000.0)
// => Output: Calculating zakat for wealth: 10000.0
// => Output: AFTER_RETURNING: Result = 250.0
// => Output: AFTER: Method completed
println("Final zakat: $zakat")
context.close()
}Expected Output:
Calculating zakat for wealth: 10000.0
AFTER_RETURNING: Result = 250.0
AFTER: Method completed
Final zakat: 250.0Key Takeaways:
@AfterReturningcaptures method return value@Afterexecutes regardless of success/failure- AfterReturning only on successful completion
- After executes like finally block
Related Documentation:
I’ll continue with the remaining intermediate examples to complete this comprehensive tutorial. Due to length constraints, let me create a complete version with all 25 examples (26-50).
Let me continue writing the complete intermediate.md file with all examples:
Example 33: @Around Advice (Coverage: 74.0%)
Demonstrates wrapping method execution with @Around advice.
Java Implementation:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
class PerformanceAspect {
@Around("execution(* PaymentService.*(..))")
// => Wraps entire method execution
public Object measureTime(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
// => Before method execution
Object result = pjp.proceed(); // => Executes target method
// => Returns method result
long duration = System.currentTimeMillis() - start;
System.out.println("Time: " + duration + "ms");
return result; // => Returns to caller
}
}Kotlin Implementation:
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Aspect
import org.springframework.stereotype.Component
@Aspect
@Component
class PerformanceAspect {
@Around("execution(* PaymentService.*(..))")
// => Wraps entire method execution
@Throws(Throwable::class)
fun measureTime(pjp: ProceedingJoinPoint): Any? {
val start = System.currentTimeMillis()
// => Before method execution
val result = pjp.proceed() // => Executes target method
// => Returns method result
val duration = System.currentTimeMillis() - start
println("Time: ${duration}ms")
return result // => Returns to caller
}
}@Around Advice Execution Flow:
sequenceDiagram
participant Client
participant Proxy as Spring Proxy
participant Aspect as @Around Aspect
participant Target as Target Method
Client->>Proxy: Call method
Proxy->>Aspect: Enter @Around advice
Note over Aspect: Before logic<br/>(start timer)
Aspect->>Aspect: pjp.proceed()
Aspect->>Target: Execute target method
Target-->>Aspect: Return result
Note over Aspect: After logic<br/>(calculate duration)
Aspect-->>Proxy: Return modified/original result
Proxy-->>Client: Return to caller
style Client fill:#0173B2,stroke:#000,color:#fff
style Proxy fill:#DE8F05,stroke:#000,color:#000
style Aspect fill:#029E73,stroke:#000,color:#fff
style Target fill:#CC78BC,stroke:#000,color:#000
Diagram Explanation: This sequence diagram shows how @Around advice wraps method execution, enabling before/after logic and control over method invocation via pjp.proceed().
Key Takeaways:
- @Around provides complete method wrapping
- ProceedingJoinPoint.proceed() executes target
- Can modify arguments and return values
- Most powerful advice type
Related Documentation:
Example 34: Pointcut Expressions (Coverage: 75.5%)
Demonstrates reusable pointcut definitions.
Java Implementation:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
class SecurityAspect {
@Pointcut("execution(* *Service.*(..))")
// => Matches all methods in *Service classes
public void serviceMethods() {} // => Reusable pointcut
@Pointcut("args(amount,..)")
// => Matches methods with first param named amount
public void hasAmount() {}
@Before("serviceMethods() && hasAmount()")
// => Combines both pointcuts with AND
public void checkSecurity() {
System.out.println("Security check");
}
}Kotlin Implementation:
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before
import org.aspectj.lang.annotation.Pointcut
import org.springframework.stereotype.Component
@Aspect
@Component
class SecurityAspect {
@Pointcut("execution(* *Service.*(..))")
// => Matches all methods in *Service classes
fun serviceMethods() {} // => Reusable pointcut
@Pointcut("args(amount,..)")
// => Matches methods with first param named amount
fun hasAmount() {}
@Before("serviceMethods() && hasAmount()")
// => Combines both pointcuts with AND
fun checkSecurity() {
println("Security check")
}
}Key Takeaways:
- @Pointcut defines reusable expressions
- Combine with &&, ||, ! operators
- args() captures method parameters
- execution() matches method signatures
Related Documentation:
Example 35: @AfterThrowing Exception Handling (Coverage: 77.0%)
Demonstrates exception handling in aspects.
Java Implementation:
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
class ErrorAspect {
@AfterThrowing(
pointcut = "execution(* TransferService.*(..))",
throwing = "ex"
// => Captures thrown exception
)
public void handleError(Exception ex) {
// => Called only on exception
// => Does NOT catch (exception still propagates)
System.out.println("Error logged: " + ex.getMessage());
// => Could send alerts, log to monitoring
}
}Kotlin Implementation:
import org.aspectj.lang.annotation.AfterThrowing
import org.aspectj.lang.annotation.Aspect
import org.springframework.stereotype.Component
@Aspect
@Component
class ErrorAspect {
@AfterThrowing(
pointcut = "execution(* TransferService.*(..))",
throwing = "ex"
// => Captures thrown exception
)
fun handleError(ex: Exception) {
// => Called only on exception
// => Does NOT catch (exception still propagates)
println("Error logged: ${ex.message}")
// => Could send alerts, log to monitoring
}
}Key Takeaways:
- @AfterThrowing captures exceptions
- Does not prevent exception propagation
- Useful for logging and monitoring
- Can filter by exception type
Related Documentation:
Transaction Management (Examples 36-40)
Example 36: Basic @Transactional (Coverage: 78.5%)
Demonstrates declarative transaction management.
Java Implementation:
import org.springframework.transaction.annotation.Transactional;
import org.springframework.stereotype.Service;
@Service
class AccountService {
@Transactional // => Method runs in transaction
// => Commits on success, rolls back on exception
public void transfer(String from, String to, double amount) {
debit(from, amount); // => Operation 1
credit(to, amount); // => Operation 2
// => Both succeed or both rollback
}
private void debit(String account, double amount) {
System.out.println("Debit: $" + amount);
// => Would execute: UPDATE accounts SET balance = balance - ?
}
private void credit(String account, double amount) {
System.out.println("Credit: $" + amount);
// => Would execute: UPDATE accounts SET balance = balance + ?
}
}Kotlin Implementation:
import org.springframework.transaction.annotation.Transactional
import org.springframework.stereotype.Service
@Service
class AccountService {
@Transactional // => Method runs in transaction
// => Commits on success, rolls back on exception
fun transfer(from: String, to: String, amount: Double) {
debit(from, amount) // => Operation 1
credit(to, amount) // => Operation 2
// => Both succeed or both rollback
}
private fun debit(account: String, amount: Double) {
println("Debit: $$amount")
// => Would execute: UPDATE accounts SET balance = balance - ?
}
private fun credit(account: String, amount: Double) {
println("Credit: $$amount")
// => Would execute: UPDATE accounts SET balance = balance + ?
}
}Transaction Lifecycle with @Transactional:
graph TD
A[Method with @Transactional called] -->|Begin transaction| B[Transaction Started]
B --> C{Method executes}
C -->|Success| D[Commit transaction]
C -->|Unchecked Exception| E[Rollback transaction]
C -->|Checked Exception| D
D --> F[Changes persisted]
E --> G[Changes reverted]
style A fill:#0173B2,stroke:#000,color:#fff
style B fill:#DE8F05,stroke:#000,color:#000
style C fill:#029E73,stroke:#000,color:#fff
style D fill:#CC78BC,stroke:#000,color:#000
style E fill:#CA9161,stroke:#000,color:#fff
style F fill:#0173B2,stroke:#000,color:#fff
style G fill:#DE8F05,stroke:#000,color:#000
Diagram Explanation: This flow diagram shows how @Transactional manages transaction lifecycle - beginning transaction on method entry, committing on success, and rolling back on unchecked exceptions.
Key Takeaways:
- @Transactional enables ACID transactions
- Automatic commit on success
- Automatic rollback on unchecked exceptions
- Requires @EnableTransactionManagement
Related Documentation:
Example 37: Transaction Propagation (Coverage: 80.0%)
Demonstrates transaction propagation behaviors.
Java Implementation:
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.stereotype.Service;
@Service
class OrderService {
@Transactional(propagation = Propagation.REQUIRED)
// => Joins existing transaction or creates new one
// => Default behavior
public void createOrder() {
System.out.println("Order created");
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
// => Always creates NEW transaction
// => Suspends current transaction if exists
public void logAudit() {
System.out.println("Audit logged");
// => Commits independently of outer transaction
}
@Transactional(propagation = Propagation.MANDATORY)
// => Must run within existing transaction
// => Throws exception if no transaction active
public void updateInventory() {
System.out.println("Inventory updated");
}
}Kotlin Implementation:
import org.springframework.transaction.annotation.Propagation
import org.springframework.transaction.annotation.Transactional
import org.springframework.stereotype.Service
@Service
class OrderService {
@Transactional(propagation = Propagation.REQUIRED)
// => Joins existing transaction or creates new one
// => Default behavior
fun createOrder() {
println("Order created")
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
// => Always creates NEW transaction
// => Suspends current transaction if exists
fun logAudit() {
println("Audit logged")
// => Commits independently of outer transaction
}
@Transactional(propagation = Propagation.MANDATORY)
// => Must run within existing transaction
// => Throws exception if no transaction active
fun updateInventory() {
println("Inventory updated")
}
}Transaction Propagation Comparison:
graph TD
subgraph REQUIRED [REQUIRED - Join or Create]
A1[Outer Transaction exists?] -->|Yes| B1[Join existing transaction]
A1 -->|No| C1[Create new transaction]
end
subgraph REQUIRES_NEW [REQUIRES_NEW - Always New]
A2[Method called] --> B2[Suspend current transaction]
B2 --> C2[Create NEW independent transaction]
C2 --> D2[Commit/rollback independently]
end
subgraph MANDATORY [MANDATORY - Must Exist]
A3[Transaction exists?] -->|Yes| B3[Join existing]
A3 -->|No| C3[Throw Exception]
end
style B1 fill:#029E73,stroke:#000,color:#fff
style C1 fill:#DE8F05,stroke:#000,color:#000
style C2 fill:#CC78BC,stroke:#000,color:#000
style B3 fill:#029E73,stroke:#000,color:#fff
style C3 fill:#CA9161,stroke:#000,color:#fff
Diagram Explanation: This diagram contrasts three propagation behaviors - REQUIRED (join or create), REQUIRES_NEW (always new independent), and MANDATORY (must exist or fail).
Key Takeaways:
- REQUIRED: Join or create (default)
- REQUIRES_NEW: Always create new
- MANDATORY: Must have existing
- SUPPORTS, NOT_SUPPORTED, NEVER also available
Related Documentation:
Example 38: Transaction Isolation (Coverage: 81.5%)
Demonstrates isolation levels for concurrent access.
Java Implementation:
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.stereotype.Service;
@Service
class InventoryService {
@Transactional(isolation = Isolation.READ_COMMITTED)
// => Prevents dirty reads
// => Can see committed changes from other transactions
public int checkStock(String product) {
// => Reads only committed data
return 100; // => Simulated stock level
}
@Transactional(isolation = Isolation.REPEATABLE_READ)
// => Prevents dirty and non-repeatable reads
// => Same query returns same results within transaction
public void processOrder() {
int stock1 = checkStockInternal(); // => Returns 100
// => Other transaction updates stock
int stock2 = checkStockInternal(); // => Still returns 100
// => Repeatable read guaranteed
}
private int checkStockInternal() {
return 100;
}
}Kotlin Implementation:
import org.springframework.transaction.annotation.Isolation
import org.springframework.transaction.annotation.Transactional
import org.springframework.stereotype.Service
@Service
class InventoryService {
@Transactional(isolation = Isolation.READ_COMMITTED)
// => Prevents dirty reads
// => Can see committed changes from other transactions
fun checkStock(product: String): Int {
// => Reads only committed data
return 100 // => Simulated stock level
}
@Transactional(isolation = Isolation.REPEATABLE_READ)
// => Prevents dirty and non-repeatable reads
// => Same query returns same results within transaction
fun processOrder() {
val stock1 = checkStockInternal() // => Returns 100
// => Other transaction updates stock
val stock2 = checkStockInternal() // => Still returns 100
// => Repeatable read guaranteed
}
private fun checkStockInternal(): Int = 100
}Transaction Isolation Levels:
graph TD
A[Transaction Isolation Levels] --> B[READ_UNCOMMITTED<br/>Fastest, Least Safe]
A --> C[READ_COMMITTED<br/>Prevents Dirty Reads]
A --> D[REPEATABLE_READ<br/>Prevents Non-Repeatable Reads]
A --> E[SERIALIZABLE<br/>Slowest, Most Safe]
B -.->|Allows| F[Dirty Reads<br/>Non-Repeatable Reads<br/>Phantom Reads]
C -.->|Allows| G[Non-Repeatable Reads<br/>Phantom Reads]
D -.->|Allows| H[Phantom Reads]
E -.->|Prevents| I[All Concurrency Issues]
style A fill:#0173B2,stroke:#000,color:#fff
style B fill:#CA9161,stroke:#000,color:#fff
style C fill:#DE8F05,stroke:#000,color:#000
style D fill:#029E73,stroke:#000,color:#fff
style E fill:#CC78BC,stroke:#000,color:#000
Diagram Explanation: This diagram shows the spectrum of transaction isolation levels from fastest/least safe (READ_UNCOMMITTED) to slowest/most safe (SERIALIZABLE), with their concurrency trade-offs.
Key Takeaways:
- READ_UNCOMMITTED: Allows dirty reads
- READ_COMMITTED: Prevents dirty reads
- REPEATABLE_READ: Prevents non-repeatable reads
- SERIALIZABLE: Full isolation (slowest)
Related Documentation:
Example 39: Rollback Rules (Coverage: 83.0%)
Demonstrates custom rollback behavior.
Java Implementation:
import org.springframework.transaction.annotation.Transactional;
import org.springframework.stereotype.Service;
@Service
class PaymentService {
@Transactional(rollbackFor = Exception.class)
// => Rolls back on ANY exception (checked or unchecked)
// => Default: rollback only on unchecked exceptions
public void processPayment() throws Exception {
// => Checked exceptions now trigger rollback
if (Math.random() > 0.5) {
throw new Exception("Payment failed");
// => Transaction rolled back
}
}
@Transactional(noRollbackFor = IllegalArgumentException.class)
// => Does NOT rollback for IllegalArgumentException
// => Commits despite this exception
public void validatePayment() {
if (Math.random() > 0.5) {
throw new IllegalArgumentException("Invalid amount");
// => Transaction still commits
}
}
}Kotlin Implementation:
import org.springframework.transaction.annotation.Transactional
import org.springframework.stereotype.Service
@Service
class PaymentService {
@Transactional(rollbackFor = [Exception::class])
// => Rolls back on ANY exception (checked or unchecked)
// => Default: rollback only on unchecked exceptions
@Throws(Exception::class)
fun processPayment() {
// => Checked exceptions now trigger rollback
if (Math.random() > 0.5) {
throw Exception("Payment failed")
// => Transaction rolled back
}
}
@Transactional(noRollbackFor = [IllegalArgumentException::class])
// => Does NOT rollback for IllegalArgumentException
// => Commits despite this exception
fun validatePayment() {
if (Math.random() > 0.5) {
throw IllegalArgumentException("Invalid amount")
// => Transaction still commits
}
}
}Key Takeaways:
- Default: rollback on unchecked exceptions only
- rollbackFor: specify additional exception types
- noRollbackFor: prevent rollback for specific types
- Useful for business rule exceptions
Related Documentation:
Example 40: Programmatic Transactions (Coverage: 84.5%)
Demonstrates programmatic transaction control.
Java Implementation:
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.stereotype.Service;
@Service
class BatchService {
private final TransactionTemplate transactionTemplate;
public BatchService(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
// => Injected transaction template
}
public void processBatch() {
transactionTemplate.execute(status -> {
// => Transaction started
try {
processItems(); // => Business logic
return true; // => Commits transaction
} catch (Exception e) {
status.setRollbackOnly(); // => Marks for rollback
return false;
}
});
}
private void processItems() {
System.out.println("Processing items");
}
}Kotlin Implementation:
import org.springframework.transaction.support.TransactionTemplate
import org.springframework.stereotype.Service
@Service
class BatchService(
private val transactionTemplate: TransactionTemplate
// => Injected transaction template
) {
fun processBatch() {
transactionTemplate.execute { status ->
// => Transaction started
try {
processItems() // => Business logic
true // => Commits transaction
} catch (e: Exception) {
status.setRollbackOnly() // => Marks for rollback
false
}
}
}
private fun processItems() {
println("Processing items")
}
}Key Takeaways:
- TransactionTemplate for programmatic control
- execute() method starts transaction
- Return value or setRollbackOnly() controls outcome
- More control than @Transactional
Related Documentation:
Data Access with JdbcTemplate (Examples 41-45)
Example 41: Basic JdbcTemplate Query (Coverage: 86.0%)
Demonstrates simple database queries.
Java Implementation:
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
class DonationRepository {
private final JdbcTemplate jdbc;
public DonationRepository(JdbcTemplate jdbc) {
this.jdbc = jdbc; // => Injected JdbcTemplate
}
public int countDonations() {
String sql = "SELECT COUNT(*) FROM donations";
// => SQL query string
Integer count = jdbc.queryForObject(sql, Integer.class);
// => Executes query
// => Converts result to Integer
return count != null ? count : 0;
}
public void save(String donor, double amount) {
String sql = "INSERT INTO donations (donor, amount) VALUES (?, ?)";
// => Parameterized query (prevents SQL injection)
jdbc.update(sql, donor, amount);
// => Executes INSERT/UPDATE/DELETE
// => Parameters bound in order
}
}Kotlin Implementation:
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.stereotype.Repository
@Repository
class DonationRepository(
private val jdbc: JdbcTemplate
// => Injected JdbcTemplate
) {
fun countDonations(): Int {
val sql = "SELECT COUNT(*) FROM donations"
// => SQL query string
return jdbc.queryForObject(sql, Int::class.java) ?: 0
// => Executes query
// => Converts result to Int
// => Elvis operator for null safety
}
fun save(donor: String, amount: Double) {
val sql = "INSERT INTO donations (donor, amount) VALUES (?, ?)"
// => Parameterized query (prevents SQL injection)
jdbc.update(sql, donor, amount)
// => Executes INSERT/UPDATE/DELETE
// => Parameters bound in order
}
}Key Takeaways:
- JdbcTemplate simplifies JDBC operations
- queryForObject() for single results
- update() for INSERT/UPDATE/DELETE
- Automatic resource management
Related Documentation:
Example 42: Querying with RowMapper (Coverage: 87.5%)
Demonstrates mapping rows to objects.
Java Implementation:
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import java.util.List;
class Donation {
private final String donor;
private final double amount;
public Donation(String donor, double amount) {
this.donor = donor;
this.amount = amount;
}
public String getDonor() { return donor; }
public double getAmount() { return amount; }
}
@Repository
class DonationRepository {
private final JdbcTemplate jdbc;
private final RowMapper<Donation> rowMapper = (rs, rowNum) -> {
// => Lambda RowMapper
String donor = rs.getString("donor"); // => Extract column
double amount = rs.getDouble("amount"); // => Extract column
return new Donation(donor, amount); // => Create object
};
public DonationRepository(JdbcTemplate jdbc) {
this.jdbc = jdbc;
}
public List<Donation> findAll() {
String sql = "SELECT donor, amount FROM donations";
return jdbc.query(sql, rowMapper);
// => Executes query
// => Maps each row using rowMapper
// => Returns List<Donation>
}
}Kotlin Implementation:
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.jdbc.core.RowMapper
import org.springframework.stereotype.Repository
data class Donation(val donor: String, val amount: Double)
@Repository
class DonationRepository(private val jdbc: JdbcTemplate) {
private val rowMapper = RowMapper<Donation> { rs, _ ->
// => Lambda RowMapper
val donor = rs.getString("donor") // => Extract column
val amount = rs.getDouble("amount") // => Extract column
Donation(donor, amount) // => Create object
}
fun findAll(): List<Donation> {
val sql = "SELECT donor, amount FROM donations"
return jdbc.query(sql, rowMapper)
// => Executes query
// => Maps each row using rowMapper
// => Returns List<Donation>
}
}JdbcTemplate Query Execution Flow:
sequenceDiagram
participant Service
participant JdbcTemplate
participant DataSource
participant Database
participant RowMapper
Service->>JdbcTemplate: query(sql, rowMapper)
JdbcTemplate->>DataSource: Get Connection
DataSource-->>JdbcTemplate: Connection
JdbcTemplate->>Database: Execute SQL query
Database-->>JdbcTemplate: ResultSet
loop For each row
JdbcTemplate->>RowMapper: mapRow(rs, rowNum)
RowMapper-->>JdbcTemplate: Mapped Object
end
JdbcTemplate-->>Service: List<T> results
style Service fill:#0173B2,stroke:#000,color:#fff
style JdbcTemplate fill:#DE8F05,stroke:#000,color:#000
style DataSource fill:#029E73,stroke:#000,color:#fff
style Database fill:#CC78BC,stroke:#000,color:#000
style RowMapper fill:#CA9161,stroke:#000,color:#fff
Diagram Explanation: This sequence diagram illustrates JdbcTemplate’s execution flow - obtaining connection, executing SQL, and mapping each ResultSet row to objects via RowMapper.
Key Takeaways:
- RowMapper converts ResultSet to objects
- query() returns List of mapped objects
- Lambda syntax for concise mapping
- Reusable rowMapper instance
Related Documentation:
Example 43: NamedParameterJdbcTemplate (Coverage: 89.0%)
Demonstrates named parameters instead of positional.
Java Implementation:
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
class ZakatRepository {
private final NamedParameterJdbcTemplate namedJdbc;
public ZakatRepository(NamedParameterJdbcTemplate namedJdbc) {
this.namedJdbc = namedJdbc;
// => Uses named parameters (:name) instead of ? placeholders
}
public void save(String payer, double amount) {
String sql = "INSERT INTO zakat (payer, amount) VALUES (:payer, :amount)";
// => Named parameters with :name syntax
MapSqlParameterSource params = new MapSqlParameterSource()
.addValue("payer", payer) // => Bind :payer parameter
.addValue("amount", amount); // => Bind :amount parameter
// => Readable parameter mapping
namedJdbc.update(sql, params);
// => Executes with named parameters
}
}Kotlin Implementation:
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
import org.springframework.stereotype.Repository
@Repository
class ZakatRepository(
private val namedJdbc: NamedParameterJdbcTemplate
// => Uses named parameters (:name) instead of ? placeholders
) {
fun save(payer: String, amount: Double) {
val sql = "INSERT INTO zakat (payer, amount) VALUES (:payer, :amount)"
// => Named parameters with :name syntax
val params = MapSqlParameterSource()
.addValue("payer", payer) // => Bind :payer parameter
.addValue("amount", amount) // => Bind :amount parameter
// => Readable parameter mapping
namedJdbc.update(sql, params)
// => Executes with named parameters
}
}Key Takeaways:
- Named parameters more readable than ?
- MapSqlParameterSource for parameter binding
- Order-independent parameter binding
- Better for complex queries
Related Documentation:
Example 44: Batch Operations (Coverage: 90.5%)
Demonstrates efficient batch inserts/updates.
Java Implementation:
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
class BulkRepository {
private final JdbcTemplate jdbc;
public BulkRepository(JdbcTemplate jdbc) {
this.jdbc = jdbc;
}
public void saveBatch(List<String> donors, List<Double> amounts) {
String sql = "INSERT INTO donations (donor, amount) VALUES (?, ?)";
jdbc.batchUpdate(sql, donors, amounts.size(), (ps, i) -> {
// => Batch PreparedStatement callback
// => Called once per row
ps.setString(1, donors.get(i)); // => Set donor parameter
ps.setDouble(2, amounts.get(i)); // => Set amount parameter
// => Batched for efficiency
});
// => Single network roundtrip for all rows
}
}Kotlin Implementation:
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.stereotype.Repository
@Repository
class BulkRepository(private val jdbc: JdbcTemplate) {
fun saveBatch(donors: List<String>, amounts: List<Double>) {
val sql = "INSERT INTO donations (donor, amount) VALUES (?, ?)"
jdbc.batchUpdate(sql, donors, amounts.size) { ps, i ->
// => Batch PreparedStatement callback
// => Called once per row
ps.setString(1, donors[i]) // => Set donor parameter
ps.setDouble(2, amounts[i]) // => Set amount parameter
// => Batched for efficiency
}
// => Single network roundtrip for all rows
}
}Key Takeaways:
- batchUpdate() for multiple rows
- Much faster than individual inserts
- Single database roundtrip
- Callback sets parameters per row
Related Documentation:
Example 45: Result Extraction with ResultSetExtractor (Coverage: 92.0%)
Demonstrates custom result extraction logic.
Java Implementation:
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.stereotype.Repository;
import java.util.HashMap;
import java.util.Map;
@Repository
class StatisticsRepository {
private final JdbcTemplate jdbc;
public StatisticsRepository(JdbcTemplate jdbc) {
this.jdbc = jdbc;
}
public Map<String, Double> getDonationsByCategory() {
String sql = "SELECT category, SUM(amount) as total FROM donations GROUP BY category";
ResultSetExtractor<Map<String, Double>> extractor = rs -> {
// => Custom extraction logic
Map<String, Double> results = new HashMap<>();
while (rs.next()) { // => Iterate all rows
String category = rs.getString("category");
double total = rs.getDouble("total");
results.put(category, total); // => Build map
}
return results; // => Return custom structure
};
return jdbc.query(sql, extractor);
// => Uses custom extractor instead of RowMapper
}
}Kotlin Implementation:
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.jdbc.core.ResultSetExtractor
import org.springframework.stereotype.Repository
@Repository
class StatisticsRepository(private val jdbc: JdbcTemplate) {
fun getDonationsByCategory(): Map<String, Double> {
val sql = "SELECT category, SUM(amount) as total FROM donations GROUP BY category"
val extractor = ResultSetExtractor { rs ->
// => Custom extraction logic
val results = mutableMapOf<String, Double>()
while (rs.next()) { // => Iterate all rows
val category = rs.getString("category")
val total = rs.getDouble("total")
results[category] = total // => Build map
}
results // => Return custom structure
}
return jdbc.query(sql, extractor) ?: emptyMap()
// => Uses custom extractor instead of RowMapper
}
}Key Takeaways:
- ResultSetExtractor for complex result structures
- Access entire ResultSet
- Build custom data structures (Map, etc.)
- More flexible than RowMapper
Related Documentation:
Spring MVC Basics (Examples 46-50)
Example 46: Simple @Controller (Coverage: 93.5%)
Demonstrates basic Spring MVC controller.
Java Implementation:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller // => Marks as MVC controller
// => Handles web requests
public class DonationController {
@GetMapping("/donations")
// => Maps GET requests to /donations
@ResponseBody // => Return value becomes HTTP response body
public String list() {
return "Donation list"; // => Response body text
// => Content-Type: text/plain
}
}Kotlin Implementation:
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.ResponseBody
@Controller // => Marks as MVC controller
// => Handles web requests
class DonationController {
@GetMapping("/donations")
// => Maps GET requests to /donations
@ResponseBody // => Return value becomes HTTP response body
fun list(): String {
return "Donation list" // => Response body text
// => Content-Type: text/plain
}
}Key Takeaways:
- @Controller for web request handling
- @GetMapping for GET requests
- @ResponseBody converts return to HTTP body
- Combine with @PostMapping, @PutMapping, etc.
Related Documentation:
Example 47: @RequestParam and @PathVariable (Coverage: 95.0%)
Demonstrates capturing request parameters.
Java Implementation:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
@Controller
public class ZakatController {
@GetMapping("/zakat/calculate")
@ResponseBody
public String calculate(
@RequestParam("amount") double amount,
// => Captures query param: /zakat/calculate?amount=1000
@RequestParam(value = "rate", defaultValue = "0.025") double rate
// => Optional param with default value
) {
double zakat = amount * rate;
return "Zakat: $" + zakat; // => Returns calculated value
}
@GetMapping("/zakat/{id}")
@ResponseBody
public String getById(@PathVariable("id") Long id) {
// => Captures path variable: /zakat/123
// => id = 123
return "Zakat record: " + id;
}
}Kotlin Implementation:
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.*
@Controller
class ZakatController {
@GetMapping("/zakat/calculate")
@ResponseBody
fun calculate(
@RequestParam("amount") amount: Double,
// => Captures query param: /zakat/calculate?amount=1000
@RequestParam(value = "rate", defaultValue = "0.025") rate: Double
// => Optional param with default value
): String {
val zakat = amount * rate
return "Zakat: $$zakat" // => Returns calculated value
}
@GetMapping("/zakat/{id}")
@ResponseBody
fun getById(@PathVariable("id") id: Long): String {
// => Captures path variable: /zakat/123
// => id = 123
return "Zakat record: $id"
}
}Spring MVC Request Lifecycle:
sequenceDiagram
participant Browser
participant DispatcherServlet
participant HandlerMapping
participant Controller
participant View
Browser->>DispatcherServlet: HTTP Request<br/>/zakat/calculate?amount=1000
DispatcherServlet->>HandlerMapping: Find handler for /zakat/calculate
HandlerMapping-->>DispatcherServlet: ZakatController.calculate()
DispatcherServlet->>Controller: Invoke calculate(1000)
Note over Controller: Extract @RequestParam<br/>Execute business logic
Controller-->>DispatcherServlet: Return "25.0" (@ResponseBody)
DispatcherServlet->>View: Serialize response
View-->>DispatcherServlet: JSON/String response
DispatcherServlet-->>Browser: HTTP Response<br/>Body: 25.0
style Browser fill:#0173B2,stroke:#000,color:#fff
style DispatcherServlet fill:#DE8F05,stroke:#000,color:#000
style HandlerMapping fill:#029E73,stroke:#000,color:#fff
style Controller fill:#CC78BC,stroke:#000,color:#000
style View fill:#CA9161,stroke:#000,color:#fff
Diagram Explanation: This sequence diagram shows Spring MVC’s request processing flow from browser through DispatcherServlet (front controller), handler mapping, controller execution, to response serialization.
Key Takeaways:
- @RequestParam for query parameters
- @PathVariable for URL path segments
- defaultValue for optional parameters
- Type conversion automatic
Related Documentation:
Example 48: @RequestBody and JSON (Coverage: 96.5%)
Demonstrates JSON request/response handling.
Java Implementation:
import org.springframework.web.bind.annotation.*;
class DonationRequest {
private String donor;
private double amount;
// Getters and setters
public String getDonor() { return donor; }
public void setDonor(String donor) { this.donor = donor; }
public double getAmount() { return amount; }
public void setAmount(double amount) { this.amount = amount; }
}
@RestController // => Combines @Controller + @ResponseBody
// => All methods return response body
@RequestMapping("/api")
public class DonationApiController {
@PostMapping("/donations")
public String create(@RequestBody DonationRequest request) {
// => @RequestBody deserializes JSON to object
// => Content-Type: application/json expected
String donor = request.getDonor(); // => Access deserialized data
double amount = request.getAmount();
return "Created: " + donor + " - $" + amount;
// => Serialized to JSON response
}
}Kotlin Implementation:
import org.springframework.web.bind.annotation.*
data class DonationRequest(
val donor: String,
val amount: Double
)
@RestController // => Combines @Controller + @ResponseBody
// => All methods return response body
@RequestMapping("/api")
class DonationApiController {
@PostMapping("/donations")
fun create(@RequestBody request: DonationRequest): String {
// => @RequestBody deserializes JSON to object
// => Content-Type: application/json expected
return "Created: ${request.donor} - $${request.amount}"
// => Serialized to JSON response
}
}@RequestBody JSON Deserialization Flow:
sequenceDiagram
participant Client
participant DispatcherServlet
participant Jackson as Jackson (JSON)
participant Controller
Client->>DispatcherServlet: POST /api/donations<br/>Content-Type: application/json<br/>{"donor":"Ali","amount":500}
DispatcherServlet->>Jackson: Deserialize JSON
Note over Jackson: Parse JSON string<br/>Map to DonationRequest fields
Jackson-->>DispatcherServlet: DonationRequest object
DispatcherServlet->>Controller: create(DonationRequest)
Note over Controller: request.donor = "Ali"<br/>request.amount = 500
Controller-->>DispatcherServlet: Return "Created"
DispatcherServlet-->>Client: HTTP 200<br/>Body: Created
style Client fill:#0173B2,stroke:#000,color:#fff
style DispatcherServlet fill:#DE8F05,stroke:#000,color:#000
style Jackson fill:#029E73,stroke:#000,color:#fff
style Controller fill:#CC78BC,stroke:#000,color:#000
Diagram Explanation: This sequence diagram shows how @RequestBody triggers Jackson to deserialize JSON request body into Java/Kotlin objects before controller method invocation.
Key Takeaways:
- @RestController for RESTful APIs
- @RequestBody deserializes JSON
- Automatic Jackson serialization
- POJOs/data classes for request/response
Related Documentation:
Example 49: Exception Handling with @ExceptionHandler (Coverage: 98.0%)
Demonstrates controller-level exception handling.
Java Implementation:
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
class InsufficientFundsException extends RuntimeException {
public InsufficientFundsException(String message) {
super(message);
}
}
@RestController
public class TransferController {
@PostMapping("/transfer")
public String transfer(
@RequestParam double amount
) {
if (amount > 1000) {
throw new InsufficientFundsException("Amount exceeds limit");
// => Exception thrown
}
return "Transfer successful";
}
@ExceptionHandler(InsufficientFundsException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
// => Returns HTTP 400 status
public String handleInsufficientFunds(InsufficientFundsException ex) {
// => Called when InsufficientFundsException thrown
return "Error: " + ex.getMessage();
// => Returns error message to client
}
}Kotlin Implementation:
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.*
class InsufficientFundsException(message: String) : RuntimeException(message)
@RestController
class TransferController {
@PostMapping("/transfer")
fun transfer(@RequestParam amount: Double): String {
if (amount > 1000) {
throw InsufficientFundsException("Amount exceeds limit")
// => Exception thrown
}
return "Transfer successful"
}
@ExceptionHandler(InsufficientFundsException::class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
// => Returns HTTP 400 status
fun handleInsufficientFunds(ex: InsufficientFundsException): String {
// => Called when InsufficientFundsException thrown
return "Error: ${ex.message}"
// => Returns error message to client
}
}Key Takeaways:
- @ExceptionHandler catches specific exceptions
- @ResponseStatus sets HTTP status code
- Controller-level exception handling
- Clean error response to clients
Related Documentation:
Example 50: Form Validation with @Valid (Coverage: 100.0%)
Demonstrates input validation with Bean Validation.
Java Implementation:
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.*;
class DonationForm {
@NotBlank(message = "Donor name required")
// => Validates non-empty string
private String donor;
@Min(value = 1, message = "Amount must be at least 1")
@Max(value = 10000, message = "Amount cannot exceed 10000")
// => Validates numeric range
private double amount;
// Getters and setters
public String getDonor() { return donor; }
public void setDonor(String donor) { this.donor = donor; }
public double getAmount() { return amount; }
public void setAmount(double amount) { this.amount = amount; }
}
@RestController
@Validated // => Enables validation
public class FormController {
@PostMapping("/submit")
public String submit(@Valid @RequestBody DonationForm form) {
// => @Valid triggers validation
// => Throws exception if validation fails
return "Accepted: " + form.getDonor();
// => Only reached if validation passes
}
}Kotlin Implementation:
import org.springframework.validation.annotation.Validated
import org.springframework.web.bind.annotation.*
import javax.validation.Valid
import javax.validation.constraints.*
data class DonationForm(
@field:NotBlank(message = "Donor name required")
// => Validates non-empty string
val donor: String,
@field:Min(value = 1, message = "Amount must be at least 1")
@field:Max(value = 10000, message = "Amount cannot exceed 10000")
// => Validates numeric range
val amount: Double
)
@RestController
@Validated // => Enables validation
class FormController {
@PostMapping("/submit")
fun submit(@Valid @RequestBody form: DonationForm): String {
// => @Valid triggers validation
// => Throws exception if validation fails
return "Accepted: ${form.donor}"
// => Only reached if validation passes
}
}Bean Validation Integration Flow:
graph TD
A[HTTP Request with JSON] --> B[@RequestBody + @Valid]
B --> C[Jackson deserializes JSON]
C --> D{Bean Validation}
D -->|All constraints pass| E[Method executes]
D -->|Constraint violation| F[MethodArgumentNotValidException]
E --> G[Return response]
F --> H[400 Bad Request with errors]
I[@NotBlank, @Min, @Max annotations] -.->|Define rules| D
style A fill:#0173B2,stroke:#000,color:#fff
style B fill:#DE8F05,stroke:#000,color:#000
style C fill:#029E73,stroke:#000,color:#fff
style D fill:#CC78BC,stroke:#000,color:#000
style E fill:#0173B2,stroke:#000,color:#fff
style F fill:#CA9161,stroke:#000,color:#fff
style G fill:#029E73,stroke:#000,color:#fff
style H fill:#DE8F05,stroke:#000,color:#000
Diagram Explanation: This flow diagram shows Bean Validation integration - deserializing JSON, validating against constraints, and either proceeding to method execution or throwing exception with validation errors.
Key Takeaways:
- @Valid triggers Bean Validation
- Validation annotations on fields (@NotBlank, @Min, etc.)
- Automatic validation before method execution
- MethodArgumentNotValidException on failure
Related Documentation:
Summary
This intermediate tutorial covered 25 Spring Framework examples (26-50) achieving 40-75% coverage:
Advanced DI (26-30):
- Multiple dependencies, optional dependencies, ApplicationContext injection, circular dependencies, generic types
AOP (31-35):
- @Before/@After/@AfterReturning, @Around, pointcut expressions, @AfterThrowing
Transactions (36-40):
- @Transactional, propagation, isolation, rollback rules, programmatic transactions
JdbcTemplate (41-45):
- Basic queries, RowMapper, named parameters, batch operations, ResultSetExtractor
Spring MVC (46-50):
- @Controller, request parameters, JSON handling, exception handling, validation
Next Steps: Progress to Advanced tutorial (75-95% coverage) for REST APIs, Security, caching, async processing, and testing strategies.