15.JAVA高级--黑马程序员

JAVA高级

D:\code\黑马\code\junit-reflect-annotation-proxy-app

1. 单元测试

1.1 单元测试概述

1.1.1 单元测试

针对最小的功能单元即方法的测试,编写测试代码来对方法进行正确性测试

1.1.2 使用main方法进行单元测试缺点

在这里插入图片描述

  • 只能在main方法编写测试代码,去调用其他方法进行测试
  • 无法实现自动化测试,一个方法测试失败,可能影响其他方法的测试
  • 无法得到测试的报告,需要程序员自己去观察测试是否成功
1.1.3 使用Junit框架进行单元测试

由第三方公司开发,用于对方法进行测试(IDEA集成了Juint框架)

  • 可以灵活的编写测试代码,可以针对某个方法执行测试,也支持一键完成对全部方法的自动化测试,且各自独立
  • 不需要程序员去分析测试的结果,会自动生成测试报告出来

1.2 Junit单元测试快速入门

1.2.1 需求

某个系统,有多个业务方法,请使用ut单元测试框架,编写测试代码,完成对这些方法的正确性测试

1.2.2 具体步骤
  1. 将Junit框架的jar包导入到项目中(IDEA已经集成,无需手动导入,导入方式如下图)

    在这里插入图片描述

  2. 为需要测试的业务类定义对应的测试类,并为每个业务方法编写对应的测试方法(测试方法的固定格式:public修饰、无入参、无返回值)

  3. 测试方法上必须声明@Test注解,然后在测试方法中,编写代码调用待测试的业务方法

  4. 开始测试:选中测试方法,右键选择"Junit运行”,如果测试通过则是绿色;如果测试失败,则是红色;如果业务逻辑通过,但是断言(预期与得到结果不一致),则是黄色

1.2.3 代码实现
/*
待测试的三个方法
*/
public class StringUtil {
    public static void printNumber(String name){
        System.out.println("名字长度是:" + name.length());
    }

    public static void print(String name){
        System.out.println(name);
    }

    public static int getMaxIndex(String data){
        if(data == null) {
            return -1;
        }
        return data.length();
    }
}
/*
编写的测试类
*/

//类的命名规则:待测试的类名+Test
public class StringUtilTest {
    //方法的命名规则:test+待测试的方法名
    @Test
    public void testPrintNumber(){
        StringUtil.printNumber("测试用例");
        StringUtil.printNumber(null);
    }

    @Test
    public void testPrint(){
        StringUtil.print("测试用例");
        StringUtil.print(null);
    }

    @Test
    public void testGetMaxIndex(){
        int maxIndex1 = StringUtil.getMaxIndex("测试用例");
        int maxIndex2 = StringUtil.getMaxIndex(null);
        System.out.println("maxIndex1 = " + maxIndex1);
        System.out.println("maxIndex2 = " + maxIndex2);
        //断言用于判断实际结果与预期结果是否一致
        //第一个参数是不一致时的输出结果,第二个参数是预期结果,第三个参数是实际结果
        Assert.assertEquals("测试用例的最大索引与预期结果不一致", 3, maxIndex1);
    }
}

(1)右键方法并点击运行则单独测试该方法,不测试其他的方法

在这里插入图片描述

在这里插入图片描述

(2)右键类名并点击运行,则测试该测试类中的所有测试方法

在这里插入图片描述

在这里插入图片描述

(3)右键该模块,则运行该模块下的所有测试类中的所有测试方法

在这里插入图片描述

1.3 Juint单元测试框架常见注解

1.3.1 Junit4版本的注解
注解说明
@Test测试类中的方法必须用它修饰才能成为测试方法,才能启动执行
@Before用来修饰一个实例方法,被修饰的方法会在每一个测试方法执行之前执行一次
@After用来修饰一个实例方法,被修饰的方法会在每一个测试方法执行之后执行一次
@BeforeClass用来修饰一个静态方法,被修饰的方法会在所有测试方法之前只执行一次
@AfterClass用来修饰一个静态方法,被修饰的方法会在所有测试方法之后只执行一次

被@Before或@BeforeClass修饰的方法的作用:初始化资源,方便测试方法使用(IO流管道等)

被@After或@AfterClass修饰的方法的作用:释放资源(关闭IO流管道等)

//类的命名规则:待测试的类名+Test
public class StringUtilTest {
    @Before
    public void initialize(){
        System.out.println("================实例方法初始化=========================");
    }

