欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

14Junit、反射、注解

程序员文章站 2022-05-25 16:37:45
14Junit、反射、注解 14.1.1 Junit的概述 一般IDE都内置了junit,若需要自行下载jar包,可以访问官网,官网地址如下:http://www.junit.org 1. 特点 - 方法命名规则:以test开头,使用驼峰命名法。 - 方法声明上:必须使用注解:@Test,必须使用p ......


14Junit、反射、注解

14.1.1 Junit的概述
      一般IDE都内置了junit,若需要自行下载jar包,可以访问官网,官网地址如下:http://www.junit.org 

1. 特点
   - 方法命名规则:以test开头,使用驼峰命名法。
   - 方法声明上:必须使用注解:@Test,必须使用public修饰符,没有返回值,方法没有参数。
  
2. 运行测试方法
   - 选中方法名:右键 --> Run 测试方法名,则运行选中的测试方法
     比如测试方法名为testSum,则右键 --> Run testSum
   - 选中类名:右键 --> Run 类名,则运行该类的所有测试方法
     比如类名为TestCalculte,则右键 --> Run TestCalculte
   - 选中模块名或项目名:右键 --> Run 'All Tests',则运行整个模块中所有类的所有测试方法。
3. 查看测试结果
   - 绿色:表示测试通过,
   - 红色:表示失败或出现错误,
  

14.1.2 常用注解

- @Before:在每个测试方法之前都会运行一次
- @After:在每个测试方法运行以后运行的方法
- @BeforeClass:在所有的测试方法运行之前,只运行一次,而且必须用在静态方法上面。
- @AfterClass:所有的测试方法运行以后,只运行一次,必须用在静态方法上面。

- 业务类

   /**
    业务类
    */
   public class Calculate {
       /*
          求a和b之和
        */
       public int sum(int a,int b){
           return a + b;
      }
 
       /**
        求a和b之差
        */
       public int sub(int a,int b){
           return a - b;
      }
  }

- 测试类

   import org.junit.*;
 
   public class Test2 {
 
       @BeforeClass
       public static void testBeforeClass() {
           System.out.println("类加载时, 只运行一次.");
      }
 
       @Before
       public void testBefore() {
           System.out.println("测试方法运行前被执行 ...");
      }
 
       @Test
       public void testSum() {
           Calculator calculator = new Calculator();
           int sum = calculator.sum(10, 20);
           System.out.println("sum = " + sum);
      }
 
       @Test
       public void testSub() {
           Calculator calculator = new Calculator();
           int sub = calculator.sub(100, 20);
           System.out.println("sub = " + sub);
      }
 
       @After
       public void testAfter() {
           System.out.println("每个测试方法被执行后执行 ...");
      }
 
       @AfterClass
       public static void testAfterClass() {
           System.out.println("类结束前, 只执行一次.");
      }
  }
 
   输出结果 :
   类加载时, 只运行一次.
   测试方法运行前被执行 ...
   sub = 80
   每个测试方法被执行后执行 ...
   测试方法运行前被执行 ...
   sum = 30
   每个测试方法被执行后执行 ...
   类结束前, 只执行一次.


14.2 反射

14.2.1 反射的基本概念

        2.1 什么是反射


                反射是一种机制,利用该机制可以在程序运行过程中对类进行解剖并操作类中的方法,属性,构造方法等成员。

        2.2 使用反射机制解剖类的前提


    必须先要获取到该类的字节码文件对象,即Class类型对象。关于Class描述字节码文件如下图所示:

tips:

1)Java中使用Class类表示某个class文件.

2)任何一个class文件都是Class这个类的一个实例对象.

        2.3 获取Class对象的三种方式


- 创建测试类:Student

   public class Student {
       // 属性
     
       // 行为
  }



