Java反射和动态代理用法(附10道练习题)

发布于:2024-07-18 ⋅ 阅读:(167) ⋅ 点赞:(0)

一、什么是反射

解释一:

Java 反射机制是在运行状态中,对于任意一个,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个属性和方法。这种动态获取信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。

解释二:
Java 反射是指在运行时动态检查和操作类的能力。通过反射,(对于一个对象)程序可以在运行时获取关于类、方法、属性、构造函数等的详细信息,并且可以动态地创建对象、调用方法以及访问和修改字段。反射提供了一种灵活的机制,使得程序可以在编译时不知道确切类型的情况下操作这些类型

二、反射的核心接口和类

Java 反射主要涉及以下几个核心类和接口,它们位于包 java.lang.reflect中:

Class:每个类和接口在 JVM 中都表示为一个 Class 对象。通过 Class 对象,程序可以获取类的全限定名、实现的接口、父类、构造函数、方法、字段等信息。
Constructor:表示类的构造函数。通过 Constructor 对象,程序可以创建类的新实例。
Field:表示类的属性。通过 Field 对象,程序可以获取或修改属性的值。
Method:表示类的方法。通过 Method 对象,程序可以调用方法。

三、测试代码 Bean 类和目录结构

Person 类

Person类有nameage属性,无参构造方法有参构造方法gettersettertoString以及自定义的sayHellosayGoodbye方法。

public class Person {
    private String name;
    private int age;

