JavaEE回顾

1、JavaEE回顾

1.1、Junit单元测试(白盒测试)

  • 黑盒测试:不需要写代码,给输入值,看程序是否能够输出期望值
  • 白盒测试:需要写代码。关注程序具体的执行流程。

步骤

  1. 定义一个测试类(测试用例)
    • 建议:
      • 测试类名:被测试的类名Test 例如CalculatorTest
      • 包名:xxx.xxx.xx.test
  2. 定义测试方法:可以独立运行
    • 建议:
      • 方法名:test测试的方法名 testAdd()
      • 返回值:void
      • 参数列表:空参
  3. 给方法加@Test 注意是大写开头
  4. 导入junit依赖环境

判定结果:

  • 红色:失败
  • 绿色:成功
  • 一般会使用断言操作 Assert.assertEquals()来处理结果

补充:

  • @Before: 修饰的方法会在测试方法执行之前被自动执行
  • @After: 修饰的方法会在测试方法执行之后自动被执行
public class Calculator {
    /**
     * 加法
     * @param a
     * @param b
     * @return
     */
    public int add (int a , int b){
        return a + b;
    }

    /**
     * 减法
     * @param a
     * @param b
     * @return
     */
    public int sub (int a , int b){
        return a - b;
    }
}
public class CalculatorTest {
    /**
     * 初始化方法
     * 用于资源申请,所有测试方法Test在执行之前都会先执行该方法
     */
    @Before
    public void init(){
        //写入资源申请代码
        System.out.println("init...");
    }

    /**
     * 释放资源的方法
     * 在所有测试方法Test执行完后,都会自动执行该方法
     */
    @After
    public void close(){
        //写入资源释放代码
        System.out.println("close...");
    }
    /**
     * 测试add方法
     */
    @Test
    public void testAdd(){
        Calculator calculator = new Calculator();
        int result = calculator.add(2, 3);
        //断言
        Assert.assertEquals(5,result);
    }
}
控制台输出:
init...
TestAdd...
close...

1.2、反射:框架设计的灵魂

反射 - 廖雪峰的官方网站 (liaoxuefeng.com)

①简介

1、框架

半成品软件。可以在框架的基础上进行软件开发,简化代码

2、反射

将类的各个组成部分封装为其他对象,这就是反射机制。反射机制是构建框架技术的基础所在。

Java的反射机制它知道类的基本结构,这种对Java类结构探知的能力,我们称为Java类的“自审”。大家都用过IDEA和eclipse。当我们构建出一个对象的时候,去调用该对象的方法和属性的时候。一按点,编译工具就会自动的把该对象能够使用的所有的方法和属性全部都列出来,供用户进行选择。这就是利用了Java反射的原理,是对我们创建对象的探知、自审。

3、Class类的好处
  1. 可以在程序的运行过程中,操作这些对象
  2. 可以解耦,来提高程序的可扩展性。

②获取Class对象的三种方式

1、Class.forName("全类名")

(全类名:包名.类名) 将字节码文件加载进内存,返回Class对象,对应Source 源代码阶段

多用于配置文件,将类名定义在配置文件中。读取文件,加载类

2、类名.class

通过类名的属性class获取 对应Class 类对象阶段

多用于参数的传递

3、对象.getClass()

getClass()方法在Object类中定义着,所有对象都能用。 对应Runtime 运行时阶段

多用于对象的获取字节码的方式

4、结论

同一个字节码文件(*.class)在一次程序运行中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个

