After several weeks of Learning Java, the relearning Java series has finally entered the annotations and Reflection chapter. This chapter will explain the two characteristics of Java annotations and reflection, so that readers have a deeper understanding of Java features.

annotations

Annotation is an Annotation mechanism introduced in JDK1.5. Classes, methods, variables, parameters and packages can be annotated in Java language, and the annotated content can be obtained through reflection mechanism.

There are two types of annotations: built-in annotations and custom annotations.

Built-in annotations

Built-in annotations represent annotations defined by Java itself. There are 7 annotations, 3 in java.lang and 4 in java.lang. Annotations, which can be used directly.

Typical built-in annotations include @Override, @SuppressWarnings and @deprecated. All three are common annotations that we use in Java programming.

Override: Checks if the method is an Override method. If the method is not found in the parent class or in the referenced interface, a compilation error is reported.

@SuppressWarnings: Instructs the compiler to ignore warnings declared in annotations.

@deprecated: Marks obsolete methods. If you use this method, a compile warning is reported.

The remaining four types of annotations are meta-annotations, which are used by users to write custom annotations and I won’t cover them in the built-in annotations section.

Custom annotations

When it comes to custom annotations, we start with meta-annotations, which are also built-in annotations in Java and are used to identify custom annotations. There are @Retention, @Documented, @Target, and @Inherited.

@Retention: Identifies how the annotation is stored, whether it is only in code, encoded in a class file, or accessible through reflection at runtime.

Documented Whether these annotations are included in the user document.

@target: Marks which Java member the annotation should be.

Inherited: Indicates which annotation class this annotation belongs to. The default annotation does not inherit from any child class.

@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Logger {
    boolean isLogger() default true;
}
Copy the code

Here is an example of a simple annotation, but there are a few things worth noting.

1) ElementType represents the region to which the annotation is applied, and Target is defined in the JDK as follows.

public enum ElementType {
	TYPE,
	FIELD,  / / field
	METHOD, / / method
	PARAMETER, / / parameters
	CONSTRUCTOR, // constructor
	LOCAL_VARIABLE,
	ANNOTATION_TYPE,
	PACKAGE, / / package
	TYPE_PARAMETER,
	TYPE_USE
}
Copy the code

2) Describe the operating environment, generally using most of the RUNTIME.

public enum RetentionPolicy {
	SOURCE,  // Information about this annotation type is only recorded in the source file and will be discarded by the compiler at compile time
	CLASS, // The compiler records annotations in a class file, but they are not loaded into the JVM.
	RUNTIME // Annotation information is retained in source files, class files, and loaded into the Java JVM at execution time, so it can be read reflexively.
}
Copy the code

Annotations using

We defined Logger annotations in the previous article, but in this section we’ll focus on how to use them.

public class Test {
    @Logger()
    private LoggerFactory loggerFactory;

    public void demo(a) {
        loggerFactory.info("test func!"); }}Copy the code

This is a simple example of how to use this method. We set the Target of the Logger to Method, so all we need to do is add a written annotation to the demo method. Of course, we can change the Target to attach it to the appropriate Class.

So how do I use it? This brings us to the second part of the article, reflection, which explains how to use our written annotations through reflection.

reflection

The JAVA reflection mechanism allows you to know all the properties and methods of any class in the running state. For any object, you can call any of its methods and properties; This ability to dynamically retrieve information and invoke methods on objects is called the Reflection mechanism of the Java language.

Before we dive into reflection, let’s take a look at some of the common interface apis for reflection, which are all in the cas.java file:

// Get the class type information
forName(String className);
// Create a class
newInstance();
// Get all the fields in the class
getDeclaredFields();
// Get the specified field in the class
getDeclaredField(String field);
// Get the constructor
getConstructors();
// Get all the methods of the current class
getMethods();
Copy the code

Create a class by reflection:

public class AnnotationManager {

