学习日记(单元测试、反射、注解、动态代理)

学习日记(单元测试、反射、注解、动态代理)

一、单元测试

单元测试就是针对最小的功能单元编写测试代码,Java 程序最小的功能单元是方法

单元测试就是针对 Java 方法的测试,进而检查方法的正确性,需要用到 JUnit 单元测试框架。

JUnit 单元测试框架是使用 Java 语言实现的,是开源的,几乎所有的 IDE 工具都集成了 JUnit。

JUnit 单元测试框架的优点

  • JUnit 可以灵活地选择执行哪些测试方法,也可以一键执行全部测试方法;
  • JUnit 可以生成全部方法的测试报告,测试良好为绿色,测试失败为红色;
  • 单元测试中的某个方法测试失败了,不会影响其他测试方法的测试。

1. 单元测试实践

使用单元测试进行业务方法预期结果、正确性的测试,步骤

  • 将 JUnit 的 jar 包导入到项目中,IDEA 通常整合好了 JUnit 框架,一般不需要导入;
  • 单独创建一个测试类,编写测试方法,每个测试方法必须是公共的、无参数、无返回值的非静态方法;
  • 在测试方法上使用 @Test 注解,第一次使用时需要 Alt + Enter 导入包 org.junit.Test,标注该方法是一个测试方法;
  • 在测试方法中完成被测试方法的预期正确性测试,选择测试方法,选择“ JUnit 运行”。
方法名说明
public static void assertEquals(String message, long expected, long actual)断言,类 Assert 直接调用

说明:参数一是预期值与实际值不相同时,给出的提示信息;参数二是预期值;参数三是实际值。

当没有问题时

当出现问题时

注意

  • 测试类和测试方法的命名应该规范。

  • 每个测试方法必须是公共的无参数无返回值非静态方法。

  • 测试方法应该使用 @Test 注解标记。

  • 一个业务方法对应一个测试方法。

  • 运行测试方法时,可以单独测试方法,也可以一起测试所有方法。

2. JUnit 常用注解

JUnit 4 版本

注解说明
@Test测试方法
@Before用来修饰实例方法,该方法会在每一个测试方法执行之前执行一次
@After用来修饰实例方法,该方法会在每一个测试方法执行之后执行一次
@BeforeClass用来修饰静态方法,该方法会在所有测试方法执行之前只执行一次
@AfterClass用来修饰静态方法,该方法会在所有测试方法执行之后只执行一次

注意

  • 开始执行的方法:一般用来完成初始化资源;
  • 执行完之后的方法:一般用来释放资源。

二、反射

反射是在运行时获取类的字节码文件对象,然后可以解析这个类的全部成分。

在运行时,可以直接得到这个类的构造器对象(Constructor)、成员变量对象(Field)、成员方法对象(Method)。

这种运行时动态获取类信息以及动态调用类中成分的能力称为 Java 语言的反射机制。

反射的核心思想和关键:得到编译后的 Class文件对象。

反射的作用

  • 可以在运行时得到一个类的全部成分然后操作;
  • 可以破坏封装性;
  • 可以破坏泛型的约束性。

反射适合的用途:做 Java 的高级框架。

1. 反射获取类对象

反射的第一步是先得到编译后的 Class 类对象,有三种方法。

  • 方法一:调用 Class 类中的一个静态方法 forName
  • 方法二:类调用 class 方法。
  • 方法三:对象调用 getClass 方法获取对应类的 Class 对象。
方法名说明
方法一public static Class<?> forName(String className)Class 类调用
方法二class调用
方法三public final native Class<?> getClass()对象调用

注意:方法一中的参数为全限名,也就是包名 + 类名。

2. 反射获取构造器对象

总体步骤

  • 获得 Class 对象;
  • (通过 Class 对象)获得构造器 Constructor 对象;
  • (通过构造器)创建对象。

第二步:Class 类获得构造器 Constructor 对象的方法:

方法名说明
public Constructor<?>[] getDeclaredConstructors()返回所有构造器对象的数组,存在就能拿到
public Constructor getDeclaredConstructor(Class<?>… parameterTypes)返回单个构造器对象,存在就能拿到

注意

  • 这两个方法都是 Class 对象调用。

  • 这两个方法获取构造器,无所谓构造器的声明类型,即,无所谓构造器是 public 还是 private。

  • 返回单个构造器对象时,方法的参数为 Class 对象且为可变参数,如:String.classint.class

  • 返回单个构造器对象时,若不写参数,则返回无参构造器,有参数,返回有参构造器。

第三步:创建对象的方法:

方法名说明
public T newInstance(Object … initargs)根据指定的构造器创建对象
public void setAccessible(boolean flag)参数设置为 true,表示取消访问检查,进行暴力反射

