java学习-自定义注解,使用注解,注解的信息获取,注解继承

本文深入讲解Java注解的定义、使用及实现原理,包括注解的类型、参数、保留策略、重复使用、继承等核心概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

注解(区别于注释)

注释 常用 “//” 和 “/* */” 是程序员编写程序时添加的一些注释性内容,不会被程序读取,对程序没有影响

注解(英文:Annotation)注解在框架中很常用 如spring中的@configation @bean @sercice @controller等。
注解是对代码的一种增强,可以在代码编译或者程序运行期间获取注解的信息,然后根据这
些信息做各种牛逼的事情。
注解可以使代码更简洁,可以完成一定的功能。下面我们来学习并使用注解

定义注解

jdk中和注解有关的类都定义在java.lang.annotation包中
注解的本质还是接口,注解继承了Annotation接口!!!

注解使用@interface来定义
例如:定义一个Myannotation注解

package com.test;
public @interface MyAnnotation {
    
}

带参数的注解
[public] 参数类型 参数名称() [default 默认值]
[]代表可写可不写。

package com.test;
public @interface MyAnnotation {
    public String name() default "tom";
    public int age() default 20;
    public String address() default "BeiJing";
}

注意:

1.访问修饰符必须是public 不写默认public
2.参数类型必须是基本数据类型、string、class、枚举类型、注解类型以及上述类型的数组。
  像Integer、Double这些都不行。
3.参数名字一般定义为名词,如果只有一个参数,一般定义为value。
  因为:只有一个参数,并且名称为value时,注解使用时可以省略参数名。
  即:
  public @interface MyAnnotation {
 	public String name() default "tom";
  	public int age() default 20;
    public String address() default "BeiJing";
 }
 这时可以直接写@MyAnnotation("aaa")
  default设定参数默认值,使用注解时不指定参数,参数使用默认值。
4.参数名称后面的()内不能添加任何东西,这只是个特殊的语法。
5.如果没有默认值,使用注解时必须赋值

指定注解的使用范围:@Target

它的参数value赋值需从ElementType枚举类中选择。
自定义注解上也可以不使用@Target注解,如果不使用,表示自定义注解可以用在任何地方。
举例:

@Target(value = {ElementType.TYPE,ElementType.METHOD})

贴一下ElementType枚举类型

public enum ElementType {
    /* 类、接口(包括注释类型)或枚举声明 */
    TYPE,
    /* 字段声明(包括enum常量) 就是成员变量 */
    FIELD,
    /* 方法声明 */
    METHOD,
    /* 正式的参数声明 */
    PARAMETER,
    /* 构造函数声明 */
    CONSTRUCTOR,
    /* 局部变量声明 */
    LOCAL_VARIABLE,
    /* 注释类型声明 */
    ANNOTATION_TYPE,
    /* 包声明 */
    PACKAGE,
    /* 类型参数声明 jdk1.8及以后 */
    TYPE_PARAMETER,
    /** 类型的使用 jdk1.8及以后 */
    TYPE_USE
}

TYPE_PARAMETER 这里说一下用于泛型时的情况
先定义注解Annotation1

