深入理解JAVA中的注解
本文来说下JAVA中的注解,这个技术虽然我们每天都在使用,但是不一定知道其实现原理。本文来详细介绍下JAVA中注解相关的知识。
文章目录
- 概述
- 什么是注解
- 注解的本质是什么
- 注解体系图
- 常用元注解
- @Target元注解
- @Retention元注解
- @Inherited元注解
- @Documented元注解
- 自定义注解
- 自定义注解语法
- 本文小结
概述
现在主流的项目架构都是ssm、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出来之后,大量的注解眼花缭乱,作用各异,但他们都有一个共同作用:让我们在编码过程中简化了不少重复性的代码。
注解的本质是什么
注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。而我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象$Proxy1。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池。
实际上Java注解与普通修饰符(public、static、void等)的使用方式并没有多大区别,下面的例子是常见的注解:
public class AnnotationDemo {@Testpublic static void A(){System.out.println("Test.....");}@Deprecated@SuppressWarnings("uncheck")public static void B(){}}
@Controller注解反编译
相信大家肯定都使用过@Controller注解,我们从这个注解一起来看下注解的本质到底是什么。
@Controller注解的源代码如下
/** Copyright 2002-2017 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** https://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package org.springframework.stereotype;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;/*** Indicates that an annotated class is a "Controller" (e.g. a web controller).** <p>This annotation serves as a specialization of {@link Component @Component},* allowing for implementation classes to be autodetected through classpath scanning.* It is typically used in combination with annotated handler methods based on the* {@link org.springframework.web.bind.annotation.RequestMapping} annotation.** @author Arjen Poutsma* @author Juergen Hoeller* @since 2.5* @see Component* @see org.springframework.web.bind.annotation.RequestMapping* @see org.springframework.context.annotation.ClassPathBeanDefinitionScanner*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {/*** The value may indicate a suggestion for a logical component name,* to be turned into a Spring bean in case of an autodetected component.* @return the suggested component name, if any (or empty String otherwise)*/@AliasFor(annotation = Component.class)String value() default "";}
对@Controller注解生成的class文件进行反编译之后,我们来看下jvm到底为我们生成的是什么。
我们看到jvm为我们生成的内容很简单,就是一个继承了Annotation接口的接口,里面有一个抽象的value()方法。那么我们知道了注解的本质其实就是一个特殊的接口。
注解体系图
元注解
java.lang.annotation中提供了元注解,可以使用这些注解来定义自己的注解。主要使用的是Target和Retention注解。
Annotation接口
前面经过反编译后,我们知道Java所有注解都继承了Annotation接口,也就是说Java使用Annotation接口代表注解元素,该接口是所有Annotation类型的父接口。同时为了运行时能准确获取到注解的相关信息,Java在java.lang.reflect 反射包下新增了AnnotatedElement接口,它主要用于表示目前正在 VM 中运行的程序中已使用注解的元素,通过该接口提供的方法可以利用反射技术地读取注解的信息,如反射包的Constructor类、Field类、Method类、Package类和Class类都实现了AnnotatedElement接口。
AnnotationElement接口
既然上面定义了注解,那得有办法拿到我们定义的注解啊。java.lang.reflect.AnnotationElement接口则提供了该功能。注解的处理是通过java反射来处理的。如下,反射相关的类Class, Method, Field都实现了AnnotationElement接口。
反射处理
AnnotationElement接口方法
因此,只要我们通过反射拿到Class, Method, Field类,就能够通过getAnnotation(Class)拿到我们想要的注解并取值。
常用元注解
因此我们在了解了注解的原理之后,必须要能自定义一些注解并且使用它到项目中去,才能让我们更好的了解和使用它。而要想自定义注解,就必须得了解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被保留的时间长短,取值在java.lang.annotation.RetentionPolicy中:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。
它的取值(RetentionPoicy)有:
- SOURCE:在源文件中有效(即源文件保留),注解只保留在源代码中,编译器直接丢弃这种注解。
- CLASS:在class文件中有效(即class保留),编译器把注解记录在class文件中,当Java程序运行时,JVM不能获取该注解的信息。
- RUNTIME:在运行时有效(即运行时保留),编译器将把注解记录在class文件中,当Java运行时,JVM可以获取注解的信息,程序可以通过反射获取该注解的信息。
只有定义为RetentionPolicy.RUNTIME时,我们才能通过注解反射获取到注解。所以,假设我们要自定义一个注解,它用在字段上,并且可以通过反射获取到,功能是用来描述字段的长度和作用,可以定义如下。
使用实例:
@Target(ElementType.FIELD) // 注解用于字段上
@Retention(RetentionPolicy.RUNTIME) // 保留到运行时,可通过注解获取
public @interface MyField {String description();int length();
}
@Inherited元注解
@Inherited注解指定被它修饰的注解将具备继承性:如果莫个类使用了@XXX注解,则其子类自动被@XXX修饰
使用实例:
@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)@Inherited@interface TargetTest6{}//所有使用了@TargetTest6注解的类讲具备继承性,//也就是它的子类自动带上@TargetTest6注解
@Documented元注解
@Documented用于指定被该注解修饰的注解类将被javadoc工具提取成文档.
使用实例:
@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)@Inherited@Documented@interface TargetTest6{}//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 {@AnonTestpublic Integer tes;
}
上面看到,我们已经使用了注解,但是我们并没发现@AnonTest对我们的tes成员变量有任何作用,这是因为注解本身在程序中是不会生效的,而是需要程序来提取数据并且处理注解本应该做的工作。
有了AnnotationElement接口中的这些方法,我们就可以在类、方法、成员变量等程序元素中获取到注解信息:
package cn.wideth.util.other;import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import java.lang.annotation.Annotation;@Component
@Service
public class Test {@AnonTestpublic Integer tes;@Deprecatedpublic void m1(){}public static void main(String[] args) {try {//获取所有test类上的注解Annotation[] ar=Class.forName("cn.wideth.util.other.Test").getAnnotations();//获取所有test类上m1方法的注解Annotation[] ar1=Class.forName("cn.wideth.util.other.Test").getMethod("m1").getAnnotations();//遍历所有的注解,检测是否有我们想要的某个注解 如@Component注解for(Annotation a:ar){System.out.println(a.getClass());if(a instanceof Component)System.out.println("用了注解:"+a);}} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
程序结果
利用AnnotatedElement提供的方法能获取到我们想要的注解信息之后,就可以针对注解做一些特定的行为。
例如,对于所有的注册用户信息,系统需要把名称和年龄上报到另一个年龄分布表中,就可以利用注解就可以完成这样的动作
package cn.wideth.util.other;import java.lang.reflect.Field;public class Test {@AnonTest(name="张小小",age=20)public Integer tes;public static void getInfo(Class<?> clazz) {//获取目标类的所有成员变量Field[] fields = clazz.getDeclaredFields();for (Field field : fields) {//查找变量中是否有存在@AnonTest注解if (field.isAnnotationPresent(AnonTest.class)) {//如果存在,则做相应处理AnonTest anon= field.getAnnotation(AnonTest.class);System.out.println("名称是:"+anon.name());System.out.println("年龄是:"+anon.age());System.out.println("上报信息到用户年龄分布表中");//上报信息到用户年龄分布表中//uploadInfoToLog(anon.name()),anon.age())}}}public static void main(String[] args) {Test.getInfo(Test.class);}}
程序结果
最终程序按照我们的需求运行并且输出正确的信息,由此可见,注解对于Java来说是一种补充,他的存在与否对程序来说应该是无影响的,灵活使用注解能使开发的效率更高,而且程序规范性也得到增强。
本文小结
本文详细介绍了注解相关的知识。
深入理解JAVA中的注解相关推荐
- 22、java中的注解
注解是什么? 注解可以理解成注释.标记.标签的意思,用来标记类.方法等.就相当于现实生活中的一些事物,上边贴一个标签或者写一些注释性文字来描述它可以用来做什么.怎么用.何时用等信息.Java中的注解也 ...
- Java中的注解是如何工作的
转载自 Java中的注解是如何工作的? 自Java5.0版本引入注解之后,它就成为了Java平台中非常重要的一部分.开发过程中,我们也时常在应用代码中会看到诸如@Override,@Deprecate ...
- Java中的注解(Annotation)处理器解析
Java中的注解(Annotation)是一个很神奇的东西,特别现在有很多Android库都是使用注解的方式来实现的.一直想详细了解一下其中的原理.很有幸阅读到一篇详细解释编写注解处理器的文章.本文的 ...
- java中的注解(二)
今天我继续来介绍java中的注解.注解与接口和类不同的是注解是不允许继承的,但是注解中有一个和继承有关的元注解:@Inherited.如果我们在定义注解时候加上这个元注解那么我们就可以在子类中监测到该 ...
- 深入理解Java中的内存泄漏
理解Java中的内存泄漏,我们首先要清楚Java中的内存区域分配问题和内存回收的问题本文将分为三大部分介绍这些内容. Java中的内存分配 Java中的内存区域主要分为线程共享的和线程私有的两大区域: ...
- 理解Java中的弱引用(Weak Reference)
理解Java中的弱引用(Weak Reference) 本篇文章尝试从What.Why.How这三个角度来探索Java中的弱引用,理解Java中弱引用的定义.基本使用场景和使用方法.由于个人水平有限, ...
- 深入理解Java中的final关键字
深入理解Java中的final关键字 http://www.importnew.com/7553.html Java中的final关键字非常重要,它可以应用于类.方法以及变量.这篇文章中我将带你看看什 ...
- 详解Java中的注解
在Java中,注解(Annotation)引入始于Java5,用来描述Java代码的元信息,通常情况下注解不会直接影响代码的执行,尽管有些注解可以用来做到影响代码执行. 注解可以做什么 Java中的注 ...
- 如何理解 JAVA 中的 volatile 关键字
如何理解 JAVA 中的 volatile 关键字 最近在重新梳理多线程,同步相关的知识点.关于 volatile 关键字阅读了好多博客文章,发现质量高适合小白的不多,最终找到一篇英文的非常通俗易懂. ...
最新文章
- C#可选参数、命名参数、参数数组
- Mybaits 运行原理流程图
- 在创业公司做架构师,你需要解决哪些问题?
- 误删mysql数据库密码后,如何恢复密码
- 结构型模式——适配器模式
- 用于大型事件处理的Akka Java
- SAGAN(G-lab介绍)
- OAuth和OpenID的区别
- ORACLE 11G DATA GUARD配置之Dataguard简介
- 如何利用净推荐值(NPS)测量用户忠诚度?
- word2019关闭时无响应
- 锁定计算机和睡眠有什么区别,电脑win7休眠和睡眠有什么区别?
- Maxima 的绘图功能 1
- 2020城市大脑与超级智能建设规范研究报告(附下载)
- 第五章:3ds max UV展开和BP贴图绘制(下)
- 语言哲学与计算机语言,20世纪语言哲学和心智哲学的发展走向——以塞尔为例...
- C语言中的指针应用,函数指针,指针函数,结构体中定义函数指针。
- easyexcel的使用-个人笔记
- 计算方法 4.线性方程组解法(2)
- 伟平在重阳节给深爱的父母鞠一躬