反射与注解
------contents------
1. Junit单元测试
2. 反射
3. 注解
1. Junit单元测试:
-
测试分类:
- 黑盒测试:点点点,不需要写代码,给输入值,看程序是否能够输出期望的值。
- 白盒测试:关注程序执行的具体流程,需要写代码
-
Junit使用:白盒测试
-
步骤:
- 定义一个测试类(测试用例)
- 建议:
- 测试类名:被测试的类名Test CalculatorTest
- 包名:xxx.xxx.xxx.test cn.itcast.test
- 建议:
- 定义测试方法:可以独立运行
- 建议:
- 方法名:test测试的方法名 testAdd()
- 返回值:void
- 参数列表:空参
- 建议:
- 给方法加@Test注解
- 导入Junit依赖环境
-
判定结果:
-
红色:失败
-
绿色:成功
-
一般我们会使用断言操作来处理
-
Assert.assertEquals(期望的结果,运算的结果)
-
-
-
补充:
-
@Before:
-
修饰的方法会在测试方法之前被自动执行
(不论程序是否有错误,总是被执行)
-
-
@After:
-
修饰的方法会在测试方法执行之后自动被执行
(不论程序是否有错误,总是被执行)
-
2. 反射:框架设计的灵魂
-
框架:半成品软件。可以在框架的基础上进行软件的开发,可以简化编码。
(掌握反射知识,对于框架的设计开发非常重要)
Java代码在计算机中经历的三个阶段:
Source 源代码阶段 → Class类对象阶段 → Runtime运行时阶段