    @BeforeClass
    public static void initializeClass(){
        System.out.println("============静态方法初始化==============");
    }

    @After
    public void destroy(){
        System.out.println("============实例方法销毁==============");
    }

    @AfterClass
    public static void destroyClass(){
        System.out.println("=============静态方法销毁==============");
    }

    //方法的命名规则:test+待测试的方法名
    @Test
    public void testPrintNumber(){
        StringUtil.printNumber("测试用例");
        StringUtil.printNumber(null);
    }

    @Test
    public void testGetMaxIndex(){
        int maxIndex1 = StringUtil.getMaxIndex("测试用例");
        int maxIndex2 = StringUtil.getMaxIndex(null);
        System.out.println("maxIndex1 = " + maxIndex1);
        System.out.println("maxIndex2 = " + maxIndex2);
        //断言用于判断实际结果与预期结果是否一致
        //第一个参数是不一致时的输出结果,第二个参数是预期结果,第三个参数是实际结果
        Assert.assertEquals("测试用例的最大索引与预期结果不一致", 3, maxIndex1);
    }
}

在这里插入图片描述

1.3.2 Junit5版本的注解(相比于4,只改了名称,作用不变)
注解说明
@Test测试类中的方法必须用它修饰才能成为测试方法,才能启动执行
@BeforeEach用来修饰一个实例方法,被修饰的方法会在每一个测试方法执行之前执行一次
@AfterEach用来修饰一个实例方法,被修饰的方法会在每一个测试方法执行之后执行一次
@BeforeAll用来修饰一个静态方法,被修饰的方法会在所有测试方法之前只执行一次
@AfterAll用来修饰一个静态方法,被修饰的方法会在所有测试方法之后只执行一次

2. 反射(Reflection) java.lang.reflect

2.1 认识反射

反射就是:加载类,并允许以编程的方式解剖类中的各种成分(成员变量、方法、构造器等

学习反射就是学习如何获取类的信息并操作它们

  1. 加载类,获取类的字节码:Class对象
  2. 获取类的构造器:Constructor对象
  3. 获取类的成员变量:Field对象
  4. 获取类的成员方法:Method对象

2.2 加载类、获取类的字节码:class对象

在这里插入图片描述

后面所有反射相关的内容加载的类都是该Student类

public class Student {
    private String name;
    private int age;
    private double score;
    public String address;

    public Student() {
    }

    public Student(String name, int age, double score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }
    //private修饰的构造方法
    private Student(String name) {
        this.name = name;
    }

    //public修饰的方法
    public void show() {
        System.out.println("我是一个好学生");
    }
    //private修饰的方法
    private void privateMethod() {
        System.out.println("我是一个私有方法");
    }
    private void privateMethod(String name) {
        System.out.println("我是一个重载的私有方法,名字是:" + name);
    }

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

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }
}
2.2.1 获取类的三种方式
  1. Class c1 = 类名.class

  2. 调用Class提供方法:public static Class forName(String package)

  3. Object供的方法:public Class getClass(); Class c3 = 对象.getClass()

public class Test1Class {
    public static void main(String[] args) throws Exception {
        //1.使用class c1 = 类名.class方式加载类
        Class c1 = Student.class;
        String name = c1.getName();//获取全类名
        System.out.println(name);// com.lmh.reflect.Student
        String simpleName = c1.getSimpleName();//获取简类名,不包含包名
        System.out.println(simpleName);// Student

        //2.使用public static Class forName(String package)加载类
        //入参为类的全类名
        Class c2 = Class.forName("com.lmh.reflect.Student");

        //3.使用对象.getClass()方法加载类
        Student student = new Student();
        Class c3 = student.getClass();
        System.out.println(c1 == c2);//true
        System.out.println(c1 == c3);//true
        //因为Student类的字节码文件只有一份,所以该类在内存中也只有一个,故c1、c2、c3都是同一个对象
    }
}

2.3 获取类的构造器Constructor对象

