Java注解学习

是什么?

        Java注解(Annotation)是Java语言中的一种特殊标记,它可以用来为类、方法、变量、参数等元素添加元数据(metadata)信息。注解可以在源代码中嵌入元数据,这些元数据可以被编译器、工具和运行时环境使用。元数据是添加到程序元素如方法,字段,类和包上的额外信息。注解是一种分散式的元数据设置方式,xml是集中式的设置方式。注解不能直接干扰程序代码的运行。

有什么功能?

  • 作为特定标记,用来告诉编译器一些信息,如 @Override注解,检查当前的方法签名是否真正重写的父类的方法。
  • 编译时动态处理,如lombok的lombok@Data可以动态生成代码。
  • 运行时动态处理,作为额外信息的载体,如获取注解信息

 注解的分类?

  • 标准注解  
  1. @Override:用于标记方法覆盖(重写)了父类的方法。
  2. @Deprecated:用于标记已经过时(不推荐使用)的元素。
  3. @SuppressWarnings:用于抑制编译器警告。
  4. @SafeVarargs:用于标记方法使用了可安全使用的可变参数。
  5. @FunctionalInterface:用于标记接口为函数式接口。
  • 元注解
  1. @Target:指定注解可以应用的程序元素类型,如类、方法、字段等。
  2. @Retention:指定注解的保留策略,包括SOURCE(源码级别保留)、CLASS(类文件保留)和RUNTIME(运行时保留)。
  3. @Documented:指定注解是否包含在Java文档中。
  4. @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
}
  1. SOURCE:表示注解只保留在源代码中,编译器会丢弃这种类型的注解,不会包含在编译后的字节码文件中,也不会被加载到JVM中。这意味着这种类型的注解在运行时不可见,只在编译期起作用。

  2. CLASS:表示注解会被记录在编译后的字节码文件(.class文件)中,但在运行时不会被加载到JVM中。这意味着虽然这种类型的注解会被保留到编译后的文件中,但在程序运行时无法通过反射等手段获取到这些注解信息。

  3. 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接口定义了一系列方法,用于获取注解以及注解相关的信息。

  1. getAnnotation(Class<T> annotationClass):返回指定类型的注解,如果该类型的注解不存在,则返回null。

  2. getAnnotations():返回此元素上存在的所有注解,包括从父类、接口继承而来的注解。

  3. isAnnotationPresent(Class<? extends Annotation> annotationClass):判断指定类型的注解是否存在于该元素上。

  4. 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=》自动生成一个中间的代理对象去实现注解接口,因为这个代理对象是在运行时生成的,所以称为动态代理对象

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值