public class ReflectDemo1 {
    /**
     *  获取Class对象的方式:
     *   1. Class.forName("全类名")     (全类名:包名.类名)    将字节码文件加载进内存,返回Class对象,**对应Source 源代码阶段**
     *   2. 类名.class: 通过类名的属性class获取   **对应Class 类对象阶段**
     *   3. 对象.getClass()  : getClass()方法在Object类中定义着,所有对象都能用。   **对应Runtime 运行时阶段**
     */
    public static void main(String[] args) throws Exception {
        //1. Class.forName("全类名")
        Class cls1 = Class.forName("com.domain.Person");
        System.out.println(cls1);//class com.domain.Person
        //2. 类名.class
        Class<Person> cls2 = Person.class;
        System.out.println(cls2);//class com.domain.Person
        //3. 对象.getClass()
        Person person = new Person();
        Class cls3 = person.getClass();
        System.out.println(cls3);//class com.domain.Person

        //用==去比较三个对象,结论:同一个字节码文件(*.class)在一次程序运行中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个
        System.out.println(cls1 == cls2); //true
        System.out.println(cls1 == cls3); //true
    }
}

③Class对象功能

Class - Java 11中文版 - API参考文档 (apiref.com)

public class Person {
    private String name = "Smith";
    public int age = 17;

    public String public_A;
    protected String protected_B;
    String default_C;
    private String private_D;
}
1、获取成员变量们Field
Field 	getField(name):根据字段名获取某个public的field(包括父类和继承的接口)
Field 	getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类和继承的接口)
Field[] getFields():获取所有public的field(包括父类和继承的接口)
Field[] getDeclaredFields():获取当前类的所有field(不包括父类和继承的接口)
public static void main(String[] args) throws Exception {
    
    //Field[] getFields()
    Field[] fields = personClass.getFields();
    for (Field field : fields) {
        System.out.println(field);  //public int com.domain.Person.age
                                    //public java.lang.String com.domain.Person.public_A
    }
    System.out.println("------------------------------------------");
    
    //Field  getField(String name)
    Field public_A = personClass.getField("public_A");
    System.out.println(public_A);//public java.lang.String com.domain.Person.public_A
    
    //获取成员变量public_A的值
    Person person = new Person();
    Object value_A = public_A.get(person);
    System.out.println(value_A);//null   public_A的默认值就是null
    
    //设置成员变量public_A的值
    public_A.set(person,"this is public_A");
    System.out.println(person.public_A);//this is public_A
    System.out.println("--------------------------------------------");

    //Field[] getDeclaredFields()  所有字段,不管什么修饰符
    Field[] declaredFields = personClass.getDeclaredFields();
    for (Field declaredField : declaredFields) {
        System.out.println(declaredField);   //private java.lang.String com.domain.Person.name
                                             //public int com.domain.Person.age
                                          //public java.lang.String com.domain.Person.public_A
                                    //protected java.lang.String com.domain.Person.protected_B
                                                //java.lang.String com.domain.Person.default_C
                                	    //private java.lang.String com.domain.Person.private_D
        }

    //Field  getDeclaredField(String name)
    Field name = personClass.getDeclaredField("name");
    //先要忽略访问修饰符的安全检查,
    //不忽略会报非法访问异常Exception in thread "main" java.lang.IllegalAccessException
    name.setAccessible(true);//暴力反射
    Object valueName = name.get(person);
    System.out.println(valueName);//Smith
}

操作Field(反射拿到的成员变量)

Field - Java 11中文版 - API参考文档 (apiref.com)

1、设置值

void	Field对象.set(Object obj, Object value)//将指定对象参数上此 字段对象表示的字段设置为指定的新值。

2、获取值

Object	Field对象.get(Object obj)//返回指定对象上此 字段表示的字段的值。

3、忽略访问权限修饰符的安全检查(暴力反射)

Field对象.setAccessible(true);