2.3.1 Class对象提供的从类中获取构造器的方法
方法说明
Constructor<?>[ ] getConstructors( )获取所有public修饰的构造器
Constructor<?>[ ] getDeclaredConstructors( )获取所有构造器,无论什么关键字修饰
Constructor<T> getConstructor(Class<?> … parameterTypes)入参为可变参数类型,根据参数类型获取public修饰的某个构造器
Constructor<T> getDeclaredConstructor(Class<?> … parameterTypes)入参为可变参数类型,根据参数类型获取某个构造器,无论什么关键字修饰
public class Test2Constructor {
    @Test
    public void testGetConstructors() throws Exception {
        //1. 加载类
        Class c = Student.class;
        //2. 获取所有public修饰的构造方法
        Constructor[] cs1 = c.getConstructors();
        //3. 获取所有构造方法(推荐使用)
        Constructor[] cs2 = c.getDeclaredConstructors();
        //4. 遍历构造方法
        print(cs1);
        print(cs2);
        //5. 获取public修饰的指定的构造方法
        Constructor c1 = c.getConstructor(String.class, int.class, double.class);
        //6. 获取指定的构造方法
        Constructor c2 = c.getDeclaredConstructor(String.class);
        System.out.println(c1.getName() + "----->"
                + c1.getParameterCount());
        System.out.println("--------------------");
        System.out.println(c2.getName() + "----->"
                + c2.getParameterCount());
    }
    
    public void print(Constructor[] cs){
        for (Constructor constructor : cs) {
            //获取构造方法的名字和参数个数
            System.out.println(constructor.getName() + "----->"
                    + constructor.getParameterCount());
        }
        System.out.println("--------------------");
    }
}

在这里插入图片描述

2.3.2 Constructor对象的方法

获取类构造器的作用:初始化对象并返回

方法说明
T newInstance( Object … initargs )调用此构造器对象表示的构造器,并传入参数,完成对象的初始化并返回
public void setAccessible( boolean flag )设置为true,表示禁止检查访问控制(暴力反射),private修饰的构造器也可以直接使用
public class Test2Constructor {
    @Test
    public void testConstructorMethod() throws Exception {
        Class c = Student.class;
        Constructor constructor = c.getDeclaredConstructor(String.class);
        /*
        无论是否为public修饰,均可以用以下方法创建对象,是通用模板
         */
        //当构造方法是private修饰时,需要先设置为ture,才能调用,否则会报错
        constructor.setAccessible(true);
        //调用构造方法
        Student s = (Student) constructor.newInstance("张三");
        System.out.println(s.getName());//张三
    }
}

2.4 获取类的成员变量

2.4.1 Class对象提供的从类中获取成员变量的方法
方法说明
public Field[ ] getFields( )获取所有public修饰的成员变量
public Field[ ] getDeclaredFields( )获取所有成员变量,无论什么关键字修饰
public Field getField( String name )获取public修饰的指定成员变量,入参为变量名
public Field getDeclaredField( String name )获取任意关键字修饰的指定成员变量,入参为变量名
public class Test3Field {
    private Class c;
    private Constructor constructor;
    Student student;
    @Before
    public void init() throws Exception {
        c = Student.class;
        constructor = c.getDeclaredConstructor(String.class, int.class, double.class);
        constructor.setAccessible(true);
        student = (Student) constructor.newInstance("lmh", 18, 100);
    }
    @Test
    public void testGetFields() throws Exception {
        // 获取所有的public修饰的成员变量
        Field[] fs1 = c.getFields();
        print(fs1);
        // 获取所有的成员变量
        Field[] fs2 = c.getDeclaredFields();
        print(fs2);
        // 获取指定的public修饰的成员变量
        Field address = c.getField("address");
        System.out.println(address.getName() + "---->"
                + address.getType());
        System.out.println("===================================");
        // 获取指定的成员变量
        Field age = c.getDeclaredField("age");
        System.out.println(age.getName() + "---->"
                + age.getType());
    }
    public void print(Field[] fs) {
        for (Field f : fs) {
            System.out.println(f.getName() + "---->"
                    + f.getType());
        }
        System.out.println("===================================");
    }
}

在这里插入图片描述

2.4.2 Field对象的方法

获取成员变量的作用:对成员变量进行赋值与取值

方法说明
void set( object obj, object value )赋值,第一个参数为实体类对象,第二个参数为要赋的值
public void setAccessible( boolean flag )设置为true,表示禁止检查访问控制(暴力反射),private修饰的成员变量也可以直接操作
object get( object obj )取值,入参为实体类对象
public class Test3Field {
    private Class c;
    private Constructor constructor;
    Student student;
    @Before
    public void init() throws Exception {
        c = Student.class;
        constructor = c.getDeclaredConstructor(String.class, int.class, double.class);
        constructor.setAccessible(true);
        student = (Student) constructor.newInstance("lmh", 18, 100);
    }

