java中如何自定义注解

本文深入探讨Java注解的定义、作用及自定义方法,详解元注解、注解属性及反射获取注解值,附带JDK自带注解介绍。

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

作为一名开发人员,注解的的使用是最常见的了,比如Spring框架里的业务层注解@Service、@Transaction,控制层用的@Controller、@Autowired,SpringBoot框架的启动类注解@SpringBootApplication等等。那么如何自定义注解呢?

一、什么是注解

注解(Annotation)是元数据的一种形式,从JDK5.0 引入,它能为代码提供一些相关数据,以便于在代码编译或运行时使用。

二、注解的作用

Java中的注解可以修饰类、方法、变量、参数等。注解可以通过反射手段获取其内容,在编译器生成类文件时,注解可以被嵌入到字节码中。当然JVM也可以保留注解的内容,在运行时动态,总结起来主要是以下几个层面:

  • 编译器根据注解在编译代码时行进行提示警告或错误信息
    例如我们在使用java.util包下的Date类时,调用了类中被@Deprecated标注的方法,IDE会在编译时会有警告信息
public static void main(String[] args) {
    Date date = new Date();
    //JDK源码中,Date类的getDay方法被@Deprecated注解标注,代表方法已过时
    int day = date.getDay();
}
  • 编译运行时时根据注解动态生成代码
    例如在springboot框架中,我们实现一个Controller层方法的前置通知,通过使用@Aspect、@Before等注解即可,当然这些注解是框架封装好的,屏蔽了底层的细节,但是AOP的原理,大家应该都很熟悉
@Slf4j
@Component
@Aspect
public class MyAspect {
     
    @Before(value = "execution(public * com.test.controller.*.*(..))")
    public void before(JoinPoint joinPoint) {
        log.info("CLASS_METHOD:[{}]" , joinPoint.getSignature().getName());
    }
}
  • 程序运行时使用注解进行动态赋值
    比如通过使用@Value注解,将配置文件参数赋给代码里面变量,例如SpringBoot里面集成RabbitMq时,账户密码等配置信息通过注解进行配置
@Configuration
public class RabbitMqConfig {

    @Value("${spring.rabbitmq.host}")
    private String rabbitMqHost;

    @Value("${spring.rabbitmq.port}")
    private String rabbitMqPort;
}

三、自定义注解

首先我们先看下一个注解示例,下面是javafx.beans包下的@DefaultProperty注解 :

package javafx.beans;

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

/**
 * Specifies a property to which child elements will be added or set when an
 * explicit property is not given.
 *
 * @since JavaFX 2.0
 */
@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface DefaultProperty {
    /**
     * The name of the default property.
     */
    public String value();
}

我们看到定义注解与定义一个class类相似,不过其中class关键字被替换为了@interface,注解里声明了一个String类型的value属性,注意这种声明属性方式!稍后会详细说明。同时还可以看到DefaultProperty注解被@Inherited、@Documented、@Retention(RetentionPolicy.RUNTIME)、@Target(ElementType.TYPE)这些注解所修饰,这就是我们需要知道另外一个概念——元注解(meta-annotations)。

1.元注解

元注解是我们在定义注解时需要用到的一些特殊含义的注解,可以说它们是对声明注解时的注解。java语言为我们默认提供了以下元注解,在java.lang.annotation包下,我们来看下:

  • @Retention(RetentionPolicy.XX) 注解的保留域,表示注解的保留范围,可选项有

    • RetentionPolicy.SOURCE – 源代码级别保留,编译器编译后该类型的注解就被丢弃掉了,生成的.class字节码文件中,将不再存在该类型的注解.
    • RetentionPolicy.CLASS – .class字节码文件中保留,编译器编译后保留,JVM加载后丢弃掉,运行时无法获取
    • RetentionPolicy.RUNTIME – 运行时保留,在运行时,JVM使用反射,可以获取注解属性内容,绝大多数注解在定义是使用都是该级别
  • @Target( ElementType.XX) 指定该注解可以使用的地方,如类声明、方法声明,变量声明等等, 在定义注解时,如果没有使用Target指定,默认都可以使用。如果使用了Target指定使用的位置,那么该注解只能在所指定的位置使用

    • ElementType.ANNOTATION_TYPE 注解类型声明
    • ElementType.CONSTRUCTOR 构造方法
    • ElementType.FIELD 字段声明(包括枚举常量)
    • ElementType.LOCAL_VARIABLE 局部变量声明
    • ElementType.METHOD 方法声明
    • ElementType.PACKAGE 包声明
    • ElementType.PARAMETER 方法的参数声明
    • ElementType.TYPE 类、接口(包括注解类型)或enum声明
  • @Documented 表示在使用Javadoc工具生成文档时,包含此注解信息

  • @Inherited 表示当前注解是可继承的,父类中所使用的注解如果被@Inherited修饰,子类会继承父类中对应的注解

