Java Annotations
Annotation in Java is pretty interesting and gives ground to a lot of designing custom solutions for frameworks.
About @nnotations
Annotations, a form of metadata, provide data about a program that is not part of it.
Annotations have several uses, among them:
- Information for the compiler: The compiler can use annotations to detect errors or suppress warnings.
- Compile-time and deployment-time processing: Software tools can process annotation information to generate code, XML files, and other output.
- Runtime processing: Some annotations are available to be examined at runtime.
Declaration
@metaAnnotations
public @interface anotationName{
String author() default "Vinod Atwal";
}
Meta annotations
Meta-annotations are annotations that are applied to other annotations.
1. @Retention:
It specifies the level up to which the annotation will be available.
Syntax: @Retention(RetentionPolicy)
- RetentionPolicy.SOURCE: Annotation is available only at the source level and is ignored by the compiler.
- RetentionPolicy.CLASS: The annotation is available to the compiler at compile-time, but is ignored by the Java Virtual Machine (JVM).
- RetentionPolicy.RUNTIME: The annotation is available to the JVM.
2. @Target
It is used to restrict an annotation from being applied to specific targets.
Syntax: @Target(ElementType)
All Possible Element Type
ElementType.ANNOTATION_TYPE
ElementType.CONSTRUCTOR
ElementType.FIELD
ElementType.LOCAL_VARIABLE
ElementType.METHOD
ElementType.PACKAGE
ElementType.PARAMETER
ElementType.TYPE
3. @Repeatable
It enables annotation to be applied multiple times to the same declaration.
4. @Inherited
It is used to inherit an annotation from a superclass and transfer it to a subclass. This capability is not available by default.
5. @Documented
Custom annotations are excluded from the official Java documentation by default. We must explicitly enable their inclusion to ensure they are included in the Javadoc.
//Example
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.Type)
@Documented
@Inherited
@Repeatable
public @interface MyCustomAnnotation{ ... }
@MyCustomAnnotation
public class SampleClass{ ... }
Lets Play 🧑💻
Git Repo: https://github.com/VinodAtwal/AnnotationMagic
/**
* This annotation is used to validate that a field contains only numeric strings.
* <p>
* It can be applied to fields, and during validation, the specified message
* can be displayed if the field does not meet the numeric requirement.
* </p>
* @Retention(RetentionPolicy.RUNTIME) Indicates the annotation is retained at runtime.
* @Target(ElementType.FIELD) Specifies that this annotation can only be applied to fields.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface IsNumber {
String message() default "Field should have be Number";
}
----------------------------------------------------------------------------------------------------------
/**
* A sample class demonstrating the use of the {@link IsNumber} annotation.
* <p>
* This class contains a single field, {@code sampleKey}, which is validated to
* ensure it contains only numeric strings using the {@link IsNumber} annotation.
* </p>
*/
public class SampleClass {
@IsNumber
private String sampleKey;
public SampleClass(String sampleKey) {
this.sampleKey = sampleKey;
}
public String getSampleKey() {
return sampleKey;
}
}
----------------------------------------------------------------------------------------------------------
/**
* A class that demonstrates annotation processing to validate that a field contains only numeric strings.
* Implements {@link Runnable} to allow execution in a separate thread.
*/
public class SimpleAnnotationProcess implements Runnable{
/**
* A pattern to match numeric strings (both integer and floating-point numbers).
*/
private final String testData;
/**
* The test data to be validated.
*/
private final Pattern pattern = Pattern.compile("-?\\d+(\\.\\d+)?");
public SimpleAnnotationProcess(String testData) {
this.testData = testData;
}
/**
* Executes the annotation processing in a separate thread.
* This method creates a {@link SampleClass} object and validates its fields.
* If a field annotated with {@link IsNumber} does not contain a valid numeric string,
* an error message is printed.
*/
@Override
public void run() {
SampleClass sampleClass = new SampleClass(testData);
try {
validate(sampleClass);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
/**
* Validates an object by inspecting its fields for the {@link IsNumber} annotation.
* If a field is annotated with {@code @IsNumber} and its value is not numeric or is {@code null},
* a validation error message is printed.
*
* @param obj the object to be validated.
* @throws IllegalAccessException if a field is not accessible via reflection.
*/
public void validate(Object obj) throws IllegalAccessException {
Class<?> clazz = obj.getClass(); // Get the class of the object
for (Field field : clazz.getDeclaredFields()) { // Iterate over fields
if (field.isAnnotationPresent(IsNumber.class)) { // Check if @IsNumber is present
field.setAccessible(true); // Allow access to private fields
Object value = field.get(obj); // Get the field value
if (value == null || !isNumeric((String) value) ) {
IsNumber annotation = field.getAnnotation(IsNumber.class);
System.out.println("Validation error with Field "+ field.getName()+ ": "+ annotation.message());
}
}
}
}
/**
* Checks if a given string represents a valid numeric value.
* This method uses a regular expression to validate both integer and floating-point numbers.
*
* @param strNum the string to be validated.
* @return {@code true} if the string is numeric, {@code false} otherwise.
*/
public boolean isNumeric(String strNum) {
if (strNum == null) {
return false;
}
return pattern.matcher(strNum).matches();
}
}
Inputs:
public static void main(String[] args) {
new SimpleAnnotationProcess("abc").run();
new SimpleAnnotationProcess("123").run();
}
Outputs
/Users/vinodatwal/Library/Java/JavaVirtualMachines/corretto-22.0.1/Contents/Home/bin/java -Dmaven.multiModuleProjectDirectory=/Users/vinodatwal/Documents/projects/AnnotationMagic -Djansi.passthrough=true -Dmaven.home=/Applications/IntelliJ IDEA CE.app/Contents/plugins/maven/lib/maven3 -Dclassworlds.conf=/Applications/IntelliJ IDEA CE.app/Contents/plugins/maven/lib/maven3/bin/m2.conf -Dmaven.ext.class.path=/Applications/IntelliJ IDEA CE.app/Contents/plugins/maven/lib/maven-event-listener.jar -javaagent:/Applications/IntelliJ IDEA CE.app/Contents/lib/idea_rt.jar=58757:/Applications/IntelliJ IDEA CE.app/Contents/bin -Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -Dsun.stderr.encoding=UTF-8 -classpath /Applications/IntelliJ IDEA CE.app/Contents/plugins/maven/lib/maven3/boot/plexus-classworlds.license:/Applications/IntelliJ IDEA CE.app/Contents/plugins/maven/lib/maven3/boot/plexus-classworlds-2.7.0.jar org.codehaus.classworlds.Launcher -Didea.version=2024.1.1 exec:java -Dexec.mainClass="org.va.Main"
[INFO] Scanning for projects...
[INFO]
[INFO] -----------------------< org.va:AnnotationMagic >-----------------------
[INFO] Building AnnotationMagic 1.0-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- exec:3.0.0:java (default-cli) @ AnnotationMagic ---
Validation error with Field sampleKey: Field should have be Number
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.234 s
[INFO] Finished at: 2024-12-29T02:14:32+05:30
[INFO] ------------------------------------------------------------------------
Annotations are a powerful tool in Java, and they can add a lot of functionality to code. Let’s try explore some of these capabilities.