目录
- 一、什么是反射
- 二、反射的核心接口和类
- 三、测试代码 Bean 类和目录结构
- Person 类
- 代码目录结构
- 四、反射的用法
- 1. 获取 Class 对象
- 2. 获取构造方法 Constructor 并使用
- 3. 获取成员变量 Field 并使用
- 4. 获取成员方法 Method 并使用
- 五、动态代理与反射
- 1. 动态代理三要素
- (1)代理接口
- (2)代理处理器
- (3)代理对象的创建
- 2. 动态代理实现步骤
- 3. 案例:获取函数的执行时间
- 六、练习
- 1. 使用反射获取String类的所有公有方法,并把方法名打印出来。
- 2. 使用反射创建一个对象,并调用其无参构造方法。
- 3. 使用反射修改一个对象的私有字段值。
- 4. 使用反射获取一个ArrayList的所有父类(包括间接父类)。
- 5. 使用反射调用一个类的静态方法。
- 6. 使用反射获取某个类的所有公有成员变量,并打印出每个成员变量的名称和类型。
- 7. 使用反射获取某个类的所有成员方法,并打印出每个方法变量的名称和返回值类型。
- 8. 使用反射调用一个对象的公有方法,并传递参数。
- 9. 使用Java的动态代理实现一个简单的日志记录功能。
- 10. 使用Java的动态代理实现一个简单的权限校验功能。
一、什么是反射
解释一:
Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个属性和方法。这种动态获取信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。
解释二:
Java 反射是指在运行时动态检查和操作类的能力。通过反射,(对于一个对象)程序可以在运行时获取关于类、方法、属性、构造函数等的详细信息,并且可以动态地创建对象、调用方法以及访问和修改字段。反射提供了一种灵活的机制,使得程序可以在编译时不知道确切类型的情况下操作这些类型。
二、反射的核心接口和类
Java 反射主要涉及以下几个核心类和接口,它们位于包 java.lang.reflect
中:
Class
:每个类和接口在 JVM 中都表示为一个 Class 对象。通过 Class 对象,程序可以获取类的全限定名、实现的接口、父类、构造函数、方法、字段等信息。
Constructor
:表示类的构造函数。通过 Constructor 对象,程序可以创建类的新实例。
Field
:表示类的属性。通过 Field 对象,程序可以获取或修改属性的值。
Method
:表示类的方法。通过 Method 对象,程序可以调用方法。
三、测试代码 Bean 类和目录结构
Person 类
该Person
类有name
和age
属性,无参构造方法
、有参构造方法
、getter
、setter
、toString
以及自定义的sayHello
和sayGoodbye
方法。
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"); // 权限校验失败
//输出: 权限校验失败,用户名或密码错误!
}
}