十四、单元测试、反射、注释、动态代理
需要学会什么?
- 单元测试:开发好的系统中存在很多的方法,如何对这些方法的正确性进行测试。
- 反射:如何在程序运行时去得到Class对象,然后去获取Class中的每个成分。
- 注解:注解是什么,具体是如何在Java程序中解决问题的。
- 动态代理:框架技术的底层会用到。
1.单元测试
a.单元测试概述
单元测试:
- 单元测试就是针对最小的功能单元编写测试代码,Java程序最小的功能单元是方法。因此单元测试就是针对Java方法的测试,进而简称方法的正确性。
目前测试方法是怎么进行的,存在什么问题?
- 只有一个main方法,如果一个方法的测试失败了,其他方法测试会收到影响。
- 无法得到测试的结果报告,需要程序员自己去观察测试是否成功。
- 无法实现自动化测试。
JUnit单元测试框架:
- JUnit是使用Java语言实现的单元测试框架,它是开源的,Java开发者都应当学习并使用JUnit编写单元测试。
- 几乎所有的IDE工具都集成了JUnit,这样我们就可以直接在IDE中编写并运行JUnit测试,JUnit目前最新版本是5。
JUnit优点:
- JUnit可以灵活的选择执行哪些测试方法,可以一键执行全部测试方法。
- JUnit可以生成全部方法的测试报告。
- 单元测试中的某个方法测试失败了,不会影响其他测试方法的测试。
总结:
- JUnit单元测试是做什么的?
- 测试类中方法的正确性。
- JUnit单元测试的优点报告是什么?
- JUnit可以选择执行哪些测试方法,可以一键执行全部测试方法的测试。
- JUnit可以生成测试报告,如果是测试良好则是绿色,如果是测试失败则是红色。
- 单元测试中的某个方法测试失败了,不会影响其他测试方法的测试。
b.单元测试快速入门
需求:
- 使用单元测试进行业务方法预期结果、正确性测试的快速入门。
分析:
- 将JUnit的jar包导入到项目中。
- IDEA通常整合好了JUnit框架,一般不需要导入。(输入@Test后报红提示,
Alt + Enter
选择Add 'JUnit5.x.x' to classpath
,之后点击OK即可) - 如果IDEA没有整合好,需要自己手工导入如下2个JUnit的Jar包到模块。
- IDEA通常整合好了JUnit框架,一般不需要导入。(输入@Test后报红提示,
- 编写测试方法:该测试方法必须是公共的无参数无返回值的非静态方法。
- 在测试方法上使用@Test注解:标准该方法是一个测试方法。
- 在测试方法中完成被测试方法的预期正确性测试。
- 选中测试方法,选择”JUnit运行“,如果测试良好则是绿色,如果测试失败则是红色。
UserService.java
/**
* 用户服务
*/
public class UserService {
/**
* 用户登录
* @param username 用户名
* @param password 密码
* @return 登录状态
*/
public String loginName(String username, String password) {
if ("admin".equals(username) && "123456".equals(password)) {
return "登录成功!";
} else {
return "用户名或密码错误!";
}
}
/**
* 查看全部用户
*/
public void selectUsers() {
System.out.println(10 / 0);
System.out.println("查询全部用户成功!");
}
}
TestUserService.java
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/**
* 测试用户服务类
*/
public class TestUserService {
/**
* 测试用户登录方法
* 注意:
* 1.必须是公开的 无参数 无返回值的方法
* 2.测试方法必须使用@Test注解标记
*/
@Test
public void testLoginName() {
UserService userService = new UserService();
String result = userService.loginName("admin", "123456");
// 进行预期结果的正确性测试:断言
// 期待值 实际值 期待值与实际值不符时提示信息
Assertions.assertEquals("登录成功!", result, "用户登录业务可能出现问题!");
}
/**
* 测试查看全部用户方法
*/
@Test
public void testSelectUsers() {
UserService userService = new UserService();
userService.selectUsers();
}
}
JUnit单元测试执行结果:
注意:
- 单元测试可以一次一个方法独单进行测试。
- 单元测试可以一次整个类进行测试。
- 单元测试可以一次整个项目进行测试。(IDEA中对项目文件目录右击 -> Run “All Tests”)
总结:
- JUnit单元测试的实现过程是什么样的?
- 必须导入JUnit框架的jar包。
- 定义的测试方法必须是无参数无返回值且公开的方法。
- 测试方法使用@Test注解标记。
- JUnit测试某个方法,测试全部方法怎么处理?成功的标志是什么?
- 测试某个方法直接右键该方法启动测试。
- 测试全部方法,可以选择类或者模块启动。
- 红色失败,绿色通过,黄色表示没有出异常但是结果没有通过。
c.单元测试常用注解
JUnit常用注解
JUnit4 | JUnit5 | 说明 |
---|---|---|
@Test | @Test | 测试方法。 |
@Before | @BeforeEach | 用来修饰实例方法,该方法会在每一个测试方法执行之前执行一次。 |
@After | @AfterEach | 用来修饰实例方法,该方法会在每一个测试方法执行之后执行一次。 |
@BeforeClass | @BeforeAll | 用来修饰静态方法,该方法会在所有测试方法执行之前执行一次。 |
@AfterClass | @AfterAll | 用来修饰静态方法,该方法会在所有测试方法执行之后执行一次。 |
- 开始执行的方法:初始化资源。
- 执行完之后的方法:释放资源。
TestUserService.java
import org.junit.jupiter.api.*;
/**
* 测试用户服务类
*/
public class TestUserService {
@BeforeEach
public void beforeEach() {
System.out.println("===beforeEach方法执行一次===");
// 作用:如初始化I/O流
}
@AfterEach
public void afterEach() {
System.out.println("===afterEach方法执行一次===");
// 作用:如关闭I/O流
}
@BeforeAll
public static void beforeAll() {
System.out.println("===beforeAll方法执行一次===");
// 作用:如初始化静态资源
}
@AfterAll
public static void afterAll() {
System.out.println("===afterAll方法执行一次===");
// 作用:如回收静态资源
}
/**
* 测试用户登录方法
* 注意:
* 1.必须是公开的 无参数 无返回值的方法
* 2.测试方法必须使用@Test注解标记
*/
@Test
public void testLoginName() {
System.out.println("===测试用户登录方法===");
UserService userService = new UserService();
String result = userService.loginName("admin", "123456");
// 进行预期结果的正确性测试:断言
// 期待值 实际值 期待值与实际值不符时提示信息
Assertions.assertEquals("登录成功!", result, "用户登录业务可能出现问题!");
}
/**
* 测试查看全部用户方法
*/
@Test
public void testSelectUsers() {
System.out.println("===测试查看全部用户方法===");
UserService userService = new UserService();
userService.selectUsers();
}
}
JUnit常用注解执行结果:
2.反射
a.反射概述
反射的概述:
- 反射是指对于任何一个Class类,在“运行的时候”都可以直接得到这个类全部成分。
- 在运行时,可以直接得到这个类的构造器对象:Constructor。
- 在运行时,可以直接得到这个类的成员变量对象:Field。
- 在运行时,可以直接得到这个类的成员方法对象:Method。
- 这种运行时动态获取类信息以及动态调用类中成分的能力被称为Java语言的反射机制。
反射的关键:
反射的第一步都是先得到编译后的Class对象,然后就可以得到Class的全部成分。
HelloWorld.java -> javac -> HelloWorld.class Class c = HelloWorld.class;
总结:
- 反射的基本作用、关键?
- 反射是在运行时获取类的字节码文件对象:可以解析类中的全部成分。
- 反射的核心思想和关键就是:得到编译以后的Class文件对象。
b.反射获取类对象
反射的第一步:获取Class类的对象
Student.java
public class Student {
}
Test.java
/**
* 目标:反射的第一步 获取Class对象
*/
public class Test {
public static void main(String[] args) throws Exception {
// 1.Class类中的一个静态方法 forName 全限名/包名+类名
Class<?> studentClass = Class.forName("com.javase.reflectclassdemo.Student");
// class com.javase.reflectclassdemo.Student
System.out.println(studentClass);
// 2.类名.class
Class<Student> studentClass1 = Student.class;
// class com.javase.reflectclassdemo.Student
System.out.println(studentClass1);
// 3.对象.getClass() 获取对象对应类的Class对象
Student student = new Student();
Class<? extends Student> studentClass2 = student.getClass();
// class com.javase.reflectclassdemo.Student
System.out.println(studentClass2);
}
}
总结:
- 反射的第一步是什么?
- 获取Class类对象,可以解析类的全部成分。
- 获取Class类的对象的三种方式。
- 方式一:Class c1 = Class.forName(“全限名”);
- 方式二:Class c2 = 类名.class;
- 方式三:Class c3 = 对象.getClass();
c.反射获取构造器对象
使用反射技术获取构造器对象并使用:
- 反射得第一步是先得到类对象,然后从类对象中获取类得成分对象。
- Class类中用于获取构造器得方法。
方法 | 说明 |
---|---|
Constructor<?>[] getConstructors() | 返回所有构造器对象得数组(只能拿public的)。 |
Constructor<?>[] getDeclaredConstructors() | 返回所有构造器对象的数组,存在就能拿到。 |
Constructor getConstructor(Class<?>… parameterTypes) | 返回单个构造器对象(只能拿public的)。 |
Constructor getDeclaredConstructor(Class<?>… parameterTypes) | 返回单个构造器对象,存在就能拿到。 |
Student.java
public class Student {
private String name;
private int age;
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;
}
private Student() {
System.out.println("无参构造器执行!");
}
public Student(String name, int age) {
System.out.println("有参构造器执行!");
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
使用反射技术获取构造器对象并使用:
- 获取构造器的作用依然是初始化一个对象返回。
Constructor类中用于创建对象的方法:
符号 | 说明 |
---|---|
T newInstanc(Object… initargs) | 根据指定的构造器创建对象。 |
public void setAccessible(boolean flag) | 设置未true,表示取消访问检查,进行暴力反射。 |
Student.java
public class Student {
private String name;
private int age;
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;
}
private Student() {
System.out.println("无参构造器执行!");
}
public Student(String name, int age) {
System.out.println("有参构造器执行!");
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
TestStudent.java
import org.junit.jupiter.api.Test;
import java.lang.reflect.Constructor;
public class TestStudent {
/**
* 获取单个构造器
*/
@Test
public void getDeclaredConstructor() throws Exception {
// 1.获取类对象
Class<Student> studentClass = Student.class;
// 2.获取单个的构造器对象 此处为无参构造器
Constructor constructor = studentClass.getDeclaredConstructor();
System.out.println(constructor.getName() + "==>" + constructor.getParameterCount());
// 如果遇到了私有的构造器 可以暴力反射 只在本次生效
constructor.setAccessible(true);
// 3.创建Student对象
Student student = (Student) constructor.newInstance();
System.out.println(student);
}
}
总结:
- 利用反射技术获取构造器对象的方式?
- getDeclaredConstructors()
- getDeclaredConstructors(Class<?>… parameterTypes)
- 反射得到的构造器可以做什么?
- 依然用来是创建对象的
- public newInstance(Object… initargs)
- 如果是非public的构造器,需要打卡权限(暴力反射),然后再创建对象。
- setAccessible(boolean)
- 反射可以破坏封装性,私有的也可以执行了。
- 依然用来是创建对象的
d.反射获取成员变量对象
使用反射技术获取成员变量对象并使用:
- 反射的第一步是先得到类对象,然后从类对象中获取类的成分对象。
- Class类中用于获取成员变量的方法。
方法 | 说明 |
---|---|
Field[] getFields() | 返回所有成员变量对象的数组(只能拿public的)。 |
Field[] getDeclaredField() | 返回所有成员变量对象的数组,存在就能拿到。 |
Field getField(String name) | 返回单个成员变量对象(只能拿public的)。 |
Field getDeclareField(String name) | 返回单个成员变量对象,存在就能拿到。 |
Student.java
public class Student {
private String name;
private int age;
public static String schoolName;
public static final String Country = "中国";
private 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 static String getSchoolName() {
return schoolName;
}
public static void setSchoolName(String schoolName) {
Student.schoolName = schoolName;
}
public Student() {
System.out.println("无参构造器执行!");
}
public Student(String name, int age) {
System.out.println("有参构造器执行!");
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
FieldDemo1.java
import org.junit.jupiter.api.Test;
import java.lang.reflect.Field;
public class FieldDemo1 {
/**
* 1.获取全部的成员变量
*/
@Test
public void getDeclaredFields() {
// a.定位Class对象
Class<Student> studentClass = Student.class;
// b.定位全部成员变量
Field[] fields = studentClass.getDeclaredFields();
// c.遍历
for (Field field : fields) {
// name==>class java.lang.String
// age==>int
// schoolName==>class java.lang.String
// Country==>class java.lang.String
System.out.println(field.getName() + "==>" + field.getType());
}
}
/**
* 2.获取指定的成员变量
*/
@Test
public void getDeclareField() throws Exception {
// a.定位Class对象
Class<Student> studentClass = Student.class;
// b.根据名称定位某个成员变量
Field field = studentClass.getDeclaredField("age");
// age==>int
System.out.println(field.getName() + "==>" + field.getType());
}
}
使用反射技术获取成员变量对象并使用:
- 获取成员变量的作用依然是在某个对象中取值、赋值。
符号 | 说明 |
---|---|
void set(Object obj, Object value) | 赋值。 |
Object get(Object obj) | 取值。 |
Student.java
public class Student {
private String name;
private int age;
public static String schoolName;
public static final String Country = "中国";
private 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 static String getSchoolName() {
return schoolName;
}
public static void setSchoolName(String schoolName) {
Student.schoolName = schoolName;
}
public Student() {
System.out.println("无参构造器执行!");
}
public Student(String name, int age) {
System.out.println("有参构造器执行!");
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
FieldDemo2.java
import org.junit.jupiter.api.Test;
import java.lang.reflect.Field;
public class FieldDemo2 {
@Test
public void setField() throws Exception {
// a.获取类对象
Class<Student> studentClass = Student.class;
// b.提取某个成员变量
Field ageField = studentClass.getDeclaredField("age");
// 暴力打开权限
ageField.setAccessible(true);
// c.赋值
Student student = new Student();
ageField.set(student, 18);
// Student{name='null', age=18}
System.out.println(student);
// d.取值
int age = (int) ageField.get(student);
// 18
System.out.println(age);
}
}
总结:
- 利用反射技术获取成员变量的方式?
- 获取类中成员变量对象的方法:
- getDeclaredFields()
- getDeclareField(String name)
- 获取类中成员变量对象的方法:
- 反射得到成员变量可以做什么?
- 依然是在某个对象中取值和赋值。
- void set(Object obj, Object value)
- Object get(Object obj)
- 如果某成员变量是非public的,需要打开权限(暴力反射),然后再取值、赋值。
- setAccessible(booleann)
- 依然是在某个对象中取值和赋值。
e.反射获取成员方法对象
使用反射技术获取方法对象并使用:
- 反射的第一步是先得到类对象,然后从类对象中获取类的成分对象。
- Class类中用于获取成员方法的方法。
方法 | 说明 |
---|---|
Method[] getMethods() | 返回所有成员方法对象的数组。(只能拿public的) |
Method[] getDeclaredMethods() | 返回所有成员方法对象的数组,存在就能拿到。 |
Method getMethod(String name, Class<?>… parameterTypes) | 返回单个成员方法对象。(只能拿public的) |
Method getDeclaredMethod(String name, Class<?>… parameterTypes) | 返回单个成员方法对象,存在就能拿到。 |
使用反射技术获取方法对象并使用:
- 获取成员方法的作用依然是在某个对象中进行执行此方法。
Method类中用于触发执行的方法:
方法 | 说明 |
---|---|
Object invoke(Object obj, Object… args) | 运行方法: 参数一:用obj对象调用该方法。 参数二:调用方法的传递的参数(如果没有就不写) 返回值:方法的返回值(如果没有就不写) |
Dog.java
public class Dog {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Dog() {
}
public Dog(String name) {
this.name = name;
}
public void run() {
System.out.println("狗跑的很快!");
}
public void eat() {
System.out.println("狗啃骨头!");
}
public String eat(String name) {
System.out.println("狗吃" + name);
return "吃的很开心!";
}
public static void address() {
System.out.println("灌江口");
}
}
MethodDemo.java
import org.junit.jupiter.api.Test;
import java.lang.reflect.Method;
public class MethodDemo {
/**
* 1.获取全部方法
*/
@Test
public void getDeclaredMethods() {
// a.获取类对象
Class<Dog> dogClass = Dog.class;
// b.提取全部方法
Method[] methods = dogClass.getDeclaredMethods();
// c.遍历全部方法
for (Method method : methods) {
// getName 返回值类型:class java.lang.String 参数个数:0
// run 返回值类型:void 参数个数:0
// setName 返回值类型:void 参数个数:1
// address 返回值类型:void 参数个数:0
// eat 返回值类型:void 参数个数:0
// eat 返回值类型:class java.lang.String 参数个数:1
System.out.println(method.getName() + " 返回值类型:" + method.getReturnType() + " 参数个数:" + method.getParameterCount());
}
}
@Test
public void getDeclardMethod() throws Exception {
// a.获取类对象
Class<Dog> dogClass = Dog.class;
// b.提取单个方法对象
Method method = dogClass.getMethod("eat");
Method method1 = dogClass.getMethod("eat", String.class);
// 暴力打开权限
method.setAccessible(true);
method1.setAccessible(true);
// c.触发方法的执行
Dog dog = new Dog();
// 注意:方法如果是没有返回结果的,那么返回的是null
// 狗啃骨头!
Object result = method.invoke(dog);
// null
System.out.println(result);
// 狗吃骨头
Object result1 = method1.invoke(dog, "骨头");
// 吃的很开心!
System.out.println(result1);
}
}
总结:
- 利用反射技术获取成员方法对象的方式:
- 获取类中成员方法对象:
- getDeclaredMethods()
- getDeclaredMethod(String name, Class<?>… parameterTypes)
- 获取类中成员方法对象:
- 反射得到成员方法可以做什么?
- 依然是在某个对象中触发该方法执行:
- Object invoke(Object obj, Object… args)
- 如果某成员方法是非public的,需要打开权限(暴力反射),然后再触发执行:
- setAccessible(boolean)
- 依然是在某个对象中触发该方法执行:
f.反射的作用-泛型擦除/绕过编译阶段为集合添加数据
反射的作用-绕过编译阶段为集合添加数据:
反射是作用在运行时的技术,此时集合的泛型将不能产生约束了,此时是可以为集合存入任意类型的元素的。
ArrayList<Integer> list = new ArrayList<>(); list.add(100); // 报错 // list.add("白子画"); list.add(99);
泛型只是在编译阶段可以约束集合只能操作某种数据类型,在编译成Class文件进入运行阶段的时候,其真实类型都是ArrayList,泛型相当于被擦除了。
ReflectDemo.java
import java.lang.reflect.Method;
import java.util.ArrayList;
public class ReflectDemo {
public static void main(String[] args) throws Exception {
// 需求: 反射实现泛型擦除后 加入其他类型的元素
ArrayList<String> list = new ArrayList<>();
ArrayList<Integer> list1 = new ArrayList<>();
System.out.println(list.getClass());
System.out.println(list1.getClass());
// ArrayList.class
System.out.println(list.getClass() == list1.getClass());
System.out.println("-----------------");
ArrayList<Integer> list2 = new ArrayList<>();
list2.add(9);
list2.add(10);
Class studentClass = list2.getClass();
// 定位studentClass类中的add方法
Method add = studentClass.getDeclaredMethod("add", Object.class);
boolean result = (boolean) add.invoke(list2, "Python");
// true
System.out.println(result);
// [9, 10, Python]
System.out.println(list2);
/* 不用反射突破泛型限制 */
ArrayList list3 = list2;
list3.add("Java");
// [9, 10, Python, Java]
System.out.println(list3);
}
}
总结:
- 反射为什么可以给约定了泛型的集合存入其他类型的元素?
- 编译成Class文件进入运行阶段的时候,泛型会自动擦除。
- 反射是作用在运行时的技术,此时已经不存在泛型了。
g.反射的作用-通用框架的底层原理
反射做通用框架:
需求:
- 给任意一个对象,在不清楚对象字段的情况,可以把对象的字段名称和对应值存储到文件中去。
分析:
- 定义一个方法,可以接收任意类的对象。
- 每次收到一个对象后,需要解析这个对象的全部成员变量名称。
- 这个对象可能是任意的,怎么样才可以知道这个对象的全部成员变量名称?
- 使用反射获取对象的Class类对象,然后获取全部成员变量信息。
- 遍历成员变量信息,然后提取本成员变量在对象中的具体值。
- 存入成员变量名称和值到文件中去即可。
Student.java
/**
* 学生类
*/
public class Student {
private String name;
private char sex;
private int age;
private String className;
private String hobby;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public String getHobby() {
return hobby;
}
public void setHobby(String hobby) {
this.hobby = hobby;
}
public Student() {
}
public Student(String name, char sex, int age, String className, String hobby) {
this.name = name;
this.sex = sex;
this.age = age;
this.className = className;
this.hobby = hobby;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", sex=" + sex +
", age=" + age +
", className='" + className + '\'' +
", hobby='" + hobby + '\'' +
'}';
}
}
Teacher.java
**
* 老师类
*/
public class Teacher {
private String name;
private char sex;
private double salary;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public Teacher() {
}
public Teacher(String name, char sex, double salary) {
this.name = name;
this.sex = sex;
this.salary = salary;
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
", sex=" + sex +
", salary=" + salary +
'}';
}
}
MyUtil.java
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Field;
public class MyUtil {
/**
* 保存任意类型的对象
* @param object 任意类型的对象
*/
public static void save(Object object) {
try {
// 创建打印流对象 追加写入
PrintStream printStream = new PrintStream(
new FileOutputStream("day17-oop-demo/src/com/javase/reflectframework/data.txt", true)
);
// 1.提取该对象的全部成员变量 只有反射可以解决
Class<?> objectClass = object.getClass();
// 保存类名信息 getSimpleName()获取当前类名 getName()获取全限名
printStream.println("===" + objectClass.getSimpleName() + "===");
// 2.提取它的全部成员变量
Field[] fields = objectClass.getDeclaredFields();
// 3.获取成员变量的信息
for (Field field : fields) {
// 成员变量名称
String variableName = field.getName();
// 取值 提取该成员变量在object对象中的值
field.setAccessible(true);
String variableValue = field.get(object) + "";
// 保存变量名称和变量值
printStream.println(variableName + "=" + variableValue);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
ReflectDemo.java
/**
* 目标:提供一个通用框架 支持保存所有对象的具体信息
*/
public class ReflectDemo {
public static void main(String[] args) {
Student student = new Student();
student.setName("花千骨");
student.setClassName("葵班");
student.setAge(16);
student.setHobby("修仙");
student.setSex('女');
// 保存student对象
MyUtil.save(student);
Teacher teacher = new Teacher();
teacher.setName("朽木清流");
teacher.setSex('男');
teacher.setSalary(10000);
// 保存teacher对象
MyUtil.save(teacher);
}
}
生成的文件:data.txt
===Student===
name=花千骨
sex=女
age=16
className=葵班
hobby=修仙
===Teacher===
name=朽木清流
sex=男
salary=10000.0
总结:
- 反射的作用?
- 可以在运行时得到一个类的全部成分然后操作。
- 可以破坏封装性(很突出)。
- 可以破坏泛型的约束性(很突出)。
- 更重要的用途是:做Java高级框架。
3.注解
a.注解概述
注解概述:
- Java注解(Annotation)又称Java标志,是JDK5.0引入的一种注释机制。
- Java语言中的类、构造器、方法、成员变量、参数等都可以被注解进行标注。
注解的作用是什么呢?
- 对Java中类、方法、成员变量做标记,然后进行特殊处理,至于到底做何种处理业务需求来决定。
b.自定义注解
自定义注解(格式):
自定义注解就是自己做一个注解来使用。
public @interface 注解名称 { public 属性类型 属性名() default 默认值; }
特殊属性:
- value属性,如果只有一个value属性的情况下,使用value属性的时候可以省略value名称不写。
- 如果有多个属性,且多个属性没有默认值,那么value名称是不能省略的。
MyBook.java
public @interface MyBook {
String name();
String[] authors();
double price();
}
Book.java
public @interface Book {
// 特殊属性
String value();
}
AnnotationDemo.java
/**
* 目标:学会自定义注解 掌握其定义格式和语法
*/
@MyBook(name = "《硅谷之火》", authors = {"迈克尔·斯韦因"}, price = 33.9)
@Book("/add")
public class AnnotationDemo {
@MyBook(name = "《硅谷之火》", authors = {"迈克尔·斯韦因"}, price = 33.9)
private AnnotationDemo() {
}
@MyBook(name = "《硅谷之火》", authors = {"迈克尔·斯韦因"}, price = 33.9)
public static void main(String[] args) {
}
}
c.元注解
元注解:
- 注解注解的注解。
元注解有两个:
- @Target:约束自定义注解只能在哪些地方使用,@Target中可使用的值定义在ElementType枚举类中,常用值如下:
- TYPE:类、接口。
- FIELD:成员变量。
- METHOD,成员方法。
- PARAMETER,方法参数。
- CONSTRUCTOR,构造器。
- LOCAL_VARIABLE,局部变量。
- @Retention:申明注解的生命周期,@Retention中可使用的值定义在RetentionPolicy枚举类中,常用之如下:
- SOURCE:注解只作用在源码阶段,生成的字节码文件中不存在。
- CLASS:注解作用在源码阶段、字节码文件阶段、运行阶段不存在,默认值。
- RUNTIME:注解作用在源码阶段、字节码文件阶段、运行阶段(开发常用)。
JUnit框架中源码:
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@API(
status = Status.STABLE,
since = "5.0"
)
@Testable
public @interface Test {
}
MyTest.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义注解
* 约束只能用于方法、成员变量
*/
@Target({ElementType.METHOD, ElementType.FIELD})
// 注解一直活着 在运行阶段也不消失
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
}
AnnotationDemo.java
/**
* 目标:认识元注解
*/
// 此处用该注解会报错
// @MyTest
public class AnnotationDemo {
@MyTest
private String name;
@MyTest
public void test() {
}
@MyTest
public static void main(String[] args) {
}
}
总结:
- 元注解是什么?
- 注解注解的注解。
- @Target约束自定义注解可以标记的范围,@Retention用来约束自定义注解的存活范围。
d.注解解析
注解的解析:
- 注解的操作中经常需要进行解析,注解的解析就是判断是否存在注解,存在注解就解析出内容。
与注解解析相关的接口:
Annotation:注解的顶级接口,注解都是Annotation类型的对象。
AnnotatedElement:该接口定义了与注解解析相关的解析方法。
方法 说明 Annotation[] getDeclaredAnnotations() 获得当前对象上使用的所有注解,返回注解数组。 T gteDeclaredAnnotation(Class annotationClass) 根据注解类型获得对应注解对象。 boolean isAnnotationPresent(Class annotationClass) 判断当前对象是否使用了指定的注解,如果使用了则返回true,否则返回false。 所有的类成分:Class、Method、Field,Constructor,都实现了AnnotatedElement接口他们都拥有解析注解的能力。
解析注释的技巧:
- 注解在哪个成分上,我们就先拿哪个成分对象。
- 比如:注解作用成员方法,则要获得该成员方法对应得Metho对象,再来拿上面得注解。
- 比如:注解作用在类上,则要该类得Class对象,再来拿上面得注解。
- 比如:注解作用在成员变量上,则要获得该成员变量对应的Field对象,再来拿上面的注解。
案例:注解解析
需求:
- 定义注解Book,要求如下:
- 包含属性:String value() 书名。
- 包含属性:double price() 价格,默认值100元。
- 包含属性:String [] authors() 多位作者。
- 限制注解使用的位置:类和成员方法上。
- 指定注解的有效范围:RUNTIME。
- 定义BookStore类,在类和成员方法上使用Book注解。
- 定义AnnotationDemo测试类获取Book注解上的数据。
Book.java
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();
double price() default 100;
String[] author();
}
BookStoreTest.java
import org.junit.jupiter.api.Test;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* 目标:完成注解的解析
*/
public class BookStoreTest {
@Test
public void parseClass() {
// a.先得到类对象
Class<BookStore> bookStoreClass = BookStore.class;
// b.判断这个类上面是否存在这个注解
if (bookStoreClass.isAnnotationPresent(Book.class)) {
// c.直接获取该注释对象
Book book = (Book) bookStoreClass.getDeclaredAnnotation(Book.class);
System.out.println(book.value());
System.out.println(book.price());
System.out.println(Arrays.toString(book.author()));
}
}
@Test
public void parseMethod() throws Exception {
// a.先得到类对象
Class<BookStore> bookStoreClass = BookStore.class;
Method method = bookStoreClass.getDeclaredMethod("test");
// b.判断这个类上面是否存在这个注解
if (method.isAnnotationPresent(Book.class)) {
// c.直接获取该注释对象
Book book = (Book) method.getDeclaredAnnotation(Book.class);
System.out.println(book.value());
System.out.println(book.price());
System.out.println(Arrays.toString(book.author()));
}
}
}
@Book(value = "《一往无前》", price = 78, author = {"范海涛"})
class BookStore {
@Book(value = "《价值》", price = 118, author = {"张磊"})
public void test() {
}
}
e.注解的应用场景一:jUnit框架
案例:模拟jUnit框架
需求:
- 定义若干个方法,只要加了MyTest注解,就可以在启动时被触发执行。
分析:
- 定义一个自定义注解MyTest,只能注解方法,存活范围是一直都在。
- 定义若干个方法,只要有@MyTest注解的方法就能在启动时被触发执行,没有这个注解的方法不能执行。
MyTest.java
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 {
}
AnnotationDemo.java
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class AnnotationDemo {
@MyTest
public void test1() {
System.out.println("===test1===");
}
public void test2() {
System.out.println("===test2===");
}
@MyTest
public void test3() {
System.out.println("===test3===");
}
/**
* 启动菜单:有注解的方法才调用 没有注解的方法不调用
*/
public static void main(String[] args) throws Exception {
AnnotationDemo annotationDemo = new AnnotationDemo();
// a.获取类对象
Class<AnnotationDemo> annotationDemoClass = AnnotationDemo.class;
// b.提取全部方法
Method[] methods = annotationDemoClass.getDeclaredMethods();
// c.遍历方法
for (Method method : methods) {
// 如果有MyTest注解 启动该方法
if (method.isAnnotationPresent(MyTest.class)) {
// d.启动方法
method.invoke(annotationDemo);
}
}
}
}
执行结果:
===test1===
===test3===
4.动态代理
a.动态代理概述、快速入门
什么是代理?
- 代理指:某些场景下对象会找一个代理对象,来辅助自己完成一些工作,如果:歌星(经纪人),买房的人(房产中介)。
代理主要干什么,是如何工作的?
- 代理主要对“对象的行为”额外做一些辅助操作。
如何创建代理对象:
Java中代理的代表类是:java.lang.reflect.Proxy
Proxy提供了一个静态方法,用于为对象产生一个代理对象返回。
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) /* 为对象返回一个代理对象 */ // 参数一:定义代理类的类加载器。 // 参数二:代理类要实现的接口列表。 // 参数三:将方法调用分派到的处理程序。(代理对象的核心处理程序)
Java中如何生成代理,并指定代理干什么事?
Skill.java
/**
* 唱歌跳舞技能接口
*/
public interface Skill {
/**
* 唱歌
*/
void sing();
/**
* 跳舞
*/
void jump();
}
Star.java
/**
* 明星类
*/
public class Star implements Skill {
private String name;
public Star(String name) {
this.name = name;
}
/**
* 唱歌
*/
@Override
public void sing() {
System.out.println(name + "开始唱歌,唱得很好!");
}
/**
* 跳舞
*/
@Override
public void jump() {
System.out.println(name + "开始跳舞,跳得很好!");
}
}
StarProxy.java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 明星代理对象
*/
public class StarProxy {
/**
* 设计一个方法来返回一个明星对象的代理对象
* @param star 明星对象
* @return 明星代理对象
*/
public static Skill getProxy(Star star) {
// 为明星对象生成一个代理对象
return (Skill) Proxy.newProxyInstance(
star.getClass().getClassLoader(),
star.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
System.out.println("收首付款...");
System.out.println("把明星送出去唱歌跳舞。");
// 让明星唱歌跳舞
Object result = method.invoke(star, objects);
System.out.println("收尾款,把明星接回来。");
return result;
}
}
);
}
}
Test.java
public class Test {
public static void main(String[] args) {
/* 目标:学习开发出一个动态代理的对象出来,理解动态代理的执行流程 */
// 1.创建一个明星类对象 明星类对象必须实现 唱歌跳舞技能接口
Star star = new Star("花千骨");
// 2.为明星对象生成一个明星代理对象
Skill starProxy = StarProxy.getProxy(star);
// 3.执行方法
starProxy.sing();
// starProxy.jump();
}
}
总结:
- 代理是什么?
- 一个对象,用来对被代理对象的行为额外做一些辅助工作。
- 在Java中实现动态代理的步骤是什么样的?
- 必须存在接口。
- 被代理对象需要实现接口。
- 使用Proxy类提供的方法,得到对象的代理对象。
- 通过代理对象调用方法,执行流程是什么样的?
- 先走代理。
- 代理可以为方法额外做一些辅助工作。
- 开发真正触发对象的方法执行。
- 回到代理中,由代理负责返回结果给方法的调用者。
b.动态代理的应用案例:做性能分析、代理的好处
案例:模拟企业业务功能开发,完成每个功能的性能统计
需求:
- 模拟某企业用户管理业务,需包含用户登录、用户删除、用户查询功能,并要统计每个功能的耗时。
分析:
- 定义一个UserService表示用户业务接口,规定必须完成用户登录,用户删除,用户查询功能。
- 定义一个实现类UserServicelmpl实现UserService,并完成相关功能,且统计每个功能的耗时。
- 定义测试类,创建实现类对象,调用方法。
优化代码之前:
UserService.java
/**
* 用户服务接口
*/
public interface UserService {
/**
* 登录
*
* @param username 用户名
* @param password 密码
* @return 是否登录成功
*/
String login(String username, String password);
/**
* 删除用户
*/
void deleteUsers();
/**
* 查询所有用户
* @return 所有用户
*/
String selectUsers();
}
UserServiceImpl.java
/**
* 用户服务类
*/
public class UserServiceImpl implements UserService {
/**
* 登录
*
* @param username 用户名
* @param password 密码
* @return 是否登录成功
*/
@Override
public String login(String username, String password) {
// 开始时间
long startTime = System.currentTimeMillis();
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
String result = "登录名或密码错误!";
if ("admin".equals(username) && "123456".equals(password)) {
result = "登录成功!";
}
// 结束时间
long endTime = System.currentTimeMillis();
System.out.println("login方法耗时:" + (endTime - startTime) / 1000.0 + "秒");
return result;
}
/**
* 删除用户
*/
@Override
public void deleteUsers() {
// 开始时间
long startTime = System.currentTimeMillis();
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
// 结束时间
long endTime = System.currentTimeMillis();
System.out.println("deleteUsers方法耗时:" + (endTime - startTime) / 1000.0 + "秒");
}
/**
* 查询所有用户
*
* @return 所有用户
*/
@Override
public String selectUsers() {
// 开始时间
long startTime = System.currentTimeMillis();
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
// 结束时间
long endTime = System.currentTimeMillis();
System.out.println("selectUsers方法耗时:" + (endTime - startTime) / 1000.0 + "秒");
return null;
}
}
Test.java
/**
* 目标:掌握使用动态代理解决问题, 理解使用动态代理的优势
* 用户服务类多个方法都需要统计执行时间, "统计执行时间代码重复", 使用代理来解决,只需要写一次"统计时间代码".
*/
public class Test {
public static void main(String[] args) {
// 创建用户服务对象
UserService userService = new UserServiceImpl();
// 登录
System.out.println(userService.login("admin", "123456"));
// 删除用户
userService.deleteUsers();
// 查询所有用户
userService.selectUsers();
}
}
执行结果:
login方法耗时:1.0秒
登录成功!
deleteUsers方法耗时:1.0秒
selectUsers方法耗时:1.0秒
优化代码:
UserServiceImpl.java
/**
* 用户服务类
*/
public class UserServiceImpl implements UserService {
/**
* 登录
*
* @param username 用户名
* @param password 密码
* @return 是否登录成功
*/
@Override
public String login(String username, String password) {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
String result = "登录名或密码错误!";
if ("admin".equals(username) && "123456".equals(password)) {
result = "登录成功!";
}
return result;
}
/**
* 删除用户
*/
@Override
public void deleteUsers() {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 查询所有用户
*
* @return 所有用户
*/
@Override
public String selectUsers() {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
UserServiceProxy.java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 用户服务代理类
*/
public class UserServiceProxy {
/**
* 通过一个静态方法, 为用户业务对象返回一个代理对象
*/
public static UserService getProxy(UserService userService) {
return (UserService) Proxy.newProxyInstance(
userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
// 开始时间
long startTime = System.currentTimeMillis();
// 真正触发对象的行为执行
Object result = method.invoke(userService, objects);
// 结束时间
long endTime = System.currentTimeMillis();
System.out.println(method.getName() + ":" + (endTime - startTime) / 1000.0 + "秒");
return result;
}
}
);
}
}
Test.java
/**
* 目标:掌握使用动态代理解决问题, 理解使用动态代理的优势
* 用户服务类多个方法都需要统计执行时间, "统计执行时间代码重复", 使用代理来解决,只需要写一次"统计时间代码".
*/
public class Test {
public static void main(String[] args) {
// 创建用户服务代理对象
UserService userServiceProxy = UserServiceProxy.getProxy(new UserServiceImpl());
// 登录
System.out.println(userServiceProxy.login("admin", "123456"));
// 删除用户
userServiceProxy.deleteUsers();
// 查询所有用户
userServiceProxy.selectUsers();
}
}
执行结果:
login:1.0秒
登录成功!
deleteUsers:1.001秒
selectUsers:1.001秒
动态代理的优点:
可以在不改变方法源码的情况下,实现对方法功能的增强,提高了代码的复用。
简化了编程工作、提高了开发效率,同时提高了软件系统的可扩展性。
可以被代理对象的所有方法做代理。
非常的灵活,支持任意接口类型的实现类对象做代理,也可以直接为接口本身做代理。
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 用户服务代理类 */ public class UserServiceProxy { /** * 通过一个静态方法, 为用户业务对象返回一个代理对象 */ public static <T> T getProxy(T t) { return (T) Proxy.newProxyInstance( t.getClass().getClassLoader(), t.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object o, Method method, Object[] objects) throws Throwable { // 开始时间 long startTime = System.currentTimeMillis(); // 真正触发对象的行为执行 Object result = method.invoke(t, objects); // 结束时间 long endTime = System.currentTimeMillis(); System.out.println(method.getName() + ":" + (endTime - startTime) / 1000.0 + "秒"); return result; } } ); } }