2.2.1 方式1:通过类名.class获取

       @Test
       public void test1() {
           // 方式一 : 通过类名获取 class 对象
           // 格式 : 类名.class 属性
           // 常用场景 : 反射获取方法时, 确定方法的形参列表类型
           Class<Student> cls = Student.class;
           System.out.println("cls = " + cls);
      }
 
   输出结果 :
   cls = class cn.itcast.test2.Student



2.2.2  方式2:通过Object类的成员方法getClass()方法获取

       @Test
       public void test2() {
 
           // 1. 创建一个 Student 类型的对象
           Student stu = new Student();
 
           // 2. 调用方法, 并传入 stu 对象
           showInfo(stu);
      }
 
       public void showInfo(Object obj) {
 
           // 方式二 : 使用对象名调用 getClass() 方法.
           // 格式 : 对象名.getClass() 方法.
           // 使用场景 : 在方法内部, 确定传入形参的真实类型.
 
           Class<?> cls = obj.getClass();
           System.out.println("cls = " + cls);
      }
 
   输出结果 :
   cls = class cn.itcast.test2.Student



2.2.3  方式3:通过Class.forName("全限定类名")方法获取

       @Test
       public void test3() throws ClassNotFoundException {
 
           // 方式三 : 使用 Class 调用静态方法 forName(全限定类名);   `包名+类名`
           // 使用场景 : 加载外部的配置文件时使用
 
           Class<?> cls = Class.forName("cn.itcast.test2.Student");
           System.out.println("cls = " + cls);
      }
 
   输出结果 :
   cls = class cn.itcast.test2.Student


2.3 获取Class对象的信息


    知道怎么获取Class对象之后,接下来就介绍几个Class类中常用的方法了。

2.3.1  Class对象相关方法


    String getSimpleName(); 获得简单类名,只是类名,没有包   
    String getName(); 获取完整类名,包含包名+类名   
    T newInstance() ;创建此 Class 对象所表示的类的一个新实例。要求:类必须有public的无参数构造方法
public class TestDate {
 
       @Test
       public void testDate1() throws Exception {
 
           // 1. 获取 Date 表示的 Class 对象.
           Class<?> cls = Class.forName("java.util.Date");
 
           // 2. 获取简单类名
           String simpleName = cls.getSimpleName();
           System.out.println("simpleName = " + simpleName);
 
           // 3. 获取完成名称 (包名 + 类名)
           String name = cls.getName();
           System.out.println("name = " + name);
 
           // 3. 创建一个日期对象
           // cls.newInstance(); 已过时.
           Object obj = cls.getDeclaredConstructor().newInstance();
           System.out.println("obj = " + obj);
      }
  }
 
   输出结果 :
   simpleName = Date
   name = java.util.Date
   obj = Sun Jul 15 15:39:01 CST 2018

2.4 获取Class对象的Constructor信息


    一开始在阐述反射概念的时候,我们说到利用反射可以在程序运行过程中对类进行解剖并操作里面的成员。而一般常操作的成员有构造方法,成员方法,成员变量等等,那么接下来就来看看怎么利用反射来操作这些成员以及操作这些成员能干什么,先来看看怎么操作构造方法。而要通过反射操作类的构造方法,我们需要先知道一个Constructor类。

2.4.1 Constructor类概述


      Constructor是构造方法类,类中的每一个构造方法都是Constructor的对象,通过Constructor对象可以实例化对象。

 

2.4.2 Class类中与Constructor相关方法


    1. Constructor getConstructor(Class... parameterTypes)
        根据参数类型获取构造方法对象,只能获得public修饰的构造方法。
        如果不存在对应的构造方法,则会抛出 java.lang.NoSuchMethodException 异常。
       
    2. Constructor getDeclaredConstructor(Class... parameterTypes)
          根据参数类型获取构造方法对象,包括private修饰的构造方法。
          如果不存在对应的构造方法,则会抛出 java.lang.NoSuchMethodException 异常。
      
    3. Constructor[] getConstructors()
           获取所有的public修饰的构造方法
      
    4. Constructor[] getDeclaredConstructors()
           获取所有构造方法,包括privat修饰的

