Table of Contents
- Introduction to Annotations
- Built-in Annotations
- Custom Annotations
- Annotation Retention
- Annotation Targets
- Annotation Parameters
- Repeatable Annotations
- Annotation Processing
- Best Practices
- Complete Examples
Introduction to Annotations
Annotations are metadata that provide data about a program but are not part of the program itself. They have no direct effect on the operation of the code they annotate.
Basic Syntax:
@AnnotationName public class MyClass { @AnnotationName private String field; @AnnotationName public void method() { // method body } } Simple Example:
// Using built-in annotation @Deprecated public class OldClass { @SuppressWarnings("unchecked") public void oldMethod() { List list = new ArrayList(); // Raw type - generates warning list.add("test"); } } public class AnnotationDemo { public static void main(String[] args) { OldClass old = new OldClass(); old.oldMethod(); } } Built-in Annotations
1. @Override:
public class OverrideExample { static class Animal { public void makeSound() { System.out.println("Animal makes a sound"); } public void eat() { System.out.println("Animal is eating"); } } static class Dog extends Animal { // ✅ Correct - overrides parent method @Override public void makeSound() { System.out.println("Dog barks"); } // ❌ Compile-time error - method doesn't exist in parent // @Override // public void makeNoise() { // This would cause error // System.out.println("Dog makes noise"); // } // No @Override - but if parent method changes, no error public void eat() { System.out.println("Dog is eating"); } } public static void main(String[] args) { Dog dog = new Dog(); dog.makeSound(); // Dog barks dog.eat(); // Dog is eating } } 2. @Deprecated:
public class DeprecatedExample { /** * Old way of greeting - use {@link #newGreet(String)} instead * @deprecated This method is deprecated because it uses old formatting */ @Deprecated(since = "2.0", forRemoval = true) public static void oldGreet(String name) { System.out.println("Hello, " + name + "!"); } /** * New way of greeting with better formatting */ public static void newGreet(String name) { System.out.println("🌟 Hello, " + name + "! 🌟"); } public static void main(String[] args) { // This will show warning in IDE oldGreet("Alice"); // Deprecated method call // Recommended way newGreet("Bob"); // Using reflection to check annotation try { Method oldMethod = DeprecatedExample.class.getMethod("oldGreet", String.class); Deprecated deprecated = oldMethod.getAnnotation(Deprecated.class); if (deprecated != null) { System.out.println("oldGreet is deprecated since: " + deprecated.since()); System.out.println("Scheduled for removal: " + deprecated.forRemoval()); } } catch (NoSuchMethodException e) { e.printStackTrace(); } } } 3. @SuppressWarnings:
import java.util.*; public class SuppressWarningsExample { // Suppress unchecked warnings for this entire class @SuppressWarnings("unchecked") public void processRawList() { List rawList = new ArrayList(); // Raw type usage rawList.add("String"); rawList.add(123); // Mixing types String str = (String) rawList.get(0); // Unchecked cast } public void methodWithMultipleWarnings() { // Suppress multiple warnings @SuppressWarnings({"rawtypes", "unchecked"}) List list = new ArrayList(); list.add("test"); // Suppress deprecation warning @SuppressWarnings("deprecation") Date date = new Date(2020, 1, 1); // Deprecated constructor } // Common warning types: // - "unchecked" - for generic type safety // - "deprecation" - for use of deprecated items // - "rawtypes" - for use of raw types // - "unused" - for unused variables // - "all" - for all warnings public static void main(String[] args) { SuppressWarningsExample example = new SuppressWarningsExample(); example.processRawList(); example.methodWithMultipleWarnings(); } } 4. @FunctionalInterface:
public class FunctionalInterfaceExample { // Valid functional interface - has exactly one abstract method @FunctionalInterface interface Calculator { int calculate(int a, int b); // Can have default methods default void printResult(int result) { System.out.println("Result: " + result); } // Can have static methods static String getOperationName() { return "Calculation"; } } // ❌ Invalid - will cause compile error // @FunctionalInterface // interface InvalidFunctional { // void method1(); // void method2(); // More than one abstract method // } public static void main(String[] args) { // Using lambda with functional interface Calculator adder = (a, b) -> a + b; Calculator multiplier = (a, b) -> a * b; System.out.println("Addition: " + adder.calculate(5, 3)); System.out.println("Multiplication: " + multiplier.calculate(5, 3)); adder.printResult(adder.calculate(10, 20)); System.out.println("Operation: " + Calculator.getOperationName()); } } Custom Annotations
Creating Custom Annotations:
import java.lang.annotation.*; import java.lang.reflect.*; // Marker annotation (no parameters) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @interface Development { // No elements - marker annotation } // Single-value annotation @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.TYPE}) @interface Author { String value(); // Special case - can use without name when only one element } // Multi-value annotation @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @interface TestCase { int id(); String description(); String[] tags() default {}; // Optional element with default value boolean enabled() default true; } // Using the custom annotations @Development @Author("John Doe") class PaymentProcessor { @TestCase( id = 1, description = "Test successful payment", tags = {"payment", "success"}, enabled = true ) public void processPayment(double amount) { System.out.println("Processing payment: $" + amount); } @TestCase( id = 2, description = "Test failed payment", tags = {"payment", "failure"}, enabled = false // This test is disabled ) public void processFailedPayment(double amount) { System.out.println("Processing failed payment: $" + amount); } @Author("Jane Smith") public void refundPayment(double amount) { System.out.println("Refunding payment: $" + amount); } } public class CustomAnnotationDemo { public static void main(String[] args) { // Process annotations using reflection Class<PaymentProcessor> processorClass = PaymentProcessor.class; // Check class-level annotations if (processorClass.isAnnotationPresent(Development.class)) { System.out.println("PaymentProcessor is in development"); } Author classAuthor = processorClass.getAnnotation(Author.class); if (classAuthor != null) { System.out.println("Class author: " + classAuthor.value()); } // Process method annotations for (Method method : processorClass.getDeclaredMethods()) { System.out.println("\nMethod: " + method.getName()); // Check TestCase annotation TestCase testCase = method.getAnnotation(TestCase.class); if (testCase != null) { System.out.println(" Test ID: " + testCase.id()); System.out.println(" Description: " + testCase.description()); System.out.println(" Enabled: " + testCase.enabled()); System.out.println(" Tags: " + String.join(", ", testCase.tags())); } // Check Author annotation Author author = method.getAnnotation(Author.class); if (author != null) { System.out.println(" Author: " + author.value()); } } } } Output:
PaymentProcessor is in development Class author: John Doe Method: processPayment Test ID: 1 Description: Test successful payment Enabled: true Tags: payment, success Method: processFailedPayment Test ID: 2 Description: Test failed payment Enabled: false Tags: payment, failure Method: refundPayment Author: Jane Smith
Annotation Retention
Retention Policies:
import java.lang.annotation.*; // SOURCE - discarded by compiler, not available at runtime @Retention(RetentionPolicy.SOURCE) @interface SourceLevelAnnotation { String value(); } // CLASS - stored in .class file but not available at runtime (default) @Retention(RetentionPolicy.CLASS) @interface ClassLevelAnnotation { String value(); } // RUNTIME - stored in .class file and available at runtime @Retention(RetentionPolicy.RUNTIME) @interface RuntimeLevelAnnotation { String value(); } @SourceLevelAnnotation("This won't be available at runtime") @ClassLevelAnnotation("This might be available depending on JVM") @RuntimeLevelAnnotation("This will be available at runtime") class RetentionDemo { @SourceLevelAnnotation("source method") @ClassLevelAnnotation("class method") @RuntimeLevelAnnotation("runtime method") public void demoMethod() { System.out.println("Method demonstration"); } } public class RetentionExample { public static void main(String[] args) { Class<RetentionDemo> demoClass = RetentionDemo.class; // Check runtime annotations System.out.println("=== Runtime Annotations ==="); RuntimeLevelAnnotation runtime = demoClass.getAnnotation(RuntimeLevelAnnotation.class); if (runtime != null) { System.out.println("Class Runtime: " + runtime.value()); } // Check class-level annotations (may not be available) System.out.println("\n=== Class-level Annotations ==="); ClassLevelAnnotation classLevel = demoClass.getAnnotation(ClassLevelAnnotation.class); if (classLevel != null) { System.out.println("Class Class-level: " + classLevel.value()); } else { System.out.println("Class-level annotation not available at runtime"); } // Check source-level annotations (won't be available) System.out.println("\n=== Source-level Annotations ==="); SourceLevelAnnotation source = demoClass.getAnnotation(SourceLevelAnnotation.class); if (source != null) { System.out.println("Class Source: " + source.value()); } else { System.out.println("Source-level annotation not available at runtime"); } // Check method annotations try { Method method = demoClass.getMethod("demoMethod"); System.out.println("\n=== Method Annotations ==="); RuntimeLevelAnnotation methodRuntime = method.getAnnotation(RuntimeLevelAnnotation.class); if (methodRuntime != null) { System.out.println("Method Runtime: " + methodRuntime.value()); } } catch (NoSuchMethodException e) { e.printStackTrace(); } } } Output:
=== Runtime Annotations === Class Runtime: This will be available at runtime === Class-level Annotations === Class-level annotation not available at runtime === Source-level Annotations === Source-level annotation not available at runtime === Method Annotations === Method Runtime: runtime method
Annotation Targets
Target Elements:
import java.lang.annotation.*; // Annotation that can be applied to different elements @Target({ ElementType.TYPE, // Class, interface, enum ElementType.FIELD, // Field (including enum constants) ElementType.METHOD, // Method ElementType.PARAMETER, // Method parameter ElementType.CONSTRUCTOR, // Constructor ElementType.LOCAL_VARIABLE, // Local variable ElementType.ANNOTATION_TYPE,// Annotation type ElementType.PACKAGE, // Package ElementType.TYPE_PARAMETER, // Type parameter (Java 8+) ElementType.TYPE_USE // Type use (Java 8+) }) @Retention(RetentionPolicy.RUNTIME) @interface MultiTarget { String value(); } // Specific target examples @Target(ElementType.TYPE) @interface ClassInfo { String author(); String version(); } @Target(ElementType.METHOD) @interface Operation { String name(); boolean transactional() default true; } @Target(ElementType.FIELD) @interface Column { String name(); boolean nullable() default true; } @Target(ElementType.PARAMETER) @interface NotNull { String message() default "Parameter cannot be null"; } @Target(ElementType.TYPE_USE) @interface NonNull { // For type checking } // Using the targeted annotations @ClassInfo(author = "Alice", version = "1.0") class UserService { @Column(name = "user_id", nullable = false) private Long userId; @Column(name = "username") private String username; public UserService(@NotNull String initialUser) { this.username = initialUser; } @Operation(name = "createUser", transactional = true) public void createUser(@NotNull String username, @NotNull String email) { this.username = username; System.out.println("Creating user: " + username); } @Operation(name = "deleteUser") public void deleteUser(Long userId) { System.out.println("Deleting user: " + userId); } // Using TYPE_USE annotation public @NonNull String getUserName() { return username; } public void setUserName(@NonNull String username) { this.username = username; } } public class TargetExample { public static void main(String[] args) throws NoSuchMethodException { Class<UserService> serviceClass = UserService.class; // Class-level annotation ClassInfo classInfo = serviceClass.getAnnotation(ClassInfo.class); if (classInfo != null) { System.out.println("Class Author: " + classInfo.author()); System.out.println("Class Version: " + classInfo.version()); } // Method annotations Method createUserMethod = serviceClass.getMethod("createUser", String.class, String.class); Operation operation = createUserMethod.getAnnotation(Operation.class); if (operation != null) { System.out.println("\nMethod Operation: " + operation.name()); System.out.println("Transactional: " + operation.transactional()); } // Parameter annotations Annotation[][] paramAnnotations = createUserMethod.getParameterAnnotations(); for (int i = 0; i < paramAnnotations.length; i++) { for (Annotation annotation : paramAnnotations[i]) { if (annotation instanceof NotNull) { System.out.println("Parameter " + i + " has @NotNull: " + ((NotNull) annotation).message()); } } } // Field annotations for (java.lang.reflect.Field field : serviceClass.getDeclaredFields()) { Column column = field.getAnnotation(Column.class); if (column != null) { System.out.println("\nField: " + field.getName()); System.out.println("Column: " + column.name()); System.out.println("Nullable: " + column.nullable()); } } } } Output:
Class Author: Alice Class Version: 1.0 Method Operation: createUser Transactional: true Parameter 0 has @NotNull: Parameter cannot be null Parameter 1 has @NotNull: Parameter cannot be null Field: userId Column: user_id Nullable: false Field: username Column: username Nullable: true
Annotation Parameters
Parameter Types and Defaults:
import java.lang.annotation.*; import java.util.*; // Annotation with various parameter types @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @interface TestConfig { // Primitive types int timeout() default 30; // seconds boolean enabled() default true; // String String name(); String description() default ""; // Class Class<?> expectedException() default None.class; // Enum Priority priority() default Priority.MEDIUM; // Array String[] tags() default {}; // Annotation Author author() default @Author("Unknown"); // Special marker for no exception class None extends Throwable { private None() {} } } // Enum for priority enum Priority { LOW, MEDIUM, HIGH, CRITICAL } // Nested annotation @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @interface Author { String value(); String email() default ""; } // Using the annotation with different parameter combinations class TestConfigurationExample { @TestConfig( name = "userCreationTest", timeout = 60, priority = Priority.HIGH, tags = {"user", "creation", "integration"}, author = @Author(value = "Bob", email = "[email protected]") ) public void testUserCreation() { System.out.println("Testing user creation..."); } @TestConfig( name = "quickTest", description = "A quick sanity check", expectedException = IllegalArgumentException.class ) public void testWithException() { System.out.println("Testing exception case..."); throw new IllegalArgumentException("Expected exception"); } @TestConfig( name = "disabledTest", enabled = false ) public void disabledTest() { System.out.println("This test is disabled"); } // Using default values for everything @TestConfig(name = "defaultTest") public void defaultTest() { System.out.println("Test with default values"); } } public class ParameterExample { public static void main(String[] args) throws NoSuchMethodException { Class<TestConfigurationExample> testClass = TestConfigurationExample.class; // Process each method with @TestConfig annotation for (Method method : testClass.getDeclaredMethods()) { TestConfig config = method.getAnnotation(TestConfig.class); if (config != null) { System.out.println("\n=== " + method.getName() + " ==="); System.out.println("Name: " + config.name()); System.out.println("Description: " + config.description()); System.out.println("Timeout: " + config.timeout() + "s"); System.out.println("Enabled: " + config.enabled()); System.out.println("Priority: " + config.priority()); System.out.println("Expected Exception: " + config.expectedException().getSimpleName()); System.out.println("Tags: " + Arrays.toString(config.tags())); Author author = config.author(); System.out.println("Author: " + author.value() + (author.email().isEmpty() ? "" : " (" + author.email() + ")")); // Simulate test execution based on configuration if (config.enabled()) { System.out.println("✅ Test would be executed"); } else { System.out.println("❌ Test is disabled"); } } } } } Output:
=== testUserCreation === Name: userCreationTest Description: Timeout: 60s Enabled: true Priority: HIGH Expected Exception: None Tags: [user, creation, integration] Author: Bob ([email protected]) ✅ Test would be executed === testWithException === Name: quickTest Description: A quick sanity check Timeout: 30s Enabled: true Priority: MEDIUM Expected Exception: IllegalArgumentException Tags: [] Author: Unknown ✅ Test would be executed === disabledTest === Name: disabledTest Description: Timeout: 30s Enabled: false Priority: MEDIUM Expected Exception: None Tags: [] Author: Unknown ❌ Test is disabled === defaultTest === Name: defaultTest Description: Timeout: 30s Enabled: true Priority: MEDIUM Expected Exception: None Tags: [] Author: Unknown ✅ Test would be executed
Repeatable Annotations
Java 8+ Repeatable Annotations:
import java.lang.annotation.*; import java.util.*; // Container annotation @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @interface Roles { Role[] value(); } // Repeatable annotation @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Repeatable(Roles.class) @interface Role { String name(); String[] permissions() default {}; } // Pre-Java 8 way (without @Repeatable) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @interface OldRoles { String[] value(); } // Using repeatable annotations (Java 8+) @Role(name = "ADMIN", permissions = {"READ", "WRITE", "DELETE"}) @Role(name = "USER", permissions = {"READ"}) @Role(name = "GUEST", permissions = {"READ_PUBLIC"}) class SecureService { public void adminOperation() { System.out.println("Admin operation"); } public void userOperation() { System.out.println("User operation"); } } // Old way (pre-Java 8) @OldRoles({"ADMIN", "USER", "GUEST"}) class OldSecureService { // ... } public class RepeatableExample { public static void main(String[] args) { Class<SecureService> serviceClass = SecureService.class; // Get roles using container annotation Roles rolesContainer = serviceClass.getAnnotation(Roles.class); if (rolesContainer != null) { System.out.println("=== Roles using Container ==="); for (Role role : rolesContainer.value()) { printRole(role); } } // Get roles directly (Java 8+) System.out.println("\n=== Roles Directly ==="); Role[] rolesDirect = serviceClass.getAnnotationsByType(Role.class); for (Role role : rolesDirect) { printRole(role); } // Demonstrate the difference System.out.println("\n=== Annotation Details ==="); Annotation[] annotations = serviceClass.getAnnotations(); for (Annotation annotation : annotations) { System.out.println("Annotation: " + annotation.annotationType().getSimpleName()); if (annotation instanceof Roles) { System.out.println(" - This is the container annotation"); } else if (annotation instanceof Role) { System.out.println(" - This is a repeatable annotation"); } } } private static void printRole(Role role) { System.out.println("Role: " + role.name()); System.out.println(" Permissions: " + Arrays.toString(role.permissions())); } } Output:
=== Roles using Container === Role: ADMIN Permissions: [READ, WRITE, DELETE] Role: USER Permissions: [READ] Role: GUEST Permissions: [READ_PUBLIC] === Roles Directly === Role: ADMIN Permissions: [READ, WRITE, DELETE] Role: USER Permissions: [READ] Role: GUEST Permissions: [READ_PUBLIC] === Annotation Details === Annotation: Roles - This is the container annotation
Annotation Processing
Runtime Annotation Processing:
import java.lang.annotation.*; import java.lang.reflect.*; import java.util.*; // Custom annotations for validation @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @interface NotNull { String message() default "Field cannot be null"; } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @interface Size { int min() default 0; int max() default Integer.MAX_VALUE; String message() default "Size constraint violated"; } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @interface Email { String message() default "Invalid email format"; } // Validation processor class Validator { public static List<String> validate(Object obj) { List<String> errors = new ArrayList<>(); Class<?> clazz = obj.getClass(); for (Field field : clazz.getDeclaredFields()) { field.setAccessible(true); try { Object value = field.get(obj); // Check @NotNull if (field.isAnnotationPresent(NotNull.class)) { NotNull notNull = field.getAnnotation(NotNull.class); if (value == null) { errors.add(field.getName() + ": " + notNull.message()); } } // Check @Size if (field.isAnnotationPresent(Size.class) && value != null) { Size size = field.getAnnotation(Size.class); int length = value.toString().length(); if (length < size.min() || length > size.max()) { errors.add(field.getName() + ": " + size.message() + " (min: " + size.min() + ", max: " + size.max() + ", actual: " + length + ")"); } } // Check @Email if (field.isAnnotationPresent(Email.class) && value != null) { String email = value.toString(); if (!isValidEmail(email)) { Email emailAnnotation = field.getAnnotation(Email.class); errors.add(field.getName() + ": " + emailAnnotation.message() + " (" + email + ")"); } } } catch (IllegalAccessException e) { errors.add("Cannot access field: " + field.getName()); } } return errors; } private static boolean isValidEmail(String email) { return email != null && email.matches("^[A-Za-z0-9+_.-]+@(.+)$"); } } // Class to validate class User { @NotNull(message = "Username is required") @Size(min = 3, max = 20, message = "Username must be 3-20 characters") private String username; @NotNull @Email private String email; @Size(min = 8, message = "Password must be at least 8 characters") private String password; private String optionalField; public User(String username, String email, String password) { this.username = username; this.email = email; this.password = password; } // Getters and setters public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } public class AnnotationProcessingExample { public static void main(String[] args) { // Test cases User[] testUsers = { new User("jo", "invalid-email", "short"), // Multiple errors new User(null, "[email protected]", "validpassword123"), // Null username new User("validuser", "invalid", "validpassword123"), // Invalid email new User("validuser", "[email protected]", "validpassword123") // Valid }; for (int i = 0; i < testUsers.length; i++) { System.out.println("\n=== Test User " + (i + 1) + " ==="); System.out.println("Username: " + testUsers[i].getUsername()); System.out.println("Email: " + testUsers[i].getEmail()); System.out.println("Password: " + testUsers[i].getPassword()); List<String> errors = Validator.validate(testUsers[i]); if (errors.isEmpty()) { System.out.println("✅ Validation passed!"); } else { System.out.println("❌ Validation errors:"); errors.forEach(error -> System.out.println(" - " + error)); } } // Demonstrate reflection with annotations System.out.println("\n=== Reflection Analysis ==="); Class<User> userClass = User.class; for (Field field : userClass.getDeclaredFields()) { System.out.println("\nField: " + field.getName()); System.out.println(" Type: " + field.getType().getSimpleName()); Annotation[] annotations = field.getAnnotations(); for (Annotation annotation : annotations) { System.out.println(" Annotation: @" + annotation.annotationType().getSimpleName()); } } } } Output:
=== Test User 1 === Username: jo Email: invalid-email Password: short ❌ Validation errors: - username: Username must be 3-20 characters (min: 3, max: 20, actual: 2) - email: Invalid email format (invalid-email) - password: Size constraint violated (min: 8, max: 2147483647, actual: 5) === Test User 2 === Username: null Email: [email protected] Password: validpassword123 ❌ Validation errors: - username: Username is required === Test User 3 === Username: validuser Email: invalid Password: validpassword123 ❌ Validation errors: - email: Invalid email format (invalid) === Test User 4 === Username: validuser Email: [email protected] Password: validpassword123 ✅ Validation passed! === Reflection Analysis === Field: username Type: String Annotation: @NotNull Annotation: @Size Field: email Type: String Annotation: @NotNull Annotation: @Email Field: password Type: String Annotation: @Size Field: optionalField Type: String
Best Practices
1. Use Meaningful Names:
// ✅ Good - clear and descriptive @interface CacheConfig { int duration(); TimeUnit unit(); boolean refreshable(); } // ❌ Avoid - vague names @interface Config { int value(); String data(); } 2. Provide Sensible Defaults:
// ✅ Good - sensible defaults @interface ApiEndpoint { String path(); String method() default "GET"; boolean secure() default true; int timeout() default 30; } // ❌ Avoid - no defaults when they make sense @interface BadEndpoint { String path(); String method(); // Should have default boolean secure(); // Should have default } 3. Use Enums for Fixed Values:
// ✅ Good - using enums enum HttpMethod { GET, POST, PUT, DELETE, PATCH } @interface RequestMapping { String path(); HttpMethod method() default HttpMethod.GET; } // ❌ Avoid - using strings for fixed values @interface BadMapping { String path(); String method(); // Could be any string } 4. Document Your Annotations:
/** * Marks a method as an API endpoint that can be called remotely * * @author Developer * @version 1.0 * @since 2024 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @interface ApiMethod { /** * The HTTP path for this endpoint * @return the endpoint path */ String value(); /** * HTTP method to use * @return the HTTP method */ HttpMethod method() default HttpMethod.GET; /** * Whether authentication is required * @return true if authentication is required */ boolean requiresAuth() default true; } Complete Examples
Complete Framework Example:
```java
import java.lang.annotation.; import java.lang.reflect.;
import java.util.*;
// Framework annotations
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Service {
String name() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Command {
String value();
String description() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Inject {
String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Scheduled {
long fixedRate() default -1;
String cron() default "";
}
// Service classes
@Service(name = "userService")
class UserService {
@Command("createUser") public String createUser(String username, String email) { return "User created: " + username + " (" + email + ")"; } @Command("deleteUser") public String deleteUser(String username) { return "User deleted: " + username; } @Scheduled(fixedRate = 5000) public void cleanupInactiveUsers() { System.out.println("Cleaning up inactive users..."); } }
@Service(name = "emailService")
class EmailService {
@Inject private UserService userService; @Command("sendEmail") public String sendEmail(String to, String subject) { return "Email sent to: " + to + " - " + subject; } @Scheduled(cron = "0 0 9 * * *") // Daily at 9 AM public void sendDailyReports() { System.out.println("Sending daily reports..."); } }
// Simple DI Container and Framework
class ApplicationContext {
private Map services = new HashMap<>();
private Map commands = new HashMap<>();
public ApplicationContext(Class<?>... configClasses) { // Scan and instantiate services for (Class<?> configClass : configClasses) { scanPackage(configClass.getPackage()); } // Inject dependencies injectDependencies(); // Register commands registerCommands(); } private void scanPackage(Package pkg) { // In real implementation, you would scan the package // For this example, we'll manually register our services registerService(new UserService()); registerService(new EmailService()); } private void registerService(Object service) { Class<?> serviceClass = service.getClass(); Service serviceAnnotation = serviceClass.getAnnotation(Service.class); String serviceName = serviceAnnotation != null && !serviceAnnotation.name().isEmpty() ? serviceAnnotation.name() : serviceClass.getSimpleName(); services.put(serviceName, service); System.out.println("Registered service: " + serviceName); } private void injectDependencies() { for (Object service : services.values()) { for (Field field : service.getClass().getDeclaredFields()) { if (field.isAnnotationPresent(Inject.class)) { Inject inject = field.getAnnotation(Inject.class); String dependencyName = inject.value().isEmpty() ? field.getType().getSimpleName() : inject.value(); Object dependency = services.get(dependencyName); if (dependency != null) { field.setAccessible(true); try { field.set(service, dependency); System.out.println("Injected " + dependencyName + " into " + service.getClass().getSimpleName()); } catch (IllegalAccessException e) { System.err.println("Failed to inject " + dependencyName + ": " + e.getMessage()); } } } } } } private void registerCommands() { for (Object service : services.values()) { for (Method method : service.getClass().getDeclaredMethods()) { Command command = method.getAnnotation(Command.class); if (command != null) { commands.put(command.value(), method); System.out.println("Registered command: " + command.value() + " - " + command.description()); } } } } public Object executeCommand(String commandName, Object... args) { Method method = commands.get(commandName); if (method == null) { throw new IllegalArgumentException("Unknown command: " + commandName); } try { Object service = services.values().stream() .