注意

  • 这两个方法都是构造器对象调用。
  • 第二个方法是当构造器方法不是用 public 修饰时使用,打开权限,可以有效一次,反射可以破坏封装性,私有的也可以执行。
  • 第一个方法中,当构造器为无参时,方法不写参数;当构造器为有参时,方法中的参数为初始化值。

第二步代码示例:获取所有构造器对象的数组、获取单个构造器对象

第三步代码示例:初始化对象、暴力反射

3. 反射获取成员变量对象

总体步骤

  • 获得 Class 对象;
  • (通过 Class 对象)获得成员变量 Field 对象;
  • (通过成员变量)赋值或者取值。

第二步:Class 类获得成员变量 Field 对象的方法:

方法名说明
public Field[] getDeclaredFields()返回所有成员变量对象的数组,存在就能拿到
public Field getDeclaredField(String name)返回单个成员变量对象,存在就能拿到

注意

  • 这两个方法都是 Class 对象调用。

  • 这两个方法获取成员变量对象,无所谓成员变量的声明类型,即,无所谓成员变量是 public 还是 private。

  • 第二个方法的参数为成员变量的名称,即属性的名称。

第三步:赋值或者取值的方法:

方法名说明
public void set(Object obj, Object value)赋值
public Object get(Object obj)取值
public void setAccessible(boolean flag)参数设置为 true,表示取消访问检查,进行暴力反射

注意

  • 这三个方法都是成员变量对象调用。
  • 当成员变量对象不是用 public 修饰时使用 setAccessible,打开权限,反射可以破坏封装性,私有的也可以执行。
  • 在赋值方法中,参数一是创建的对象,参数二是为该对象指定的属性赋值。

第二步代码示例:获取所有成员变量对象的数组、获取单个成员变量对象

第三步代码示例:赋值、取值、暴力反射

4. 反射获取成员方法对象

总体步骤

  • 获得 Class 对象;
  • (通过 Class 对象)获得成员方法 Method 对象;
  • (通过成员方法对象)运行方法。

第二步:Class 类获得成员方法对象的方法:

方法名说明
public Method[] getDeclaredMethods()返回所有成员方法对象的数组,存在就能拿到
public Method getDeclaredMethod(String name, Class<?>… parameterTypes)返回单个成员方法对象,存在就能拿到

注意

  • 这两个方法都是 Class 对象调用。

  • 这两个方法获取成员方法对象,无所谓成员方法的声明类型,即,无所谓成员方法是 public 还是 private。

  • 第二个方法的第一个参数为:方法名;第二个参数为:该方法参数类型的 Class 对象,如:int.class,若该方法没有参数,则可以不写第二个参数。

第三步:运行方法:

方法名说明
public Object invoke(Object obj, Object… args)运行方法
public void setAccessible(boolean flag)参数设置为 true,表示取消访问检查,进行暴力反射

注意

  • 这三个方法都是成员方法对象调用。
  • 当成员方法对象不是用 public 修饰时使用 setAccessible,打开权限,反射可以破坏封装性,私有的也可以执行。
  • 第一个方法的第一个参数:对象;第二个参数:传递的参数(如果没有参数可以不写)。

第二步代码示例:获取所有成员方法对象的数组、获取单个成员方法对象

第三步代码示例:运行方法、暴力反射


三、反射的作用举例

1. 绕过编译阶段为集合添加数据

泛型只是在编译阶段可以约束集合只能操作某种数据类型,在编译成 Class 文件进入运行阶段的时候,其真实类型都是 ArrayList,泛型相当于被擦除,此时,可以为集合存入其他任意类型的元素,这就用到了反射,因为反射是在运行的技术

通过反射为集合添加其他类型的数据

2. 通用框架

需求:给定任意一个对象,在不清楚对象字段的情况下,可以把对象的字段名称和对应值存储到文件中去。

分析

  • 定义一个类 MybatisUtil 存放 save 方法,可以接收任意类型的对象;
  • save 方法中,应用反射获取该对象的全部成员变量名称以及对应值;
  • 用打印流 PrintStream 将获取的数据写入到文件中;
  • main 方法中对象调用 save 方法,得到目标文件。

注意

  • 当成员变量是非公有属性时,要用 setAccessible 来打开权限,暴力反射。
  • Class 对象获取类名的两个方法,如下:
方法名说明
public String getName()获取当前 Class 对象的全限名:包名 + 类名
public String getSimpleName()获取当前 Class 对象的类名


四、注解

注解:Annotation,又称标注。Java 语言中的类、构造器、方法、成员变量、参数等都可以被注解进行标注。

