Annotation 注解

注解常常与反射一起使用。

一、注解是什么及作用

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

  使用Annotation之前(甚至在使用之后),XML被广泛的应用于描述元数据。不知何时开始一些应用开发人员和架构师发现XML的维护越来越糟糕了。他们希望使用一些和代码紧耦合的东西,而不是像XML那样和代码是松耦合的(在某些情况下甚至是完全分离的)代码描述。

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

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

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

二、注解的分类

1. 按照运行机制划分:源码注解、编译时注解、运行时注解
  • 源码注解:只在源码中存在,编译成.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. 元注解

元注解是给注解进行注解,可以理解为注解的注解就是元注解。

三、元注解

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 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。注意,仅针对类,成员属性、方法并不受此注释的影响。

四、自定义注解

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());
                
            }
        }
        
    }
}

五、一些使用

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。

 

posted @ 2020-03-14 13:45  codedot  阅读(344)  评论(0编辑  收藏  举报