    @Test
    public void testFieldMethod() throws Exception{
        Field Fname = c.getDeclaredField("name");
        //设置访问权限
        Fname.setAccessible(true);
        //获取成员变量的值
        String name = (String) Fname.get(student);
        System.out.println(name);//lmh
        //设置成员变量的值
        Fname.set(student, "dadada");
        System.out.println(student.getName());//dadada
    }
}

2.5 获取类的成员方法

2.5.1 Class对象提供的从类中获取成员方法的方法
方法说明
Method[ ] getMethods( )获取所有public修饰的成员方法
Method[ ] getDeclaredMethods( )获取所有成员方法,无论什么关键字修饰
Method getMethod(String name, class<?> … parameterTypes)入参为可变参数类型,根据方法名和参数类型获取public修饰的某个成员方法
Method getDeclaredMethod(String name, class<?> … parameterTypes)入参为可变参数类型,根据方法名和参数类型获取某个成员方法,无论什么关键字修饰
public class Test4Method {
    private Class c;
    private Constructor constructor;
    Student student;
    @Before
    public void init() throws Exception {
        c = Student.class;
        constructor = c.getDeclaredConstructor(String.class, int.class, double.class);
        constructor.setAccessible(true);
        student = (Student) constructor.newInstance("lmh", 18, 100);
    }

    @Test
    public void testGetMethod() throws Exception {
        // 获取所有的public修饰的方法
        Method[] methods1 = c.getMethods();
        print(methods1);
        // 获取所有的方法
        Method[] methods2 = c.getDeclaredMethods();
        print(methods2);
        // 获取public修饰的指定的方法
        Method m1 = c.getMethod("show");
        System.out.println(m1.getName() + "---->"
                + m1.getReturnType() + "---->"
                + m1.getParameterCount());
        System.out.println("===================================");
        // 获取指定的方法
        Method m2 = c.getDeclaredMethod("privateMethod", String.class);
        System.out.println(m2.getName() + "---->"
                + m2.getReturnType() + "---->"
                + m2.getParameterCount());
    }
    public void print(Method[] ms) {
        for (Method m : ms) {
            System.out.println(m.getName() + "---->"
                    + m.getReturnType() + "---->"
                    + m.getParameterCount());
        }
        System.out.println("===================================");
    }
}

在这里插入图片描述

2.5.2 Method对象的方法

获取成员变量的作用:运行方法

方法说明
public object invoke( object obj, object … args )运行方法,第一个参数为实体类对象,第二个参数为可变参数,为传入的成员方法的参数
public void setAccessible( boolean flag )设置为true,表示禁止检查访问控制(暴力反射),private修饰的成员方法也可以直接访问
public class Test4Method {
    private Class c;
    private Constructor constructor;
    Student student;
    @Before
    public void init() throws Exception {
        c = Student.class;
        constructor = c.getDeclaredConstructor(String.class, int.class, double.class);
        constructor.setAccessible(true);
        student = (Student) constructor.newInstance("lmh", 18, 100);
    }
    
    @Test
    public void testMethod() throws Exception{
        Method m = c.getDeclaredMethod("privateMethod", String.class);
        m.setAccessible(true);
        m.invoke(student, "ddd");
        //我是一个重载的私有方法,名字是:ddd
    }
}

2.6 作用、应用场景

2.6.1 反射的作用

基本作用:可以解析一个类的全部成分并进行操作

可以通过设置访问权限破坏对象的封装性

最主要用途:适合做Java的框架,基本上主流的框架都会基于反射设计出一些通用的功能

2.6.2 应用场景(简单案例)

使用反射做一个简易版的框架,该框架可以实现对于任意一个对象,该框架都可以把对象的字段名和对应的值保存到文件中去

在这里插入图片描述

实现步骤

  1. 定义一个方法,可以接收任意对象
  2. 每收到一个对象后,使用反射获取该对象的Clss对象,然后获取全部的成员变量
  3. 遍历成员变量,然后提取成员变量在该对象中的具体值
  4. 把成员变量名和其值写出到文件中去

具体实现

public class Student {
    private String name;
    private int age;
    private double score;
    public String address;

