注解的概述
从JDK1.5开始,Java增加了对元数据的支持,也就是Annotation(注解),它是一种元数据形式。属于Java中的一种数据类型,其地位和类、接口、数组、枚举这些都是一样的。
简单理解注解其实就是代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过使用Annotation可以在不改变原有逻辑的情况下,在源文件中加入一些补充信息,代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。
注解和注释的区别
-
注释:简单来说注释是对代码的解释和说明类似于使用说明,其目的是提高代码的可读性。它只存在于我们的java源代码中,对于编译和运行没有任何的作用,是不会被编译到我们的class文件当中的
-
注解:注解主要是通过标注包、类、字段、方法、局部变量、方法参数等元素,告诉JVM这些元素的附加信息。它可以通过配置,在运行中让JVM去读取它,并完成对应的操作,一般我们会通过反射来获取某个元素上标注的注解。
-
最大的区别在于注解可以被编译器打包进class文件而注释不行,总结一句话就是注释是给我们人看的,而注解则是给编译器和JVM看的。
例如我们书写如下代码:
public class Test{
public string toString(int x){
return "我还没有秃";
}
这个示例中我们想重写Test类的toString()方法,那么toString()方法被重写了吗?
答案肯定是否定的,因为Object定义的toString()方法是一个无参方法,这种写法属于是方法的重载。编译器会认为这是一个全新的方法,故而也不会报错。
这时我们如果在toString方法前,加上一个@Override注解后,编译就会发生错误。
public class Test{
@Override
public string toString(int x){
return "我还没有秃";
}
这是为什么呢?
因为加上@Override注解之后,编译器会强制检测该方法是否重写了父类方法。如果没有,则编译错误。那么从这个例子中我们就能看出,@Override这个Annotation注解,不仅仅是给人读的,同样还会影响到编译器。可以看到注解Annotation会在编译期,影响到编译器的编译动作。所以注解Annotation不是注释。
注解的分类
注解语法支持两种注解:
- JDK自带的Annotation注解
- 开发者自定义的Annotation注解
– 常见的Annotation注解
-
常见的Annotation示例:
- 示例一:生成文档相关的注解
注解 | 作用 |
---|---|
@author | 标明开发该类模版的作者,多个作者之间使用,分割 |
@version | 标明该类模块的版本 |
@see | 参考转向,也就是相关主题 |
@since | 从哪个版本开始增加的 |
@param | 对方法中某参数的说明,如果没有参数就不能写 |
@return | 对方法返回值的说明,如果方法的返回值类型是void就不能写 |
@exception | 对方法可能抛出的异常进行说明,如果方法没有用thorws显示抛出的异常就不能写 |
其中:
- @param、@return 和@exception 这三个标记都是只用于方法的。
- @param的格式要求:@param形参名 形参类型 形参说明
- @return的格式要求:@return 返回值类型 返回值说明
- @exception的格式要求:@exception 异常类型 异常说明
- @param和@exception可以并列多个
代码演示:
/**
* @author 琦玉
* @version 1.0
*/
public class Test{
/**
* 程序的主方法,程序的入口
* @param args String[] 命令行参数
* @see Student
*/
public static void main(String[] args) {
new Student();
}
/**
*
* @param age 年龄
* @return String
* @exception RuntimeException 当年龄大于100的时候
* @exception IndexOutOfBoundsException 当年龄小于0的时候
*/
public String play(int age){
if (age>100){
throw new RuntimeException();
}
if (age<0){
throw new IndexOutOfBoundsException();
}
return “变秃”;
}
}
}
-
-
示例二:在编译时进行格式检查(JDK内置的三个基本注解)
-
@Override:限定重写父类方法,对于子类中被@Override修饰的方法,如果存在对应的被重写方法,则编译通过;如果不存在,则编译报错。
- @Override只能用于方法,不能作用于其他程序元素。
-
@Deprecated:用于表示某个程序元素(类、方法)已过时。如果使用被@Deprecated修饰的类或方法等,编译器会发出警告。
- 需注意:过时的类或方法并不代表错误,只是有了更好的替代方法,而不再推荐使用。
-
@SuppressWarnings:抑制编译器警告,指示被@SuppressWarnings修饰的程序元素(以及该程序元素中的所有子元素,例如类以及该类中的方法……)取消显示的编译器警告。
- @SuppressWarnings抑制的警告类型:
-
@Override:限定重写父类方法,对于子类中被@Override修饰的方法,如果存在对应的被重写方法,则编译通过;如果不存在,则编译报错。
-
示例二:在编译时进行格式检查(JDK内置的三个基本注解)
关键字 | 被抑制的警告类型 |
---|---|
all | 抑制所有警告 |
deprecation | 抑制使用了过时的类或方法的警告 |
fallthrough | 抑制switch块中没有break语句警告 |
finally | 抑制finally块不能正常完成的警告 |
rawtypes | 抑制没有使用泛型的警告 |
serial | 抑制可序列化类没有使用序列化ID的警告 |
unchecked | 抑制未检查操作的警告 |
unused | 抑制变量或方法申明定义后未使用的警告 |
代码演示:
public class AnnotationTest {
public static void main(String[] args) {
@SuppressWarnings("unused") int a = 10;
}
@Deprecated
public void print() {
System.out.println(" 过时的方法");
}
@Override
public String toString() {
return " 重写的 toString 方法() " ;
}
}
-
- 示例三:跟踪代码依赖性,实现替代配置文件功能
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>com.servlet.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
- 示例四:spring框架中关于“事务”的管理
@Transactional(propagation = Propagation.REQUIRES_NEW,
isolation=Isolation.READ_COMMITTED, readOnly = false, timeout = 3)
public void buyBook(String username,String isbn){
//1.查询书的单价
int price = bookShopDao.findBookPriceByIsbn(isbn);
//2.更新库存
bookShopDao.updateBookStock(isbn);
//3.更新用户的余额
bookShopDao.updateUserAccount(username,price);
}
<!-- 配置事务属性 -->
<tx:advice transaction-manager="dataSourceTransactionManager" id="txAdvice">
<tx:attributes>
<!-- 配置每个方法使用的事务属性 -->
<tx:method name="buyBook" propagation="REQUIRES_NEW"
isolation="READ_COMMITTED" read-only="false" timeout="3" />
</tx:attributes>
</tx:advice>
自定义的Annotation注解
在开发中,开发者有时候需要自定义注解完成某些配置。自定义注解使用的基本流程如下:
- 第一步,定义注解——相当于定义标记
- 第二步,配置注解——把标记打在需要用到的程序代码中
- 第三步,解析注解——在编译期或运行时检测到标记,并进行特殊操作
注解定义的关键字:@interface
- 自定义注解自动继承了java.lang.annotation.Annotation接口
- Annotation的成员变量在Annotation定义中以无参数方法的形式来声明。其方法名和返回值 定义了该成员的名字和类型。我们称为配置参数。类型只能是八种基本数据类型、String类 型、Class类型、枚举类型、注解类型、以及以上所有类型的一维数组。
- 注解元素的访问修饰符只能是public,不写也默认为public。
- ()不是定义方法参数列表的地方,也不能在括号中定义任何参数,就是一个特殊的语法。
- 可以在定义Annotation的成员变量时为其指定初始值,指定成员变量的初始值可使用default关键字
- 如果定义的注解含有配置参数,那么使用时必须指定参数值,除非它有默认值。格式是“参数名 = 参数值”,如果只有一个参数成员,且名称为value,可以省略“value=”
自定义注解
@MyAnnotation(value = "琦玉")
public class MyAnnotationTest {
public static void main(String[] args) {
Class cla = MyAnnotationTest.class;
Annotation an = cla.getAnnotation(MyAnnotation.class);
MyAnnotation m = (MyAnnotation) an;
String info = m.value();
System.out.println(info);
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface MyAnnotation {
String value() default "111111" ;
}
元注解
元注解是专门修饰注解的注解,是为了更好的设计自定义注解的细节而专门设计的。
JDK1.5中提供的四个4个标准的meta-annotation类型,分别是:
一、@Target
@Target注解,是专门用来限定某个自定义注解能被应用到Java代码的那些位置。它使用一个枚举类型ElementType定义了限定范围的选择。
@Target的使用:
枚举值 | 注解能够被应用的地方 |
---|---|
ElementType.ANNOTTION_TYPE | 注解类型的声明 |
ElementType.CONSTRUCTOR | 构造方法的声明 |
ElementType.FIELD | 属性的声明 |
ElementType.LOCAL.VARIABLE | 局部变量的声明 |
ElementType.METHOD | 方法的声明 |
ElementType.PACKAGE | 包的声明 |
ElementType.PARAMETER | 方法参数的声明 |
ElementType.TYPE | 类、接口及枚举的声明 |
二、@Retention:
只能用于修饰一个Annotation定义,用于指定该Annotation的生命周期,@Rentention包含一个RetentionPolicy类型的成员变量,使用@Rentention时必须为该value成员变量指定值:
- RetentionPolicy.SOURCE:该注解被限定在Java源文件中,该注解不会参与编译也不会在运行期起任何作用。只能在源文件中可见,和注释没什么区别。
- RetentionPolicy.CLASS:该注解被编译到Class文件中,编译器可以在编译时根据注解做一些处理动作。但在运行时,JVM会忽略它,在运行期也不能读取到。这是默认值
- RetentionPolicy.RUNTIME:该注解可以在运行期的加载阶段被加载到Class对象中。在程序运行阶段,可以通过反射得到该注解,并通过判断是否有这个注解或这个注解中属性的值,从而执行不同的程序代码段。实际开发中的自定义注解,几乎都使用RetentionPolicy.RUNTIME
@Retention(RetentionPolicy.RUNTIME) //RetentionPolicy.CLASS/RetentionPolicy.SOURCE
public @interface MyAnnotation{ //MyAnnotation的生命力被保持到运行阶段。运行时可以通过反射的方式获得注解信息
public String name();
int age();
int[] array();
}
三、@Documented:
@Documented注解,是被用来指定自定义注解是否能随着被定义的java文件生成到javadoc文档中。默认情况下,javadoc是不包括注解的。
- 需注意:定义为Documented的注解必须设置Retention值为RUNTIME
四、@Inherited:
@Inherited注解,是指定某个自定义注解如果写在了父类的声明部分,那么子类的声明部分也能自动拥有该注解。
- 比如:如果把标有@Inherited注解的自定义的注解标注在类级别上,子类则可以继承父类类级别的注解,实际应用中,较少使用
注解的使用
定义注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
public String name();
int age() default 18;
int[] scores();
}
- TestAnnotation的@Target定义为Element.TYPE,书写位置为类型定义的上方。
- TestAnnotation有注解类型元素,而且有些元素没有默认值。这要求在使用时必须在标记名后打上(),并且在()内以“元素名=元素值”的形式挨个填上所有没有默认值的注解类型元素(有默认值的也可以填上,重新赋值),中间以“,”号分割。
定义学生类,使用TestAnnotation 注解:
@TestAnnotation(name="tom",age=24,scores={80,65,92})
public class Student {
age元素被重新赋值为24
}
@TestAnnotation(name="tom", scores={80,65,92})
public class Student {
age元素使用默认值18
}
注解Annotation的特殊语法
- 如果注解本身没有定义注解类型元素,那么在使用注解时可以省略()
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {}
@TestAnnotation
public class Test {
}
- 如果注解本身在其内部只定义了一个注解类型元素,并且命名为value,那么在使用注解时,可以直接书写为@注解名(注解值)。它和标准写法:@注解名(value=注解值)等效!
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
String value();
}
@TestAnnotation(“琦玉”)
public class Test {
}
- 如果注解中某个注解类型元素是一个数组类型,在使用时只需要填入一个值,那么,在使用注解时,可以直接书写为@注解名(类型名=类型值)。它和标准写法@注解名(类型名={类型值})等效。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
String[] addresses();
}
@TestAnnotation(addresses=“NYC”)
public class Test {
}
- 如果一个注解的@Target定义为Element.PARAMETER,那么这个注解是配置在方法的形参前面的。
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
String param();
}
public class Test {
public void speak(
@TestAnnotation(param="name") String userName){
}}
自定义注解Annotation的运行时解析
只有当一个注解使用了@Retention(RetentionPolicy.RUNTIME)修饰,才表示该注解能够保持到运行期,才能在运行期获到它。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
public String name();
int age() default 18;
int[] scores();
}
@TestAnnotation(name="琦玉",age=24,scores={80,65,92})
public class Student {}
Class studentClass = Student.class;
//判断Student类中,是否定义了@TestAnnotation注解
if(studentClass.isAnnotationPresent(TestAnnotation.class)) {
//得到Student类中定义的注解
TestAnnotation anno = (TestAnnotation)
studentClass.getAnnotation(TestAnnotation.class);
//得到注解元素的值
System.out.println(anno.name()+" "+
anno.age()+" "+anno.scores());
}
注解和XML的比较
- XML:全称叫做:可扩展标记语言。它是W3C组织于1998年2月提出的一种SGML(标准通用标记语言)的一个子集。XML是一种独立的文本格式,语法能够很丰富的表达各种数据结构。所以又被大量用来作为不同平台之间数据传递的标准格式,它的使用越来越广泛,包括WebService、AJAX等都是基于它的,所以配置文件只是XML的一种用途而已。
- 注解:注解是Java在JDK1.5中提供的一种新类型。它是在Java代码中的特殊标记,这些标记可以在编译、类加载或运行期被读取,从而执行相应的特殊操作。所以,注解Annotation是Java语言独有的,只能Java用,并且其设计目的就是为了做配置的。
- XML用途比Java注解要广泛得多,它不限语言,只要需要表示数据的地方都可以使用,这是Java注解绝对不可比拟的。所以,java注解要替代XML是不可能的。
XML的优缺点:
- 优点:
-
- 可扩展性强。XML和它的定义文件(DTD)是文本文件,添加或修改都很方便。
-
- 满足开闭原则。XML的配置文件是独立于Java代码之外的,所以如果要修改配置是不需要改动Java代码的。
- 缺点:
-
- 解析麻烦,需要第三方类库配合
-
- 配置文件过多后,会导致项目管理、维护变得困难。
-
- 在做代码的关联配置时麻烦,会让开发人员不断在java代码和XML配置文件之间来回切换。
-
- 查错困难,XML只是文本文件,很多报错只能在运行期间体现,IDE集成开发环境不能在编译期排查。
注解的优缺点:
- 优点:
-
- JDK自带反射方式解析,不引入第三方库
-
- 直接配置在Java源代码中,所以不会引入额外的配置文件,项目管理和维护变得简单
-
- 无论是定义、配置还是解析都是java代码,所以不存在配置时需要在不同语法格式中切换的问题。
-
- IDE支持度高,排查错误方便,提高开发效率
- 缺点
-
- 任何对配置、对定义的修改都需要改动Java代码,然后重新编译,重新打包应用。
-
- 不是通用标准,只能在Java中使用。
XML和注解分别配置的使用场景
XML与注解在配置这个应用场景中互补性是非常高的。XML的缺点就是注解的优点,反之亦然。这也是目前在JavaEE的各大框架中XML+Annotation配置流的原因。
一般来说,把与代码关联度不高,并且改动可能性大的配置写在XML文件中。专门配置java代码级别关联度的,以后改动度小的则使用注解Annotation。