2、获取构造方法们
Constructor<T>		getConstructor(Class...):获取某个public的Constructor;
Constructor<T>		getDeclaredConstructor(Class...):获取某个Constructor;
Constructor<?>[]	getConstructors():获取所有public的Constructor;
Constructor<?>[]	getDeclaredConstructors():获取所有Constructor。
public static void main(String[] args) throws Exception {
    //0.获取Person的Class对象
    Class personClass = Person.class;
    
    //Constructor<T>		getConstructor(类<?>... parameterTypes)
    //有参构造函数
    Constructor constructor1 = personClass.getConstructor(String.class, int.class);
    System.out.println(constructor1);//public com.domain.Person(java.lang.String,int)
    	
    //创建对象
    Object person1 = constructor1.newInstance("张三", 31);
    System.out.println(person1);//Person{name='张三', age=31, public_A='null', protected_B='null', default_C='null', private_D='null'}
	
    //无参构造函数
    Constructor constructor2 = personClass.getConstructor();	
    Object person2 = constructor2.newInstance();
    System.out.println(person2);//Person{name='Smith', age=17, public_A='null', protected_B='null', default_C='null', private_D='null'}
    
    //无参构造函数的简化  clazz.newInstance()已弃用
    Object person3 = personClass.getDeclaredConstructor().newInstance();
    System.out.println(person3);//Person{name='Smith', age=17, public_A='null', protected_B='null', default_C='null', private_D='null'}
}
操作Constructor

1、创建对象

Object person1 = constructor1.newInstance("张三", 31);

2、如果使用空参数构造方法创建对象,操作可以简化

Object person3 = personClass.getDeclaredConstructor().newInstance();

3、constructor也有暴力反射方式

constructor2.setAccessible(true);

3、获取成员方法们
Method		getMethod(name, Class...):获取某个public的Method(包括父类和继承的接口)
Method		getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类和继承的接口)
Method[]	getMethods():获取所有public的Method(包括父类和继承的接口)
Method[]	getDeclaredMethods():获取当前类的所有Method(不包括父类和继承的接口)
public static void main(String[] args) throws Exception {
    //0.获取Person的Class对象
    Class personClass = Person.class;
    
    //Method	getMethod(String name, 类<?>... parameterTypes)
    Method method = personClass.getMethod("setAge", int.class);
    
    //执行方法
    Person person = new Person();
    method.invoke(person,52);
    System.out.println(person);//Person{name='Smith', age=52, public_A='null', protected_B='null', default_C='null', private_D='null'}
    
    //获取方法名称
    String name = method.getName();
    System.out.println(name);//setAge
    
    }

注意:

  • getMethods() :返回的Method[] 包括从父类和接口继承而来的public方法
  • getDeclaredMethods() :返回的Method[] 是自身所有访问类型的方法,但不包括继承而来的方法,
操作Method

Method - Java 11中文版 - API参考文档 (apiref.com)

1、执行方法

Object	invoke(Object obj, Object... args)	//在具有指定参数的指定对象上调用此 方法对象表示的基础方法。

2、支持暴力反射

method.setAccessible(true);

3、获取方法名称

String	getName()//返回此 方法对象表示的方法的名称,如 String 。

4、获取类名
String	getName()//返回此 类对象表示的实体名称(类,接口,数组类,基本类型或void),作为 String 。全类名
//获取类名
String className = personClass.getName();
System.out.println(className);//com.domain.Person   全类名

④反射案例

需求

写一个“框架”,不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法

实现
  1. 配置文件
  2. 反射
步骤
  1. 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
  2. 在程序中加载读取配置文件
  3. 使用反射技术来加载类文件进内存
  4. 创建对象
  5. 执行方法

具体代码

用例类 Person\Student

package com.domain;

private class Person {
    public void eat(){
        System.out.println("eating...");
    }
}

private class Student {
    public void sleep(){
        System.out.println("sleeping...");
    }
}

配置文件pro.properties

className=com.domain.Person
methodName=eat

自定义框架RefFrame

public class RefFrame {
    public static void main(String[] args) throws Exception {

        //1、加载配置文件
            //创建Properties对象
        Properties pro = new Properties();
            //加载配置文件,转换为一个集合,需要获取class目录下的配置文件
        ClassLoader classLoader = RefFrame.class.getClassLoader();
        InputStream is = classLoader.getResourceAsStream("pro.properties");
        pro.load(is);
        //2、获取配置文件中定义的数据
        String className = pro.getProperty("className");
        String methodName = pro.getProperty("methodName");
        //3、加载该类进内存
        Class cls = Class.forName(className);
        //4、创建对象
        Object obj = cls.getDeclaredConstructor().newInstance();
        //5、获取方法对象
        Method method = cls.getDeclaredMethod(methodName);
        //6、执行方法(如果不是public需要暴力反射)
        method.setAccessible(true);
        method.invoke(obj);//eating...
    }
}

