Java篇 (48):一个测试用例让你理解Java注解的设计与使用!

2019年08月10日

目录

与XML对比

剖析注解

三种标准注解

@Override 注解

@Deprecated 注解

@Suppresswarnings 注解

元注解

@Target

@Retention

@Documented

@Inherited

注解实例

参考文献:


与XML对比

以前,『XML』是各大框架的青睐者,它以松耦合的方式完成了框架中几乎所有的配置,但是随着项目越来越庞大,『XML』的内容也越来越复杂,维护成本变高。

于是就有人提出来一种标记式高耦合的配置方式,『注解』。方法上可以进行注解,类上也可以注解,字段属性上也可以注解,反正几乎需要配置的地方都可以进行注解。

关于『注解』和『XML』两种不同的配置模式,争论了好多年了,各有各的优劣,注解可以提供更大的便捷性,易于维护修改,但耦合度高,而 XML 相对于注解则是相反的。

追求低耦合就要抛弃高效率,追求效率必然会遇到耦合。本文意不再辨析两者谁优谁劣,而在于以最简单的语言介绍注解相关的基本内容。

 

剖析注解

  • 注解的本质就是一个继承了 Annotation 接口的接口。有关这一点,你可以去反编译任意一个注解类,你会得到结果的。
  • 《Java编程思想》介绍注解的作用:可以形式化的添加信息,方便开发者使用。好处是:干净易读,并且编译器检查;

元注解:负责定义注解的注解。

Java只有3种标准注解,4种元注解。

三种标准注解

JDK5提供的简单注解类型只有3个. 这三个都是用来预防错误或者进行提醒的,分别是:

  • Override
  • Deprecated
  • Suppresswarnings

@Override 注解

Override 注解指明被注解的方法需要覆写超类中的方法,如果某个方法使用了该注解,却没有覆写超类中的方法(比如大小写写错了,或者参数错了,或者是子类自己定义的方法),编译器就会生成一个错误,(注意: JRE5中实现接口中的方法时不能使用Override注解,JRE6允许了,很多时候JRE5会报这个错)。

package java.lang;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {

}

@Deprecated 注解

这个注解表明如果程序调用一个废弃的(Deprecated,废弃的,过时的)元素时,编译器应该显示警告信息. 示例2显示了如何使用Deprecated 注解.

public class Test_Deprecated {
   @Deprecated
   public void doSomething() {
      System.out.println("测试使用 弃用 注解: 'Deprecated'");
   }
}

接着,尝试从另一个类调用这个方法:

public class TestAnnotations {
   public static void main(String arg[]) throws Exception {
      new TestAnnotations(); 
   }
   public TestAnnotations() {
   Test_Deprecated t2=new Test_Deprecated();
   t2.doSomething();
}

本例中的doSomething()方法被声明为废弃的方法. 因此,一般情况下不应该调用这个方法. 在编译Test_Deprecated.java 文件时是不会有警告消息的. 但在编译 TestAnnotations.java 时编译器就会给出类似这样的警告信息(Eclipse 会有警告):

Compiling 1 source file to D:tempNew Folder
(2)TestJavaApplication1buildclasses
D:tempNew Folder
(2)TestJavaApplication1srctestmyannotation
    TestAnnotations.java:27:
warning: [deprecation] doSomething() in
test.myannotation.Test_Deprecated has been deprecated
t2.doSomething();
1 warning

@Suppresswarnings 注解

这个注解告诉编译器应该屏蔽带注解的元素和所有子元素的警告信息. 会压制一个元素集和子元素的所有警告信息. 比如,假设你在一个class上使用了Suppresswarnings 注解压住一个警告,在它的一个方法上用Suppresswarnings 注解来压制另一个警告,则两种警告都会在方法级别被压制住。

public class TestAnnotations {
   public static void main(String arg[]) throws Exception {
      new TestAnnotations().doSomeTestNow();
   }
   @SuppressWarnings({"deprecation"})
   public void doSomeTestNow() {
      Test_Deprecated t2 = new Test_Deprecated();
      t2.doSomething();
   }
}

在本例中,使用 @SuppressWarnings压住了示例2中所示的deprecation警告信息. 因为该方法的这类警告被压住了,所以你不会再看到"deprecation"警告。注意: 在最内层的元素上使用该注解是比较好的. 因此,如果你只想在一个特定的方法上压制一个警告,你应该在方法上标注,而不是在类上使用注解.

元注解

JAVA 中有以下几个『元注解』:

  • @Target:注解的作用目标
  • @Retention:注解的生命周期
  • @Documented:注解是否应当被包含在 JavaDoc 文档中
  • @Inherited:是否允许子类继承该注解

@Target

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}