    public static void reflectObject(String className) throws Exception {
        // Get the type information
        Class clazz = Class.forName(className);
	// Call newInstance creation
        LoggerFactory loggerFactory = (LoggerFactory) clazz.newInstance();
	// Trigger logging method
        loggerFactory.info("test");
    }

    public static void main(String[] args) throws Exception {
	// reflection gets objects
        AnnotationManager.reflectObject("annotation.LoggerFactory"); }}Copy the code

The above code example is a simple procedure for creating a class through reflection.

After we have implemented the creation of a class through reflection, we try again to check whether the specified class has a specified annotation through reflection.

public void registerLogger(T t, LoggerFactory loggerFactory) throws Exception {
	// Get the type object to be injected
	Class clazz = ((Object)t).getClass();
	// Get the field to be injected
        Field[] fields = clazz.getDeclaredFields();
	// Iterate over the fields
        for (Field field : fields) {
	    // Get the annotation configuration
            Logger logger =  field.getAnnotation(Logger.class);
            // Determine whether the field satisfies the assignment condition
            if(logger.isLogger() && field.getType().getSimpleName().equals("LoggerFactory")) {
                field.setAccessible(true);
                / / assignmentfield.set(t, loggerFactory); }}}Copy the code

If our annotation is loaded to assign a value to a field, we simply call the getAnnotation interface and pass in the corresponding class type. The above code is an operation that assigns a value to a specified field.

In actual combat

And finally, we’re done with annotations and reflection so let’s put this into practice with what we’ve learned.

Target: We assign a value to the specified field by specifying an annotation, and finally call it successfully.

This exercise will be divided into three steps: define annotations, define test classes, and define annotation injectors.

Define notes:

@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Logger {
    boolean isLogger(a) default true;
}
Copy the code

A Logger annotation is defined with the Target flag applied to field attributes, Retention selected for runtime, and provided with a default value of true.

Define test classes:

Once the annotations are defined, we need to define a complete test class to verify them. The test class is divided into two parts, one is the object to be injected, and the other is the method to be injected.

public class Test {

    @Logger()
    private LoggerFactory loggerFactory; // The object to be injected

    public void demo(a) { // The method to test
        loggerFactory.info("test func!"); }}Copy the code

The above code is a simple test class defined, in which loggerFactory is the object to be injected. After that, we will initialize the loggerFactory through Logger annotations and annotation parser, and finally verify it in demo Function.

Define the annotation parser

public class LoggerManager<T> {

    public static LoggerFactory registerObject(String className) throws Exception {
        Class clazz = Class.forName(className);
        return (LoggerFactory) clazz.newInstance();
    }

    public void registerLogger(T t, LoggerFactory loggerFactory) throws Exception {
	// Get the type information of the object passed in
        Class clazz = ((Object)t).getClass();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            Logger logger =  field.getAnnotation(Logger.class);
	    // Determine whether the injection meets the condition
            if(logger.isLogger() && field.getType().getSimpleName().equals("LoggerFactory")) {
                field.setAccessible(true);
		// Inject the loggerFactory utility classfield.set(t, loggerFactory); }}}public static void main(String[] args) throws Exception {
        LoggerFactory loggerFactory = LoggerManager.registerObject("annotation.LoggerFactory");
        LoggerManager<Object> loggerManager = new LoggerManager<Object>();
        Test test = new Test();
        loggerManager.registerLogger(test, loggerFactory);
	// Invoke method validationtest.demo(); }}Copy the code

This is a complete annotation parser. We get the LoggerFactory from the registerObject, inject the public registerLogger into the Test object, and call the test.demo.

conclusion

Today’s description gives us a general idea of the status of annotations and reflection in Java. Annotations have evolved over the years since the release of JDK1.5 and have been used in a variety of frameworks, most notably the Spring framework, which uses annotations and reflection to the extreme. In the future, I will also do analysis related to Spring and output relevant articles. I look forward to making progress with you.