要执行另外一个类的方法只需改配置文件

className=com.domain.Student
methodName=sleep
public class RefFrame {
	...
    method.invoke(obj);//sleeping...
}

好处:当项目过大时,更改配置文件远比更改源代码简单,不需要重新上线测试等操作。


1.3、注解 Annotation

从JDK1.5开始, Java增加对元数据的支持,也就是注解。

①注解和注释的区别

  • 注释:用文字描述程序,是给程序员看的。注释会被编译器直接忽略
  • 注解: 注解是放在Java源码的类、方法、字段、参数前的一种特殊“注释”。注解则可以被编译器打包进入class文件,因此,注解是一种用作标注的“元数据”。

②注解的作用

从JVM的角度看,注解本身对代码逻辑没有任何影响,如何使用注解完全由工具决定。

Java的注解可以分为三类:

第一类:编译检查

通过代码里标识的元数据(注解)让编译器能够实现基本的编译检查

是由编译器使用的注解,例如:

  • @Override:让编译器检查该方法是否正确地实现了覆写;
  • @SuppressWarnings:告诉编译器忽略此处代码产生的警告。

这类注解不会被编译进入.class文件,它们在编译后就被编译器扔掉了。

第二类:编写文档

通过代码里标识的注解生成doc文档

方法:在xxx.java文件目录下,打开命令行窗口cmd,输入javadoc xxx.java 注意cmd窗口和java文件的编码要一致

第三类:代码分析(主要)

通过代码里标识的注解对代码进行分析【使用反射


③JDK中预定义的一些注解(非重点)

  • @Override:用在方法上,表示这个方法重写了父类的方法,如toString()。如果父类没有这个方法,那么就无法编译通过。

  • @Deprecated:表示这个方法已经过期,不建议开发者使用,但是仍然可以使用。(暗示在将来某个不确定的版本,有可能会取消掉)

  • @SuppressWarnings:Suppress英文的意思是抑制的意思,这个注解的用处是忽略警告信息。
    比如大家使用集合的时候,有时候为了偷懒,会不写泛型,像这样:

    List heros = new ArrayList();
    

    那么就会导致编译器出现警告,而加上

    @SuppressWarnings({ "rawtypes", "unused" })
    

    就对这些警告进行了抑制,即忽略掉这些警告信息。
    @SuppressWarnings 有常见的值,分别对应如下意思

    • deprecation:使用了不赞成使用的类或方法时的警告(使用@Deprecated使得编译器产生的警告);
    • unchecked:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型; 关闭编译器警告
    • fallthrough:当 Switch 程序块直接通往下一种情况而没有 Break 时的警告;
    • path:在类路径、源文件路径等中有不存在的路径时的警告;
    • serial:当在可序列化的类上缺少 serialVersionUID 定义时的警告;
    • finally:任何 finally 子句不能正常完成时的警告;
    • rawtypes 泛型类型未指明
    • unused 引用定义了,但是没有被使用
    • all:关于以上所有情况的警告。
  • @FunctionalInterface这是 Java1.8 新增 的注解,用于约定函数式接口
    函数式接口概念: 如果接口中只有一个抽象方法(可以包含多个默认方法或多个static方法),该接口称为函数式接口。函数式接口其存在的意义,主要是配合Lambda 表达式 来使用。