2.4.3 Constructor类中常用方法


    1. T newInstance(Object... initargs)
          根据指定参数创建对象。
    2. void setAccessible(true)
          暴力反射,设置为可以直接访问私有类型的构造方法。

2.5 获取Class对象的Method信息


    操作完构造方法之后,就来看看反射怎么操作成员方法了。同样的在操作成员方法之前我们需要学习一个类:Method类。

2.5.1 Method类概述


    Method是方法类,类中的每一个方法都是Method的对象,通过Method对象可以调用方法。    

 

 

2.5.2 Class类中与Method相关方法


    1. Method getMethod("方法名", 方法的参数类型... 类型) 
        根据方法名和参数类型获得一个方法对象,只能是获取public修饰的
       
    2. Method getDeclaredMethod("方法名", 方法的参数类型... 类型)
          根据方法名和参数类型获得一个方法对象,包括private修饰的
         
    3. Method[] getMethods() (了解)
          获取所有的public修饰的成员方法,包括父类中。
   
    4. Method[] getDeclaredMethods() (了解)
          获取当前类中所有的方法,包含私有的,不包括父类中。

2.5.3 Method类中常用方法


    1.  Object invoke(Object obj, Object... args)
          根据参数args调用对象obj的该成员方法   
          如果obj=null,则表示该方法是静态方法
     
    2.  void setAccessible(boolean flag)
          暴力反射,设置为可以直接调用私有修饰的成员方法

 

2.5.4 示例代码

测试一 :

       @Test
       public void testMethod1() throws Exception {
 
           // 1. 获取 Student 类表示的 Class 对象
           Class<?> cls = Class.forName("cn.itcast.test2.Student");
 
           // 2. 调用 getMethod 方法
           Method eat = cls.getMethod("eat", String.class);
 
           // 3. 调用 invoke 方法
           Object obj = cls.getDeclaredConstructor().newInstance();
           eat.invoke(obj, "牛肉");
      }
 
   输出结果 :
   正在吃牛肉



测试二 :

       @Test
       public void testDeclaredMethod2() throws Exception {
 
           // 1. 获取 Student 类表示的 Class 对象
           Class<?> cls = Class.forName("cn.itcast.test2.Student");
 
           // 2. 调用 declaredMethod 方法
           Method sleep = cls.getDeclaredMethod("fallInLove");
 
           // 3. 暴力反射 (设置可访问权限)
           sleep.setAccessible(true);
 
           // 4. 调用 invoke 执行
           Object obj = cls.getDeclaredConstructor().newInstance();
           sleep.invoke(obj);
      }
 
   输出结果 :
   正在谈恋爱 ...



测试三 :

       @Test
       public void testStaticMethod3() throws Exception {
 
           // 1. 获取 Student 类表示的 Class 对象
           Class<?> cls = Class.forName("cn.itcast.test2.Student");
 
           // 2. 调用 getMethod 方法
           Method study = cls.getMethod("study");
 
           // 3. 调用 invoke 方法
           study.invoke(null);
      }
 
   输出结果 :
   正在学习中 ...



测试四 :

       @Test
       public void tesMethods4() throws Exception {
 
           // 1. 获取 Student 类表示的 Class 对象
           Class<?> cls = Class.forName("cn.itcast.test2.Student");
 
           // 2. 调用 getMethods 方法
           Method[] methods = cls.getMethods();
 
           // 3. 遍历 methods 数组
           for (Method method : methods) {
               System.out.println(method);
          }
      }

测试五 :

       @Test
       public void tesDelcaredMethods5() throws Exception {
 
           // 1. 获取 Student 类表示的 Class 对象
           Class<?> cls = Class.forName("cn.itcast.test2.Student");
 
           // 2. 调用 getDeclaredMethods 方法
           Method[] methods = cls.getDeclaredMethods();
 
           // 3. 遍历 methods 数组
           for (Method method : methods) {
               System.out.println(method);
          }
      }

