是什么?
Java注解(Annotation)是Java语言中的一种特殊标记,它可以用来为类、方法、变量、参数等元素添加元数据(metadata)信息。注解可以在源代码中嵌入元数据,这些元数据可以被编译器、工具和运行时环境使用。元数据是添加到程序元素如方法,字段,类和包上的额外信息。注解是一种分散式的元数据设置方式,xml是集中式的设置方式。注解不能直接干扰程序代码的运行。
有什么功能?
- 作为特定标记,用来告诉编译器一些信息,如 @Override注解,检查当前的方法签名是否真正重写的父类的方法。
- 编译时动态处理,如lombok的lombok@Data可以动态生成代码。
- 运行时动态处理,作为额外信息的载体,如获取注解信息
注解的分类?
- 标准注解
@Override
:用于标记方法覆盖(重写)了父类的方法。@Deprecated
:用于标记已经过时(不推荐使用)的元素。@SuppressWarnings
:用于抑制编译器警告。@SafeVarargs
:用于标记方法使用了可安全使用的可变参数。@FunctionalInterface
:用于标记接口为函数式接口。
- 元注解
@Target
:指定注解可以应用的程序元素类型,如类、方法、字段等。@Retention
:指定注解的保留策略,包括SOURCE
(源码级别保留)、CLASS
(类文件保留)和RUNTIME
(运行时保留)。@Documented
:指定注解是否包含在Java文档中。@Inherited
:指定注解是否可以被继承。
- 自定义注解
元注解?
-
@Target
在idea中查看其源码:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation interface
* can be applied to.
* @return an array of the kinds of elements an annotation interface
* can be applied to
*/
ElementType[] value();
}
Target接口中定义了一个value方法,返回值为枚举类型的数组。再查看枚举类型的源码:
public enum ElementType {
/** Class, interface (including annotation interface), enum, or record
* declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation interface declaration (Formerly known as an annotation type.) */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE,
/**
* Module declaration.
*
* @since 9
*/
MODULE,
/**
* Record component
*
* @jls 8.10.3 Record Members
* @jls 9.7.4 Where Annotations May Appear
*
* @since 16
*/
RECORD_COMPONENT;
}
以spring框架中的@Controller注解举例
package org.springframework.stereotype;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
此处@Target({ElementType.TYPE})代表了该注解只能作用在类级别上,以此类推
- ElementType.METHOD:表示该注解可以作用在方法级别上。
- ElementType.FIELD:表示该注解可以作用在字段(成员变量)级别上。
- ElementType.PARAMETER:表示该注解可以作用在参数级别上。
- ElementType.CONSTRUCTOR:表示该注解可以作用在构造函数级别上。
- ElementType.LOCAL_VARIABLE:表示该注解可以作用在局部变量级别上。
- ElementType.ANNOTATION_TYPE:表示该注解可以作用在注解级别上。
所以@Target源码中的@Target(ElementType.ANNOTATION_TYPE)注解表示,该注解可以作用在注解上
再看spring框架中的@Autowired注解
package org.springframework.beans.factory.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
根据@Target注解的枚举参数@Autowired可以作用在:构造函数,方法,参数,成员变量,注解上
-
@Retention
在idea中查看@Retention源码:
package java.lang.annotation;
/**
* Indicates how long annotations with the annotated interface are to
* be retained. If no Retention annotation is present on
* an annotation interface declaration, the retention policy defaults to
* {@code RetentionPolicy.CLASS}.
*
* <p>A Retention meta-annotation has effect only if the
* meta-annotated interface is used directly for annotation. It has no
* effect if the meta-annotated interface is used as a member interface in
* another annotation interface.
*
* @author Joshua Bloch
* @since 1.5
* @jls 9.6.4.2 @Retention
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
其中存在一个value方法, 返回值是一个枚举类型RetentionPolicy,查看该枚举类型源码
package java.lang.annotation;
/**
* Annotation retention policy. The constants of this enumerated class
* describe the various policies for retaining annotations. They are used
* in conjunction with the {@link Retention} meta-annotation interface to
* specify how long annotations are to be retained.
*
* @author Joshua Bloch
* @since 1.5
*/
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
SOURCE
:表示注解只保留在源代码中,编译器会丢弃这种类型的注解,不会包含在编译后的字节码文件中,也不会被加载到JVM中。这意味着这种类型的注解在运行时不可见,只在编译期起作用。
CLASS
:表示注解会被记录在编译后的字节码文件(.class文件)中,但在运行时不会被加载到JVM中。这意味着虽然这种类型的注解会被保留到编译后的文件中,但在程序运行时无法通过反射等手段获取到这些注解信息。
RUNTIME
:表示注解会被记录在编译后的字节码文件中,并且在运行时可以通过反射等手段获取到这些注解信息。这种类型的注解在编译后的文件中被保留,并且可以在程序运行时通过反射机制来读取和处理这些注解。
如何实现自定义注解?
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PersonInfoAnnotation {
// 名字
public String name();
// 年龄
public int age() default 19;
// 性别
public String gender() default "男";
// 开发语言
public String[] language();
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CourseInfoAnnotation {
//课程名称
public String courseName();
//课程标签
public String courseTag();
//课程简介
public String courseProfile();
//课程序号
public int courseIndex() default 303;
}
自定义两个注解以后,尝试去使用它们
package demo.annotation;
@CourseInfoAnnotation(courseName = "java基础", courseTag = "基础课",
courseProfile = "Java相关的核心知识“
)
public class MyCourse {
@PersonInfoAnnotation(name = "张三", language = {"Java","C++","Python","JS"})
private String author;
@CourseInfoAnnotation(courseName = "管理系统",
courseTag = "项目实战",
courseProfile = "前后端分类管理系统,"
courseIndex = 144)
public void getCourseInfo() {
}
}
通过查看Java类库源码的继承关系,发现Class,Constructor,Filed,Method都继承了AnnotateElemen接口。反射相关的类都继承了和注解相关的AnnotateElemen接口,该接口提供了多种方法获取注解。
在Java中,
AnnotatedElement
接口代表可以包含注解的程序单元,如类、方法、字段等。AnnotatedElement
接口定义了一系列方法,用于获取注解以及注解相关的信息。
getAnnotation(Class<T> annotationClass)
:返回指定类型的注解,如果该类型的注解不存在,则返回null。
getAnnotations()
:返回此元素上存在的所有注解,包括从父类、接口继承而来的注解。
isAnnotationPresent(Class<? extends Annotation> annotationClass)
:判断指定类型的注解是否存在于该元素上。
getDeclaredAnnotations()
:返回直接存在于此元素上的所有注解,不包括从父类、接口继承而来的注解。通过这些方法,可以在运行时获取到指定程序单元上的注解信息,并根据注解的内容进行相应的处理。这为开发者提供了一种在运行时根据注解来实现特定逻辑的方式,例如实现自定义的依赖注入、权限控制等功能。
所以,可以通过反射的方法,拿到class对象,调用AnnotateElemen接口提供的方法,获取注解和注解信息。
public class AnnotationParser {
//解析类的注解
public static void parseTypeAnnotation() throws ClassNotFoundException {
Class clazz = Class.forName("demo.annotation.MyCourse");
//这里获取的是class对象的注解,而不是其里面的方法和成员变量的注解
Annotation[] annotations = clazz.getAnnotations();
for(Annotation annotation : annotations){
CourseInfoAnnotation courseInfoAnnotation = (CourseInfoAnnotation) annotation;
System.out.println("课程名:" + courseInfoAnnotation.courseName() + "\n" +
"课程标签:" + courseInfoAnnotation.courseTag() + "\n" +
"课程简介:" + courseInfoAnnotation.courseProfile() + "\n" +
"课程序号:" + courseInfoAnnotation.courseIndex() );
}
}
//解析成员变量上的标签
public static void parseFieldAnnotation() throws ClassNotFoundException {
Class clazz = Class.forName("demo.annotation.MyCourse");
Field[] fields = clazz.getDeclaredFields();
for(Field f : fields){
//判断成员变量中是否有指定注解类型的注解
boolean hasAnnotation = f.isAnnotationPresent(PersonInfoAnnotation.class);
if(hasAnnotation){
PersonInfoAnnotation personInfoAnnotation = f.getAnnotation(PersonInfoAnnotation.class);
System.out.println("名字:" + personInfoAnnotation.name() + "\n" +
"年龄:" + personInfoAnnotation.age() + "\n" +
"性别:" + personInfoAnnotation.gender() + "\n");
for(String language : personInfoAnnotation.language()){
System.out.println("开发语言:" + language);
}
}
}
}
//解析方法注解
public static void parseMethodAnnotation() throws ClassNotFoundException{
Class clazz = Class.forName("demo.annotation.MyCourse");
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
/*
* 判断方法中是否有指定注解类型的注解
*/
boolean hasAnnotation = method.isAnnotationPresent(CourseInfoAnnotation.class);
if(hasAnnotation){
CourseInfoAnnotation courseInfoAnnotation = method.getAnnotation(CourseInfoAnnotation.class);
System.out.println("课程名:" + courseInfoAnnotation.courseName() + "\n" +
"课程标签:" + courseInfoAnnotation.courseTag() + "\n" +
"课程简介:" + courseInfoAnnotation.courseProfile() + "\n"+
"课程序号:" + courseInfoAnnotation .courseIndex() + "\n");
}
}
}
public static void main(String[] args) throws ClassNotFoundException {
//parseTypeAnnotation();
parseFieldAnnotation();
//parseMethodAnnotation();
}
}
注解获取属性值的底层实现是怎样的?
当我们在运行时需要获取注解的属性值时,Java虚拟机(JVM)会使用反射机制来加载类,并在内存中创建代表这些类的Class对象。通过这些Class对象,我们可以获取到类、方法、字段等程序单元的注解信息。为了实现更复杂的逻辑,Java中可能会使用代理对象来处理注解。例如,当使用动态代理模式时,可以在运行时生成代理对象,这些代理对象可以用来拦截对目标对象的访问,并在访问前后执行一些额外的逻辑,包括获取注解的属性值。
在Java虚拟机(JVM),为了获取注解及其注解信息,会在sun=》proxy=》自动生成一个中间的代理对象去实现注解接口,因为这个代理对象是在运行时生成的,所以称为动态代理对象