④自定义注解

  1. 创建注解类型的时候即不使用class也不使用interface, 而是使用@interface

    public @interface MyAnnotation {
        //属性    但属性定义成 方法 的样子
    } 
    

    通过编译javac MyAnnotation.java和反编译javap MyAnnotation.class,可以得到一个MyAnnotation.java文件

    本质:注解本质上就是一个接口,该接口默认继承Annotation接口

    public interface MyAnnotation extends java.lang.annotation.Annotation {
    }
    
  2. 元注解
    @Target({METHOD,TYPE}) 表示这个注解可以用用在类/接口上,还可以用在方法上
    @Retention(RetentionPolicy.RUNTIME) 表示这是一个运行时注解,即运行起来之后,才获取注解中的相关信息,而不像基本注解如@Override 那种不用运行,在编译时IDEA就可以进行相关工作的编译时注解。
    @Inherited 表示这个注解可以被子类继承
    @Documented 表示当执行javadoc的时候,本注解会生成相关文档

  3. 注解元素,这些注解元素就用于存放注解信息,在解析的时候获取出来。

    接口的方法有默认修饰符public abstract

    public @interface MyAnnotation { 
       //默认为 public abstract
       String ip();
       int port() default 3306;
       String database();
       String encoding();
       String loginName();
       String password();
    }
    
  4. 注解元素的返回值类型有下列取值,例如:void 不行

    • 基本数据类型
    • String
    • 枚举Enum
    • 注解Annotation
    • 以上类型的数组 []
  5. 注解参数配置

    • 因为配置参数必须是常量,所以,上述限制保证了注解在定义时就已经确定了每个参数的值。
    • 注解的配置参数可以有默认值default,缺少某个配置参数时将使用默认值。
    • 此外,大部分注解会有一个名为value的配置参数,如果只value赋值,可以只写常量,例如@MyAnnotation(123)相当于省略了value参数。如果只写注解,相当于全部使用默认值。
    • 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可省略。@MyAnnotation(strs={"abc","qwerty"})

⑤元注解

元注解 meta annotation:用于注解 自定义注解 的注解。

元注解有这么几种:

@Target

表示这个注解能作用的位置

@Target({ElementType.METHOD,ElementType.TYPE})// 省略了 value= 
//表示他可以用在方法和类型上(类和接口或枚举类型),但是不能放在属性等其他位置。

可以选择的位置列表如下:

  • ElementType.TYPE:能修饰类、接口或枚举类型
  • ElementType.FIELD:能修饰成员变量
  • ElementType.METHOD:能修饰方法
  • ElementType.PARAMETER:能修饰参数
  • ElementType.CONSTRUCTOR:能修饰构造器
  • ElementType.LOCAL_VARIABLE:能修饰局部变量
  • ElementType.ANNOTATION_TYPE:能修饰注解
  • ElementType.PACKAGE:能修饰包

@Retention

表示生命周期

Java的注解本身对代码逻辑没有任何影响。根据@Retention的配置:

  • RetentionPolicy.SOURCE类型的注解在编译期就被丢掉了;
  • RetentionPolicy.CLASS类型的注解仅保存在class文件中,它们不会被加载进JVM;
  • RetentionPolicy.RUNTIME类型的注解会被加载进JVM,并且在运行期可以被程序读取。

如何使用注解完全由工具决定。

  • SOURCE类型的注解主要由编译器使用,因此我们一般只使用,不编写。
  • CLASS类型的注解主要由底层工具库使用,涉及到class的加载,一般我们很少用到。
  • 只有RUNTIME类型的注解不但要使用,还经常需要编写。

@Inherited

表示该注解具有继承性。


@Documented

描述在用javadoc命令生成API文档后,注解是否被抽取到API文档中


@Repeatable (java1.8 新增)

当没有@Repeatable修饰的时候,注解在同一个位置,只能出现一次

如例所示:
@JDBCConfig(ip = "127.0.0.1", database = "test", encoding = "UTF-8", loginName = "root", password = "admin")
@JDBCConfig(ip = "127.0.0.1", database = "test", encoding = "UTF-8", loginName = "root", password = "admin")
重复做两次就会报错了。
使用@Repeatable之后,再配合一些其他动作,就可以在同一个地方使用多次了。


⑥注解的使用

如何使用注解完全由工具决定。

  • SOURCE类型的注解主要由编译器使用,因此我们一般只使用,不编写。
  • CLASS类型的注解主要由底层工具库使用,涉及到class的加载,一般我们很少用到。
  • 只有RUNTIME类型的注解不但要使用,还经常需要编写。