2.6 获取Class对象的Field信息(了解)


2.6.1 Field类概述


    Field是属性类,类中的每一个属性(成员变量)都是Field的对象,通过Field对象可以给对应的成员变量赋值和取值。

2.6.2 Class类中与Field相关方法


    1. Field getDeclaredField(String name)
       根据属性名获得属性对象,包括private修饰的
    
    2. Field getField(String name)
       根据属性名获得属性对象,只能获取public修饰的
      
    3. Field[]    getFields()
        获取所有的public修饰的属性对象,返回数组。
       
    4. Field[]    getDeclaredFields()
          获取所有的属性对象,包括private修饰的,返回数组。

 

2.6.3 Field类中常用方法


    void set(Object obj, Object value)
    Object get(Object obj) 
   
    void setAccessible(true);暴力反射,设置为可以直接访问私有类型的属性。
    Class getType(); 获取属性的类型,返回Class对象。


2.6.4 示例代码

测试一 :

       @Test
       public void testField1() throws Exception {
 
           // 1. 获取 Student 类表示的 Class 对象
           Class<?> cls = Class.forName("cn.itcast.test2.Student");
 
           // 2. 调用 getField 方法
           Field description = cls.getField("description");
 
           // 3. 设置属性
           Object obj = cls.getDeclaredConstructor().newInstance();
           description.set(obj, "这就是那个神奇的学生.");
 
           // 4. 获取属性
           Object desc = description.get(obj);
           System.out.println("desc = " + desc);
      }
 
   输出结果 :
   desc = 这就是那个神奇的学生.



测试二 :

       @Test
       public void testDeclaredField2() throws Exception {
 
           // 1. 获取 Student 类表示的 Class 对象
           Class<?> cls = Class.forName("cn.itcast.test2.Student");
 
           // 2. 调用 getDeclaredField 方法
           Field name = cls.getDeclaredField("name");
 
           // 3. 暴力反射
           name.setAccessible(true);
 
           // 4. 设置属性
           Object obj = cls.getDeclaredConstructor().newInstance();
           name.set(obj, "111");
 
           // 5. 查看
           System.out.println(obj);
      }
 
   输出结果 :
   Student{name='111', age=0, gender= }



测试三 :

       @Test
       public void testFields3() throws Exception {
 
           // 1. 获取 Student 类表示的 Class 对象
           Class<?> cls = Class.forName("cn.itcast.test2.Student");
 
           // 2. 调用 getFields 方法
           Field[] fields = cls.getFields();
 
           // 3. 遍历 fields 数组
           for (Field field : fields) {
               System.out.println(field);
          }
      }
 
   输出结果 :
   public java.lang.String cn.itcast.test2.Student.description



测试四 :

       @Test
       public void testDeclaredFields4() throws Exception {
 
           // 1. 获取 Student 类表示的 Class 对象
           Class<?> cls = Class.forName("cn.itcast.test2.Student");
 
           // 2. 调用 getDeclaredFields 方法
           Field[] fields = cls.getDeclaredFields();
 
           // 3. 遍历 fields 数组
           for (Field field : fields) {
               System.out.println(field);
          }
      }
 

2.7 反射案例


   编写一个工厂方法可以根据配置文件产任意类型的对象。

- 例如有配置文件stu.properties,存储在当前项目下,内容如下:

   className=cn.itcast.reflect.Student
   name=rose
   age=18
   gender=女

- 根据配置文件信息创建一个学生对象。



Student 类 :

   public class Student {
       // 属性
       private String name;
       private int age;
       private char gender;
 
       // 公开构造方法 :
       public Student(String name, int age, char gender) {
           this.name = name;
           this.age = age;
           this.gender = gender;
      }
 
       // 公开无参构造方法
       public Student() {
      }
 
       @Override
       public String toString() {
           return "Student{" +
                   "name='" + name + '\'' +
                   ", age=" + age +
                   ", gender=" + gender +
                   '}';
      }
 
       public String getName() {
           return name;
      }
 
       public void setName(String name) {
           this.name = name;
      }
 
       public int getAge() {
           return age;
      }
 
       public void setAge(int age) {
           this.age = age;
      }
 
       public char getGender() {
           return gender;
      }
 
       public void setGender(char gender) {
           this.gender = gender;
      }
  }

