Java Annotations and Reflection: Built‑in Annotations, Meta‑annotations, Custom Annotations, Class Loading, and Reflection API
This article provides a comprehensive overview of Java annotations—including built‑in, meta‑ and custom annotations—explains how they are used with reflection, describes class loading and ClassLoader mechanisms, and demonstrates how to obtain generic and annotation information at runtime using the Reflection API.
Preface
Annotations are not programs themselves, but they can describe programs and be read by other programs such as compilers.
The syntax of an annotation starts with @AnnotationName and may include parameters, e.g., @SuppressWarnings(value="unchecked") .
Annotations can be placed on packages, classes, methods, fields, etc., providing auxiliary metadata that can be accessed via reflection.
1. Built‑in (Common) Annotations
1.1 @Override
Indicates that a method is intended to override a method declared in a superclass.
1.2 @RequestMapping
Maps web requests to handler methods. Important attributes include:
value : the request URL or its alias
params : filter requests based on the presence, default, or value of HTTP parameters
1.3 @RequestBody
Used on method parameters to bind the request body to an object, with optional validation via @Valid .
1.4 @GetMapping
A shortcut for @RequestMapping(method = RequestMethod.GET) that handles HTTP GET requests.
1.5 @PathVariable
Binds a method parameter to a URI template variable defined in @RequestMapping .
1.6 @RequestParam
Binds a method parameter to a web request parameter, allowing easy access to query parameters.
1.7 @ComponentScan
Configures the packages that Spring should scan for components, using basePackages or its alias value .
1.8 @Component
Marks a generic component class for inclusion in the Spring container.
1.9 @Service
A specialization of @Component that indicates a business‑logic service.
1.10 @Repository
A specialization of @Component used to mark DAO (data‑access) classes.
2. Meta‑annotations
The four meta‑annotations provided by the Java API are @Target , @Retention , @Documented , and @Inherited . They are used to define other annotations.
@Target Specifies where an annotation can be applied. Possible ElementType values include: TYPE : classes, interfaces, enums METHOD : methods FIELD : member variables
@Retention Defines how long the annotation is retained. Example: RetentionPolicy.RUNTIME Annotations with RUNTIME retention are available to the JVM at runtime and can be read via reflection.
@Documented Indicates whether the annotation should be included in generated Javadoc.
@Inherited Specifies whether the annotation is inherited by subclasses.
3. Custom Annotations
Creating custom annotations helps understand Spring’s underlying mechanisms, even if they are not used directly in production code.
/**
* Custom annotation example
*/
public class CustomAnnotation {
/**
* Annotation parameter must be assigned a value if no default is provided
*/
@MyAnnotation(value = "explanation")
public void test() {}
}
// Definition of the custom annotation
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value() default "";
}4. Overview of Reflection
4.1 Dynamic vs. Static Languages
4.1.1 Dynamic Languages
Languages that can change their structure at runtime (e.g., add or remove functions). Examples include Objective‑C, C#, PHP, Python, JavaScript.
/**
* Demonstrates eval in JavaScript
*/
function f(){
var x = "var a = 3; var b = 5; alert(a+b)";
eval(x);
}4.1.2 Static Languages
Languages whose structure is fixed at compile time, such as Java, C, C++. Java is considered a "quasi‑dynamic" language because it can use reflection to achieve runtime flexibility.
4.2 Java Reflection
Reflection allows a program to inspect and manipulate classes, fields, methods, constructors, and annotations at runtime.
Class c = Class.forName("java.lang.String");After loading, a Class object representing the runtime type is created, enabling inspection of the class’s structure.
4.2.1 Main Functions of Reflection
Inspect and invoke any class’s members (fields, methods, constructors) at runtime.
Access generic type information.
Process annotations.
Create dynamic proxies.
4.2.2 Core Reflection APIs
java.lang.Class : represents a class.
java.lang.reflect.Field : represents a field.
java.lang.reflect.Method : represents a method.
java.lang.reflect.Constructor : represents a constructor.
5. Understanding the Class Object and Obtaining Class Instances
5.1 The Class Class
Every loaded class has a unique Class instance that holds its metadata.
5.2 Ways to Obtain a Class Instance
Using the class literal: Class<User> c = User.class;
Calling getClass() on an object: Class<?> c = user.getClass();
Using Class.forName("com.example.User") with the fully qualified name.
For primitive types, using the TYPE field, e.g., Class<Integer> c = Integer.TYPE;
Through a ClassLoader .
5.3 Types of Class Objects That Can Be Obtained
Classes (including inner, static, local, anonymous): Class<Person> c = Person.class;
Interfaces: Class<Comparable> c = Comparable.class;
Arrays: Class<String[]> c = String[].class;
Enums: Class<ElementType> c = ElementType.class;
Annotations: Class<Data> c = Data.class;
Primitive types: Class<int> c = int.class;
Void: Class<void> c = void.class;
6. Class Loading and ClassLoader
6.1 Class Loading Process
When a class is first used, the JVM performs three steps:
Load: Read the bytecode, create a Class object.
Link: Verify and prepare the class, resolve symbolic references.
Initialize: Execute static initializers and initialize static fields.
6.2 ClassLoaders
The JVM defines two primary loader types: the BootstrapClassLoader and user‑defined ClassLoaders (including ExtensionClassLoader, Application/System ClassLoader, and custom loaders).
public class Test03 {
public static void main(String[] args) {
// Get the system class loader
ClassLoader sysLoader = ClassLoader.getSystemClassLoader();
System.out.println(sysLoader);
// Get its parent (bootstrap loader)
ClassLoader parent = sysLoader.getParent();
System.out.println(parent);
}
}7. Obtaining the Complete Runtime Class Object
Using reflection, you can retrieve a class’s full structure, including fields, methods, constructors, superclasses, interfaces, and annotations.
public class Test04 {
public static void main(String[] args) throws ClassNotFoundException {
Class c1 = Class.forName("com.dcone.zhuzqc.demo.User");
// Get all fields
Field[] fields = c1.getDeclaredFields();
for (Field f : fields) {
System.out.println(f);
}
// Get all methods
Method[] methods = c1.getDeclaredMethods();
for (Method m : methods) {
System.out.println(m);
}
}
}8. Obtaining Generic Information via Reflection
Java erases generic types at compile time, but the Reflection API can retrieve generic information for fields, method parameters, and return types using ParameterizedType .
public static void getMethodParametricGeneric() throws NoSuchMethodException {
Method setListMethod = MyClass.class.getMethod("setList", List.class);
Type[] genericParameterTypes = setListMethod.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
ParameterizedType parameterizedType = (ParameterizedType) genericParameterType;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
Class realType = (Class) actualTypeArgument;
System.out.println("Generic parameter type: " + realType);
}
}
}9. Accessing Annotation Information via Reflection
Annotations on classes, fields, and methods can be inspected at runtime.
@Data
class User {
@ApiModelProperty(value = "Name")
private String userName;
@ApiModelProperty(value = "User ID")
private Long userId;
@ApiModelProperty(value = "Login Time")
private Date loginTime;
}
public static void main(String[] args) throws ClassNotFoundException {
if (User.class.isAnnotationPresent(ApiModel.class)) {
System.out.println(User.class.getAnnotation(ApiModel.class).value());
}
// Access field annotations
for (Field f : User.class.getDeclaredFields()) {
if (f.isAnnotationPresent(ApiModelProperty.class)) {
System.out.print(f.getAnnotation(ApiModelProperty.class).name() + ",");
}
}
}Additional examples show how to retrieve method‑level and method‑parameter annotations.
These notes summarize the author’s study of Java annotations and reflection; feedback and corrections are welcome.
Java Captain
Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.