@Target(ElementType.TYPE_PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Annotation1 {
    String value();
}
package com.test.annotation;

import java.lang.reflect.TypeVariable;

public class TestAnnotation <@Annotation1("在类上声明的第一个泛型") T0,@Annotation1("在类上声明的第二个泛型") T1>{

    public <@Annotation1("在方法上声明的泛型") T3> void testMethod(){

    }

    public static void main(String[] args) {
        TypeVariable<Class<TestAnnotation>>[] typeParameters = TestAnnotation.class.getTypeParameters();
        for (TypeVariable index:typeParameters){
            System.out.println(index.getName());
            Annotation[] annotations = index.getAnnotations();
            for ( Annotation example:annotations) {
                System.out.println(example);
            }
        }
    }

}

TypeVariable:用来表示类型变量信息,如:类上定义的泛型类型变量,方法上面定义的泛型类型变量
getTypeParameters()方法是获取实例的类型参数数组
getAnnotations()获取注解信息
输出结果:

T0
@com.test.annotation.Annotation1(value=在类上声明的第一个泛型)
T1
@com.test.annotation.Annotation1(value=在类上声明的第二个泛型)

指定注解的保留策略:@Retention

value:RetentionPolicy枚举类型值

public enum RetentionPolicy {
    SOURCE,
    CLASS,
    RUNTIME
}

java程序的3个过程:源码,class字节码文件,字节码文件被虚拟机加载运行。
这三个过程对应上面枚举类的三个值。
举例:

@Retention(RetentionPolicy.SOURCE)
package Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(value=RetentionPolicy.RUNTIME)

public @interface myannotation {
	//属性
	String name() default "未赋值";//默认值
	int age() default 0 ; 
}

文档注解:@Documented

在声明注解时指定 @Documented,则它会被 javadoc 之类的工具处理, 注解类型信息也会会被包括在生成的文档中。
在对API生成文档是起作用。使API文档有此注解信息。
一般jdk中自带的注解都有此注解标识,使得其生成的api文档显示自带的注解类型信息。

使用注解

无参数注解

package com.test.annotation;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Annotation1 {
    
}
@Ann1 //@2
public class UseAnnotation1 {
}

一个参数的注解

package com.test.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author zhangzhibin
 * @create 2021-04-28 8:09
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Annotation1 {
    public String value() default "Tom";
}
@Annotation1(value = "Jack")
public class Test{

}

参数为value的注解,可以省略参数名称

@Annotation1("jack")
public class Test{

}

数组类型参数

package com.test.annotation;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Annotation1 {
    public String value() default "Tom";
    public String[] address();
}
@Annotation1(address = {"上海","北京","广州"})
class Test{

}

注解信息的获取

Java在 java.lang.reflect 反射包下增设了AnnotatedElement 接口

主要用于表示目前正在虚拟机中运行的程序中已使用注解的元素,通过该
接口提供的方法可以利用反射技术地读取注解的信息

Package:用来表示包的信息
Class:用来表示类的信息
Constructor:用来表示构造方法信息
Field:用来表示类中属性信息
Method:用来表示方法信息
Parameter:用来表示方法参数信息

以上这些类实现了AnnotatedElement接口

设定Annotation1注解

package com.test.annotation;
@Target({
        ElementType.PACKAGE,ElementType.TYPE,ElementType.FIELD,
        ElementType.CONSTRUCTOR,ElementType.METHOD,ElementType.PARAMETER,
        ElementType.TYPE_PARAMETER,ElementType.TYPE_USE
})
@Retention(RetentionPolicy.RUNTIME)
public @interface Annotation1 {
    String value();
}

测试类

package com.test.annotation;
import java.util.Map;

@Annotation1("用在了类上")
public class TestAnnotation<@Annotation1("用在了类变量类型V1上") V1, @Annotation1("用在了类变量类型V2上") V2> {

    @Annotation1("用在了字段上")
    private String name;

    private Map<@Annotation1("用在了泛型类型上1") String, @Annotation1("用在了泛型类型上2") Integer> map;

    @Annotation1("用在了构造方法上")
    public TestAnnotation() {
        this.name = name;
    }

    @Annotation1("用在了返回值上")
    public String m1(@Annotation1("用在了参数上") String name) {
        return null;
    }
}

下面我们来解析以上注解信息

package com.test.annotation;

import org.jboss.logging.NDC;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;

public class Main {
    public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
        //解析类上的注解
        Annotation[] annotations0 = TestAnnotation.class.getAnnotations();
        for ( Annotation ex:annotations0){
            System.out.println(ex);
        }
        //解析类上的类型变量注解 就是2个泛型
        for (TypeVariable<Class<TestAnnotation>> index:TestAnnotation.class.getTypeParameters()){
            Annotation[] annotations1 = index.getAnnotations();
            for (Annotation ex:annotations1){
                System.out.println(ex);
            }
        }
        //解析字段上的注解
        Field name = TestAnnotation.class.getDeclaredField("name");
        for (Annotation ex:name.getAnnotations()){
            System.out.println(ex);
        }
        //解析泛型字段map上的注解
        Field map = TestAnnotation.class.getDeclaredField("map");
        AnnotatedParameterizedType annotatedType = (AnnotatedParameterizedType)map.getAnnotatedType();
        AnnotatedType[] annotatedActualTypeArguments = annotatedType.getAnnotatedActualTypeArguments();
        int i = 0;
        for (AnnotatedType actualTypeArgument:annotatedActualTypeArguments){
            for (Annotation annotation : actualTypeArgument.getAnnotations()) {
                System.out.println(annotation);
            }
        }
        //解析方法上的注解
        Method method = TestAnnotation.class.getMethod("m1", String.class);
        for (Annotation annotation : method.getAnnotations()) {
            System.out.println(annotation);
        }
        //解析方法参数注解
        method = TestAnnotation.class.getMethod("m1", String.class);
        for (Parameter parameter : method.getParameters()) {
            for (Annotation annotation : parameter.getAnnotations()) {
                System.out.println(annotation);
            }
        }
    }
}