CreateObject 类 :

   import java.io.FileReader;
   import java.io.IOException;
   import java.lang.reflect.Field;
   import java.util.Properties;
   import java.util.Set;
 
   public class CreateObject {
 
       // 属性
       private static Properties prop;
 
       // 静态方法 : 加载配置文件
       static {
           // 初始化 :
           prop = new Properties();
 
           try {
               prop.load(new FileReader("stu.properties"));
          } catch (IOException e) {
 
               // e.printStackTrace();
               throw new RuntimeException("配置文件加载失败!");
          }
      }
 
       // 方法 : 根据配置文件, 创建对象
       public static Object createObject() throws Exception {
 
           // 1. 获取 class 名称
           String className = prop.getProperty("className");
 
           // 2. 获取 class 对象
           Class<?> cls = Class.forName(className);
 
           // 3. 创建一个 cls 表示的对象
           Object obj = cls.getDeclaredConstructor().newInstance();
 
           // 4. 获取属性集对象的所有 `键集`
           Set<String> keys = prop.stringPropertyNames();
 
           // 5. 遍历
           for (String key : keys) {
 
               // 判断 :
               if ("class".equals(key)) continue;
 
               // 6. 根据 key 获取对应的 value
               String value = prop.getProperty(key);
 
               // 7. 获取所有的 fields 数组
               Field field = cls.getDeclaredField(key);
 
               // 8. 设置访问权限
               field.setAccessible(true);
 
               // 9. 获取属性的类型
               Class<?> type = field.getType();
 
               // 10. 判断类型
               if (type == int.class) {
                   int v = Integer.parseInt(value);
                   // 设置属性
                   field.set(obj, v);
              } else if (type == char.class) {
                   char c = value.charAt(0);
                   // 设置属性
                   field.set(obj, c);
              } else {
                   field.set(obj, value);
              }
          }
 
           // 11. 返回对象
           return obj;
      }
  }

测试类 :

   public class Test {
       public static void main(String[] args) throws Exception {
 
           Object obj = CreateObject.createObject();
           System.out.println("obj = " + obj);
      }
  }
 
   输出结果 :
   obj = Student{name='rose', age=18, gender=女}

14.3 注解

3.1 注解的概述

3.1.1 注解的概念


- 注解是JDK1.5的特性。

- 注解相当一种标记,是类的组成部分,可以给类携带一些额外的信息。
- 标记(注解)可以加在包,类,字段,方法,方法参数以及局部变量上。
- 注解是给编译器或JVM看的,编译器或JVM可以根据注解来完成对应的功能。
  注解(Annotation)相当于一种标记,在程序中加入注解就等于为程序打上某种标记,以后,javac编译器、开发工具和其他程序可以通过反射来了解你的类及各种元素上有无何种标记,看你的程序有什么标记,就去干相应的事,标记可以加在包、类,属性、方法,方法的参数以及局部变量上。

3.1.2 注解的作用


 注解的作用就是给程序带入参数。

以下几个常用操作中都使用到了注解:

1. 生成帮助文档:@author和@version
   - @author:用来标识作者姓名。
   - @version:用于标识对象的版本号,适用范围:文件、类、方法。
     - 使用@author和@version注解就是告诉Javadoc工具在生成帮助文档时把作者姓名和版本号也标记在文档中。如下图:
      
2. 编译检查:@Override
   - @Override:用来修饰方法声明。
     - 用来告诉编译器该方法是重写父类中的方法,如果父类不存在该方法,则编译失败。如下图
    
