Java注解(Annotation)

注解经常与反射一块儿使用。java

1、注解是什么及做用

  注解(Annotation)至关于一种标记,在程序中加入注解就等于为程序打上某种标记,没有加,则等于没有任何标记,之后,javac编译器、开发工具和其余程序能够经过反射来了解你的类及各类元素上有无何种标记,看你的程序有什么标记,就去干相应的事,标记能够加在包、类,属性、方法,方法的参数以及局部变量上。程序员

  使用Annotation以前(甚至在使用以后),XML被普遍的应用于描述元数据。不知什么时候开始一些应用开发人员和架构师发现XML的维护愈来愈糟糕了。他们但愿使用一些和代码紧耦合的东西,而不是像XML那样和代码是松耦合的(在某些状况下甚至是彻底分离的)代码描述。数组

   假如你想为应用设置不少的常量或参数,这种状况下,XML是一个很好的选择,由于它不会同特定的代码相连。若是你想把某个方法声明为服务,那么使用Annotation会更好一些,由于这种状况下须要注解和方法紧密耦合起来,开发人员也必须认识到这点。安全

  另外一个很重要的因素是Annotation定义了一种标准的描述元数据的方式。在这以前,开发人员一般使用他们本身的方式定义元数据。例如,使用标记interfaces,注释,transient关键字等等。每一个程序员按照本身的方式定义元数据,而不像Annotation这种标准的方式。架构

  目前,许多框架将XML和Annotation两种方式结合使用,平衡二者之间的利弊。
  事实上使用注解仍是XML的判断标准应该是:该配置与代码的相关度。若是代码与配置相关度高,那么使用注解配置,不然使用XML配置。框架

2、注解的分类

1. 按照运行机制划分:源码注解、编译时注解、运行时注解ide

  • 源码注解:只在源码中存在,编译成.class文件就不存在了
  • 编译时注解:在源码和.class文件中都存在,像@Override、@Deprecated、@SuppressWarnings,他们都属于编译时注解。
  • 运行时注解:在运行阶段还起做用,甚至会影响运行逻辑的注解。像@Autowired自动注入的这样一种注解就属于运行时注解,它会在程序运行的时候把你的成员变量自动的注入进来。

2. 按照来源划分:JDK的注解、第三方的注解、自定义注解函数

(1) JDK自带的注解工具

Java提供了三种内建注解。开发工具

  • @Override:当咱们想要复写父类中的方法时,咱们须要使用该注解去告知编译器咱们想要复写这个方法。这样一来当父类中的方法移除或者发生更改时编译器将提示错
  • @Deprecated:当咱们但愿编译器知道某一方法不建议使用时,咱们应该使用这个注解。Java在javadoc 中推荐使用该注解,咱们应该提供为何该方法不推荐使用以及替代的方法。
  • @SuppressWarnings:这个仅仅是告诉编译器忽略特定的警告信息,例如在泛型中使用原生数据类型。它的保留策略是SOURCE(在源文件中有效)而且被编译器丢弃。
  • @SafeVarargs:参数安全类型。JDK1.7引入的,主要目的是处理可变长参数中的泛型,此注解告诉编译器:在可变长参数中的泛型是类型安全的。
  • @FunctionalInterface:函数式接口注解。JDK1.8引入的。

(2) 第三方注解

来自于各类框架 例如:@Service @Controller 等等

3. 元注解

元注解是给注解进行注解,能够理解为注解的注解就是元注解。

3、元注解

J2SE5.0版本在 java.lang.annotation提供了四种元注解,专门注解其余的注解:

  • @Documented – 注解是否将包含在JavaDoc中
  • @Retention – 何时使用该注解
  • @Target – 注解用于什么地方
  • @Inherited – 是否容许子类继承该注解

1. @Retention注解

  @Retention定义注解的生命周期

  一个注解的生命周期有三个阶段:java源文件是一个阶段,class文件是一个阶段,内存中的字节码是一个阶段,javac把java源文件编译成.class文件时,有可能去掉里面的注解,类加载器把.class文件加载到内存时也有可能去掉里面的注解,所以在自定义注解时就可使用Retention注解指明自定义注解的生命周期,自定义注解的生命周期是在RetentionPolicy.SOURCE阶段(java源文件阶段),仍是在RetentionPolicy.CLASS阶段(class文件阶段),或者是在RetentionPolicy.RUNTIME阶段(内存中的字节码运行时阶段),根据JDK提供的API能够知道默认是在RetentionPolicy.CLASS阶段 (JDK的API写到:the retention policy defaults to RetentionPolicy.CLASS.)。

  @Retention(RetentionPolicy.SOURCE) //注解仅存在于源码中,在class字节码文件中不包含
  @Retention(RetentionPolicy.CLASS) // 默认的保留策略,注解会在class字节码文件中存在,但运行时没法得到,
  @Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时能够经过反射获取到

首先要明确生命周期长度 SOURCE < CLASS < RUNTIME ,因此前者能做用的地方后者必定也能做用。通常若是须要在运行时去动态获取注解信息,那只能用 RUNTIME 注解;若是要在编译时进行一些预处理操做,好比生成一些辅助代码(如 ButterKnife),就用 CLASS注解;若是只是作一些检查性的操做,好比 @Override 和 @SuppressWarnings,则可选用 SOURCE 注解。

2. @Target注解

