Java进阶(十四)单元测试、反射、注释、动态代理

发布于:2022-11-05 ⋅ 阅读:(341) ⋅ 点赞:(0)

十四、单元测试、反射、注释、动态代理

需要学会什么?

  • 单元测试:开发好的系统中存在很多的方法,如何对这些方法的正确性进行测试。
  • 反射:如何在程序运行时去得到Class对象,然后去获取Class中的每个成分。
  • 注解:注解是什么,具体是如何在Java程序中解决问题的。
  • 动态代理:框架技术的底层会用到。

1.单元测试

a.单元测试概述

单元测试:

  • 单元测试就是针对最小的功能单元编写测试代码,Java程序最小的功能单元是方法。因此单元测试就是针对Java方法的测试,进而简称方法的正确性。

目前测试方法是怎么进行的,存在什么问题?

  • 只有一个main方法,如果一个方法的测试失败了,其他方法测试会收到影响。
  • 无法得到测试的结果报告,需要程序员自己去观察测试是否成功。
  • 无法实现自动化测试。

JUnit单元测试框架:

  • JUnit是使用Java语言实现的单元测试框架,它是开源的,Java开发者都应当学习并使用JUnit编写单元测试。
  • 几乎所有的IDE工具都集成了JUnit,这样我们就可以直接在IDE中编写并运行JUnit测试,JUnit目前最新版本是5。

JUnit优点:

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

总结:

  1. JUnit单元测试是做什么的?
    • 测试类中方法的正确性。
  2. JUnit单元测试的优点报告是什么?
    • JUnit可以选择执行哪些测试方法,可以一键执行全部测试方法的测试。
    • JUnit可以生成测试报告,如果是测试良好则是绿色,如果是测试失败则是红色。
    • 单元测试中的某个方法测试失败了,不会影响其他测试方法的测试。

b.单元测试快速入门

需求:

  • 使用单元测试进行业务方法预期结果、正确性测试的快速入门。

分析:

  1. 将JUnit的jar包导入到项目中。
    • IDEA通常整合好了JUnit框架,一般不需要导入。(输入@Test后报红提示,Alt + Enter选择Add 'JUnit5.x.x' to classpath,之后点击OK即可)
    • 如果IDEA没有整合好,需要自己手工导入如下2个JUnit的Jar包到模块。
  2. 编写测试方法:该测试方法必须是公共的无参数无返回值的非静态方法。
  3. 在测试方法上使用@Test注解:标准该方法是一个测试方法。
  4. 在测试方法中完成被测试方法的预期正确性测试。
  5. 选中测试方法,选择”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”)

总结:

  1. JUnit单元测试的实现过程是什么样的?
    • 必须导入JUnit框架的jar包。
    • 定义的测试方法必须是无参数无返回值且公开的方法。
    • 测试方法使用@Test注解标记。
  2. 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;
    

总结:

  1. 反射的基本作用、关键?
    • 反射是在运行时获取类的字节码文件对象:可以解析类中的全部成分。
    • 反射的核心思想和关键就是:得到编译以后的Class文件对象。

b.反射获取类对象

反射的第一步:获取Class类的对象

编译
提取到内存中去
java文件
Class文件/字节码文件
Class对象/内存中
内存
Class对象/Student.class
private String name
Field
private int age
无参构造器
Constructor
有参构造器
成员方法
Method

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);
    }
}

总结:

  1. 反射的第一步是什么?
    • 获取Class类对象,可以解析类的全部成分。
  2. 获取Class类的对象的三种方式。
    • 方式一:Class c1 = Class.forName(“全限名”);
    • 方式二:Class c2 = 类名.class;
    • 方式三:Class c3 = 对象.getClass();

c.反射获取构造器对象

内存
Student
private String name
2.获得Constructor对象
无参构造器
有参构造器
3.创建对象
1.获取对象
private int age
成员方法

使用反射技术获取构造器对象并使用:

  • 反射得第一步是先得到类对象,然后从类对象中获取类得成分对象。
  • 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);

    }
}

总结:

  1. 利用反射技术获取构造器对象的方式?
    • getDeclaredConstructors()
    • getDeclaredConstructors(Class<?>… parameterTypes)
  2. 反射得到的构造器可以做什么?
    • 依然用来是创建对象的
      • public newInstance(Object… initargs)
    • 如果是非public的构造器,需要打卡权限(暴力反射),然后再创建对象。
      • setAccessible(boolean)
      • 反射可以破坏封装性,私有的也可以执行了。

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