3. 框架的配置(框架=代码+配置)
   - 具体使用请关注框架课程的内容的学习。

3.1.3 常见注解

1. @author:用来标识作者名。
2. @version:用于标识对象的版本号。
3. @Override :用来修饰方法声明,告诉编译器该方法是重写父类中的方法,如果父类不存在该方法,则编译失败。
4. @Deprecated: 用来表示不赞成使用.

 

3.2 自定义注解

3.2.1 定义格式


   public @interface 注解名 {
 
  }
 
   如:定义一个名为 Student 的注解
   public @interface Student {
 
  }

- 以上定义出来的注解就是一个最简单的注解了,但这样的注解意义不大,因为注解中没有任何内容,就好像我们定义一个类而这个类中没有任何成员变量和方法一样,这样的类意义也是不大的,所以在定义注解时会在里面添加一些成员来让注解功能更加强大,这些成员就是属性。接下来就看看怎么给注解添加属性。

3.2.2 注解的属性


1. 属性的作用
   - 可以让用户在使用注解时传递参数,让注解的功能更加强大。
2. 属性的格式
   - 格式1:数据类型 属性名();
   - 格式2:数据类型 属性名() default 默认值;
3. 属性定义示例
      // 该注解拥有三个属性 (name, age, gender)
      public @interface Student {
     
          String name();
     
          int age() default 18;
     
          char gender() default '男';
      }
4. 属性适用的数据类型
   - 八种基本数据类型(byte, short, int, long, float, double, char, boolean)
   - String类型,Class类型,枚举类型,注解类型
   - 以上所有类型的一维数组

3.3 使用自定义注解

3.3.1 定义注解


1. 定义一个注解:Book
   - 包含属性:String value()   书名
   - 包含属性:double price()  价格,默认值为 100
   - 包含属性:String[] authors() 多位作者   
   说明 : 当注解中只有一个属性且名称是value,在使用注解时给value属性赋值可以直接给属性值,无论value是单值元素还是数组类型。
  
2. 代码实现
 public @interface Book {
           // 书名
           String value();
           // 价格
           int price() default 100;
           // 多位作者
           String[] authors();
  }
3.3.2 使用注解


1. 定义类在成员方法上使用Book注解

   public class BookShelf {
 
       @Book(value = "西游记", price=998, authors = {"吴承恩", "白求恩"})
       public void show() {
         
      }
  }

使用注意事项

- 如果属性有默认值,则使用注解的时候,这个属性可以不用赋值。

- 如果属性没有默认值,那么在使用注解时一定要给属性赋值。

 

3.4 注解之元注解

3.4.1 元注解的概述


- Java API 提供的注解
- 专门用来定义注解的注解。
- 任何 Java 官方提供的非元注解的定义中都使用到了元注解。
3.4.2 常用元注解


- @Target 注释的使用位置.
- @Retention 注解的声明周期.
3.4.2.1 元注解之@Target


- 作用:指明此注解用在哪个位置,如果不写默认是任何地方都可以使用。
  - 可选的参数值在枚举类ElemenetType中包括:
         TYPE: 用在类,接口上
         FIELD:用在成员变量上
         METHOD: 用在方法上
         PARAMETER:用在参数上
         CONSTRUCTOR:用在构造方法上
         LOCAL_VARIABLE:用在局部变量上
3.4.2.2 元注解之@Retention


- 作用:定义该注解的生命周期(有效范围)。
  - 可选的参数值在枚举类型RetentionPolicy中包括
        SOURCE:注解只存在于Java源代码中,编译生成的字节码文件中就不存在了。
        CLASS:注解存在于Java源代码、编译以后的字节码文件中,运行的时候内存中没有,默认值。
        RUNTIME:注解存在于Java源代码中、编译以后的字节码文件中、运行时内存中,程序可以通过反射获取该注解。

 