因此,我们只讨论如何读取RUNTIME类型的注解。

因为注解定义后也是一种class,所有的注解都继承自java.lang.annotation.Annotation,因此,读取注解,需要使用反射API。

Java提供的使用反射API读取Annotation的方法包括:

1、判断某个注解是否存在于ClassFieldMethodConstructor
  • Class.isAnnotationPresent(Class)
  • Field.isAnnotationPresent(Class)
  • Method.isAnnotationPresent(Class)
  • Constructor.isAnnotationPresent(Class)
// 判断@Report是否存在于Person类:
Person.class.isAnnotationPresent(Report.class);
2、使用反射API读取Annotation:
  • Class.getAnnotation(Class)
  • Field.getAnnotation(Class)
  • Method.getAnnotation(Class)
  • Constructor.getAnnotation(Class)
// 获取Person定义的@Report注解:
Report report = Person.class.getAnnotation(Report.class);
int type = report.type();
String level = report.level();

使用反射API读取Annotation有两种方法。

方法一

先判断Annotation是否存在,如果存在,就直接读取:

Class cls = Person.class;
if (cls.isAnnotationPresent(Report.class)) {
    Report report = cls.getAnnotation(Report.class);
    ...
}
方法二

直接读取Annotation,如果Annotation不存在,将返回null

Class cls = Person.class;
Report report = cls.getAnnotation(Report.class);
if (report != null) {
   ...
}

读取方法、字段和构造方法的Annotation和Class类似。

但要读取方法参数的Annotation就比较麻烦一点,因为方法参数本身可以看成一个数组,而每个参数又可以有多个注解,所以,一次获取方法参数的所有注解就必须用一个二维数组来表示

例如,对于以下方法定义的注解:

public void hello(@NotNull @Range(max=5) String name, @NotNull String prefix) {
}

要读取方法参数的注解,我们先用反射获取Method实例,然后读取方法参数的所有注解:

// 获取Method实例:
Method m = ...
// 获取所有参数的Annotation:
Annotation[][] annos = m.getParameterAnnotations();
// 第一个参数(索引为0)的所有Annotation:
Annotation[] annosOfName = annos[0];
for (Annotation anno : annosOfName) {
    if (anno instanceof Range) { // @Range注解
        Range r = (Range) anno;
    }
    if (anno instanceof NotNull) { // @NotNull注解
        NotNull n = (NotNull) anno;
    }
}
3、解析注解(重要)

获取注解中定义的属性值,代替配置文件xxx.properties

步骤:

  1. 获取注解定义位置的对象 (Class、Method、Field)

    • Class ---> 类名.class
    • Method ---> 拿到Class对象后可以调用方法 cls.getDeclaredMethod(methodName)
    • Field ---> 同上
  2. 获取指定的注解定义位置对象 obj.getAnnotation(注解名.class);

    //其实就是在内存中生成了一个该注解接口的子类实现对象
    public class ProImpl implements Pro{
        public String className(){
            return "com.domain.Student";
        }
        public String methodName(){
            return "sleep";
        }
    }
    
  3. 调用注解对象对象中定义的抽象方法来获取返回值 注解对象.方法名();

  4. 使用反射来执行方法

    • 加载该类进内存

      Class<?> aClass = Class.forName(className);
      
    • 创建对象

      Object obj = aClass.getDeclaredConstructor().newInstance();
      
    • 获取方法对象

      Method method = aClass.getDeclaredMethod(methodName);
      
    • 调用该方法 , 如果方法不是public需使用暴力反射

      method.invoke(obj, param);//如无参数则可不填
      

案例:

//需要执行类中的方法
public class Student {
    private void sleep(){
        System.out.println("sleep...");
    }
}
//自定义注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Pro {
    String className();
    String methodName();
}
@Pro(className = "com.domain.Student",methodName = "sleep")
public class AnnotationTest {
    public static void main(String[] args) throws Exception {
        /*
         * 可以创建任意类的对象,可以执行任意方法,包括私有
         * 前提:不能改变该类的任何代码
         * 用注解的方式
         */

    //   解析注解
    //1 获取该类的字节码文件对象
        //这里必须使用泛型,不然下一步需要强制向下转型Annotation->Pro, 
        //因为.getAnnotation(Pro.class)返回的是Annotation类型
        Class<AnnotationTest> annotationTestClass = AnnotationTest.class;
    //2 获取该类上的注解对象
        Pro pro = annotationTestClass.getAnnotation(Pro.class);
        //其实就是在内存中生成了一个该注解接口的子类实现对象
        /*
        public class ProImpl implements Pro{
            public String className(){
                return "com.domain.Student";
            }
            public String methodName(){
                return "sleep";
            }
        }
        */
    //3 调用注解对象对象中定义的抽象方法来获取返回值
        String className = pro.className();
        String methodName = pro.methodName();
    //4 利用反射来执行方法
        Class<?> aClass = Class.forName(className);//加载该类进内存
        Object obj = aClass.getDeclaredConstructor().newInstance();//创建对象
        Method method = aClass.getDeclaredMethod(methodName);//获取方法对象
        method.setAccessible(true);//暴力反射,因为该方法是private
        method.invoke(obj);//sleeping...
    }
}

⑦测试框架用例

1、待测试方法,在类里面
public class Calculator {

    //加法
    @Check
    public void add(){
        System.out.println("1 + 0 = " + (1 + 0));
    }

    //减法
    @Check
    public void sub(){
        System.out.println("1 - 0 = " + (1 - 0));
    }

    //乘法
    @Check
    public void mul(){
        System.out.println("1 * 0 = " + (1 * 0));
    }

    //除法
    @Check
    public void div(){
        System.out.println("1 / 0 = " + (1 / 0));   //这里会有除零异常
    }
    
    @Check
    public void toStr(){
        String str = null;
        String s = str.toString();   //空指针异常
    }

    public void show(){
        System.out.println("这个方法未被测试");
    }
}
2、自定义注解(别忘了写元注解)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Check {
}
3、写测试框架
/**
 * 简单的测试框架
 * 当main方法执行后,会自动执行被@Check注解的方法
 * 判断方法是否有异常,记录到文件中
 * @author 肖南海
 * @version 1.0
 */
public class TestCheck {
    public static void main(String[] args) throws IOException {
        //1、创建计算器对象
        Calculator calculator = new Calculator();
        //2、获取字节码文件对象
        Class cls = calculator.getClass();
        //3、获取所有的方法
        Method[] methods = cls.getMethods();

        int cnt = 0;//出现异常的次数
        BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt"));

        for (Method method : methods) {
            //4、判断方法上是否有Check注解
            if (method.isAnnotationPresent(Check.class)){
                //5、如有,则执行方法
                try {
                    method.invoke(calculator);
                    //6、捕获异常
                } catch (Exception e) {
                    //7、记录文件信息到文件中
                    cnt++;

                    bw.write(method.getName() + " 方法出异常了");
                    bw.newLine();
                    bw.write("异常的名称:" + e.getCause().getClass().getSimpleName());
                    bw.newLine();
                    bw.write("异常的原因:" + e.getCause().getMessage());
                    bw.newLine();
                    bw.write("---------------------------");
                    bw.newLine();
                }
            }
        }

        bw.write("本次测试一共出现 " + cnt + " 次异常");
        bw.flush();
        bw.close();
    }
}
4、测试结果 bug.txt
toStr 方法出异常了
异常的名称:NullPointerException
异常的原因:null
---------------------------
div 方法出异常了
异常的名称:ArithmeticException
异常的原因:/ by zero
---------------------------
本次测试一共出现 2 次异常

⑧小结

  1. 以后大多数时候,我们会使用注解,而不是自定义注解
  2. 注解给谁用?
    • 编译器
    • 给解析程序用
  3. 注解不是程序的一部分,可以理解为注解就是一个标签
posted @   猫的心情  阅读(25)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示