    public Person() {
        this.name = "unknown";
        this.age = 0;
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

	// 省略getter()和setter()

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

	// public类型
    public void sayHello() {
        System.out.println("Hello, my name is " + this.name);
    }

	// private类型
    private void sayGoodbye() {
        System.out.println("Goodbye, my name is " + this.name);
    }

}

代码目录结构

在这里插入图片描述

四、反射的用法

1. 获取 Class 对象

Class 对象包含了类的结构信息,是反射的入口点。获取 Class 对象有三种方法,如下所示:

1. 类.class
2. 对象.getClass()
3. Class.forName()

public class Main {
    public static void main(String[] args) {

        // 方式1: 类.class语法
        Class<?> cls1 = Person.class;
        System.out.println(cls1);               // class Person
        System.out.println(cls1.getName());     // Person

        // 方式2: 对象.getClass()
        Class cls2 = new Person().getClass();
        System.out.println(cls2.getName());     // Person

        // 方式3: 使用静态方法Class.forName(),需要捕获ClassNotFoundException
        try {
            Class<?> cls3 = Class.forName("Person");
            System.out.println(cls3.getName()); // Person
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        
    }
}

2. 获取构造方法 Constructor 并使用

cls.getDeclaredConstructor()获取 Class 对象的所有构造方法
cls.getConstructor()获取 Class 对象的公有构造方法
constructor.newInstance()使用 Class 构造方法创建对象

import java.lang.reflect.Constructor;

public class Main {
    public static void main(String[] args) {
        try {
            // 获取Person类的Class对象
            Class cls = Class.forName("Person");
            // Class personClass = Person.class;
            // Class personClass = new Person().getClass();

            // 获取无参构造方法
            Constructor<?> noArgsConstructor = cls.getConstructor();
            // 创建对象
            Object person1 = noArgsConstructor.newInstance();
            // 重写了Person的toString方法直接打印即可
            System.out.println(person1); // Person{name='unknown', age=0}

            // 获取带参数的构造方法
            Constructor<?> paramArgsConstructor = cls.getConstructor(String.class, int.class);
            // 创建对象,并传递参数
            Object person2 = paramArgsConstructor.newInstance("Alice", 30);
            System.out.println(person2); // Person{name='Alice', age=30}

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3. 获取成员变量 Field 并使用

cls.getDeclaredField(name)获取 Class 对象的所有成员变量
cls.getField(name)获取 Class 对象的所有公有成员变量
field.setAccessible(true)设置 Class 对象的属性值可访问
field.get()获取 Class 对象的属性值
field.set()设置 Class 对象的属性值

import java.lang.reflect.Field;

public class Main {
    public static void main(String[] args) {
        try {
            // 获取Person类的Class对象
            Class cls = Class.forName("Person");
            // Class personClass = Person.class;
            // Class personClass = new Person().getClass();

            // 创建Person对象
            Person person = new Person("Alice",30);

            // 获取name字段
            Field nameField = cls.getDeclaredField("name");
            // 设置可访问性,因为name是私有的
            nameField.setAccessible(true);
            // 获取name字段的值
            String name = (String) nameField.get(person);
            System.out.println("Name: " + name);        // Name: Alice

            // 获取age字段
            Field ageField = cls.getDeclaredField("age");
            // 设置可访问性,因为age是私有的
            ageField.setAccessible(true);
            // 获取age字段的值
            int age = ageField.getInt(person);
            System.out.println("Age: " + age);          // Age: 30

            // 修改age字段的值
            ageField.setInt(person, 31);
            // 再次获取age字段的值,验证修改是否成功
            age = ageField.getInt(person);
            System.out.println("Updated Age: " + age);  // Updated Age: 31

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4. 获取成员方法 Method 并使用

cls.getDeclaredMethod(name)获取 Class 对象的所有成员方法
cls.getMethod(name)获取 Class 对象的公有成员方法
method.setAccessible(true)设置 Class 对象的方法可访问
method.invoke()调用 Class 对象的成员方法

import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) {
        try {
            // 获取Person类的Class对象
            Class<?> cls = Class.forName("Person");

            // 创建Person对象
            Person person = (Person) cls
                    .getDeclaredConstructor(String.class, int.class)
                    .newInstance("Alice", 30);
            //或 Person person =new Person("Alice", 30);

            // 获取sayHello方法
            Method sayHelloMethod = cls.getMethod("sayHello");
            // 调用sayHello方法
            sayHelloMethod.invoke(person);

            // 获取sayGoodbye方法
            Method sayGoodbyeMethod = cls.getDeclaredMethod("sayGoodbye");
            // 设置可访问性,因为sayGoodbye是私有的
            sayGoodbyeMethod.setAccessible(true);
            // 调用sayGoodbye方法
            sayGoodbyeMethod.invoke(person);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

五、动态代理与反射

Java 动态代理是 Java 语言中一种用于在运行时创建代理实例的机制,它允许拦截并处理对任何对象的调用

通过动态代理,可以在不修改原始对象的情况下,对其方法进行增强或添加额外的行为。可以在方法执行前后进行一些操作,比如日志记录、性能监测、事务管理等。

1. 动态代理三要素

在Java中,要实现动态代理,需要满足以下必备条件:

(1)代理接口

必须有一个或多个接口。动态代理只能为接口创建代理实例,不能为类创建代理。

(2)代理处理器

需要实现java.lang.reflect.InvocationHandler接口,该接口包含一个invoke方法,用于处理所有对代理对象的方法调用。

(3)代理对象的创建

使用java.lang.reflect.Proxy类的newProxyInstance方法来创建代理对象。该方法需要以下三个参数:

  • ClassLoader:用于加载代理类的类加载器
  • Class<?>[] interfaces:代理类要实现的接口数组
  • InvocationHandler:处理代理实例上的方法调用的调用处理器

2. 动态代理实现步骤

(1)定义一个或多个接口,声明需要代理的方法。
(2)创建一个实现InvocationHandler接口的类,重写invoke方法以定义如何处理方法调用。
(3)使用Proxy.newProxyInstance方法创建代理对象,传入相应的类加载器接口数组调用处理器实例。

3. 案例:获取函数的执行时间

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 定义一个简单的接口,其中 sayHello 方法接收一个字符串参数
interface Hello {
    void sayHello(String message);
    void sayGoodBye(String message);
}

// 实现这个接口
class HelloImpl implements Hello {
    // 实现接口中的 sayHello 方法,并设置默认消息
    @Override
    public void sayHello(String message) {
        try {
            // 打印接收到的消息,模拟耗时操作
            System.out.println("Hello " + message);
            Thread.sleep(300);
        } catch (InterruptedException e) {
            // 异常处理
            e.printStackTrace();
        }
    }

    @Override
    public void sayGoodBye(String message) {
        try {
            // 打印接收到的消息,模拟耗时操作
            System.out.println("GoodBye " + message);
            Thread.sleep(300);
        } catch (InterruptedException e) {
            // 异常处理
            e.printStackTrace();
        }
    }

}

// 实现 InvocationHandler 接口
class TimeInvocationHandler implements InvocationHandler {
    private final Object target; // 目标对象

    // 构造函数,接收目标对象
    public TimeInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    // 处理代理实例的所有方法调用
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 判断方法名,只对 sayHello 方法进行时间测量
        if ("sayHello".equals(method.getName())) {
            long startTime = System.currentTimeMillis(); // 记录方法开始执行的时间
            Object result = method.invoke(target, args); // 调用目标对象的方法
            long endTime = System.currentTimeMillis(); // 记录方法结束执行的时间
            System.out.println("成员方法 " + method.getName() + " 花费了 " + (endTime - startTime) + " 毫秒.");
            return result; // 返回方法的执行结果
        } else {
            // 对于 sayGoodBye 方法,直接调用目标对象的方法,不进行时间测量
            return method.invoke(target, args);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        // 创建目标对象
        Hello hello = new HelloImpl();

        // 创建调用方法的处理器
        TimeInvocationHandler timeInvocationHandler = new TimeInvocationHandler(hello);

        // 创建一个代理实例
        Hello proxyInstance = (Hello) Proxy.newProxyInstance(
                hello.getClass().getClassLoader(),  // 类加载器
                hello.getClass().getInterfaces(),   // 代理类要实现的接口
                timeInvocationHandler               // 调用方法的处理器
        );

        // 使用代理对象调用方法,实际上会执行处理器的invoke方法
        proxyInstance.sayGoodBye("Python");
        // GoodBye Python

        proxyInstance.sayHello("Java");
        // Hello Java
        // 成员方法 sayHello 花费了 305 毫秒.
    }
}

六、练习

1. 使用反射获取String类的所有公有方法,并把方法名打印出来。

import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) {
        Class<String> cls = String.class;
        Method[] methods = cls.getMethods();
        for (Method method : methods) {
            System.out.println(method.getName());
        }
    }
}


2. 使用反射创建一个对象,并调用其无参构造方法。

import java.lang.reflect.Constructor;

public class Main {
    public static void main(String[] args) {
        Class<String> cls = String.class;
        try {
            Constructor<String> constructor = cls.getConstructor(String.class);
            // 指定了String就可以不用Object
            String s = constructor.newInstance("Hello world!");
            System.out.println(s);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3. 使用反射修改一个对象的私有字段值。

import java.lang.reflect.Field;

class Employee {
    private int age;

    public Employee() {
    }

    public Employee(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    private void printAge() {
        System.out.println(this.age);
    }
}


public class Main {
    public static void main(String[] args) {

        Employee employee = new Employee(30);
        Class cls = employee.getClass();
        try {
            Field ageField = cls.getDeclaredField("age");
            ageField.setAccessible(true);
            
            int age = ageField.getInt(employee);
            System.out.println(age);                // 30
            
            ageField.setInt(employee,31);
            System.out.println(employee.getAge());  // 31
            
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

4. 使用反射获取一个ArrayList的所有父类(包括间接父类)。

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        Class cls = ArrayList.class;
        while (cls != null) {
            System.out.println(cls.getName());
            cls = cls.getSuperclass();
        }
    }
}

5. 使用反射调用一个类的静态方法。

import java.lang.reflect.Method;

class MyMath {
    public static <T extends Number> T add(T a, T b) {
        if (a instanceof Integer) {
            return (T) Integer.valueOf(a.intValue() + b.intValue());
        } else if (a instanceof Double) {
            return (T) Double.valueOf(a.doubleValue() + b.doubleValue());
        } else {
            throw new IllegalArgumentException("Unsupported number type");
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Class cls = MyMath.class;
        try {
            // 由于泛型擦除,需要指定方法的确切参数类型
            Method method = cls.getDeclaredMethod("add", Number.class, Number.class);
            // 静态方法必须指定null
            Object invoke = method.invoke(null, 1, 2);
            System.out.println(invoke); // 3
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

6. 使用反射获取某个类的所有公有成员变量,并打印出每个成员变量的名称和类型。

import java.lang.reflect.Field;

class People {
    public int id;
    public int age;
    private String name;
}

public class Main {
    public static void main(String[] args) {
        Class<People> cls = People.class;
        Field[] PeopleFields = cls.getFields();
        for (Field peopleField : PeopleFields) {
            System.out.println(peopleField.getName() + " => " + peopleField.getType());
        }
        
        //id => int
        //age => int

    }
}

7. 使用反射获取某个类的所有成员方法,并打印出每个方法变量的名称和返回值类型。

import java.lang.reflect.Method;

class People {
    public void printHello() {
        System.out.println("Hello, Java");
    }

    public String getHello(String Hello) {
        return Hello + ", Java";
    }

    private int getMoney() {
        return 0;
    }

}

public class Main {
    public static void main(String[] args) {
        Class<People> cls = People.class;
        Method[] methods = cls.getDeclaredMethods();
        for (Method method : methods) {
            System.out.println(method.getName() + " => " + method.getReturnType());
        }

        //printHello => void
        //getHello => class java.lang.String
        //getMoney => int
    }
}


8. 使用反射调用一个对象的公有方法,并传递参数。

import java.lang.reflect.Method;

class MyMath {
    public int add(int a, int b) {
        return a + b;
    }
}

public class Main {
    public static void main(String[] args) {
        Class<MyMath> myMathClass = MyMath.class;
        try {
            Method addMethod = myMathClass.getMethod("add", int.class, int.class);
            MyMath myMath = new MyMath(); // 创建 MyMath 类的实例
            Object invoke = addMethod.invoke(myMath, 1, 2); // 传递 MyMath 类的实例
            System.out.println(invoke); // 3
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

9. 使用Java的动态代理实现一个简单的日志记录功能。

题目描述:创建一个接口Operation,包含一个方法execute(String message)。实现该接口的类OperationImpl。使用动态代理为OperationImpl添加日志记录功能,即在执行execute方法前后打印日志。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface Operation {
    void execute(String message);
}

class OperationImpl implements Operation {
    @Override
    public void execute(String message) {
        System.out.println("执行操作:" + message);
    }
}

class LoggingHandler implements InvocationHandler {
    private Object target;

    public LoggingHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("开始执行方法:" + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("方法执行结束:" + method.getName());
        return result;
    }
}

public class Main {
    public static void main(String[] args) {
        Operation operation = new OperationImpl();
        Operation proxyInstance = (Operation) Proxy.newProxyInstance(
                Operation.class.getClassLoader(),   // operation.getClass().getInterfaces(),
                new Class[]{Operation.class},       // operation.getClass().getInterfaces(),
                new LoggingHandler(operation)
        );

        proxyInstance.execute("Hello, World!");

        //开始执行方法:execute
        //执行操作:Hello, World!
        //方法执行结束:execute
    }
}

10. 使用Java的动态代理实现一个简单的权限校验功能。

题目描述:创建一个接口UserService,包含一个方法login(String username, String password)。实现该接口的类UserServiceImpl。使用动态代理为UserServiceImpl添加权限校验功能,即只有当用户名为"admin"且密码为"123456"时,才允许执行login方法。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface UserService {
    boolean login(String username, String password);
}

class UserServiceImpl implements UserService {
    @Override
    public boolean login(String username, String password) {
        System.out.println(username + " 登录成功!");
        return true;
    }
}

class AuthHandler implements InvocationHandler {
    private Object target;

    public AuthHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("login")) {
            String username = (String) args[0];
            String password = (String) args[1];
            if ("admin".equals(username) && "123456".equals(password)) {
                return method.invoke(target, args);
            } else {
                System.out.println("权限校验失败,用户名或密码错误!");
                return false;
            }
        }
        return method.invoke(target, args);
    }
}

public class Main {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        UserService proxyInstance = (UserService) Proxy.newProxyInstance(
                UserService.class.getClassLoader(),
                new Class[]{UserService.class},
                new AuthHandler(userService)
        );

        proxyInstance.login("admin", "123456"); // 权限校验通过,执行login方法
        //输出: admin 登录成功!
        
        
        proxyInstance.login("user", "123456"); // 权限校验失败
        //输出: 权限校验失败,用户名或密码错误!
        
    }
}


网站公告

今日签到

点亮在社区的每一天
去签到