3.4.3 元注解使用示例


 

  // (书名, 价格, 作者)
 
   import java.lang.annotation.ElementType;
   import java.lang.annotation.Retention;
   import java.lang.annotation.RetentionPolicy;
   import java.lang.annotation.Target;
 
   // 元注解 : Target 目标 (注解使用的位置)
   @Target({ElementType.METHOD, ElementType.TYPE})
 
   // 元注解 : Retention 保留策略 (SOURCE, CLASS, RUNTIME)
   @Retention(RetentionPolicy.RUNTIME)
   public @interface Book {
 
       String value();     // 说明 : 如果注解只有一个属性, 最好取名为 value, 因为书写时, 可以省略.
 
       int price() default 100;
 
       String[] authors();  // 多位作者
  }



BookShelf 类

   @Book(value = "西游记", price=998, authors = {"吴承恩", "xxx"})
   public class BookShelf {
 
       // 属性
       // @Book(value = "西游记", price=998, authors = {"吴承恩", "xxx"})
       private int id;
 
       @Book(value = "西游记", price=998, authors = {"吴承恩", "xxx"})
       public void show() {
           String value = "";
           int price = 0;
           String[] authors = {};
           System.out.println("书名为 : " + value);
           System.out.println("价格为 : " + price);
           System.out.println("作者为 : " + Arrays.toString(authors));
      }
  }



3.5 注解解析

3.5.1 什么是注解解析


- 通过Java技术获取注解数据的过程则称为注解解析。

3.5.2 与注解解析相关的接口


- Anontation:所有注解类型的公共接口,类似所有类的父类是Object。
- AnnotatedElement:定义了与注解解析相关的方法,常用方法:
      boolean isAnnotationPresent(Class annotationClass); 判断当前对象是否有指定的注解,有则返回true,否则返回false。
      T getAnnotation(Class<T> annotationClass);  获得当前对象上指定的注解对象。

3.5.3 获取注解数据的原理


- 注解作用在那个成员上,就通过反射获得该成员的对象(Filed)来得到它的注解。

- 如注解作用在方法上,就通过方法(Method)对象得到它的注解*

- 如注解作用在类上,就通过Class对象得到它的注解

 

3.5.4 使用反射获取注解的数据


3.5.4.1 需求说明


1. 定义注解Book,要求如下:
   - 包含属性:String value()   书名
   - 包含属性:double price()  价格,默认值为 100
   - 包含属性:String[] authors() 多位作者 
   - 限制注解使用的位置:类和成员方法上
   - 指定注解的有效范围:RUNTIME
2. 定义BookStore类,在类和成员方法上使用Book注解
3. 定义TestAnnotation测试类获取Book注解上的数据

3.5.4.2 代码实现


1.注解Book
   import java.lang.annotation.ElementType;
   import java.lang.annotation.Retention;
   import java.lang.annotation.RetentionPolicy;
   import java.lang.annotation.Target;
 
   @Target({ElementType.TYPE, ElementType.METHOD})
   @Retention(RetentionPolicy.RUNTIME)
   public @interface Book {
       // 书名
       String value();
       // 价格
       int price() default 100;
       // 作者 (多位作者)
       String[] authors();
  }

2.BookShelf 类

   import java.lang.reflect.Method;
   import java.util.Arrays;
 
   @Book(value = "西游记", price=998, authors = {"吴承恩", "白求恩"})
   public class BookShelf {
 
       // 属性
       // @Book(value = "西游记", price=998, authors = {"吴承恩", "白求恩"})
       private int id;
 
       @Book(value = "西游记", price=998, authors = {"吴承恩", "白求恩"})
       public void show() {
           // 定义变量
           String value = "";
           int price = 0;
           String[] authors = {};
         
           // 获取当前类的 Class 对象
           Class<? extends BookShelf> cls = this.getClass();
           try {
               // 获取当前方法对象
               Method show = cls.getMethod("show");
               // 判断当前方法上是否有注解信息
               if (show.isAnnotationPresent(Book.class)) {
                   // 条件成立, 获取到当前注解对象
                   Book book = show.getAnnotation(Book.class);
                   // 取出信息, 并实现赋值
                   value = book.value();
                   price = book.price();
                   authors = book.authors();
              }
          } catch (NoSuchMethodException e) {
               e.printStackTrace();
          }
 
           // 输出查看
           System.out.println("书名为 : " + value);
           System.out.println("价格为 : " + price);
           System.out.println("作者为 : " + Arrays.toString(authors));
      }
  }