    public Student(String name, int age, double score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }
}
public class Teacher {
    private String name;
    private int age;
    private char sex;
    private String address;
    private double salary;

    public Teacher(String name, int age, char sex, String address, double salary) {
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.address = address;
        this.salary = salary;
    }
}
public class Frame {
    //传入参数为Object类型,可以接收任意类型的对象
    public static void saveObj(Object obj) throws Exception {
        PrintStream printStream = new PrintStream(new FileOutputStream("src/com/lmh/reflect/obj.txt", true));
        //1.根据对象加载类
        Class c = obj.getClass();
        printStream.println("========" + c.getSimpleName() + "========");
        //2.根据类获取所有的成员变量
        Field[] dfs = c.getDeclaredFields();
        for (Field df : dfs) {
            df.setAccessible(true);
            String name = df.getName();//获取成员变量的名字
            String value = df.get(obj) + "";//获取成员变量的值
            printStream.println(name + " = " + value);
        }
        //3.关闭流
        printStream.close();
    }
}
public class FrameTest {
    public static void main(String[] args) throws Exception {
        Student s = new Student("张三", 23, 98.5);
        Teacher t = new Teacher("李四", 45, '男', "北京", 5000);
        Frame.saveObj(s);
        Frame.saveObj(t);
    }
}

在这里插入图片描述

3. 注解(Annotation)

3.1 注解概述

  • 就是Java代码里的特殊标记,比如:@Override、@Test等,作用是让其他程序根据注解信息来决定怎么执行该程序
  • 注意:注解可以用在类上、构造器上、方法上、成员变量上、参数上等位置
3.1.1自定义注解
自定义注解的格式

在这里插入图片描述

//定义自定义注解
public @interface MyTest1 {
    String name();
    int age() default 18;//默认值为18
    String[] hobby();
}
/*
使用自定义注解的简单实例
 */
@MyTest1(name = "lmh", age = 20, hobby = {"basketball", "football"})
public class AnnotationTest1 {
    /*
    因为age有默认值,所以可以不用赋值,默认为18
     */
    @MyTest1(name = "ddd", hobby = {"basl", "fl"})
    public void test() {
        System.out.println("test");
    }
}
特殊属性名:value

当自定义注解只有value这一个属性或者有其他属性但是都有默认值时,在使用注解时无需写value,直接传值即可

public @interface MyTest2 {
    String value();
    int age() default 18;//默认值为18
}
//只有value或者只有一个属性时,可以省略属性名
//如果其他属性都有默认值,只有value没有默认值,那么只需要给value赋值,value不必写出来

@MyTest2("lmh")//默认给value赋值
public class AnnotationTest2 {
    
}
3.1.2 注解的原理

左边为自定义注解的java文件,右边为反编译后的字节码文件

在这里插入图片描述

  • 注解本质上就是一个接口,Java中所有的注解都继承了Annotation接口
  • 使用注解时的@MyTest2("lmh")实际上就是一个实现类对象,实现了该注解接口以及Annotation接口

3.2 元注解

用于修饰注解的注解

在这里插入图片描述

//用于声明注解可以使用的位置,下面代码表示该注解只能用于类和方法上
@Target({ElementType.TYPE, ElementType.METHOD})
//用于表示注解的保留周期,下面代码表示该注解会一直保留到运行阶段,可以通过反射机制读取到
@Retention(RetentionPolicy.RUNTIME)//开发中一般都是RUNTIME
public @interface MyTest3 {
}
@MyTest3()
public class AnnotationTest2 {
    @MyTest3()//会报错,因为声明的范围是TYPE和METHOD,而FIELD不在范围内
    private String s;
}

3.3 注解的解析

判断类上、方法上、成员变量上是否存在注解,并把注解里的内容给解析出来

3.3.1解析注解的步骤

要解析谁上面的注解,就先拿到谁对应的对象

  • 如果要解析类上面的注解,就要先通过反射拿到该类对应的Class对象,再通过Class对象解析注解
  • 如果要解析方法上面的注解,就要先通过反射拿到该方法对应的Method对象,再通过Method对象解析注解
  • Class、Method、Field、Constructor都实现了AnnotatedElement接口,所以都拥有解析注解的能力
