**
【0005】对JavaSE使用注解的总结
知识点背景:
今天下午看sevelet的代码,对于重定向JSP过程中使用的注解,有一块忘了啥意思了不理解那个底层代码接口啥效果了,哎,当初JavaSE没系统的整理注解的疑惑点,罪过罪过,想着也用不到,好家伙,现在用到看不懂了,最后去查了一些以前的笔记和网上资料(声明一下主要参考Java核心技术书),写个博客总结一下(欠的债总是要还的):
一、什么是注解,为什么要使用?(第一步)
1. 什么是注解(Annotation )?( Java SE 5.0 版本引入)
注解是那些插入到源代码中使用其他工具可以对其进行处理的标签。这些工具可以在源码层次上进行操作,或者可以处理编译器在其中放置了注解的类文件。
通俗理解:
对于作用效果方面:注解就是个标签(便利贴更贴切),标签(便利贴)上记载一些数据(抽象事物的解释)来修饰其他对象,而标签作用就是用来修饰其他东西的;而从语法上理解:注解算是一种类型,就像classs 、interface等等类型 一样性质。
用法:
1.附属文件的自动生成,例如部署描述符或者bean信息类;
2.测试、日志、事务语义等代码的自动生成;
一个简单的注释例子:
public class MyClass{
@Test public void checkRandomInsertions()//@Test用于注解 checkRandomInsertions方法
}
2.使用注解的原因:
1.将元数据(metadata)与程序元素(修饰类、成员、以及局部变量方法等)进行关联,达到想要的效果;
2.免除一些繁琐的代码,减少代码量;(写到这,让我想起了lombok这个包文件,作用就是动态封装数据@Data,也有相同的效果减少代码书写)
例如:组装时间源上的监听器:
myButton.addActionListener(()->dosomething());//只要你是用监听就需要调用一次这句话
//使用注解后
@ActionListenerFor(source="myButton") void dosomething(){...}//此后不需要再调用ActionListenerFor
3.注解的运行机制:
@ActionListenerFor ----> java编译器 ----> @ActionListenerFor ----> Java虚拟机 ---- 程序运用API反射机制处理注解
(源文件) (类文件)
二、使用注解的语法?
1.注解的注意事项:(先了解一下规则)
特点注意:
1.每个注解前面要加上@,注解是代码执行的一部分,存放位置区别于javadoc注释/***/, 并且它需要处理工具才能使用,就比如:@Test就是JUnit4测试工具支持的;
2.,每个注解都必须通过一个注解接口进行定义,这些接口中的方法与注解中的元素相对,并且对于修饰对象注解可以修饰类、成员、以及局部变量方法等等。
如:
@Target(ElementTYle.METHOD)//元注解,它注解了注解Test(底下详谈)即将Test注解标识成一个只能运用到方法上的注解,并且当类文件载入虚拟机的时候,仍可以保留下来。
@Retention(RetentionPolicy.RUNTIME)//也是元注解,底下详谈,这也是我不理解的地方,可恶!
public @interface Test{//声明创建一个真正的java接口,处理注解的工具将接收那些实现了这个注解接口的对象,这类工具可以调用timeout方法来检索某个特定Test注解的timeout元素。
long timeout() default OL;
}
3.所有的注解接口都直接隐式地扩展自java.lang.annotation.Annotation接口(无法再扩展注解接口了)。该接口是一个常规接口,不是注解接口,不用提供那些实现了注解接口的类;
4.注解接口的元素类型:八大基础类型(int、short、long…)、String、Class(具有一个可选的类型参数,例如:Class<? extends MyClass>)、enum类型、注解类型和由前面类型组成的数组类型
例如:
public @interface heihei{
enum kaka{ ONE,TWO};//enum类型
boolean heihei1() default false;//八大基础类型(int、short、long…)
String heihei2() default "[none]";//String类
Class<?> heihei3() default Void.class;//Class(具有一个可选的类型参数
Status status() default Status.UMCONFIRMED;//Status类型(函数返回值为函数结果状态代码)
Reference ref() default @Reference();//an annotation type注解类型
String[] reportedBy();//由前面类型组成的数组类型
}
5.注解的格式:@AnnotationName(elementName1=value1,elementName2=value2,…),如果元素的值未指定,那么将使用默认值,如果你不需要任何元素或者都采用默认值,可以简化书写为@AnnotationName;关于简化书写值得注意的是:1.默认值和注解并不存储在一起,他们是动态计算出来的;2.一个接口只有一个元素,而且那个元素还是特殊的名字value,可以直接简化为:@AnnotationName(“XXX”)来代替:@AnnotationName(value=“XXX”),我们称为这种简化为单值注释;
6.注解是由编辑器计算而来,所有的元素必须是编译器常量,如:@BuReport(showStopper=true,assignedTo=“Harry”,…);
7.一个注解元素永远不能设置为null,甚至默认值也是如此,这使得在定义前必须了解其默认值,相当不便,另外对于一个注解元素可以是另一个注解,这是不考虑引入循环依赖的,例如:BugReport具有一个Reference的元素,那么它就不能反过来引入一个:BugReport类型的元素了。
2.注解的各类声明及其类型用法
1.各类声明:声明和类型用法声明注解可以出现在下列声明处:包、类、enum、接口(包括注解接口)、方法、构造器、实例域、局部变量、参数变量、类型参数;而对于类和接口,需要将注解放置在class和interface关键字前面;对于变量需要将他们放置在类型的前面;泛型类或方法中的类型参数要满足基本的格式规范。如:
@Entity public class User{...}
Public User getUser(@Param("id") String userId)
public class Cache<@Immutable V>
值得注意的是:对局部变量的注解只能在源码级别上进行处理,类文件并不能描述局部变量,所以所有的局部变量注解在编译完一个类的时候就会被遗弃掉,同样地,对包的注解不能在源码级别之外的地方。如:
@GPL(version="3")
package com.horstmann.corejava
import org.gnu.GPL
2.注解类型用法
在我们使用注解时候,需要指定特定的注解可以出现在什么位置,作用在不同位置作用的对象也不一样(public User getUSer(@NonNull String userId),放在这个位置(前提@NonNull可以被应用到变量和类型用法),userId的参数String也被注解了),同时在声明注解的时候会提供正在被声明项的相关信息(断言、默认值等),我们想要这样的相关信息就需要遵循注解类型用法:将该注解放在类型引用之前,例如:
1.与泛化类型引用一起使用:List<@NonNull String> ;
2.数组中任意位置:@NonNull String[][] words(words不为空),String[][] @NonNull words(words不为空),String @NonNull[][] words(words不为空);
3.与超类和实现接口一起使用:class Warming extends @Localized Message;
4.与构造器调用一起使用:new @Localized String(...);
5.与强制转型和intanceof检查一起使用:(@Localized String) String
6.与异常规约一起使用:public String read() throws @Localized IOEXpetion
7.与通配符和类型边界一起使用:List <@ Localized ? extends Massage>或List < ? extends @ Localized Massage>
8.与方法和构造器引用一起使用:@Location Message ::getText
值得注意的是:也有一些类型位置不能被注解,如:
@NonNull String.class//ERROR:Cannot annotate class literal
import java.lang.@NonNull String;//ERROR:Cannot annotate import
而关于类型用法注解与声明直接放置有个不成文的规定:类型用法注解放在其他修饰符的后面和声明注解放置到其他修饰符的前面:如:
private @NonNull String text;// Annotates the type use
@Id private String userId;//Annotates the variable
3.注解this
1.使用this注解的场景:当调用的参数注解了时,又想要将调用者类注解,而类与this常常绑定在一起,而想要注解,就需要声明this,需要特定的语法变量声明,例如:Point.equals(Point,q);
public class Point{
public boolean equals(@ReadOnly Point this,@ReadOnly Object other){...}
//第一个参数是接收器参数,必须被命名为this,它的类型就是要构造的类
}
注意:
1.this从概念上来说:构造器中的this引用在构造器没有执行完之前还不是给定类型的对象,所以放置在造器的注解描述的是被构建的对象的属性,我们只能为方法而不能为构造器提供接收器参数。
2.如果传递的参数是外围类,即对外围类对象的引用,格式为:外围类参数名字.this,类型就是外围类,如:传递给内部类构造的是另一个不同的隐藏参数。
三、标准注解(分类):正在运行的程序中的注解
注解接口 | 应用场合 | 目的 |
---|---|---|
Deprecated | 全部 | 将项标记为过去的 |
SupperssWarnings | 除了包和注解之外的所有情况 | 阻止某个给定类型的警告信息 |
SafeVarargs | 方法和构造器 | 断言varargs参数可安全使用 |
Override | 方法 | 检查该方法是否覆盖了某一个超类方法 |
FunctionalInterface | 接口 | 将接口标记为只有一个抽象方法的函数式接口 |
PostConstruct、PreDestroy | 方法 | 被标记的方法应该在构造之后或移除之前立即被调用 |
Resource | 类、接口、方法、域 | 在类或接口上:标记为在其他地方地方要用到的资源。在方法或域上:为"注入"而标记 |
Resources | 类、接口 | 一个资源数组 |
Gennerated | 全部 | |
Target | 注解 | 指明可以应用这个注解的那些项 |
Retention | 注解 | 指明这个注解可以保留多久 |
Documented | 注解 | 指定这个注解应该包含在注解项的文档中 |
Inherited | 注解 | 指定当这个注解应用于一个类的时候,能够自动被它的子类继承 |
Repeatable | 注解 | 指定这个注解可以在同一个项上应用多次 |
1.用于编译的注解:
1.@Deprecated:添加到任何不再鼓励(已过时)使用的项上。和javadoc标签@deprecated具有同等功效;
2.@SuppressWarnings:告知编辑器阻止特定类型的警告信息,例如:@SuppressWarnings:(“unchecked”);
3.@Override:只能作用在方法上,编辑器会检测具有这种注解的方法是否真正覆盖了一个来自于超类的方法,没有会报错;
public Myclass{
@Override public boolean equals(Myclass heihei);
//会报错,equals没有覆盖Object的equals方法,此方法有一个类型为Object而不是MyClass的参数
}
4.@Gennerated:供代码生成工具来使用。任何生成的代码都可以被注解,从而与程序员提供的代码分开。如:@Gennerated(“com.bdqn.demo”,“2022-09-09”);
2.用于管理资源的注解:
1.@PostConstruct、@PreDestroy:用来控制对象声明周期的环境中,例如:Web容器和应用服务器;
2.@Resource:用于资源注入。例如:访问数据库的Web应用。
@Resource(name="jdbc/mydb")
private DataSource source
3.(五个)元注解(这也算我写这篇博客的主要原因):描述注解的行为属性
元注解作用就是给其他普通的标签进行解释说明的,通俗点将说:就像给普通的便利贴贴上特殊便利贴的行为,或者理解为用五个特殊的便利贴去修饰其他的普通便利贴一样。
1.@Target:应用到一个注解上,以限制该注解可以应用到那些项上;换句话说一条没有@Target限制的注解可以应用于任何项上,如:
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface BugReport
@Target注解的元素类型
元素类型 | 注解应用场合 |
---|---|
ANNOTATION_TYPE | 注解类型声明 |
PACKAGE | 包 |
TYPE | 类(包括enum)及接口(包括注解类型) |
CONSTRUCTOR | 构造器 |
FIFELD | 成员域(包括enum常量) |
PARAMETER | 方法或构造器参数 |
LOCAL_VARIABLE | 局部变量 |
TYPE_PARAMETER | 类型参数 |
TYPE_USE | 类型用法 |
2.@Retention:用于指定一条注解用于指定一条注解应该保留多长时间,其默认值为RetentionPolicy.CLASS
@Retention注解的保留策略
保留规则 | 描述 |
---|---|
SOURCE | 不包括在类文件中的注解 |
CLASS | 包括在类文件中的注解,但是虚拟机不需要将他们载入 |
RUNTIME | 包括在类文件中的注解,并由虚拟机载入,通过反射API可获得他们 |
3.@Documented:为像javadoc这样的归档工具提供了一些提示,该注解也会纳入归档范畴里。
注意:
1.当某个注解是暂时的(如@BugReport),就不该对他们的用法进行档。
2.将一个注解应用到自身是合法的,例如:@Documented注解为@Documented。
4.@Inherited :只能用于对类的注解。如果一个类具有继承注解,那么它的所有子类都自动具有同样的注解。事实上:一个类之所以可以序列化,是因为存在着对它的成员域进行读写的运行期支持,而不是因为任何面向对象的设计原理,注解比接口继承更擅长描述这个事实。例如:
@Inherited @interface Persistent{}
@Persistent class Employee{...}
class Manager extends Employee{...}//alse Persistent
//一个继承注解@Persistent来指明一个类的对象可以储存到数据库中,那该持久层的子类就会自动被注解为持久性的。
5.@Repeatable:指定这个注解可以在同一个项上应用多次。JDK8以后,将同种类型的注解多次应用于某一项是合法的,而对于处理可重复注解,该注解确实重复了,那就得到null,因为重复注解被包装到了容器中,解决这种情况,可以调用像getAnnotationsByType的某种方法,来’遍历’容器,并给出一个重复注解的数组。
4 .(3个)规则注解:注解你的源代码中的项
四、源码级注解处理(略,以后补充吧,目前没用到)
注解的另一种用法(区别于三):自动处理源代码以产生更多的源代码、配置文件、脚本或其他任何我们想要生成的东西。