作为一名开发人员,注解的的使用是最常见的了,比如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编译器忽略特定的编译警告)
至于注解内部详细内容,大家可以点进去源码查看。希望本篇文章对你有所帮助!