Java 注解 Annotation

注解和类,接口一样,也是一种java元素。只是注解比较特殊,其并不会改变程序的语义逻辑,只是当做一种注解,来提供一些支持辅助的作用。那么注解到底有什么用?最常用的功能有两个:1.规范代码写法,结合IDE工具等可以预防一些错误。2.结合反射实现一些代码控制目的。还有一个功能:3.使用注解对代码进行文档化(不能说不重要,但是在实际工作中往往会忽略)。

我们先来看最常用的一个注解@override,这个注解的作用就是规范代码写法,避免本来想要重写,可是不小心变成了重载,或者不小心写错了方法名。下面来看看@override的源码Override.java:

import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({java.lang.annotation.ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface Override
{
}

从代码中可以看到@interface为注解的声明方式。@Target叫做元注解,表示了该注解是针对于哪(几)种程序元素的,@Retention也是一个元注解,表示override是在source即编码阶段产生作用的。

@overrid真正要起作用,必须结合IDE比如eclipse,在编码阶段就会去检查方法是否被正确的重写了,从而可以避免一些程序员疏忽导致的运行时错误。

Java内置了三中注解,在java.lang中可以查看源码,我们经常会遇到。
@Override
@Deprecated:表示不推荐使用
@SuppressWarnings:告诉编译器可以忽略这个warning

Java包含了四个元注解,
1) @Target,这个注解有自己内部属性,而且是一个ElemenetType 枚举类型的数组(这部分会在下面分析)。查看ElemenetType源码知道其中包括:
★ CONSTRUCTOR 构造器声明,
★ FIELD 域声明(包括 enum 实例),
★ LOCAL_VARIABLE 局部变量声明,
★ METHOD 方法声明,
★ PACKAGE 包声明,
★ PARAMETER 参数声明,
★ TYPE 类,接口(包括注解类型)或enum声明。
2) @Retention,有一个内部属性,但并不是数组,这就表示一旦为注解加上元注解@Retention,那么只有一种选择。
★ SOURCE 编码阶段,编译时就不存在了。
★ CLASS 注解在class文件中可用,但会被JVM丢弃。
★ RUNTIME JVM将在运行期也保留注释,因此可以通过反射机制读取注解的信息。
3) @Documented 将此注解包含在 javadoc 中
4) @Inherited 允许子类继承父类中的注解

我们可以自己定义自己的注解,写出来和Override.java一样,如果某天你需要自己写一个自己的注解,那就应该了解下面的一些语法规定:
一个自定义注解的实例:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface Greeting {
    String name();
    // 使用枚举
    FontColor fontColor() default FontColor.RED;
}

1.所有的注解类都隐式继承于java.lang.annotation.Annotation,不允许显式继承于其他的接口。
2.一个注解可以拥有多个成员,成员声明和接口中方法声明类似,通过反射也可以调用这些方法:
a) 成员以无入参无抛出异常的方式声明,如boolean value(String str)、boolean value() throws Exception等方式是非法的;
b) 可以通过default为成员指定一个默认值,如String level() default “LOW_LEVEL”、int high() default 2是合法的,当然也可以不指定默认值;
c) 成员类型是受限的,合法的类型包括原始类型及其封装类、String、Class、enums、注解类型,以及上述类型的数组类型。如ForumService value()、List foo()是非法的。
d) 如果注解只有一个成员,则成员名必须取名为value(),在使用时可以忽略成员名和赋值号(=),如@Description(“使用注解的实例”)。注解类拥有多个成员时,如果仅对value成员进行赋值则也可不使用赋值号,如果同时对多个成员进行赋值,则必须使用赋值号,如@DeclareParents (value = “NaiveWaiter”, defaultImpl = SmartSeller.class)。
e) 注解类可以没有成员,没有成员的注解称为标识注解,解释程序以标识注解存在与否进行相应的处理;
3.如果一个成员是数组类型,那么在使用这个注解时,就可以用花括号来提供多个元素,如@Test({Exception1.class,Exception2.class})

下面来分析注解与反射相结合的使用,典型的应用是JUnit选取特定的方法来执行测试,并可以通过给出期盼的测试结果(如期望的exception):
1.阅读java.lang.class, java.reflect.Method等等的源码可以知道,他们都直接实现或者通过继承而实现了接口AnnotatedElement。实现了这个接口,就表示一旦程序获得了AnnotatedElement类型,那么就可以使用AnnotatedElement的方法,有下面四个:

<T extends Annotation> T getAnnotation(Class<T> annotationClass),返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。
Annotation[] getAnnotations(),返回该程序元素上存在的所有注解。
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass),判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false。
Annotation[] getDeclaredAnnotations(),返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。

2.通过1你应该可以知道反射是怎么结合注解使用的了吧,就是通过上面四个方法,现在直接上例子:

 public static void main(String[] args) throws Exception{
    Class clz = Class.forName("Test");
    final Method[] method = clz.getDeclaredMethods();

if(clz.isAnnotationPresent(TestAnnotation.class)) {
   // 根据注解类型返回指定类型的注解
   TestAnnotation des = (TestAnnotation)clz.getAnnotation(TestAnnotation.class);
       System.out.println("注解描述:" + des.value());
  }
for(Method m:methods){
      if(m.isAnnotationPresent(TestMethodAnnotation.class)){
            m.invoke(null);
    }
 }
}

PS:对注解的深入分析,可以参考这篇文章。spring可以完全用注解来代替配置文件的作用。但是个人觉得这两者的区别并不大,对于习惯了一种方式的人来说可能不会短时间适应另一种方式。配置文件,可以在启动时加载进所有的依赖关系,这对不启动server的内部调试来说是不是必须的呢? 而注解方式是把依赖写在对象里,相当于放进class当中了,这能够在不启动server的时候顺利的完成所有依赖的加载而进行测试吗?

深入理解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值