3.TestBookShelf 类

   public class TestBookShelf {
       public static void main(String[] args) {
 
           BookShelf bookShelf = new BookShelf();
           bookShelf.show();
      }
  }
 
   输出结果 :
   书名为 : 西游记
   价格为 : 998
   作者为 : [吴承恩, 白求恩]
 
   书名为 :
   价格为 : 0
   作者为 : []



补充 : 解析类上的注解

   import java.util.Arrays;
 
   public class TestBookShelf2 {
       public static void main(String[] args) throws ClassNotFoundException {
 
           // 1. 获取类
           Class<?> cls = Class.forName("cn.itcast.annotation.BookShelf");
           // 2. 判断该类上是否有 Book 注解信息
           if (cls.isAnnotationPresent(Book.class)) {
               // 3. 获取 Book 注解对象
               Book book = cls.getAnnotation(Book.class);
               // 4. 取出注解信息
               String value = book.value();
               int price = book.price();
               String[] authors = book.authors();
               // 5. 输出查看
               System.out.println("value = " + value);
               System.out.println("price = " + price);
               System.out.println("authors = " + Arrays.toString(authors));
          }
      }
  }
 
   输出结果 :
   value = 西游记
   price = 998
   authors = [吴承恩, 白求恩]

3.6 注解案例

3.5.1 案例说明


    模拟Junit测试的@Test
3.5.2 案例分析


1. 模拟Junit测试的注释@Test,首先需要编写自定义注解@MyTest,并添加元注解,保证自定义注解只能修饰方法,且在运行时可以获得。
2. 然后编写目标类(测试类),然后给目标方法(测试方法)使用 @MyTest注解,编写三个方法,其中两个加上@MyTest注解。
3. 最后编写调用类,使用main方法调用目标类,模拟Junit的运行,只要有@MyTest注释的方法都会运行。
3.5.3 案例代码

1. 注解MyTest

   import java.lang.annotation.ElementType;
   import java.lang.annotation.Retention;
   import java.lang.annotation.RetentionPolicy;
   import java.lang.annotation.Target;
 
   @Target(ElementType.METHOD)
   @Retention(RetentionPolicy.RUNTIME)
   public @interface MyTest {
  }

1. 目标类MyTestClass

   public class MyTestClass {
 
       @MyTest
       public void test01() {
           System.out.println("测试方法一被执行 ...");
      }
 
       public void test02() {
           System.out.println("测试方法二被执行 ...");
      }
 
       @MyTest
       public void test03() {
           System.out.println("测试方法三被执行 ...");
      }
  }

1. 调用类ParseAnnotation

   import java.lang.reflect.Method;
 
   // 解析注解类 :
   public class ParseAnnotation {
       public static void main(String[] args) throws Exception {
 
           // 1. 获取 MyTestClass 的 Class 对象
           Class<?> cls = Class.forName("cn.itcast.practice.MyTestClass");
           Object obj = cls.getDeclaredConstructor().newInstance();
 
           // 2. 调用 getMethods 获取所有方法
           Method[] methods = cls.getMethods();
 
           // 3. 遍历 methods 数组
           for (Method method : methods) {
 
               // 4. 判断当前方法上是否存在 MyTest 注解
               if (method.isAnnotationPresent(MyTest.class)) {
 
                   // 5. 执行当前方法
                   method.invoke(obj);
              }
          }
      }
  }
 
   输出结果 :
   测试方法一被执行 ...
   测试方法三被执行 ...