AnnotatedElement接口提供的方法说明
public Annotation[ ] getDeclaredAnnotations( )获取当前对象上面的注解对象的集合
public T getDeclaredAnnotation(Class<T> annotationClass)获取指定的注解对象,入参为注解的类对象
public boolean isAnnotationPresent( Class<Annotation> annotationclass )判断当前对象上是否存在某个注解,入参为注解的类对象
3.3.2 解析注解案例

需求

  1. 定义注解MyTest4,包含以下属性
    1. String value
    2. double aaa,默认值为100
    3. String[ ] bbb
    4. 限制注解使用的位置:类与成员方法上
    5. 限制注解的有效范围:一直到运行时
  2. 定义一个类Demo,在该类中定义一个test1方法并在该类及其方法上使用MyTest4注解
  3. 定义AnnotationTest3测试类,解析Demo类中的全部注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest4 {
    String value();
    double aaa() default 100;
    String[] bbb();
}
@MyTest4(value = "abc", bbb = {"aaa", "bbb"})
public class Demo {
    @MyTest4(value = "dadada", aaa = 1, bbb = {"a", "b"})
    public void test1(){

    }
}
public class AnnotationTest3 {
    @Test
    public void parseClass(){
        //获取类对象
        Class c = Demo.class;
        //解析类上的注解
        if (c.isAnnotationPresent(MyTest4.class)){
            MyTest4 myTest4 =
                    (MyTest4) c.getDeclaredAnnotation(MyTest4.class);
            System.out.println(myTest4.value());
            System.out.println(myTest4.aaa());
            System.out.println(Arrays.toString(myTest4.bbb()));
            System.out.println("====================================");
        }
    }

    @Test
    public void parseMethod() throws Exception {
        Class c = Demo.class;
        Method m = c.getDeclaredMethod("test1");
        Annotation[] annotations = m.getDeclaredAnnotations();
        if (annotations.length > 0){
            for (Annotation annotation : annotations) {
                //instanceof判断左边的对象是否是右边类的实例
                if (annotation instanceof MyTest4){
                    MyTest4 myTest4 = (MyTest4) annotation;
                    System.out.println(myTest4.value());
                    System.out.println(myTest4.aaa());
                    System.out.println(Arrays.toString(myTest4.bbb()));
                }
            }
        }
    }
}

在这里插入图片描述

3.4 注解的应用场景:模拟Junit框架

需求

定义若干个方法,只要加了MyTest注解,就会触发该方法执行

分析

  1. 定义一个自定义注解MyTest,只能注解方法,存活范围是一直都在
  2. 定义若干个方法,部分方法加上@MyTest注解修饰,部分方法不加
  3. 模拟一个junit程序,可以触发加了@MyTest注解的方法执行
//1.定义一个自定义注解MyTest,只能注解方法,存活范围是一直都在
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
}
public class Test {
    //2. 定义若干个方法,部分方法加上@MyTest注解修饰,部分方法不加
    @MyTest
    public void doSome() {
        System.out.println("====doSome()====");
    }

    public void doOther() {
        System.out.println("====doOther()====");
    }

    @MyTest
    public void doAny() {
        System.out.println("====doAny()====");
    }

    public void doAdd() {
        System.out.println("====doAdd()====");
    }

    //3.模拟一个junit程序,可以触发加了@MyTest注解的方法执行
    public static void main(String[] args) throws Exception{
        //创建Test类的对象
        Test test = new Test();
        //获取类对象
        Class c = Test.class;
        //获取类中所有的方法
        Method[] methods = c.getDeclaredMethods();
        for (Method method : methods) {
            if (method.isAnnotationPresent(MyTest.class)){
                method.invoke(test);
            }
        }
    }
}

在这里插入图片描述

4. 动态代理

4.1 动态代理概述

对象如果觉得自己身上干的事情太多,就可以转移部分工作给代理来减轻自己的工作量

(如下图所示,对象即歌星,对象要做的是唱歌和跳舞,那么准备话筒等功能就可以通过代理即经纪人来实现,经纪人干完自己的工作后,再通过调用对象即歌星的方法来让歌星唱歌和跳舞,从而完成全部的工作)

只有歌星和代理通过实现接口Star即实现接口中的sing和dance方法,代理才能够做自己的事情并让歌手完成歌手应该完成的事情

在这里插入图片描述

4.2 动态代理快速入门

实现动态代理的前提是代理的对象和被代理的对象必须实现同一个接口

动态代理对应的类:java.lang.reflect.Proxy,提供了为对象产生代理对象的方法

在这里插入图片描述