-
反射就是把java类中的各种成分映射成一个个的Java对象:
将类的各个组成部分(成员变量、构造方法、成员方法)
封装为其他对象(Field[ ] fields、Constructor[ ] cons、Method[ ] methods),这就是反射机制。
-
好处:
- 可以在程序运行过程中,操作这些对象。(调用方法)
- 可以解耦,提高程序的可扩展性。
-
获取Class类的三种方式:
- Class.forName(“全类名”):**将字节码文件加载进内存,返回Class对象
static Class<?> forName?(String className)
返回与给定字符串名称的类或接口相关联的 Class对象。
多用于配置文件,将类名定义在配置文件中。读取文件,加载类
-
类名.class:通过类名的属性class获取(多用于参数的传递)
-
对象.getClass():getClass()方法在Object类中定义着(所有对象都有这个方法,多用于对象的获取字节码的方式)。
结论:
同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。
-
Class对象的功能:
- 获取功能
-
获取成员变量们
-
Field[ ] getFields():获取所有public修饰的成员变量
-
Field getField(String name):获取指定名称的public修饰的成员变量
-
Field[ ] getDeclaredFields():获取所有的成员变量,不考虑修饰符
-
Field getDeclaredField(String name):获取指定名称的任意权限的成员变量
当访问私有成员变量时,要先**“暴力反射”**【忽略访问权限修饰符的安全检查】
field.setAccessible(true); -
-
获取构造方法们
-
Constructor<?>[] getConstructors?()
-
Constructor getConstructor?(Class<?>… parameterTypes)
-
Constructor<?>[] getDeclaredConstructors?()
-
Constructor getDeclaredConstructor?(Class<?>… parameterTypes)
-
-
获取成员方法们
- Method[] getMethods?()
- Method getMethod?(String name, Class<?>… parameterTypes)
-
- Method[] getDeclaredMethods?()
- Method getDeclaredMethod?(String name, Class<?>… parameterTypes)
-
获取类名
- String name = class.getName() :获取全类名
-
Field:成员变量
- 操作:
- 设置值
- 获取值
- 操作:
-
Constructor:构造方法
-
创建对象:
T newInstance(Object... initargs)使用由此 Constructor对象表示的构造函数,使用指定的初始化参数来创建和初始化构造函数的声明类的新实例。
-
如果使用空参数构造方法创建对象,操作可以简化:class对象.
newInstance()(从JDK1.9开始已过时)
-
-
Method:方法对象
-
执行方法:
Object invoke(Object obj, Object...args) -
获取方法名称:
- String name = method.getName():获取方法名
-
-
案例
-
需求:写一个“框架”,在不改变任何代码的前提下,可以帮我们去创建任意类的对象,并且执行其中任意方法
-
实现:
- 配置文件
- 反射
-
步骤:
- 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
- 在程序中加载读取配置文件
- 使用反射技术来加载文件进内存
- 创建对象
- 执行方法
-
代码演示:
// 1. 加载配置文件 // 1.1 创建Properties对象 Properties pro = new Properties(); // 1.2 加载(load)配置文件,转换为一个集合 // 1.2.1 获取class目录下的配置文件 ClassLoader classLoader = ReflectTest.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. 创建对象 Constructor constructor = cls.getConstructor(); Object o = constructor.newInstance(); // 5. 获取方法对象 Method method = cls.getMethod(methodName); // 6. 执行方法 method.invoke(o); -
-
配置文件
className=domain.Student methodName=sleep
3. 注解
-
概念:说明程序的。给计算机看的
-
注释:用文字描述程序的。给程序员看的
-
定义:注解(Annotation),也叫作元数据。一种代码级别的说明。它是JDK1.5之后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明、注释。
①JDK1.5+
②说明程序的
③使用注解:@注解名称
-
作用分类:
①编写文档:通过代码里标识的注解生成文档【生成文档doc文档】
②代码分析:通过代码里标识的注解对代码进行分析【使用反射】
③编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【如Override】
-
JDK中预定义的一些注解
- @Override:检测被注解标注的方法是否是继承自父类(接口)的
- @Deprecated:该注解标注的内容,表示已过时
- @SuppressWarnings:压制警告,一般传递参数"all"。
-
自定义注解
-
格式
元注解
public @interface 注解名称{
属性列表;(其实就是成员方法)
}
-
本质:注解本质上就是一个接口,该接口默认继承Annotation接口
-
public interface MyAnno extends java.lang.annotation.Annotation {}
-
-
属性:接口中的抽象方法
-
要求
-
属性的返回值类型:
① 基本数据类型
② String
③ 枚举
④ 注解
⑤ 以上类型的数组
-
定义了属性,在使用时需要给属性赋值
① 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时可以不进行属性的赋值。否则必须初始化。
② 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义即可,否则必须加=
③ 数组赋值时,必须用大括号{}包裹,如果数组中只有一个值,则{}可以省略。
-
-
-
元注解:用于描述注解的注解
-
常用的元注解:
① Target:描述注解能够作用的位置
- Element Type取值:
@Target(value = {ElementType.TYPE,ElementType.METHOD,ElementType.FIELD}) // TYPE:可以作用于类上 // METHOD:可以作用于方法上 // FIELD:可以作用于成员变量上 ② Retention:描述注解被保留的阶段
- RetentionPolicy取值:(value=可以省略)
@Retention(value = RetentionPolicy.RUNTIME) // 当前被描述的注解,会保留到class字节码文件中,并被JVM读取到 @Retention(value = RetentionPolicy.CLASS) // 当前被描述的注解,会保留到class字节码文件中,但不会被JVM读取到 @Retention(value = RetentionPolicy.SOURCE) // 当前被描述的注解,将不会保留到class字节码文件中 ③ Documented:描述注解是否被抽取到API文档中
④ Inherited:描述注解是否被子类继承
-
-
-
在程序使用(解析)注解:获取注解中定义的属性值
-
获取注解定义的位置的对象(Class,Method,Field)
-
获取指定的注解
* getAnnotation(Class) ``` // 其实就是在内存中生成了一个该注解接口的子类实现对象 public class ProImpl implements Pro{ public String className() { return "domain.Person"; } public String methodName() { return "eat"; } } ```- 调用注解中的抽象方法获取配置的属性值
- 代码演示:
package Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Method; /** * 自定的框架类 */ @Pro(className = "domain.Person", methodName = "eat", methodParam = "food") public class ReflectTest { public static void main(String[] args) throws Exception { /* 前提:不能改变该类的任何代码。可以创建任意类对象,可以执行任意方法 */ // 1. 解析注解 // 1.1 获取该类的字节码文件对象 Class<ReflectTest> reflectTestClass = ReflectTest.class; // 2. 获取上边的注解对象 // 其实就是在内存中生成了一个该注解接口的子类实现对象 Pro an = reflectTestClass.getAnnotation(Pro.class); // 3. 调用注解对象中定义的抽象方法,获取返回值 String className = an.className(); String methodName = an.methodName(); String methodParam = an.methodParam(); // 后面和配置文件反射法一样 // 3. 加载该类进内存 Class cls = Class.forName(className); // 4. 创建对象 Constructor constructor = cls.getConstructor(); Object o = constructor.newInstance(); // 5. 获取方法对象 Method method = cls.getMethod(methodName, String.class); // 6. 执行方法 method.invoke(o, methodParam); } } -
小结:
-
以后大多数时候,我们会使用注解,而不是自定义注解
-
注解给谁用?
① 编译器
② 给解析程序用
- 注解不是程序的一部分(可以理解成为标签)
----------end-----------
本文详细介绍了Java中的反射机制,包括如何使用Junit进行单元测试,以及注解在程序设计中的重要角色。深入理解反射有助于创建灵活的框架,同时掌握注解的使用能提升文档生成和代码分析能力。
2117