@Target元注解决定了一个注解能够标识到哪些成分上,如标识在在类身上,或者属性身上,或者方法身上等成分,@Target默认值为任何元素(成分),即定义注解的做用目标。

  • @Target(ElementType.TYPE) //接口、类、枚举、注解
  • @Target(ElementType.FIELD) //字段、枚举的常量
  • @Target(ElementType.METHOD) //方法
  • @Target(ElementType.PARAMETER) //方法参数
  • @Target(ElementType.CONSTRUCTOR) //构造函数
  • @Target(ElementType.LOCAL_VARIABLE)//局部变量
  • @Target(ElementType.ANNOTATION_TYPE)//注解
  • @Target(ElementType.PACKAGE) ///包

3. @Documented

@Documented:说明该注解将被包含在javadoc中。

4. @Inherited注解

Inherited是继承的意思,可是并非说注解自己能够继承,而是说若是一个超类被@Inherited 注解过的注解进行注解的话,那么若是它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。注意,仅针对类,成员属性、方法并不受此注释的影响。

4、自定义注解

1. 建立注解

@Retention(RetentionPolicy.RUNTIME) @Inherited @Target(ElementType.METHOD) public @interface MyAnnotation { }

2. 添加属性

注解能够当作是一种特殊的类,既然是类,天然能够为类添加属性。

语法:类型 属性名()

@Retention(RetentionPolicy.RUNTIME) @Inherited @Target(ElementType.METHOD) public @interface MyAnnotation { String color(); }

其实从代码的写法上来看,注解更像是一种特殊的接口,注解的属性定义方式就和接口中定义方法的方式同样,而应用了注解的类能够认为是实现了这个特殊的接口。

//属性应用:
public class MyAnnotationTest { @MyAnnotation(color="red") public void testColor(){ System.out.println("红色"); } public static void main(String[] args) { Method[] methods = MyAnnotationTest.class.getMethods(); for(Method method : methods){ if(method.isAnnotationPresent(MyAnnotation.class)){ MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class); System.out.println(myAnnotation.color()); } } } }

(1) 添加数组类型的属性

增长数组类型的属性:int[] arrayAttr() default {1,2,4};
应用数组类型的属性:@MyAnnotation(arrayAttr={2,4,5})
若是数组属性只有一个值,这时候属性值部分能够省略大括号,如:@MyAnnotation(arrayAttr=2),这就表示数组属性只有一个值,值为2。

(2) 添加枚举类型的属性

增长枚举类型的属性:EumTrafficLamp lamp() default EumTrafficLamp.RED;
应用枚举类型的属性:@MyAnnotation(lamp=EumTrafficLamp.GREEN)

3. 为属性指定默认值

语法:类型 属性名() default 默认值

@Retention(RetentionPolicy.RUNTIME) @Inherited @Target({ElementType.METHOD,ElementType.TYPE}) public @interface MyAnnotation { String color() default "blue"; }

4. value属性的说明

若是一个注解中有一个名称为value的属性,且你只想设置value属性(即其余属性都采用默认值或者你只有一个value属性),那么能够省略掉“value=”部分。

@Retention(RetentionPolicy.RUNTIME) @Inherited @Target({ElementType.METHOD,ElementType.TYPE}) public @interface MyAnnotation { String color() default "blue"; String value(); //定义一个名称为value的属性
}

属性应用:

public class MyAnnotationTest { @MyAnnotation("测试value属性") public void testColor(){ System.out.println("红色"); } public void testValue(){ } public static void main(String[] args) { Method[] methods = MyAnnotationTest.class.getMethods(); for(Method method : methods){ if(method.isAnnotationPresent(MyAnnotation.class)){ MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class); System.out.println(myAnnotation.value()); } } } }

5、一些使用

1. 根据注解选择实现类

① 公共的接口:OperationStrategy

public interface OperationStrategy { void deal(); }

② 每一个类都实现OperationStrategy并在类上添加注解@HanderType(自定义的注解), 建议放在同一个包下,便于搜索。

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Inherited public @interface HandlerType { int value(); //这里固然也可使用枚举
}

两个接口的实现类:

@HandlerType(value = 2) public class AckStrategy implements OperationStrategy{ @Override public void deal() { System.out.println("======AckStrategy======="); } }
@HandlerType(value = 1) public class CancelStrategy implements OperationStrategy{ @Override public void deal() { System.out.println("======CancelStrategy======="); } }

③ 调用时,能够先根据关键参数与@HanderType进行匹配,得到最终的接口实现类,建立实例对象,再调用具体的方法。

public class StrategyManager { public static OperationStrategy getStrategy(int flag) throws IllegalAccessException, InstantiationException { // 实例化Reflections,并指定要扫描的包名
        Reflections reflections = new Reflections(OperationStrategy.class.getPackage().getName()); // 获取某个类的全部子类
        Set<Class<? extends OperationStrategy>> subTypes = reflections.getSubTypesOf(OperationStrategy.class); for(Class<?> clazz: subTypes){ HandlerType handlerType = clazz.getAnnotation(HandlerType.class); if(handlerType.value() == flag){ return ((OperationStrategy) clazz.newInstance()); } } return null; } }

④ 测试

public class DemoTest { public static void main(String[] args) throws InstantiationException, IllegalAccessException { OperationStrategy operationStrategy = StrategyManager.getStrategy(2); operationStrategy.deal(); } }

这里用到了反射框架Reflections。