Spring Boot Custom Annotations: කෝඩ් Reuse කරමු, නියමයි නේ! | SC Guide

ආයුබෝවන් යාළුවනේ! කොහොමද ඉතින් ඔක්කොටම? ඔයාලා හැමෝම Spring Boot එක්ක වැඩ කරනවා ඇති, නැත්නම් ඉගෙන ගන්නවා ඇති කියලා හිතනවා. අද අපි කතා කරන්න යන්නේ Spring Boot project වල වැඩ කරන අයට මාර වැදගත් වෙන දෙයක් ගැන – ඒ තමයි Custom Annotations! මේක ඔයාලගේ කෝඩ් (code) එක clean කරගන්න, reusable components හදාගන්න, maintain කරන්න ලේසි කරගන්න නියම tool එකක්.
හිතලා බලන්නකෝ, ඔයා project එකක වැඩ කරනවා. හැම method එකකම මුලට logging ටිකක් දාන්න ඕනේ. ඊට පස්සේ security check එකක් කරන්න ඕනේ. ඊට පස්සේ transaction එකක් manage කරන්න ඕනේ. මේ හැමදේම හැම method එකකම ලියන්න ගියාම කෝඩ් එක ෆට්ට විදිහට වැඩි වෙනවා නේද? එකම දේට කෝඩ් ලිව්වම කම්මැලියි වගේම, එහෙම ලිව්වම ඒක maintain කරන්නත් මාර අමාරුයි. අන්න ඒ වගේ අවස්ථාවලට තමයි Custom Annotations අපිට ගොඩක් උදව් වෙන්නේ. මේවා හරියට magic වගේ, පට්ට වැඩක්!
අද අපි බලමු කොහොමද Custom Annotation එකක් හදාගෙන, ඒක Spring Boot project එකක AOP (Aspect-Oriented Programming) එක්ක implement කරලා, අපේ කෝඩ් එක කොච්චර ලස්සනට, clean විදිහට තියාගන්න පුළුවන්ද කියලා. අපි අද හදන්නේ සරල logging annotation එකක්. මේකෙන් ඔයාලට අදහසක් ගන්න පුළුවන්, මේක තව කොච්චර දේවල් වලට පාවිච්චි කරන්න පුලුවන්ද කියලා!
Custom Annotations කියන්නේ මොනවද?
මුලින්ම බලමු මේ annotations කියන්නේ මොනවද කියලා. අපි Spring Boot වල බහුලව පාවිච්චි කරන `@Service`, `@Controller`, `@Autowired`, `@Qualifier` වගේ ගොඩක් annotations දැකලා ඇති නේද? මේවා ඇත්තටම අපේ කෝඩ් එකට "metadata" එකතු කරනවා. ඒ කියන්නේ, මේ කෝඩ් එක මොකක්ද, කොහොමද හැසිරෙන්න ඕනේ කියන එක ගැන information එකක් මේ annotations හරහා Java compiler එකටයි, JVM (Java Virtual Machine) එකටයි, Spring Framework එකටයි ලබා දෙනවා.
උදාහරණයක් විදිහට, ඔයා class එකකට `@Service` කියලා දැම්මම, Spring Framework එකට තේරෙනවා මේක Business Logic තියෙන service component එකක් කියලා. ඊට පස්සේ Spring එක Autowiring කරන්න, Dependency Injection කරන්න මේ metadata එක පාවිච්චි කරනවා. නියමයි නේ?
Custom Annotations කියන්නේ අපිට ඕන විදිහට, අපේ project එකට විතරක් specific ව metadata define කරන්න පුළුවන් ක්රමයක්. ඒක හරියට ඔයාට ඕන විදිහට label එකක් හදාගන්නවා වගේ. මේ labels වලින් අපිට පුළුවන් එකම විදිහේ වැඩ කරන කෝඩ් කොටස් (cross-cutting concerns) වෙනම තියාගෙන, ඕන තැනක පාවිච්චි කරන්න. ඒකෙන්,
- Code Duplication අඩු වෙනවා (Reduced Code Duplication): එකම කෝඩ් එක තැනින් තැන ලියන එක නවතිනවා.
- Code Readability වැඩි වෙනවා (Improved Readability): කෝඩ් එක කියවන්න, තේරුම් ගන්න ලේසියි. Method එකක් උඩ annotation එකක් දැක්කම, ඒ method එකෙන් මොකක්ද වෙන්නේ කියලා ක්ෂණිකව තේරුම් ගන්න පුළුවන්.
- Maintainability වැඩි වෙනවා (Easier Maintenance): වෙනසක් කරන්න ඕන වුනොත් එක තැනකින් වෙනස් කරාම ඇති.
- Flexibility වැඩි වෙනවා (Increased Flexibility): විවිධ contexts වලට එකම functionality එක Apply කරන්න පුළුවන්.
මේවා තමයි Custom Annotations වල තියෙන ප්රධානම වාසි. දැන් අපි බලමු මේක Practical විදිහට කරන්නේ කොහොමද කියලා.
Custom Annotation එකක් හදමු!
අපි අද හදන්නේ `@LoggableMethod` කියලා Custom Annotation එකක්. මේකේ ප්රධාන අරමුණ තමයි, මේ Annotation එක apply කරපු ඕනම method එකක් run වෙනකොට ඒකේ log එකක් print කරන එක.
Custom Annotation එකක් හදද්දි අපිට අනිවාර්යයෙන්ම තේරුම් ගන්න ඕන concepts දෙකක් තියෙනවා:
@Target
: මේ Annotation එක අපිට කොහෙටද apply කරන්න පුලුවන් වෙන්නේ කියන එක මේකෙන් specify කරනවා. Class එකකටද, method එකකටද, field එකකටද, parameter එකකටද වගේ දේවල් මේකෙන් කියන්න පුළුවන්. මේකට අපිjava.lang.annotation.ElementType
enum එක පාවිච්චි කරනවා. අපේ logging annotation එක method වලට විතරක් apply කරන්න ඕන නිසා, අපිElementType.METHOD
කියලා දෙමු.@Retention
: මේ Annotation එක කොච්චර කල් retain (රැඳී) වෙනවද කියන එක මේකෙන් specify කරනවා.RetentionPolicy.SOURCE
(source code එකේ විතරයි),RetentionPolicy.CLASS
(compile කරාට පස්සේ .class file එකේ තියෙනවා, runtime එකේදී නෑ),RetentionPolicy.RUNTIME
(runtime එකේදී reflection හරහා access කරන්න පුළුවන්) කියන options තියෙනවා. අපිට AOP හරහා runtime එකේදී මේ Annotation එක detect කරගන්න ඕන නිසා, අපිRetentionPolicy.RUNTIME
කියලා දෙමු.
ඒ වගේම, Custom Annotation එකක් හදද්දී, ඒක සාමාන්ය Java interface
එකක් වගේ තමයි හැබැයි interface
කියන keyword එකට කලින් @
symbol එකක් දාන්න ඕනේ. ඒ වගේම අපිට Annotation එකට parameters (attributes) එකතු කරන්නත් පුළුවන්. ඒක හරියට public @interface MyAnnotation { String value() default ""; }
වගේ.
අපි දැන් අපේ @LoggableMethod
Annotation එක හදමු. මේක com.example.customannotation.annotation
කියන package එක ඇතුලේ හදමු.
package com.example.customannotation.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoggableMethod {
String value() default ""; // Optional: for custom log messages
}
දැක්කනේ, අපි මෙතන @Target(ElementType.METHOD)
කියලා දුන්නා. ඒ කියන්නේ මේ @LoggableMethod
Annotation එක අපිට Java methods වලට විතරයි apply කරන්න පුළුවන්. @Retention(RetentionPolicy.RUNTIME)
කියන්නේ Application එක run වෙන වෙලාවේදීත් මේ Annotation එක access කරන්න පුළුවන් කියන එක. ඒ වගේම, අපි String value() default "";
කියලා attribute එකක් දැම්මා. මේකෙන් පුළුවන් අපිට මේ Annotation එක පාවිච්චි කරන වෙලාවේදී custom message එකක් දෙන්න. උදාහරණයක් විදිහට: @LoggableMethod("User data processing")
වගේ.
Custom Annotation එක Implement කරමු! (AOP)
හරි, දැන් අපි Annotation එක හැදුවා. හැබැයි මේක තනියම කිසිම වැඩක් කරන්නේ නෑ. අපිට ඕනේ මේ Annotation එක කොහෙට හරි දැම්මම, ඒක detect කරලා, අපිට ඕන වැඩේ (මේ අවස්ථාවේ logging) ස්වයංක්රීයව කරන්න. අන්න ඒකට තමයි Aspect-Oriented Programming (AOP) කියන concept එක Spring Boot වලදී පාවිච්චි වෙන්නේ.
AOP කෙටියෙන්
AOP කියන්නේ programming paradigm එකක්. ඒකෙන් පුළුවන් අපේ application එකේ විවිධ තැන්වලට බලපාන "cross-cutting concerns" (logging, security, transaction management, caching වගේ දේවල්) වෙනම මොඩියුලයක් විදිහට තියාගෙන, ඒවාට අදාළ කෝඩ් එක (aspects) අපේ core business logic එකෙන් වෙන් කරලා ලියන්න. මේකෙන් කෝඩ් එක clean වෙනවා වගේම, maintain කරන්නත් ලේසියි.
AOP වල ප්රධාන concepts කීපයක් තියෙනවා:
- Aspect: Cross-cutting concern එකක් implement කරන module එක. අපේ අවස්ථාවේදී logging aspect එක.
- Join Point: Application එකේ execution path එකේදී AOP logic එක inject කරන්න පුළුවන් තැනක්. (Method execution, exception handling වගේ).
- Pointcut: Join Point එකක් තෝරා ගන්න ක්රමවේදය. අපේ අවස්ථාවේදී
@LoggableMethod
Annotation එක තියෙන methods. - Advice: Pointcut එකක් match වුනාම run වෙන කෝඩ් එක. (
@Before
,@After
,@Around
වගේ) - Weaving: Aspect එකක් target object එකට inject කරන ක්රියාවලිය. Spring AOP වලදී runtime එකේදී Proxy objects හරහා තමයි මේක වෙන්නේ.
අපි දැන් අපේ @LoggableMethod
Annotation එක detect කරලා logging කරන Aspect එක හදමු. මේකට අපි @Aspect
Annotation එකයි @Component
Annotation එකයි පාවිච්චි කරනවා. @Component
දැම්මම Spring Container එකට මේක bean එකක් විදිහට load වෙනවා.
අපි com.example.customannotation.aspect
කියන package එක ඇතුලේ LoggableAspect
කියලා class එකක් හදමු.
package com.example.customannotation.aspect;
import com.example.customannotation.annotation.LoggableMethod;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Aspect
@Component
public class LoggableAspect {
private static final Logger logger = LoggerFactory.getLogger(LoggableAspect.class);
@Around("@annotation(com.example.customannotation.annotation.LoggableMethod)")
public Object logMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable {
// Method signature extract කරගැනීම
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String className = methodSignature.getDeclaringType().getSimpleName();
String methodName = methodSignature.getName();
Object[] args = joinPoint.getArgs();
// Custom annotation එකේ value එක extract කරගැනීම
LoggableMethod loggableAnnotation = methodSignature.getMethod().getAnnotation(LoggableMethod.class);
String customMessage = loggableAnnotation.value();
String logPrefix = "";
if (!customMessage.isEmpty()) {
logPrefix = "[" + customMessage + "] "; // Custom message එකක් තියෙනවා නම් prefix එකට එකතු කිරීම
}
// Method එක execute වෙන්න කලින් log කිරීම
logger.info("{}Starting method: {}.{} with arguments: {}", logPrefix, className, methodName, Arrays.toString(args));
long startTime = System.currentTimeMillis();
Object result = null;
try {
result = joinPoint.proceed(); // Actual method එක execute කිරීම
} finally {
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
// Method එක execute වුනාට පස්සේ log කිරීම
logger.info("{}Finished method: {}.{} in {}ms. Return value: {}", logPrefix, className, methodName, duration, result);
}
return result;
}
}
මේ කෝඩ් එක හොඳට බලන්න.
@Aspect
Annotation එකෙන් මේ class එක Aspect එකක් කියලා Spring එකට කියනවා.@Component
Annotation එකෙන් මේක Spring Bean එකක් විදිහට Register කරනවා.logger
object එක අපි SLF4J (Simple Logging Facade for Java) පාවිච්චි කරලා හදාගත්තා.@Around("@annotation(com.example.customannotation.annotation.LoggableMethod)")
මේක තමයි ප්රධානම කොටස. මේක Pointcut expression එකක්. මේකෙන් කියන්නේcom.example.customannotation.annotation.LoggableMethod
කියන Annotation එක දාලා තියෙන ඕනම method එකක් run වෙනකොට, ඒ method එක වටේට (@Around
) මේlogMethodExecution
method එක execute වෙන්න ඕනේ කියලා.ProceedingJoinPoint joinPoint
object එකෙන් අපිට පුළුවන් actual method එකේ details (method name, arguments, class name) ගන්න වගේම,joinPoint.proceed()
කියලා call කරලා original method එක execute කරන්නත් පුළුවන්.- අපි
loggableAnnotation.value()
කියලා ගත්තේ, අපේ Annotation එකට දීපු custom message එක (උදා: "User data processing"). - අපි
try-finally
block එකක් පාවිච්චි කරලා, method එක execute වෙන්න ගත්ත වෙලාවත් calculate කරලා log කරනවා.
මේ ටිකෙන් අපේ Custom Annotation එකේ implementation එක complete වෙනවා.
අවශ්ය Dependency එක
මේ AOP වැඩේට අපිට Spring Boot project එකට spring-boot-starter-aop
dependency එක එකතු කරගන්න වෙනවා. ඔයාගේ pom.xml
file එකට මේක එකතු කරගන්න:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
ඒ වගේම, අපේ Spring Boot Application class එකේ @EnableAspectJAutoProxy
Annotation එකත් දාන්න ඕන AOP proxying enable කරන්න.
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@SpringBootApplication
@EnableAspectJAutoProxy // AOP enable කිරීමට
public class CustomAnnotationApplication { /* ... */ }
Custom Annotation එක පාවිච්චි කරමු!
දැන් අපි අපේ Custom Annotation එක හදලා, ඒක implement කරන්නත් Aspect එක හැදුවා. දැන් බලමු මේක කොහොමද පාවිච්චි කරන්නේ කියලා. අපි සරල Spring @Service
එකක් හදමු, ඒකේ methods කීපයකට අපේ @LoggableMethod
Annotation එක apply කරමු.
com.example.customannotation.service
කියන package එක ඇතුලේ MyService
කියලා class එකක් හදමු.
package com.example.customannotation.service;
import com.example.customannotation.annotation.LoggableMethod;
import org.springframework.stereotype.Service;
@Service
public class MyService {
@LoggableMethod("User data processing")
public String processUserData(String userId, int dataId) {
// Simulate some processing time
try {
Thread.sleep(500); // තත්පර 0.5ක් simulate කරනවා
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "Processed data for user: " + userId + ", dataId: " + dataId;
}
@LoggableMethod
public int calculateSum(int a, int b) {
return a + b;
}
public String greetUser(String name) {
return "Hello, " + name + "!";
}
}
මෙතන processUserData
method එකටයි, calculateSum
method එකටයි අපි @LoggableMethod
Annotation එක දැම්මා. processUserData
එකට අපි custom message එකක් දුන්නා, calculateSum
එකට දුන්නේ නෑ. greetUser
method එකට අපි කිසිම Annotation එකක් දැම්මේ නෑ, ඒකේ logging වෙන්නේ නෑ කියලා පෙන්නන්න.
දැන් අපි අපේ Spring Boot Application එක start වෙනකොටම මේ service methods ටික call වෙන්න සලස්වමු. ඒකට අපේ main application class එකට CommandLineRunner
implement කරමු.
package com.example.customannotation;
import com.example.customannotation.service.MyService;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@SpringBootApplication
@EnableAspectJAutoProxy // AOP enable කිරීමට අනිවාර්යයි
public class CustomAnnotationApplication implements CommandLineRunner {
private final MyService myService;
public CustomAnnotationApplication(MyService myService) {
this.myService = myService;
}
public static void main(String[] args) {
SpringApplication.run(CustomAnnotationApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
System.out.println("\n--- Calling Logged Methods ---");
myService.processUserData("john_doe", 123); // Custom annotation with value
myService.calculateSum(10, 20); // Custom annotation without value
System.out.println("--- Calling Non-Logged Method ---");
myService.greetUser("Amal"); // No custom annotation
}
}
දැන් ඔයාලට පුළුවන් මේ Project එක run කරලා බලන්න. mvn spring-boot:run
command එකෙන් නැත්නම් ඔයාගේ IDE එකෙන් run කරන්න.
ඔයාලගේ console එකේ මේ වගේ output එකක් පෙනෙයි (log messages වල timestamp සහ අනෙකුත් details වෙනස් වෙන්න පුළුවන්):
--- Calling Logged Methods ---
INFO [com.example.customannotation.aspect.LoggableAspect] [User data processing] Starting method: MyService.processUserData with arguments: [john_doe, 123]
INFO [com.example.customannotation.aspect.LoggableAspect] [User data processing] Finished method: MyService.processUserData in 5XXms. Return value: Processed data for user: john_doe, dataId: 123
INFO [com.example.customannotation.aspect.LoggableAspect] Starting method: MyService.calculateSum with arguments: [10, 20]
INFO [com.example.customannotation.aspect.LoggableAspect] Finished method: MyService.calculateSum in Xms. Return value: 30
--- Calling Non-Logged Method ---
දැක්කනේ! processUserData
සහ calculateSum
කියන methods call වෙනකොට ස්වයංක්රීයව log messages print වුනා. ඒත් greetUser
method එක call වෙනකොට කිසිම log එකක් ආවේ නෑ. මේක තමයි Custom Annotation එකක බලය. අපිට අවශ්ය තැනට අවශ්ය functionality එක එකතු කරන්න පුළුවන්.
නිගමනය
ඉතින් මොකද කියන්නේ යාළුවනේ? දැක්කනේ Custom Annotations කොච්චර ප්රයෝජනවත්ද කියලා. මේකෙන් අපිට පුළුවන් code duplication නැති කරලා, අපේ code base එක මාර clean විදිහට තියාගන්න. ඒ වගේම, future එකේදී මේ functionality එකේ වෙනසක් කරන්න ඕන වුනොත්, අපිට පුළුවන් Aspect එකේ විතරක් වෙනසක් කරලා, ඒක apply කරපු හැම තැනටම ඒ වෙනස බලපාන්න සලස්වන්න. මේක ඇත්තටම scalable applications හදනකොට ෆට්ට වැදගත් දෙයක්.
අපි අද කතා කලේ logging ගැන උනාට, මේ concept එක ඔයාලට authentication, authorization, caching, transaction management, rate limiting වගේ ගොඩක් දේවල් වලට පාවිච්චි කරන්න පුළුවන්. ඒක ඔයාලගේ creativity එකට බාරයි. ට්රයි කරලා බලන්න!
මේ ලිපිය ගැන මොනවා හරි ප්රශ්න තියෙනවා නම්, තේරෙන්නේ නැති තැන් තියෙනවා නම්, නැත්නම් ඔයාලට අලුත් අදහසක් ආවා නම්, අනිවාර්යයෙන්ම පහළ තියෙන comment section එකේ ඔයාලගේ අදහස්, ප්රශ්න දාන්න. අපි ඒවට උත්තර දෙන්න බලාපොරොත්තු වෙනවා.
ඊළඟ ලිපියකින් මේ වගේම වැදගත් දෙයක් ගැන කතා කරන්න බලාපොරොත්තුවෙන්, සුභ දවසක්!