引子:全中国的Java程序员都知道,现在主流的项目架构都是ssm、ssh、springcloud等,而这些框架都离不开spring,而spring中使用了大量的注解(包括spring自定义的注解)。因此想要会用注解,我们就得知道Java注解的原理和基本用法,这样有助于我们在项目中如鱼得水。
在JDK5.0中,新增了很多对现在影响很大的特性,如:枚举、自动装箱和拆箱、注解、泛型等等。其中注解的引入,是为了增加对元数据的支持。
注解:是一个接口,程序可以通过反射来获取指定程序元素的Annotation对象,然后通过Annotation对象来取得注解里的元数据,注解能用来为程序元素(包、类、方法、成员变量等)设置元数据,它不影响程序代码的执行。如果希望让程序中的Annotation在运行时起一定作用,只有通过某种配套的工具对Annotation中的信息进行访问和处理,这个工具同城为APT(Annotation program Tool)
JDK5.0除了增加注解特性之外,还提供了5个基本的Annotation:
- @Overrride:限定重写父类方法(旨在强制性提醒)
- @Deprecated:表示某个程序元素(类、方法)已过时
- @SuppressWarnings:抑制编译警告
- @SafeVarargs:堆污染警告
- @FunctionalInterface:指定某个接口必须为函数式接口 注:函数式接口是指一个接口中只包含一个抽象方法(可以包含多个默认方法或多个static方法)
以上这几个注解,在我们的项目经常可见,但是因为他们对程序只是一个强制提醒或者警告作用,并不影响程序的执行,因为我们都没在意这些注解的作用,而当spring出来之后,大量的注解眼花缭乱,作用各异,但他们都有一个共同作用: 让我们在编码过程中简化了不少重复性的代码。 因此我们在了解了注解的原理之后,必须要能自定义一些注解并且使用它到项目中去,才能让我们更好的了解和使用它。 而要想自定义注解, 就必须得了解Java提供的几个元注解 那什么是元注解呢? 元注解:就是负责注解其它注解的注解
在Java5之后定义了4个标准的元注解,分别是:
- @Target
- @Retention
- @Documented
- @Inherited
@Target
target注解用来标识注解所修饰的对象范围。
它的可用范围(ElementType的取值范围)有:
-
CONSTRUCTOR:用于描述构造器(构造方法)
-
FIELD:用于描述域(成员变量)
-
LOCAL_VARIABLE:用于描述局部变量(局部变量)
-
METHOD:用于描述方法(普通方法)
-
PACKAGE:用于描述包(包定义)
-
PARAMETER:用于描述参数(如catch等参数)
-
TYPE:用于描述类、接口(包括注解类型) 或enum声明
-
ANNOTATION_TYPE:用于注解
使用实例:
@Target(ElementType.FIELD)
public @interface TargetTest6{
}
@Target({ElementType.TYPE_PARAMETER,ElementType.METHOD})
public @interface TargetTest7{
}
复制代码
@Retention
@Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。
它的取值(RetentionPoicy)有:
-
SOURCE:在源文件中有效(即源文件保留),注解只保留在源代码中,编译器直接丢弃这种注解。
-
CLASS:在class文件中有效(即class保留),编译器把注解记录在class文件中,当Java程序运行时,JVM不能获取该注解的信息。
-
RUNTIME:在运行时有效(即运行时保留),编译器将把注解记录在class文件中,当Java运行时,JVM可以获取注解的信息,程序可以通过反射获取该注解的信息。
1@Target(ElementType.FIELD) 2@Retention(RetentionPolicy.RUNTIME) 3public @interface TargetTest6{ 4} @Inherited @Inherited注解指定被它休市的注解将具备继承性:如果莫个类使用了@XXX注解,则其子类自动被@XXX修饰
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface TargetTest6{
}
//所有使用了@TargetTest6注解的类讲具备继承性,
//也就是它的子类自动带上@TargetTest6注解
复制代码
@Documented
@Documented用于指定被该注解修饰的注解类将被javadoc工具提取城文档.
1@Target(ElementType.FIELD)
2@Retention(RetentionPolicy.RUNTIME)
3@Inherited
4@Documented
5@interface TargetTest6{
6}
7//javadoc工具生成的API文档将提取@Documented的使用信息
复制代码
以上就是所有的元注解以及他们的作用分析, 有了这些元注解有什么用呢? 。。。。 当然是为了方便我们在项目中自定义注解咯 那自定义注解怎么自定义呢? 下面我们来看看介绍如何自定义注解并利用注解完成一些实际的功能
语法:
类修饰符 @interface 注解名称{
//成员变量,在注解中以无形参的形式存在
//其方法名和返回值定义了该成员变的名字和类型
String name();
int age();
}
复制代码
可以看到:注解的语法和接口的定义非常类似,他也一样具备有作用域,但是它的成员变量的定义是以无形参的方法形式存在,名字定义了成员变量的名字,返回值定义了变量类型。 下面我们来自定义一个注解:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnonTest {
int age();
String name();
}
复制代码
注解@AnonTest在运行时有效,作用域在成员变量上,它有两个成员变量分别是int型的age和String型的name。 接下来我们就可以使用这个注解了
public class test {
@AnonTest(age = 0, name = "1")
public Integer tes;
}
复制代码
这样就是一个注解的定义和使用了,有人会疑惑说,spring中很多注解都是@xxx,为什么这个@AnonTest一定要要带上两个成员变量呢? 原因很简单: 注解中的成员变量如果没有默认值,则在使用注解时必须要给成员变量赋值 但如果成员变量有默认值,那可以直接在定义注解时,赋值上去,这样在使用时就可以省略不写
@Target(ElementType.FIELD)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface AnonTest {
int age() default 1;
String name() default "注解辣鸡";
}
复制代码
这样在调用注解时就可以不赋值
public class test {
@AnonTest
public Integer tes;
}
复制代码
上面看到,我们已经使用了注解,但是我们并没发现@AnonTest对我们的tes成员变量有任何作用,这是因为注解本身在程序中是不会生效的,而是需要程序来提取数据并且处理注解本应该做的工作。 因此 我们需要知道如何从注解中提取信息并且做相应的处理。 Java使用Annotation接口来代表程序元素前面的注解,该接口是所有注解的父接口,该接口主要有以下几个实现类:
-
Class:类定义
-
Constructor:构造器定义
-
Field:类成员变量定义
-
Method:类的方法定义
-
Package:类的包定义
AnnotatedElement接口是所有程序元素所实现的接口的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下几个方法来访问注解信息:
getAnnotation(Class annotationClass): 返回改程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。
getAnnotations():返回该程序元素上存在的所有注解。
isAnnotationPresent(Class annotatonClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.
getDeclaredAnnotations():返回直接存在于此元素上的所有注释。
复制代码
有了这些方法,我们就可以在类、方法、成员变量等程序元素中获取到注解信息 比如:
1@Component
2@Service
3public class test {
4
5 @AnonTest
6 public Integer tes;
7
8 @Deprecated
9 public void m1(){
10
11 }
12
13 public static void main(String[] args) {
14 try {
15 //获取所有test类上的注解
16 Annotation[] ar=Class.forName("com.anon.test").getAnnotations();
17 //获取所有test类上m1方法的注解
18 Annotation[] ar1=Class.forName("com.anon.test").getMethod("m1").getAnnotations();
19 //遍历所有的注解,检测是否有我们想要的某个注解 如@Component注解
20 for(Annotation a:ar){
21 System.out.println(a.getClass());
22 if(a instanceof Component)
23 System.out.println("用了注解:"+a);
24 }
25 } catch (Exception e) {
26 // TODO Auto-generated catch block
27 e.printStackTrace();
28 }
29 }
30}
31
32//输出:
33//用了注解:@org.springframework.stereotype.Component(value=)
34//class com.sun.proxy.$Proxy4
复制代码
利用AnnotatedElement提供的方法能获取到我们想要的注解信息之后,就可以针对注解做一些特定的行为。
例如,对于所有的注册用户信息,系统需要把名称和年龄上报到另一个年龄分布表中,就可以利用注解就可以完成这样的动作
1public class test {
2 @AnonTest(name="张小龙",age=25)
3 public Integer tes;
4 public static void main(String[] args) {
5 test.getInfo(test.class);
6 }
7 public static void getInfo(Class<?> clazz) {
8 //获取目标类的所有成员变量
9 Field[] fields = clazz.getDeclaredFields();
10 for (Field field : fields) {
11 //查找变量中是否有存在@AnonTest注解
12 if (field.isAnnotationPresent(AnonTest.class)) {
13 //如果存在,则做相应处理
14 AnonTest anon= field.getAnnotation(AnonTest.class);
15 System.out.println("名称是:"+anon.name());
16 System.out.println("年龄是:"+anon.age());
17 System.out.println("上报信息到用户年龄分布表中");
18 //上报信息到用户年龄分布表中
19 //uploadInfoToLog(anon.name()),anon.age())
20 }
21 }
22 }
23}
复制代码
输出:
1名称是:张小龙
2年龄是:25
3上报信息到用户年龄分布表中
复制代码
最终程序按照我们的需求运行并且输出正确的信息,由此可见,注解对于Java来说是一种补充,他的存在与否对程序来说应该是无影响的,灵活使用注解能使开发的效率更高,而且程序规范性也得到增强。
觉得本文对你有帮助?请分享给更多人
关注「编程无界」,提升装逼技能