最近上子路老师的spring源码课,发现部分刚入门的新同学对自定义注解这一块知识不太了解,于是写下这篇文章,希望能够解决一些同学心中的疑问
回到正文,什么是注解?
注解和class,Interface一样,是Java的一种数据类型。注解是不会直接对程序有说明影响的,你可以把它简单的理解为一种标记
怎么自定义一个注解?
自定义注解语句和定义类是一样的,只是声明关键字变成了@interface,如哦public @interface Feiyang
1 package com.feiyang.test; 2 3 public class AnnotationTest { 4 5 }
定义一个注解之后我们就可以使用它了,定义一个类来添加自定义注解
1 package com.feiyang.model; 2 3 import com.feiyang.annotation.FeiyangAnnotation; 4 5 @FeiyangAnnotation 6 public class Feiyang { 7 8 @FeiyangAnnotation 9 private String name; 10 11 @FeiyangAnnotation 12 public Feiyang(String name) { 13 this.name = name; 14 } 15 16 public String getName() { 17 return name; 18 } 19 20 public void setName(String name) { 21 this.name = name; 22 } 23 24 }
我们可以看到类、属性、方法和构造方法上都加上了我们定义的注解,并且程序并未报错
但是我们发现有些注解是只能在特定的地方才能使用,否则就会报错的,比如spring提供的@Component等注解只能用在类上,用在方法等其他地方是会报错的
这是因为Java为我们提供了一些源注解,可以用来定义注解的一些性质,比如@Target
1 package com.feiyang.annotation; 2 3 import java.lang.annotation.ElementType; 4 import java.lang.annotation.Target; 5 6 /** 7 * Target注解是java提供的一种源注解,可以用来定义注解的作用域 8 * 使用Target需要提供ElementType枚举类的值,值可以是一个也可以是多个组成的一个数组 9 * ElementType.TYPE 定义注解可以作用在类、接口等声明上 10 * ElementType.FIELD 定义注解可以作用在属性上 11 * ElementType.METHOD 定义注解可以作用在方法上 12 * ElementType.PARAMETER 定义注解可以作用在方法参数上 13 * ElementType.CONSTRUCTOR 定义注解可以作用在构造方法上 14 * ElementType.LOCAL_VARIABLE 定义注解可以作用在局部变量上 15 * ElementType.ANNOTATION_TYPE 定义注解可以作用在注解上 16 * ElementType.PACKAGE 定义注解可以作用在包声明上 17 * ElementType.TYPE_PARAMETER jdk1.8之后添加的,笔者也不知道 18 * ElementType.TYPE_USE 同上 19 * 如果不加@Target注解,那该注解就能作用在任意地方 20 */ 21 @Target({ElementType.FIELD, ElementType.METHOD}) 22 public @interface FeiyangAnnotation { 23 24 }
添加@Target限制之后就会发现原来注解在类上和构造方法上的注解编译报错,提示FeiyangAnnotation注解不能作用在这里
我们知道有些注解中是存在一些属性(例如Target)的,我们也可以给我们自定义的注解添加属性
1 package com.feiyang.annotation; 2 3 import java.lang.annotation.ElementType; 4 import java.lang.annotation.Target; 5 6 /** 7 * Target注解是java提供的一种源注解,可以用来定义注解的作用域 8 * 使用Target需要提供ElementType枚举类的值,值可以是一个也可以是多个组成的一个数组 9 * ElementType.TYPE 定义注解可以作用在类、接口等声明上 10 * ElementType.FIELD 定义注解可以作用在属性上 11 * ElementType.METHOD 定义注解可以作用在方法上 12 * ElementType.PARAMETER 定义注解可以作用在方法参数上 13 * ElementType.CONSTRUCTOR 定义注解可以作用在构造方法上 14 * ElementType.LOCAL_VARIABLE 定义注解可以作用在局部变量上 15 * ElementType.ANNOTATION_TYPE 定义注解可以作用在注解上 16 * ElementType.PACKAGE 定义注解可以作用在包声明上 17 * ElementType.TYPE_PARAMETER jdk1.8之后添加的,笔者也不知道 18 * ElementType.TYPE_USE 同上 19 * 如果不加@Target注解,那该注解就能作用在任意地方 20 */ 21 @Target({ElementType.FIELD, ElementType.METHOD}) 22 public @interface FeiyangAnnotation { 23 24 /** 25 * 我们可以在注解当中添加方法,这样就能丰富我们注解的功能了 26 * 方法名称就是使用该注解时的属性,返回值类型就是使用是时的属性值的类型,返回值就是使用时的属性值 27 * 例如在某个属性上使用该注解时@FeiyangAnnonation(value = "value属性值", name = "name属性值") 28 * 由于我们定义了一个方法名叫value,使用时就需要提供一个value属性 29 * 我们定义的返回值类型为Stirng,value属性的类型就要是String类型 30 * value属性的值是我们解析该注解的时候用到的 31 * 32 * @return 33 */ 34 public String value(); 35 36 /** 37 * 上面定义的方法在使用时是一定要提供value属性及其属性值的 38 * 如果想在使用时不用填写属性,可以给方法加一个默认值(default) 39 * 40 * @return 41 */ 42 public String name() default ""; 43 44 }
由于value方法没有默认值,如果不为注解提供value属性值就会报错,这里我们为注解添加属性值
有个小知识点,如果注解里面有个方法名称为value,并且使用时你只提供value属性,这时候是不需要写属性名的,可以直接写属性值
如果注解中没有value方法或者需要提供多个属性时,则必须填写"属性 = 属性值"
例如@FeiyangAnnotation("feiyang field value")等于@FeiyangAnnotation(value = "feiyang field value")
而@FeiyangAnnotation(value = "feiyang method value", name = "feiyang method name")不能简写成@FeiyangAnnotation("feiyang method value", name = "feiyang method name")
前面说过,注解是不能直接影响程序的,就像人名一样,我叫肥羊,你也可以叫肥羊,大家都可以叫肥羊,肥羊本身没有任何意义只是一个代号,一种表示
但是我们可以通过解析这种标识去赋予它特殊的意义,就像子路老师通过为大家授业解惑,使子路这个名字有了特殊意义,大家一听到子路老师的名字就浮现出大神、帅气、车快,人快、3秒等词
话不多说,上代码
1 package com.feiyang.test; 2 3 import com.feiyang.annotation.FeiyangAnnotation; 4 import com.feiyang.model.Feiyang; 5 6 import java.lang.reflect.Field; 7 8 public class AnnotationTest { 9 10 public static void main(String[] args) { 11 // 解析注解分为以下几个步骤 12 // 1、获取Class对象 13 // 获取对象可以通过类.class获取,也可以通过对象.getClass方法获取,视具体情况而定 14 Class<Feiyang> clazz = Feiyang.class; 15 // 2、获取注解作用的地方,如属性,方法等。如果注解就是作用在类中,可以跳过这一步 16 // FeiyangAnnotation只能加在属性或者方法上,以属性为例,获取类中所有属性,遍历执行第三步 17 Field[] fields = clazz.getDeclaredFields(); 18 for (Field field : fields) { 19 // 3、判断目标是否添加了该注解 20 if (field.isAnnotationPresent(FeiyangAnnotation.class)) { 21 // 如果添加了注解,我们就可以操作添加了注解的元素或者解析注解了 22 // spring的@Autowired注解就是这个原理,当解析到类中的某个属性添加了@Autowired注解时 23 // spring就会获取这个属性的类型,然后去容器中找对应的bean,如果找到了就注入给这个属性 24 25 // 4、获取注解对象,解析属性 26 // 添加注解的话我们就可以获取到这个注解对象了 27 FeiyangAnnotation annotation = field.getAnnotation(FeiyangAnnotation.class); 28 // 获取到这个注解对象之后我们就可以拿到这个注解的属性值了 29 // 前面我们使用该注解提供的属性值就相当于注解方法的返回值 30 // 如果我们方法有默认值而我们又没有提供对应的属性,我们调用方法获取到的就是默认值 31 String value = annotation.value(); 32 String name = annotation.name(); 33 // 获取到属性值之后我们就可以通过判断属性值确认逻辑了,这里笔者就不演示逻辑了,笔者把属性值打印一下给大家看一下 34 System.out.println(value + " " + name); 35 } 36 } 37 } 38 39 }
运行代码可以看到控制台并没有打印任何信息
这是什么情况?难道笔者前面说的都是错的吗?不要着急,这其实是注解缺少了一个值的原因
我们前面说过Target源注解,Java还提供了另外一个源注解@Retention标记注解存在的时间,我们需要添加这个源注解才能使代码达到我们的预期
通过上面的注释我们可以发现是没有加@Retention注解的原因导致执行时注解就被丢弃了,现在我们加上这个注解并定义为RUNTIME,再次执行程序,可以看到打印出了我们想要的结果
剩下的对方法上的注解的解析大家可以自己去试一下