反射
- 框架设计的灵魂
- 框架 : 半成品软件 , 可以在框架的基础上进行软件开发 , 简化代码
(1)反射的概述
- 将类的各个组成部分封装为其他对象 , 这就是反射机制
- 像idea中的方法提示功能就是使用反射机制 : 用户在创建一个对象后 , 底层将这个对象所拥有的方法全部封装到了这个对象中 , 当用户输入这个对象的时候 , 在下边展示封装的所有的方法即可
- 反射的好处:
- 1.可以在程序运行的过程中 , 操作这些对象
- 2.可以解耦合 , 提高程序的可扩展性
- 一个java代码 , 通过编译 , 分成一个个部分 , 这些部分通过类加载器 , 封装到一个个对象中 , 像成员变量 , 成员方法 等 , 存储到硬盘中 , 最后当创建这个对象的时候 , 会调用这些对象
(2)反射的使用
1. Class.forName("全类名 : 包名.类名的方式"); //将字节码文件加载进内存 , 返回Class对象 (这是在Source源代码阶段)
//多用于配置文件中 , 读取文件 , 加载类
2. 类名.class : //通过类名的属性class来获取 (这是在Class 类对象阶段 )
//多用于参数的传递
3. 对象.getClass(); // getClass()方法在Object类中定义的 , (这是在Runtime 运行时阶段)
//多用于对象的获取字节码的方式
package com.sichen.javaweb.reflect;
import com.sichen.javaweb.domain.Person;
public class ReflectDemo1 {
public static void main(String[] args) throws Exception {
// 1. Class.forName("全类名 : 包名.类名的方式");
Class aClass = Class.forName("com.sichen.javaweb.domain.Person");
System.out.println(aClass);
//输出 class com.sichen.javaweb.domain.Person
//2. 类名.class : //通过类名的属性class来获取
Class personClass = Person.class;
System.out.println(personClass);
//输出 class com.sichen.javaweb.domain.Person
//3. 对象.getClass();
Person person = new Person();
Class aClass1 = person.getClass();
System.out.println(aClass1);
//输出 class com.sichen.javaweb.domain.Person
//比较三个对象是否是一个对象
System.out.println(aClass == personClass); //true
System.out.println(aClass == aClass1); //true
System.out.println(personClass == aClass1); //true
}
}
- 结论 : 同一个字节码文件 (XX.class) ,在一次程序运行过程中 , 只会被加载一次 , 不论通过那种形式 , 获取到的class对象都是同一个
(3)反射的常用方法
暴力反射:
- 正常情况下 , 打印带权限修饰符的数据时 , 会报非法访问异常 ,增加下边的代码后能够忽略访问权限修饰符的安全检查
- age.setAccessible(true);
1.获取成员变量们(getField())
-
//获取成员变量对象 Field[] getFields() //获取所有public修饰的成员变量 Field getField(String name) //获取指定名称的public修饰的成员变量 //给成员变量设置值 : void set(Object obj , Object value) //获取成员变量中的值 : get(Object obj) //这里边传一个参数 , 是一个对象 , 这个值是在那个对象里边存在的 , 就传哪个对象 Field[] getDeclareFields() //获取所有的成员变量 , 不考虑修饰符 Field getDeclareField(String name)//获取指定的成员变量 , 不考虑修饰符 //正常情况下 , 打印带权限修饰符的数据时 , 会报非法访问异常 , 增加下边的代码后 //能够忽略访问权限修饰符的安全检查 age.setAccessible(true);//暴力反射
-
//上边获取到的这些对象 , 都有set 和 get方法 , 用来设置和获取对象中的值 //这个数据是在那个类里边 , 下边的方法中的Object就要传一个这个类的对象 void set(Object obj , Object value); 上边方法获取的对象名.set(类的对象名 , "要设置的值"); get(Object obj); 上边方法获取的对象名.get(类的对象名);
-
package com.sichen.javaweb; import java.lang.reflect.Field; public class Persontest { public static void main(String[] args) throws Exception { Class personClass = Person.class; Field[] fields = personClass.getFields(); for (Field field : fields){ System.out.println(field); } System.out.println("-----------------------"); Field a = personClass.getField("a"); //获取成员变量的值 Person person = new Person(); Object o = a.get(person); System.out.println(o); //输出 sichen a.set(person , "思尘"); System.out.println(a.get(person));//输出 思尘 System.out.println("-------------------------------"); Field[] declaredFields = personClass.getDeclaredFields(); for (Field declaredField : declaredFields) { //忽略访问权限修饰符的安全检查 declaredField.setAccessible(true);//暴力反射 System.out.println(declaredField.get(person)); //输出null 100 思尘 } Field age = personClass.getDeclaredField("age"); //忽略访问权限修饰符的安全检查 age.setAccessible(true);//暴力反射 System.out.println(age.get(person)); //输出 100 } }
2.获取构造方法们(getConstructors())
-
//获取构造器对象 Constructor[] getConstructor() Constructor getConstructor(类... parameterTypes) Constructor[] getDeclaredConstructors() Constructor getDeclaredConstructor(类... parameterTypes) //这些方法就是用来获取构造方法的 , 获取的构造方法对象中 , // 有一个newInstance 的方法 是用来创建对象的
-
package com.sichen.javaweb; import java.lang.reflect.Constructor; public class Persontest2 { public static void main(String[] args) throws Exception{ Class personClass = Person.class; //这里可以传一个带参的 , 也可以不带参 (访问的就是无参构造方法) Constructor constructor = personClass.getConstructor(String.class, int.class); //创建对象 , 上边如果不带参数 , 这里也不能带参数 Object sichen = constructor.newInstance("sichen", 90); System.out.println(sichen); //如果构造使用空参的构造方法 , 也可直接执行下边的代码 , 一步到位 //这个方法是Class对象中 , 直接就有的方法 Object o = personClass.newInstance(); System.out.println(o); } }
3.获取成员方法们(getMethods())
-
//获取方法对象 Method[] getMethods() Method getMethod(String name, 类... parameterTypes) Method[] getDeclaredMethods() Method getDeclaredMethod(String name, 类... parameterTypes) //这些方法中有一个方法 , 是用来执行这些获取的方法的 //这里obj专递的是调用着对象 , args是调用者传递的参数 Object invoke(Object obj , Object... args); //获取方法名称 String getName();
-
package com.sichen.javaweb; import java.lang.reflect.Method; public class Persontest3 { public static void main(String[] args) throws Exception{ Class personClass = Person.class; Person person = new Person(); //不带参的方法 Method eat = personClass.getMethod("eat"); eat.invoke(person); //带参的方法 , 方法带的什么参数 , 这里后边就要带什么参数 Method eat1 = personClass.getMethod("eat", String.class); eat1.invoke(person , "鸡"); //获取方法名 Method[] methods = personClass.getMethods(); for (Method method : methods) { String name = method.getName(); System.out.println(name); } } }
4.获取类名(getName())
-
String getName(); String name = personClass.getName(); System.out.println(name); //输出 : com.sichen.javaweb.Person
案例 : 模拟一个"框架"
需求 : 写一个"框架" , 在不改变该类任何代码的前提下 , 可以帮我们创建任意类的对象 , 并执行其中任意的方法
-
实现 :
- 1.配置文件
- 2.反射
-
步骤 :
- 1.将需要创建的对象的全类名和需要执行的方法定义在配置文件中
- 2.在程序中加载读取配置文件
- 3.使用反射技术 , 来加载类文件 , 进内存
- 4.创建对象
- 5.执行方法
-
package javaweb; import java.io.InputStream; import java.lang.reflect.Method; import java.util.Properties; public class ReflectTest { public static void main(String[] args) throws Exception { //1.加载配置文件 //1.1创建properties对象 Properties properties = new Properties(); //1.2 加载配置文件 , 转换为一个集合 //1.2.1 获取class目录下的配置文件 ClassLoader classLoader = ReflectTest.class.getClassLoader(); //获取pro.properties文件的一个字节流 InputStream resourceAsStream = classLoader.getResourceAsStream("pro.properties"); properties.load(resourceAsStream); //Properties类的重要方法 //Properties 类存在于包 Java.util 中,该类继承自 Hashtable //1. getProperty(String key),用指定的键在此属性列表中搜索属性。也就是通过参数 key ,得到 key 所对应的 value。 //2. load(InputStream inStream)从输入流中读取属性列表(键和元素对)。通过对指定的文件(比如说上面的 test.properties 文件) //进行装载来获取该文件中的所有键 - 值对。以供 getProperty ( String key) 来搜索。 //3. setProperty ( String key, String value) ,调用 Hashtable 的方法 put 。他通过调用基类的put方法来设置 键 - 值对。 //4. store ( OutputStream out, String comments) , 以适合使用 load 方法加载到 Properties 表中的格式, //将此 Properties 表中的属性列表(键和元素对)写入输出流。与 load 方法相反,该方法将键 - 值对写入到指定的文件中去。 //5. clear () ,清除所有装载的 键 - 值对。该方法在基类中提供。 //2.获取配置文件中定义的数据 String classname = properties.getProperty("classname"); //配置文件中: classname = javaweb.Person String method = properties.getProperty("method"); //配置文件中: method = eat //3.加载类进内存 Class cls = Class.forName(classname); //4.创建对象 Object o = cls.newInstance(); //5.获取方法对象 Method method1 = cls.getMethod(method); //6.执行方法 method1.invoke(o); } }
注解(Annotation)
(1)注解的概述
-
注解 : 说明程序的 , 是给计算机看的
-
它是JDK1.5及之后版本引入的一个新特性 , 与类 , 接口 , 枚举 是在同一个层次 , 它可以声明在
包 , 类 , 字段 , 方法 , 局部变量 , 方法参数等 的前面 , 用来对这些元素进行说明 , 注释 .
-
-
作用分类 :
- ①编写文档 : 通过代码里的标识的注解生成文档 (生成文档doc文档)
- 抽取doc文档 : javadoc -encoding utf-8 javadoc.java
- 在抽取时直接抽取会提示 : 编码GBK的不可映射字符 解决方法就是 增加-encoding utf-8 即可
- 抽取之后会生成很多文件
-
- 点击index.html : 能够看到 , 已经抽取成功了
-
- ②代码分析 : 通过代码里的标识的注解对代码进行分析 (使用反射)
- ③编译检查 : 通过代码里的标识的注解让编译器能够实现基本的编译检查 (@Override)
- ①编写文档 : 通过代码里的标识的注解生成文档 (生成文档doc文档)
(2)JDK中预定义的一些注解
@Override : 检测被该注解标注的方法是否是继承自父类(接口)的
@Deprecated : 将该注解标注的内容显示为已过时
@SuppressWarnings : 压制警告的
(3)自定义注解
-
格式
-
public @interface 注解名 {}
-
使用 : @注解名
-
-
本质 : 注解本质上就是一个接口 , 该接口默认继承Annotation接口
-
Compiled from "ce.java" public interface ce extends java.lang.annotation.Annotation { }
-
-
注解中的属性 : 注解本质是一个接口 , 所以接口中能够定义的 , 注解中也可以定义
- 属性 : 接口中的抽象方法
-
要求 :
- 1.属性的返回值类型有下列取值 :
- 基本数据类型
- String
- 枚举
- 注解
- 以上类型的数组 :
- 1.属性的返回值类型有下列取值 :
-
注意 : 数组赋值比较特殊 , 数组赋值时 , 值使用"{}“包裹 , 如果数组中只有一个值的时候 , 则”{}"省略
- 2.定义了这个属性 , 在使用时需要给这些属性赋值
- 定义了属性 , 就要赋值 , 定义几个属性 , 就要赋几个值
- ①也可以设置默认值 default 设置默认值
- ②如果只有一个属性需要赋值 , 并且属性名称是value , 则value可以省略 , 直接定义即可
-
- 2.定义了这个属性 , 在使用时需要给这些属性赋值
-
元注解 :
- 用于描述注解的注解
- @Target : 描述注解能够作用的位置
- @Retention : 描述注解被保留的阶段
- @Documented : 描述注解是否被抽取到API文档中
- @Inherited : 描述注解是否被子类继承
- 用于描述注解的注解
(4)在程序使用(解析)注解
-
获取注解中定义的属性值
-
1.获取注解定义的位置的对象 (Class , Method , Field)
-
2.获取指定的注解
-
getAnnotation (Class)
-
其实就是在内存中生成了一个该注解接口的子类实现对象
-
public class ProImpl implements Pro{ public String className(){ return "com.sichen.javaweb.demo1"; } public String method(){ return "show1"; } }
-
-
3.调用注解中的抽象方法获取配置的属性值
-
MyAnno注解
-
package com.sichen.javaweb;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno {
String className();
String methodName();
}- pro.java - ```java package com.sichen.javaweb; @MyAnno(className = "com.sichen.javaweb.demo1" , methodName = "show1") public class pro { public static void main(String[] args) { //1.解析注解 //1.1获取该类的字节码文件对象 Class<pro> proClass = pro.class; //2获取上边的注解对象 MyAnno an = proClass.getAnnotation(MyAnno.class); //3.调用注解的对象中定义的抽象方法 , 获取返回值 String s = an.className(); String s1 = an.methodName(); System.out.println(s); //输出 com.sichen.javaweb.demo1 System.out.println(s1); //输出 show1 } }
-
demo1
-
package com.sichen.javaweb; public class demo1 { public void show1(){ System.out.println("show1 ....."); } }
-
案例 : 注解__简单的测试框架
-
Calculater.java
-
package com.sichen.demo; public class Calculater { //加 @Check public void add(){ System.out.println("1 + 0 =" + (1 + 0)); } //减 @Check public void jian(){ System.out.println("1 - 0 =" + (1 - 0)); } //乘 @Check public void cheng(){ System.out.println("1 * 0 =" + (1 * 0)); } //除 @Check public void chu(){ System.out.println("1 / 0 =" + (1 / 0)); } public void show(){ System.out.println("show/....."); } }
-
Check.java
-
package com.sichen.demo; 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 Check { }
-
TestCheck.java
package com.sichen.demo;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class TestCheck {
public static void main(String[] args) throws IOException {
//1.获取计算器对象
Calculater calculater = new Calculater();
//2.获取字节码文件对象
Class<? extends Calculater> aClass = calculater.getClass();
//3.获取所有方法
Method[] methods = aClass.getMethods();
//出现异常的次数
int number = 0;
BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt"));
for (Method method : methods) {
//4.判断方法上是否有Check注解
if (method.isAnnotationPresent(Check.class)){
try {
method.invoke(calculater);
} catch (Exception e) {
number++;
bw.write(method.getName()+"方法出现异常了");
bw.newLine();
bw.write("异常的名称是:" + e.getCause().getClass().getSimpleName());
bw.newLine();
bw.write("异常的原因是:" + e.getCause().getMessage());
bw.newLine();
bw.write("--------------------");
}
}
}
bw.write("本次运行出现异常" +number+ "次");
}
}
-
小结 :
以后大多数时候 , 我们会使用注解 , 而不是自定义注解
-
注解给谁用 ?
- 1.编译器
- 2.给解析程序用
-
注解不是程序的一部分 , 可以理解为注解就是一个标签
详细注解:
doc文档中的注解
@param : 参数
@author : 作者
@version : 版本
@since : 从以下版本开始
JDK中预定义的一些注解
@Override :
- 检测被该注解标注的方法是否是继承自父类(接口)的
@Deprecated :
- 将该注解标注的内容 , 已过时
@SuppressWarnings :
@SuppressWarnings ("all") // 压制所有的警告
一般传递参数all , 并且将注解放在类上 , 这样这个类中所有的警告都不会出现
- 压制警告的 : 在使用工具时 , 会给你提示各种信息 , 如果不想要提示这种信息 , 就可以增加注解
元注解 :
-
用于描述注解的注解 : 都是放在注解上边的注解
-
@Target : 描述注解能够作用的位置
-
@Target(value = {ElementType.TYPE}) //可以作用于类上 @Target(value = {ElementType.METHOD}) //可以作用于方法上 @Target(value = {ElementType.FIELD}) //可以作用于成员变量上
-
-
-
@Retention : 描述注解被保留的阶段
-
@Retention(RetentionPolicy.RUNTIME) //当前被描述的注解 , 会被保留到 Runtime运行时阶段 @Retention(RetentionPolicy.CLASS) //当前被描述的注解 , 会被保留到class字节码文件中 , 并被JVM读取到 @Retention(RetentionPolicy.SOURCE) //当前被描述的注解 , 会被保留到Source 源代码阶段
-
-
@Documented : 描述注解是否被抽取到API文档中 , 这样在抽取doc文档的时候 , 就会把这个被修饰的内容一块抽取
-
@Documented //表示被该注解标识的内容可以被抽取 public @interface MyAnno {}
-
@Inherited : 描述注解是否被子类继承
-
@Inherited //表示被此注解标识的注解 , 能够被子类继承
Junit单元测试
-
测试的分类 :
- (1)黑盒测试
- 看不到详细代码 , 不需要写代码只能测试功能 , 通过输入输出 , 看到是否符合自己的设定 (相当于游戏测试 , 程序打包成成品 , 用户来体验看是否有bug)
- (2)白盒测试
- 要去关注程序执行的流程 , 看到详细代码 , 需要写代码 (就相当于边写边调试)
- (1)黑盒测试
-
Junit就是白盒测试的一种
-
使用步骤 :
- 第一步 : 定义一个测试类 (测试用例)
- 建议 : 测试类名 : 被测试的类名 + Test
- 包名 : xx.xxx.xxx.text
- 建议 : 测试类名 : 被测试的类名 + Test
- 第二步 : 定义测试方法 : 可以独立运行
- 建议 : 方法名 : text测试的方法 testAdd()
- 返回值 : void
- 参数列表 : 空参
- 建议 : 方法名 : text测试的方法 testAdd()
- 第三步 : 给方法加一个注解 : @Test
- 第四步 : 导入Junit 依赖环境
- 第五步 : 判定结果
- 第一步 : 定义一个测试类 (测试用例)
-
注意: 一般在测试的时候不会看输出的结果 , 因为输出的结果可能不准确
-
这里使用断言的形式 :
-
Assert.assertEquals(规定的结果值 , 实际的结果值)
-
//Assert中有很多方法 , 这里使用这一种就行 @Test public void testAdd(){ //创建计算器对象 Calculator calculator = new Calculator(); int add = calculator.add(1, 2); Assert.assertEquals(3,add); }
-
-
测试的时候 , 有时候代码会重复 , 比如说创建对象 , 释放资源
-
这个时候 有两个注解 : @Before @After (这两个注解仅针对@Test注解的方法)
-
package test; import com.sichen.javaweb.Calculator; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; public class CalculatorTest { //在这里定义一个变量 , 后边就可以直接使用了 , 不用每个方法中再去new private Calculator calculator; /** * 在所有方法执行前都要先执行这个方法 */ @Before public void init(){ calculator = new Calculator(); } /** * 所有程序在执行完之后都会自动执行这个方法 */ @After public void close(){ System.out.println("释放资源"); } /** * 测试方法add */ @Test public void testAdd(){ int add = calculator.add(1, 2); Assert.assertEquals(3,add); } /** * 测试方法sub */ @Test public void testsub(){ int sub = calculator.sub(1, 2); Assert.assertEquals(-1 , sub); } }
-
测试的时候 , 有两种情况
- 第一种 : 测试成功 :
- 第二种 : 有异常
-
枚举类
public void close(){
System.out.println("释放资源");
}
/**
* 测试方法add
*/
@Test
public void testAdd(){
int add = calculator.add(1, 2);
Assert.assertEquals(3,add);
}
/**
* 测试方法sub
*/
@Test
public void testsub(){
int sub = calculator.sub(1, 2);
Assert.assertEquals(-1 , sub);
}
}
```
测试的时候 , 有两种情况
- 第一种 : 测试成功 :
- [外链图片转存中…(img-6a5Y66Q0-1646802348291)]
- 第二种 : 有异常
-
枚举类
[外链图片转存中…(img-xWbGuQYa-1646802348292)]