内存
Student
private String name
2.获得Field对象
private int age
3.赋值或获取值
1.获取对象
无参构造器
有参构造器
成员方法

使用反射技术获取成员变量对象并使用:

  • 反射的第一步是先得到类对象,然后从类对象中获取类的成分对象。
  • 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);
    }
}

总结:

  1. 利用反射技术获取成员变量的方式?
    • 获取类中成员变量对象的方法:
      • getDeclaredFields()
      • getDeclareField(String name)
  2. 反射得到成员变量可以做什么?
    • 依然是在某个对象中取值和赋值。
      • void set(Object obj, Object value)
      • Object get(Object obj)
    • 如果某成员变量是非public的,需要打开权限(暴力反射),然后再取值、赋值。
      • setAccessible(booleann)

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

内存
Student
private String name
2.获得Field对象
成员方法
3.运行方法
1.获取对象
private int age
无参构造器
有参构造器

使用反射技术获取方法对象并使用:

  • 反射的第一步是先得到类对象,然后从类对象中获取类的成分对象。
  • 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);
    }

}

总结:

  1. 利用反射技术获取成员方法对象的方式:
    • 获取类中成员方法对象:
      • getDeclaredMethods()
      • getDeclaredMethod(String name, Class<?>… parameterTypes)
  2. 反射得到成员方法可以做什么?
    • 依然是在某个对象中触发该方法执行:
      • 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);
    }
}

总结:

  1. 反射为什么可以给约定了泛型的集合存入其他类型的元素?
    • 编译成Class文件进入运行阶段的时候,泛型会自动擦除。
    • 反射是作用在运行时的技术,此时已经不存在泛型了。

g.反射的作用-通用框架的底层原理

反射做通用框架:

需求:

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

分析:

  1. 定义一个方法,可以接收任意类的对象。
  2. 每次收到一个对象后,需要解析这个对象的全部成员变量名称。
  3. 这个对象可能是任意的,怎么样才可以知道这个对象的全部成员变量名称?
  4. 使用反射获取对象的Class类对象,然后获取全部成员变量信息。
  5. 遍历成员变量信息,然后提取本成员变量在对象中的具体值。
  6. 存入成员变量名称和值到文件中去即可。

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

总结:

  1. 反射的作用?
    • 可以在运行时得到一个类的全部成分然后操作。
    • 可以破坏封装性(很突出)。
    • 可以破坏泛型的约束性(很突出)。
    • 更重要的用途是:做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) {
        
    }
}

总结:

  1. 元注解是什么?
    • 注解注解的注解。
    • @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注解,就可以在启动时被触发执行。

分析:

  1. 定义一个自定义注解MyTest,只能注解方法,存活范围是一直都在。
  2. 定义若干个方法,只要有@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中如何生成代理,并指定代理干什么事?

对象
接口
proxy
newProxyInstance
代理对象

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();
    }
}

总结:

  1. 代理是什么?
    • 一个对象,用来对被代理对象的行为额外做一些辅助工作。
  2. 在Java中实现动态代理的步骤是什么样的?
    • 必须存在接口。
    • 被代理对象需要实现接口。
    • 使用Proxy类提供的方法,得到对象的代理对象。
  3. 通过代理对象调用方法,执行流程是什么样的?
    1. 先走代理。
    2. 代理可以为方法额外做一些辅助工作。
    3. 开发真正触发对象的方法执行。
    4. 回到代理中,由代理负责返回结果给方法的调用者。

b.动态代理的应用案例:做性能分析、代理的好处

案例:模拟企业业务功能开发,完成每个功能的性能统计

需求:

  • 模拟某企业用户管理业务,需包含用户登录、用户删除、用户查询功能,并要统计每个功能的耗时。

分析:

  1. 定义一个UserService表示用户业务接口,规定必须完成用户登录,用户删除,用户查询功能。
  2. 定义一个实现类UserServicelmpl实现UserService,并完成相关功能,且统计每个功能的耗时。
  3. 定义测试类,创建实现类对象,调用方法。

优化代码之前:

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;
                        }
                    }
            );
        }
    }
    
    
本文含有隐藏内容,请 开通VIP 后查看