@Repeatable重复使用注解

使得注解可以重复使用,@Repeatable中value的值为容器注解
举例子:
先定义一个容器注解A1 A1的value属性类型为 A2注解的数组

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface A1 {
    A2[] value();
}

在定义A2注解 要重复使用的注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(A1.class)//指定容器注解
@interface A2 {
    String name() default "Tom";
}

重复使用注解

package com.test.annotation;
@A2(name = "tom")
@A2(name = "tom")
public class TestAnnotation {

    public static void main(String[] args) {
    }
}

重复使用注解方法2

package com.test.annotation;
@A1({@A2(name = "tom"),@A2(name = "tom")})
public class TestAnnotation {
    public static void main(String[] args) {
    }
}


注解继承

JDK中定义注解不可继承,但是在spring中有一个@AliasFor可实现注解继承效果,介于此分类专栏是java学习而不是框架专栏,写在最后作为扩展资料。
注解虽然不可继承,但是其实现类可以。

实现类之间的注解继承:@Inherited

先看一个JDK中的一个API注解:@Inherited
@Target(ElementType.ANNOTATION_TYPE)可以看出这是一个修饰注解的注解。

package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {

}

作用:让子类可以继承修饰父类的注解,前提是此注解已被@Inherited修饰。
@Inherited使得被其修饰的注解能够被继承。

举个例子:

package com.test.annotation;

import java.lang.annotation.*;

public class TestAnnotation {
    //在类中定义注解 成员注解
    @Target(ElementType.TYPE)//修饰字段的 (成员变量)
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited //指明下面定义的注解会被继承
    @interface A1{
    }
    
    @A1   //注解修饰静态内部类C1
    static class C1{}
    //静态内部类C2继承C1
    static class C2 extends C1{}
    
    public static void main(String[] args) {
        for (Annotation annotation : C2.class.getAnnotations()) {
            System.out.println(annotation);
        }
    }
}


输出结果

@com.test.annotation.TestAnnotation$A1()

这里@Inherited使得修饰C1类的注解A1,继承到了C1的子类C2中。

扩展资料:注解的继承:spring中的@AliasFor注解

@AliasFor源码:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface AliasFor {
@AliasFor("attribute")
String value() default "";
@AliasFor("value")
String attribute() default "";
Class<? extends Annotation> annotation() default Annotation.class;
}

AliasFor注解中 value 和 attribute 互为别名,随便设置一个,同时会给另外一个设置相同的值。

先看一段简单代码。
AnnotatedElementUtils是spring提供的一个查找注解的工具类。
使用前别忘了引入spring的maven依赖。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface A1 {
String value() default "a";
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@A1
@interface B1 {
String value() default "b";
}

@B1("bbb")
public class TestAnnotation{
	@Test
	public void test1() {
	System.out.println(AnnotatedElementUtils.getMergedAnnotation(UseAnnotation13.class, B1.class));
	System.out.println(AnnotatedElementUtils.getMergedAnnotation(UseAnnotation13.class, A1.class));
	}
}

此时如果想在 TestAnnotation测试类中,给B1上A1设置值是没办法的,注解不可继承。
下面引入Spring @AliasFor:对注解进行增强
案例:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface A1 {
    String value() default "a";
}

添加了两行,意思是将A1中的value字段和B1中的a1Value字段建立映射。
a1Value是value的别名。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@A1
@interface B1 {
    String value() default "b";
    @AliasFor(annotation = A1.class,value = "value")
    String a1Value();
}

测试类

package com.test.annotation;

import org.springframework.core.annotation.AnnotatedElementUtils;


@B1(value = "bbb",a1Value = "aaa")
public class TestAnnotation {
    public static void main(String[] args) {
        B1 test1 = AnnotatedElementUtils.getMergedAnnotation(TestAnnotation.class, B1.class);
        A1 test2 = AnnotatedElementUtils.getMergedAnnotation(TestAnnotation.class, A1.class);
        System.out.println(test1);
        System.out.println(test2);
    }
}

输出结果

@com.test.annotation.B1(value=bbb, a1Value=aaa)
@com.test.annotation.A1(value=aaa)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

静安书以沫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值