//接口,必须让代理类和被代理类实现同一个接口
public interface Star {
    String sing(String SongName);
    void dance();
}
//被代理类,实现接口
public class SuperStar implements Star{
    private String name;//明星的名字

    public SuperStar(String name) {
        this.name = name;
    }

    @Override
    public String sing(String SongName) {
        System.out.println(name + "唱了一首歌:" + SongName);
        return "歌唱完了,谢谢大家!";
    }

    @Override
    public void dance() {
        System.out.println(name + "跳了一支舞!");
    }
}
//代理类
public class ProxyUtil {
    public static Star createProxy(SuperStar superStar){
        //第一个参数固定,第二个参数用于绑定被代理类实现的接口,
        // 第三个参数是一个匿名内部类,用于实现代理对象需要实现的功能
        Star starProxy =
                (Star) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),
                new Class[]{Star.class}, new InvocationHandler() {
                    @Override
                    //第一个参数是代理对象,第二个参数是被代理对象的方法对象,第三个参数是被代理对象的方法的入参
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //代理需要完成的功能
                        if (method.getName().equals("sing")) {
                            System.out.println("准备话筒,收钱20万");
                        } else if (method.getName().equals("dance")) {
                            System.out.println("准备场地,收钱1000万");
                        }
                        //指定被代理对象的方法并执行
                        //如果有返回值,需要将返回值返回给代理对象
                        return method.invoke(superStar, args);
                    }
                });
        //返回代理对象
        return starProxy;
    }
}
//测试类
public class Test {
    public static void main(String[] args) {
        SuperStar s = new SuperStar("吴晗");
        Star proxy = ProxyUtil.createProxy(s);

        System.out.println(proxy.sing("蓝莲花"));
        proxy.dance();
    }
}

在这里插入图片描述

4.3 应用案例

某系统有一个用户管理类,包含用户登录,删除用户,查询用户等功能,系统要求统计每个功能的执行耗时情况,以便后期观察程序性能。(将统计时间的代码交给代理来做)

/**
 *  用户业务接口
 */
public interface UserService {
    // 登录功能
    void login(String loginName,String passWord) throws Exception;
    // 删除用户
    void deleteUsers() throws Exception;
    // 查询用户,返回数组的形式。
    String[] selectUsers() throws Exception;
}
/**
 * 用户业务实现类
 */
public class UserServiceImpl implements UserService{
    @Override
    public void login(String loginName, String passWord) throws Exception {
        if("admin".equals(loginName) && "123456".equals(passWord)){
            System.out.println("您登录成功,欢迎光临本系统~");
        }else {
            System.out.println("您登录失败,用户名或密码错误~");
        }
        Thread.sleep(1000);
    }

    @Override
    public void deleteUsers() throws Exception{
        System.out.println("成功删除了1万个用户~");
        Thread.sleep(1500);
    }

    @Override
    public String[] selectUsers() throws Exception{

        System.out.println("查询出了3个用户");
        String[] names = {"张全蛋", "李二狗", "牛爱花"};
        Thread.sleep(500);

        return names;
    }
}
public class ProxyUtil {
    public static UserService createProxy(UserService userService){
        UserService userServiceProxy = 
                (UserService) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),
                new Class[]{UserService.class}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                        if(method.getName().equals("login") 
                                || method.getName().equals("deleteUsers")
                                || method.getName().equals("selectUsers")){
                            long startTime = System.currentTimeMillis();

                            Object rs = method.invoke(userService, args);

                            long endTime = System.currentTimeMillis();
                            System.out.println(method.getName() + "方法执行耗时:" 
                                    + (endTime - startTime)/ 1000.0 + "s");
                            return rs;
                        }else {
                            Object rs = method.invoke(userService, args);
                            return rs;
                        }
                    }
                });
        return userServiceProxy;
    }
}
public class Test {
    public static void main(String[] args) throws Exception{
        // 1、创建用户业务对象。
        UserService userService = ProxyUtil.createProxy(new UserServiceImpl());

        // 2、调用用户业务的功能。
        userService.login("admin", "123456");
        System.out.println("----------------------------------------------------");

        userService.deleteUsers();
        System.out.println("----------------------------------------------------");

        String[] names = userService.selectUsers();
        System.out.println("查询到的用户是:" + Arrays.toString(names));
        System.out.println("----------------------------------------------------");
    }
}

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值