Target注解表明注解类型适用于哪种目标元素. 它包含下面的枚举类型值:

1.@Target(ElementType.TYPE)  —— 可以适用于任何类的元素
2.@Target(ElementType.FIELD)  —— 只适用于字段或属性
3.@Target(ElementType.METHOD)  —— 只适用于方法的注解
4.@Target(ElementType.PARAMETER)  —— 只适用于方法的参数
5.@Target(ElementType.CONSTRUCTOR) —— 只适用于构造函数
6.@Target(ElementType.LOCAL_VARIABLE) —— 只适用于局部变量
7.@Target(ElementType.ANNOTATION_TYPE) —— 指明声明类型本身是一个注解类型

 

@Retention

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

注解@Retention可以用来修饰注解,Retention注解有一个属性value,是RetentionPolicy类型的,Enum RetentionPolicy是一个枚举类型,这个枚举决定了Retention注解应该如何去保持,也可理解为Rentention 搭配 RententionPolicy使用。

RetentionPolicy有3个值:CLASS  RUNTIME   SOURCE
按生命周期来划分可分为3类:
1、RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;
2、RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;
3、RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;


这3个生命周期分别对应于:Java源文件(.java文件) ---> .class文件 ---> 内存中的字节码。


那怎么来选择合适的注解生命周期呢?
首先要明确生命周期长度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用。
一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解,比如@Deprecated使用RUNTIME注解
如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS注解;
如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,使用SOURCE 注解。

注解@Override用在方法上,当我们想重写一个方法时,在方法上加@Override,当我们方法的名字出错时,编译器就会报错
注解@Deprecated,用来表示某个类或属性或方法已经过时,不想别人再用时,在属性和方法上用@Deprecated修饰
注解@SuppressWarnings用来压制程序中出来的警告,比如在没有用泛型或是方法已经过时的时候

 

@Documented

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

Documented 注解表明这个注解应该被 javadoc工具记录,默认情况下,javadoc是不包括注解的. 但如果声明注解时指定了 @Documented,则它会被 javadoc 之类的工具处理, 所以注解类型信息也会被包括在生成的文档中.(个人观点:不是重点,了解即可。勿喷)
 

@Inherited

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

这是一个稍微复杂的注解类型,它指明被注解的类会自动继承,是否允许子类继承该注解。 更具体地说,如果定义注解时使用了 @Inherited 标记,然后用定义的注解来标注另一个父类, 父类又有一个子类(subclass),则父类的所有属性将被继承到它的子类中。

 

注解实例

1、定义一个注解:UserCase

package annotation;

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

/**
 * 用例注解
 *
 * @author: mmb
 * @date: 19-8-10
 */

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UserCase {
    public int id();
    public String description() default "no description";

}

只适用于方法的注解;注解被保留到class文件;

 

2、定义一个类 PasswordUtils ,定义方法并打上注释@UserCase

package annotation;

import java.util.List;

/**
 * 注解使用例子
 *
 * @author: mmb
 * @date: 19-8-10
 */
public class PasswordUtils {

    @UserCase(id = 47,description = "Passward must contain at least one numeric.")
    public boolean validatePassword(String password){
        return (password.matches("\\w*\\d\\w*"));
    }

    @UserCase(id = 48)
    public String encryptPassward(String passward){
        return new StringBuilder(passward).reverse().toString();
    }

    @UserCase(id = 49, description = "New password can not equal previously used ones.")
    public boolean checkForNewPassword(List prePasswords, String password){
        return !prePasswords.contains(password);
    }

}

 

3、反射获取方法注解用例

package annotation;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * 反射获取方法注解用例
 *
 * @author: mmb
 * @date: 19-8-10
 */
public class UseCaseTracker {

    public static void trackUseCases(List userCases, Class cl){
        // 类通过反射取到所有方法
        for (Method m : cl.getDeclaredMethods()){
            // 获取方法的注解
            UserCase uc = m.getAnnotation(UserCase.class);
            if (uc != null ){
                System.out.println("Found Use Case - "+uc.id()+ " "+ uc.description());
            }
        }

    }

    public static void main(String[] args){
        ArrayList userCases = new ArrayList<>();
        Collections.addAll(userCases,47,48,49,50);
        trackUseCases(userCases,PasswordUtils.class);
    }

}

运行结果:

Found Use Case - 49 New password can not equal previously used ones.
Found Use Case - 48 no description
Found Use Case - 47 Passward must contain at least one numeric.

 

参考文献:

  • https://www.cnblogs.com/yangming1996/p/9295168.html
  • https://blog.csdn.net/u010002184/article/details/79166478
  • https://blog.csdn.net/limj625/article/details/70242773

 

 

你可能感兴趣的:(Java)