作用:对 Java 中类、构造器、方法、成员变量、参数等做标记,然后进行特殊处理。

例如在 JUnit 框架中,标注了注解 @Test 的方法就可以被当作测试方法执行,而没有标记的就不能当作测试方法执行。

1. 自定义注解

格式

类、构造器、方法、成员变量、参数等都可以被注解进行标注。

特殊属性 value

  • value 属性,如果自定义注解时只有一个 value 属性的情况下,使用 value 属性可以省略 value 名称不写。
  • 如果有多个属性,并且多个属性都有默认值,那么 value 名称也可以省略不写。
  • 除了上面两种情况外,value 的名称都得写。

2. 元注解

元注解:注解注解的注解。

两个元注解:

  • @Target :约束自定义注解只能在哪些地方使用。
  • @Retention :声明注解的生命周期。

3. 注解解析

注解的操作中经常需要进行解析,注解的解析就是判断是否存在注解,存在注解就解析出内容。

与注解解析相关的接口:

  • Annotation :注解的顶级接口,注解都是该类型的对象;
  • AnnotationElement :该接口定义了与注解解析相关的解析方法。

Class、Method、Field、Constructor 这些类都实现了 AnnotationElement 接口,都拥有解析注解的能力。

解析注解的方法

方法名说明
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)判断当前对象是否使用了指定的注释
public A getDeclaredAnnotation(Class annotationClass)根据注解类型获得对应的注解对象
public Annotation[] getDeclaredAnnotations()获得当前对象所有注解,返回注解数组

注意:方法一和方法二都是由 Class、Method、Field、Constructor 这些类对象调用,参数为注解的类类型,如:Book.class

解析注解的技巧:注解在哪个成分上,就先拿哪个成分对象。

  • 注解作用在类上,就先获得该类对应的 Class 对象,然后再拿注解;
  • 注解作用在成员方法上,就先获得该成员方法对应的 Method 对象,然后再拿注解;
  • 注解作用在成员变量上,就先获得该成员变量对应的 Field 对象,然后再拿注解。

4. 注解的应用:模拟 JUnit 框架

需求:自定义一个注解 MyTest,和 JUnit 框架一样,只有方法上有 MyTest 注解才可以被执行。

思路:先用反射提取类中的所有方法,用循环依次判断每一个方法是否有 MyTest 注解,有注解然后执行。


五、动态代理

代理也是一个对象,用来对被代理对象的行为额外做一些辅助操作

Java 中代理的类是:java.lang.reflect.Proxy,类中有一个静态方法 newProxyInstance,可以为对象产生一个代理对象返回。

方法名说明
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)为对象返回代理对象

注意

  • 参数一:是定义代理类的类加载器,用来产生代理对象,如:dog.getClass().getClassLoader()
  • 参数二:是代理类要实现的接口列表,用来将被代理的方法交给代理对象,如:dog.getClass().getInterfaces()
  • 参数三:是代理对象的核心处理程序,用来指定代理对象干什么事,如:new InvocationHandler()

实现动态代理的步骤

  • 必须存在接口,且被代理对象需要实现接口;
  • 使用 Proxy 类提供的方法 newProxyInstance,产生一个代理对象。

动态代理的优点

  • 可以在不改变方法源码的情况下,实现对方法功能的增强,提高了代码的复用;
  • 简化了编程工作,提高了开发效率,同时提高了软件系统的可扩展性;
  • 可以为被代理对象的所有方法做代理;
  • 非常灵活,支持为任意接口类型的实现类对象做代理,也可以直接为接口本身做代理。

1. 案例 1

需求:为 dog 对象创建一个代理对象。

注意点

  • 最好将产生代理对象的方法封装到一个类中。
  • 必须存在接口,并且被代理对象需要实现接口,代理对象的类型为接口。
  • 通过代理对象调用方法的执行流程:先走向代理;代理可以为方法额外做一些辅助工作;用 method 去触发对象方法的执行;回到代理中,由代理负责返回结果给方法的调用者。执行流程图如下。
  • method 去触发对象,method 是正在调用的方法,args 是该方法的参数,固定步骤

2. 案例 2

需求:模拟用户管理业务,包括用户登录、用户删除、用户查询功能,做性能分析,统计每个功能的耗时。

分析:把性能分析交给代理去统一完成,这样可以省去在每个方法中都进行一遍性能分析,提高了代码的复用性。

注意:必须要有接口,并且实现类要实现接口,代理通常是基于接口实现的。


注意:

  1. 通过这种方式也可以为集合添加其他类型的元素。

对比

  1. 动态代理非常灵活,支持为任意接口类型的实现类对象做代理,也可以直接为接口本身做代理。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sun 3285

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值