Annotation can be used to control the behavior of a class, method or variable
In this example, we could add an extra print statement to the annotated method
Create the following folder structure:

pom.xml: (if using Gradle, replace pom.xml with build.gradle)
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>net.maxjava</groupId> <artifactId>annotation</artifactId> <version>1.0</version> <dependencies> <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.27.0-GA</version> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>11</source> <target>11</target> </configuration> </plugin> </plugins> </build> </project>
plugins { id 'java' } sourceCompatibility = 11 targetCompatibility = 11 group 'net.maxjava' version '1.0' repositories { mavenCentral() } dependencies { implementation group: 'org.javassist', name: 'javassist', version: '3.27.0-GA' }
Some notes about pom.xml:
- javassist is used to modify the class file in order to modify the method's behavior dynamically
Some notes about gradle.build:
- javassist is used to modify the class file in order to modify the method's behavior dynamically
PrintCurrentTime.java:
package annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface PrintCurrentTime { public String timeZone(); }
Some notes about PrintCurrentTime.java:
- annotation in Java is defined by using @interface keyword putting in the normal position of interface or class
- attribute for the annotation will be defined as a function. e.g. "timeZone" with type String is a required attribute for the above annotation
- RetentionPolicy.RUNTIME is to let annotation be used in runtime
- ElementType.METHOD is to only allow the annotation be used on a method
MyClass.java:
package mypackage; import annotation.PrintCurrentTime; public class MyClass { @PrintCurrentTime(timeZone = "UTC") public void printHello() { System.out.println("Hello !"); } public void printHi() { System.out.println("Hi !"); } }
Some notes about MyClass.java:
- This is the regular class that uses the annotation. This class just have 2 methods but one of them has the annotation
MainClass.java:
import annotation.PrintCurrentTime; import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; import mypackage.MyClass; public class MainClass { private static void processAnnotation() { try { ClassPool classPool = ClassPool.getDefault(); classPool.importPackage("java.util.TimeZone"); classPool.importPackage("java.util.Calendar"); CtClass ctClass = classPool.get("mypackage.MyClass"); // For each of the method of "mypackage.MyClass" for (CtMethod ctMethod : ctClass.getMethods()) { // For each of the annotation for (Object object : ctMethod.getAnnotations()) { // If the annotation is PrintCurrentTime, insert the time, date and time zone information piece by piece into the method if (object instanceof PrintCurrentTime) { String timeZone = ((PrintCurrentTime) object).timeZone(); ctMethod.insertBefore("System.out.println(Calendar.getInstance(TimeZone.getTimeZone(\"" + timeZone + "\")).getTimeZone().getDisplayName());"); ctMethod.insertBefore("System.out.print(Calendar.getInstance(TimeZone.getTimeZone(\"" + timeZone + "\")).get(Calendar.SECOND) + \" \");"); ctMethod.insertBefore("System.out.print(Calendar.getInstance(TimeZone.getTimeZone(\"" + timeZone + "\")).get(Calendar.MINUTE) + \":\");"); ctMethod.insertBefore("System.out.print(Calendar.getInstance(TimeZone.getTimeZone(\"" + timeZone + "\")).get(Calendar.HOUR) + \":\");"); ctMethod.insertBefore("System.out.print(Calendar.getInstance(TimeZone.getTimeZone(\"" + timeZone + "\")).get(Calendar.DAY_OF_MONTH) + \" \");"); ctMethod.insertBefore("System.out.print(Calendar.getInstance(TimeZone.getTimeZone(\"" + timeZone + "\")).get(Calendar.MONTH) + 1 + \"-\");"); ctMethod.insertBefore("System.out.print(Calendar.getInstance(TimeZone.getTimeZone(\"" + timeZone + "\")).get(Calendar.YEAR) + \"-\");"); break; } } } ctClass.toClass(); ctClass.detach(); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { processAnnotation(); MyClass myClass = new MyClass(); System.out.println("start calling printHello()"); myClass.printHello(); System.out.println("start calling printHi()"); myClass.printHi(); } }
Some notes about MainClass.java::
- This is the main class. First it calls the processAnnotation method to process the annotation of mypackage.MyClass
- The annotation processing logic is to look for each method of the mypackage.MyClass, if any annotation is of type PrintCurrentTime, then use the Javassist library ctMethod.insertBefore to insert code at the beginning of the method
- After the annotation processing, create the myClass as usual. Then call printHello and printHi and you will see current time will be printed before any code is executed in printHello but not printHi
- Javassist is used here because it provides the capability to add code reflectively
Sample output:
start calling printHello()
2022-4-12 1:54:52 Coordinated Universal Time
Hello !
start calling printHi()
Hi !