2.注解属性

明白了注解的外在定义形式,那么我们就来看下注解内部的属性的定义方式,Talk is cheap. Show me the code多说无益,直接上代码

@Documented
@Inherited
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    /**
     *1、属性的定义和接口中方法声明类似,访问修饰符默认是public,可省略,注意属性名后面跟了()
     */
    String name();
    
    /**
     * 2、可以通过default为属性指定默认值,在注解使用时,可以为之赋值, 也可以不赋值
     *  当然如果不通过default为属性指定默认值,在注解使用必须使用该属性并且为之赋值
     */
    int age() default 1;
    
    /**
     * 3、注解中的属性value比较特殊,如果使用注解时仅为该属性赋值,"value="可以省略掉,
     * 但是如果和其他属性同时赋值,“value=”则不能省略,这个特性和value的属性类型无关
     */
    String value() default "";
   
    /**
     * 4.注解中属性的类型可以是基本数据类型及其数组、类、枚举、注解
     */
    boolean sex() default true;
    
}

四、通过反射获取注解属性值

在上面代码中,我们自定义了@MyAnnotation注解,并且指明@Target({ElementType.METHOD})表明此注解只能用在方法声明上@Retention(RetentionPolicy.RUNTIME)指定其保留到代码运行时,所以我们可以通过反射获取MyAnnotation的属性值。
下面我们定义了一个Person类,并在其中定义了一个sayHello方法,在方法声明上,我们使用@MyAnnotation注解,我们将使用反射调用sayHello方法,并且使用MyAnnotation注解中的属性值

package com.test.annotation;
import com.test.enu.Color;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Person {

    @MyAnnotation(name="韩梅梅",age = 30,sex = true,clothes = Color.YELLOW)
    public  void sayHello(String name){
        System.out.println("Hello!" + name);
    }

    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
        Person person = new Person();
        //获取Person类的Class对象
        Class<? extends Person> personClass = person.getClass();
        //获取类中声明的方法列表
        Method[] declaredMethods = personClass.getDeclaredMethods();
        for (Method method : declaredMethods) {
            //判断当前方法是否含有MyAnnotation注解
            if(method.isAnnotationPresent(MyAnnotation.class)){
                //获取MyAnnotation类型注解
                MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class);
                //反射调用方法,并传递注解name属性值为参数
                Object invoke = method.invoke(person,myAnnotation.name());
                //打印注解中定义的各个类型的值
                System.out.println(myAnnotation);
                System.out.println(myAnnotation.name()+","+myAnnotation.sex()+","+myAnnotation.age());
            }
        }
    }
}

此时的IDE控制台输出,说明我们通过反射在运行时获取到了@MyAnnotation注解的值

Hello!韩梅梅
韩梅梅,true,30
@com.test.annotation.MyAnnotation(value=, age=30, sex=true, name=韩梅梅, clothes=YELLOW)

五、JDK内部自带注解

JDK自带了一些原先定义好的注解,我们可以直接使用

  • @Override 表示当前方法覆盖了父类的方法
  • @Deprecation 表示方法已经过时,方法上有横线,使用时会有警告。
  • @SuppviseWarnings 表示关闭一些警告信息(通知java编译器忽略特定的编译警告)

至于注解内部详细内容,大家可以点进去源码查看。希望本篇文章对你有所帮助!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值