基本概念:注解
,顾名思义,就是对某一事物进行添加注释说明
,会存放一些信息,这些信息可能对之后某个时段来讲是颇有用处的。Java 注解
(Annotation)又称 Java 标注
,是 JDK5.0 引入的一种注释机制。Java 语言中的类
、方法
、变量
、参数
和包
等均可以被标注(添加某些信息)。在编译器生成类文件时,标注能够被嵌入到字节码中。Java 虚拟机能够保留标注内容,在运行时能够经过反射的方式获取到标注内容 。 固然它也支持自定义 的Java 标注。java
注解与注释的区别程序员
定义不一样
:注解与类、接口在同一层次的,是一种描述数据的数据,能够理解为注解就是源代码的元数据。注释则是对源代码的介绍,方便开发者理解代码的所撰写的文字。数组
做用不一样
:注解是Java 编译器能够理解的部分,是给编译器看的。经过标记包、类、字段、方法、局部变量、方法参数等元数据,告诉jvm这些元数据的信息。注释是程序员对源代码作一些记忆或提示性描述,是给人来看的。它能告诉开发者这段代码的逻辑、说明、特色等内容,对代码起到解释、说明的做用。markdown
使用范围不一样
:使用范围不一样:注解 ,参与代码编译,以@开头的,与工具一块儿使用。对于位置、语法、内容有必定的限制。注释 ,能够随意在任务位置填写内容,对代码任何没有影响。jvm
总之,注解
能够理解为对类、变量、方法和接口进行规范和约束,注释
则理为开发者对代码进行解释而撰写的文字。ide
注解能够根据来源能够分为系统注解
、自定义注解
和第三方注解
,系统注解根据用途能够分为内置注解
和元注解
,在下面的文章中,咱们主要讲解内置注解
、元注解
和自定义注解
。工具
在java.lang包下存在着咱们常常看到的注解,分别是@Deprecated
、@Override和
和@SuppressWarnings
测试
@Deprecated
能够修饰类
、方法
和变量
,被@Deprecated修饰后表示不建议使用,它的存在仅仅是为了兼容之前的程序,因为不能直接把它抛弃,因此将它设置为过期。可是被这个注解修饰的类、方法在高版本的JDK中使用时了可能会出现错误。this
源代码以下编码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
复制代码
其中@Documented、@Retention和@Target是元注解
,咱们在下文介绍
它代表了被注解的方法须要重写
父类中的方法,若是某个方法使用了该注解,却没有覆写超类中的方法,编译器就会报出错误。在子类中重写父类或接口的方法,@Overide
并非必须的。可是仍是建议使用这个注解,由于在某些状况下,假设你修改了父类的方法的名字,那么以前重写的子类方法将再也不属于重写,若是没有@Overide,你将不会察觉到这个子类的方法。有了这个注解修饰,编译器则会提示你这些信息。
源代码以下
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
复制代码
@SuppressWarnings
用来抑制编译器生成警告信息,能够修饰的元素为类
,方法
,方法参数
,属性
,局部变量
。它能够达到抑制
编译器编译时产生警告的目的,使用@SuppressWarnings注解,采用就近原则
,好比一个方法出现警告,尽可能使用@SuppressWarnings注解这个方法,而不是注解方法所在的类。所属范围越小越好,由于范围大了,不利于发现该类下其余方法的警告信息。 可是咱们一般不建议使用@SuppressWarnings注解,使用此注解,开发人员看不到编译时编译器提示的相应的警告,不能选择更好、更新的类、方法或者不能编写更规范的编码。同时后期更新JDK、jar包等源码时,使用@SuppressWarnings注解的代码可能受新的JDK、jar包代码的支持,出现错误,仍然须要修改。
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
复制代码
在上面的代码中,咱们看到了注解上还有注解,这种修饰注解的注解被称为元注解。事实上,元注解
(meta-annotation)的做用就是注解其它的注解,Java在java.lang.annotation包中定义了4个标准的元注解类型,分别为@Target
、@Retention
、@Documented
和@Inherited
@Target
用于描述注解的使用范围,即被描述的注解能够用到什么地方,@Target注解内定义了ElemenType[]
数组,数组以枚举类的形式定义了注解的修饰范围。经过下面的源代码咱们能够看出,该注解可以用于类
、接口
、构造器
、属性
和方法
、参数声明
、注解声明
等
源代码以下
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
复制代码
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/** * Type parameter declaration * * @since 1.8 */
TYPE_PARAMETER,
/** * Use of a type * * @since 1.8 */
TYPE_USE
}
复制代码
@Rentention
表示须要在什么级别保存该注释信息,用于描述注解的生命周期
,经过源代码,咱们能够看出,注解内有个RetentionPolicy
的值,咱们继续深刻往下看,RetentionPolicy
是个枚举类型的值,它有三个值可供选择,SOURCE
是在源代码层面,在编译
是将会失效;CLASS
做用在class文件
中,可是在运行时失效
;RUNTIME
在运行时依旧保留该注解,所以能够经过反射机制来读取注解内的信息。所以,这三个值对应的生命周期大小为:SOURCE<CLASS<RUNTIME
,若是不手动添加的话,则默认为CLASS。
源代码以下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/** * Returns the retention policy. * @return the retention policy */
RetentionPolicy value();
}
复制代码
public enum RetentionPolicy {
/** * Annotations are to be discarded by the compiler. */
SOURCE,
/** * Annotations are to be recorded in the class file by the compiler * but need not be retained by the VM at run time. This is the default * behavior. */
CLASS,
/** * Annotations are to be recorded in the class file by the compiler and * retained by the VM at run time, so they may be read reflectively. * * @see java.lang.reflect.AnnotatedElement */
RUNTIME
}
复制代码
@Documented
代表该注解将被包含在javadoc
中。该注解用的相对较少。
源代码以下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
复制代码
@Inherited
说明子类能够继承父类中的该注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
复制代码
经过使用元注解的组合,咱们能够按照本身的需求来自定义注解,咱们一般使用@interface
来自定义注解,它自动继承了java.lang.annotation.Annotation
接口。接下,咱们来以一段代码仔细分析下如何自定义注解:
public @interface MyAnnotation{
}
复制代码
注解一
是一个最简单的自定义注解,咱们能够看到,它以public
修饰,以@interface
用来声明一个注解,具体的格式为:public @interface 注解名{定义内容}
,若是要在注解内添加一个参数,该怎样定义呢?
@Retention(value = RetentionPolicy.RUNTIME)
@interface myAnnotantion3{
//参数名为vale,当注解内只有一个参数,使用注解时,参数名可省略
String value();
}
复制代码
特别须要注意的是,注解内的方法名称就是参数的名称,而返回值类型就是参数类型,咱们能够这样使用
@Retention(value = RetentionPolicy.RUNTIME)
@myAnnotantion3("snow")
public void test2(){
}
复制代码
由于注解内只有一个参数,因此在使用注解时,参数名称是能够省略的。
若是,咱们咱们想添加多个的参数值,该怎么自定义注解呢
Target(value = {ElementType.METHOD,ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@interface myAnnotaion2{
/** *表示注解的参数,name参数名,String 表示参数的类型 * 参数加default,在注解内可写可不写 */
String name();
int age();
int id();
}
复制代码
同理,咱们只须要在相应的位置引用该注解便可
@myAnnotaion2(name = "Simon",age=25,id=23)
public void test1(){
}
复制代码
若是咱们向给注解内的参数设定默认值,咱们能够这样作
@Target(value = {ElementType.METHOD,ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@interface myAnnotaion2{
/** *表示注解的参数,name参数名,String 表示参数的类型 * 参数加default,在注解内可写可不写 */
String name() default "";
int age() default 0;
int id() default -1;
}
复制代码
在实际的方法使用中,咱们只须要给必需要修改的值赋值便可
@myAnnotaion2(name = "Simon")
public void test1(){
System.out.println("测试注解1");
}
复制代码
所以,自定义注解能够概括以下:
@interface
用来声明一个注解,格式
:public @interface 注解名{定义内容}
其中每个方法实际上声明了一个配置参数
方法的名称
就是参数的名称
返回值类型
就是参数的类型
(返回值只能时基本类型,Class,String,enum)
能够经过default
用来声明参数的默认值
若是只有一个参数成员
,通常参数名为value
注解元素必需要有值,咱们定义注解元素时,常用空字符串0做为默认值
在以上的讲解中,咱们使用注解都是对所修饰的类、方法、变量进行规范和约束
,在大多数使用场景中,以方法
为例,咱们须要将注解中的信息同方法联系起来,即将注解中的参数信息的注入到方法中。
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation5 {
String name() default " ";
int age() default 0;
}
复制代码
@MyAnnotation5(name = "Simon", age = 25)
public void testInjectValue(String name,int age) {
System.out.println("获取注解中的参数值:");
System.out.println(name);
System.out.println(age);
}
复制代码
- 反射获取该类的方法
- 经过方法获取注解中的参数值
- 将注解中的参数值注入到相应的方法中
//反射获取类,并获得类中的方法
Class aClass = InjectValue.class;
Method method=aClass.getMethod("testInjectValue",String.class,int.class);
//获取注解中的属性值
MyAnnotation5 myAnnotation5=method.getAnnotation(MyAnnotation5.class);
String name=myAnnotation5.name();
int age=myAnnotation5.age();
//将属性值注入到相应的方法中
Object o=aClass.newInstance();
method.invoke(o,name,age);
复制代码
前面咱们讲解如何将注解中的参数为基本数据类型注入到方法中,那么如何将注解中的参数为对象注入到方法中呢?
public class Animal {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
复制代码
public class AnimalDao {
private Animal animal;
public Animal getAnimal() {
return animal;
}
@MyAnnotation6(name = "Dog",age = 12)
public void setAnimal(Animal animal) {
this.animal = animal;
}
}
复制代码
public class TestInjectObject {
public static void main(String[] args) throws Exception {
//1.使用PropertyDescriptor获得想要注入的属性
PropertyDescriptor descriptor = new PropertyDescriptor("animal", AnimalDao.class);
//2.获得要想注入属性的具体对象
Animal animal = (Animal) descriptor.getPropertyType().newInstance();
//3.获得该属性的写方法
Method method = descriptor.getWriteMethod();
//4.获得写方法的注解
Annotation annotation = method.getAnnotation(MyAnnotation6.class);
//5.获得注解上的信息
Method[] methods = annotation.getClass().getMethods();
//6.将注解上的信息填充到animal对象上
for (Method m : methods) {
//获得注解上属性的名字
String name = m.getName();
//看看animal对象有没有与之对应的方法
try {
PropertyDescriptor descriptor1 = new PropertyDescriptor(name, Animal.class);
Method method1 = descriptor1.getWriteMethod();
//获得注解中的值
Object o = m.invoke(annotation, null);
//调用animal对象的setter方法,将注解上的值设置进去
method1.invoke(animal, o);
} catch (Exception e) {
continue;
}
}
AnimalDao animalDao = new AnimalDao();
method.invoke(animalDao, animal);
System.out.println(animalDao.getAnimal().getName());
System.out.println(animalDao.getAnimal().getAge());
}
}
@Target(value = {ElementType.METHOD, ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation6 {
String name();
int age();
}
复制代码
所以,将对象注入到方法中能够总结以下