Fundamentals 19 min read

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.

Java Captain
Java Captain
Java Captain
Java Annotations and Reflection: Built‑in Annotations, Meta‑annotations, Custom Annotations, Class Loading, and 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.

JavareflectiongenericsClassLoaderAnnotationsMeta‑annotations
Java Captain
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.