title: 【Reprint】It turns out that Java annotations are just markers, not very powerful, a comprehensive article worth collecting!
date: 2021-08-16 09:18:01
comment: false
toc: true
category:
- Java
tags: - Reprint
- Java
- Annotations
- Markers
- Power
- Collection
- Mastery
This article is reprinted from: It turns out that Java annotations are just markers, not very powerful, a comprehensive article worth collecting!
Preface#
There is a magical existence in Java, annotations, that guy who @s others every day, what exactly is he?#
What are Annotations#
Starting from JDK5, Java added support for metadata, which is annotations. Annotations are somewhat different from comments; you can think of annotations as special markers in the code. These markers can be read during compilation, class loading, and runtime, and corresponding processing can be executed. Through annotations, developers can embed supplementary information in the source code without changing the original code and logic.
Built-in Annotations#
The second-in-command discovered that after creating a class and implementing an interface in the IDE, the methods implementing the interface would automatically have the @Override marker added above them.
And this marker is an annotation. There are several built-in annotations like @Override in the JDK, all of which are under the java.lang package. Let's take a look at them.
@Override#
When a subclass overrides a method from its superclass, the subclass can add this annotation. What is the use of this? It ensures that the subclass indeed overrides the superclass method, avoiding low-level errors.
At first, I didn't know its use until one day I deleted this method in the interface, and as a result, the compilation failed.
Below is the result of compiling directly from the command line. Now I understand the purpose of this marker; it can inform the compiler and readers of the source code that this method overrides or implements a method from a supertype.
@Deprecated#
This annotation is used to indicate that a certain program element, such as a class or method, is outdated. When other programs use deprecated classes or methods, the compiler will issue a warning (strikethrough, which you may have seen a lot).
@SuppressWarnings#
Suppress warnings.
package com.secondgod.annotation;
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) {
List<Integer> intList = new ArrayList<>();
List objList = intList;
objList.add("Second-in-command");
}
}
Compile with suggested warnings enabled from the command line (commands like javac -d . -Xlint Test.java, further mentions of enabling suggested warnings compilation will not be elaborated).
A slight modification to the code can eliminate the compilation warning.
package com.secondgod.annotation;
import java.util.ArrayList;
import java.util.List;
public class Test {
@SuppressWarnings({"unchecked", "rawtypes"})
public static void main(String[] args) {
List<Integer> intList = new ArrayList<>();
List objList = intList;
objList.add("Second-in-command");
}
}
@SafeVarargs#
When declaring a variable number of arguments with a wildcard type (for example: generics), the Java compiler will issue an unchecked warning. In such cases, if the programmer determines that the body of the declared constructor or method will not perform potentially unsafe operations on its varargs parameters, they can use @SafeVarargs to mark it, so the Java compiler will not issue an unchecked warning.
package com.secondgod.annotation;
import java.util.List;
public class Test {
public final void test(List<String>... stringLists) {
}
}
Compile with suggested warnings enabled from the command line.
A slight modification to the code can eliminate the compilation warning.
package com.secondgod.annotation;
import java.util.List;
public class Test {
@SafeVarargs
public final void test(List<String>... stringLists) {
}
}
@FunctionalInterface#
The functional interface annotation, which is a new feature introduced in Java 1.8. Functional programming is very popular, so Java 8 timely added this feature. What is the use of this annotation? This annotation guarantees that this interface has only one abstract method; note that this can only modify interfaces.
- An interface with no abstract methods
package com.secondgod.annotation;
@FunctionalInterface
public interface Test {
}
- An interface with more than one abstract method
package com.secondgod.annotation;
@FunctionalInterface
public interface Test {
String test1();
String test2();
}
- An interface with only one abstract method
Compiles successfully.
package com.secondgod.annotation;
@FunctionalInterface
public interface Test {
String test();
}
- An abstract class with only one abstract method
package com.secondgod.annotation;
@FunctionalInterface
public abstract class Test {
public abstract String test();
}
Meta-Annotations#
If we look at the source code of @Override, we will find that this annotation's definition itself has annotations, which are annotations of annotations, commonly referred to as meta-annotations. Meta-annotations are annotations that describe other annotations, and can be used when defining custom annotations.
The built-in meta-annotations in the JDK are all under the java.lang.annotation package.
@Target#
Target means the target; @Target specifies where the annotation can be applied.
Value | Annotation Usage Scope |
---|---|
TYPE | Can be used on classes or interfaces |
FIELD | Can be used on fields/attributes |
METHOD | Can be used on methods |
PARAMETER | Can be used on parameters |
CONSTRUCTOR | Can be used on constructors |
LOCAL_VARIABLE | Can be used on local variables |
ANNOTATION_TYPE | Can be used on annotation types (types modified by @interface) |
PACKAGE | Used to record the package information of the java file |
TYPE_PARAMETER | Can be used in type variable declaration statements |
TYPE_USE | Can be used in any statement using types |
@Documented#
Annotations marked with @Documented will be extracted into documentation by the JavaDoc tool. By default, JavaDoc does not include annotations, but if the annotation is declared with @Documented, it will be processed by tools like JavaDoc, so annotation type information will be included in the generated help documentation.
@Inherited#
Inherited means inheritance, but it does not mean that the annotation itself can be inherited; rather, it means that if a superclass is annotated with an annotation marked with @Inherited, then if its subclass has no annotations applied, the subclass inherits the superclass's annotation.
@Repeatable#
Repeatable naturally means repeatable. This was newly added in Java 8, allowing the same annotation to be repeated on the same program element. When needing to use the same annotation multiple times, the @Repeatable annotation is often required. Before Java 8, there could be at most one annotation of the same type in front of the same program element; if multiple annotations of the same type were needed, an annotation "container" had to be used.
@Retention#
@Retention is used to describe the lifecycle of the annotation, which is the duration for which the annotation is retained. The member variable (value) in the @Retention annotation is used to set the retention policy, and value is of the java.lang.annotation.RetentionPolicy enum type, which has 3 enum constants as follows.
Value | Lifecycle |
---|---|
SOURCE | Effective in the source file (can be used by the compiler) |
CLASS | Effective in the class file (can be used by the compiler and virtual machine) |
RUNTIME | Effective at runtime (can be used by the compiler, virtual machine, and program during execution) |
@Native#
Using the @Native annotation to modify a member variable indicates that this variable can be referenced by native code, often used by code generation tools. It is also under the java.lang.annotation package, but I do not consider it a meta-annotation because its usage target is not an annotation.
Custom Annotations#
In frameworks like Spring, Hibernate, Mybatis, etc., there are annotations. The second-in-command also tried to define a custom annotation; let's first look at the source code of @Override.
package java.lang;
import java.lang.annotation.*;
/**
* Indicates that a method declaration is intended to override a
* method declaration in a supertype. If a method is annotated with
* this annotation type compilers are required to generate an error
* message unless at least one of the following conditions hold:
*
* <ul><li>
* The method does override or implement a method declared in a
* supertype.
* </li><li>
* The method has a signature that is override-equivalent to that of
* any public method declared in {@linkplain Object}.
* </li></ul>
*
* @author Peter von der Ahé
* @author Joshua Bloch
* @jls 9.6.1.4 @Override
* @since 1.5
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
We will also define one ourselves.
package com.secondgod.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Test annotation
*
* @author Second-in-command's white hat https://le-yi.blog.csdn.net/
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}
Our custom annotations are usually defined with a lifecycle of RUNTIME. A lifecycle of SOURCE requires cooperation from the compiler. A lifecycle of CLASS requires cooperation from the virtual machine. Only the behavior at runtime can be customized by us.
Now, let's use this custom annotation.
package com.secondgod.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Test annotation
*
* @author Second-in-command's white hat https://le-yi.blog.csdn.net/
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
}
/**
* Testing the custom annotation
*
* @author Second-in-command's white hat https://le-yi.blog.csdn.net/
*/
public class Test {
@MyAnnotation
private String name;
}
Some annotations also have parameters, so the second-in-command added them.
package com.secondgod.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Test annotation
*
* @author Second-in-command's white hat https://le-yi.blog.csdn.net/
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
String name();
}
/**
* Testing the custom annotation
*
* @author Second-in-command's white hat https://le-yi.blog.csdn.net/
*/
public class Test {
@MyAnnotation(name = "Second-in-command")
private String name;
}
When using, the property name must be specified; for annotations like @Target, the property name does not need to be specified because the property name is "value," so it can be omitted.
package com.secondgod.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Test annotation
*
* @author Second-in-command's white hat https://le-yi.blog.csdn.net/
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
String value();
}
/**
* Testing the custom annotation
*
* @author Second-in-command's white hat https://le-yi.blog.csdn.net/
*/
public class Test {
@MyAnnotation("Second-in-command")
private String name;
}
After defining properties in the annotation, values must be assigned when used. However, default values can be defined in the annotation, allowing them to be omitted during use.
package com.secondgod.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Test annotation
*
* @author Second-in-command's white hat https://le-yi.blog.csdn.net/
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
String value() default "Second-in-command";
}
/**
* Testing the custom annotation
*
* @author Second-in-command's white hat https://le-yi.blog.csdn.net/
*/
public class Test {
@MyAnnotation
private String name;
}
The content of annotations is very minimal, and clearly, their functionality is not implemented within them. So annotations are just markers and do not have any functionality. How is their functionality implemented?
Annotation Practice#
We will use Reflection and Introspection in conjunction with annotations to implement a small tool that can automatically initialize properties with environment variables.
package com.secondgod.annotation;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
/**
* Utility class
*
* @author Second-in-command's white hat https://le-yi.blog.csdn.net/
*/
public class MyUtils {
/**
* Private constructor, cannot be instantiated
*/
private MyUtils() {}
/**
* Used to initialize properties according to environment variables by default
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface EnvironmentVariables {
/**
* Key of the environment variable
* @return
*/
String value();
}
/**
* Obtain class instance and initialize with environment variables
*
* @param clazz
* @param <T>
* @return
* @throws InstantiationException
* @throws IllegalAccessException
* @throws java.beans.IntrospectionException
* @throws InvocationTargetException
*/
public static <T> T getInstance(Class<T> clazz) throws InstantiationException, IllegalAccessException, IntrospectionException, InvocationTargetException {
T obj = clazz.newInstance();
setEnvironmentVariables(obj);
return obj;
}
/**
* Assign values to object properties according to environment variables
*
* @param o
* @throws IllegalAccessException
*/
public static void setEnvironmentVariables(Object o) throws IllegalAccessException, IntrospectionException, InvocationTargetException {
for (Field f : o.getClass().getDeclaredFields()) {
// Use reflection to read the annotation
EnvironmentVariables environmentVariables = f.getDeclaredAnnotation(EnvironmentVariables.class);
if (environmentVariables != null) {
// If the annotation exists, read the environment variable
String value = System.getProperty(environmentVariables.value());
// Use introspection to write the value
PropertyDescriptor pd = new PropertyDescriptor(f.getName(), o.getClass());
pd.getWriteMethod().invoke(o, value);
}
}
}
}
We define a class to represent virtual machine information and use annotations to mark the property fields.
package com.secondgod.annotation;
import java.text.MessageFormat;
/**
* Virtual Machine Information
*
* @author Second-in-command's white hat https://le-yi.blog.csdn.net/
*/
public class VmInfo {
@MyUtils.EnvironmentVariables(value = "java.vm.version")
private String version;
@MyUtils.EnvironmentVariables(value = "java.vm.vendor")
private String vendor;
@MyUtils.EnvironmentVariables(value = "java.vm.name")
private String name;
@MyUtils.EnvironmentVariables(value = "java.vm.specification.name")
private String specName;
@MyUtils.EnvironmentVariables(value = "java.vm.specification.vendor")
private String specVendor;
@MyUtils.EnvironmentVariables(value = "java.vm.specification.version")
private String specVersion;
@MyUtils.EnvironmentVariables(value = "java.vm.info")
private String info;
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getVendor() {
return vendor;
}
public void setVendor(String vendor) {
this.vendor = vendor;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSpecName() {
return specName;
}
public void setSpecName(String specName) {
this.specName = specName;
}
public String getSpecVendor() {
return specVendor;
}
public void setSpecVendor(String specVendor) {
this.specVendor = specVendor;
}
public String getSpecVersion() {
return specVersion;
}
public void setSpecVersion(String specVersion) {
this.specVersion = specVersion;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
public String toString() {
return MessageFormat.format("version={0},vendor={1},name={2},specName={3},specVendor={4},specVersion={5},info={6}", version, vendor, name, specName, specVendor, specVendor, info);
}
}
Now, let's test our tool.
package com.secondgod.annotation;
import java.beans.IntrospectionException;
import java.lang.reflect.InvocationTargetException;
/**
* Testing the custom annotation
*
* @author Second-in-command's white hat https://le-yi.blog.csdn.net/
*/
public class Test {
public static void main(String[] args) throws IntrospectionException, InvocationTargetException, IllegalAccessException, InstantiationException {
VmInfo info1 = new VmInfo();
System.out.println("After normal instantiation: " + info1);
VmInfo info2 = MyUtils.getInstance(VmInfo.class);
System.out.println("After instantiation using our tool: " + info2);
}
}
The content is quite extensive, and the image is not fully captured, but it is clear that our tool meets our expectations, very nice.
Epilogue#
Custom annotations are present in frameworks like Spring, Hibernate, Mybatis, often used as an alternative configuration option besides configuration files. Each has its pros and cons, and should be chosen based on the actual situation. Modifying configuration files requires restarting the program, while modifying annotations obviously requires recompilation. However, the second-in-command believes that placing configurations directly where they are used is very convenient and improves readability, so unless there is a need for modifications after release, the second-in-command prefers to use annotations. However, using annotations can be more scattered, unlike configuration files which are centralized. Well, everyone has their own opinions.