1. 反射是什么
反射是 Java 中的一个强大特性,允许程序在运行时检查和操作类、方法、字段等信息。它是一种动态机制,可以在运行时解析类,获取类的成员变量、方法、构造器等,并对它们进行操作。
2. 为什么使用反射
-
灵活性:反射可以在运行时动态地操作类,而不需要在编译时知道类的具体信息。
-
可扩展性:通过反射,可以编写通用的代码,支持多种不同的类和对象。
-
框架和工具:许多框架和工具(如 Spring、Hibernate 等)都广泛使用反射来实现功能,如依赖注入、对象关系映射等。
3. 反射的基本用法
3.1 获取 Class 对象
在 Java 中,每个类都有一个对应的 Class
对象,它是反射的入口。可以通过以下方式获取 Class
对象:
-
使用类的
.class
属性:Class clazz = Person.class;
-
使用对象的
.getClass()
方法:Class clazz = personInstance.getClass();
-
使用类加载器:
Class clazz = Class.forName("com.example.Person");
3.2 获取类的信息
一旦获取了 Class
对象,就可以通过它获取类的各种信息,如:
-
获取类名:
clazz.getName();
-
获取构造器:
clazz.getConstructors();
或clazz.getConstructor(Class<?>... parameterTypes);
-
获取方法:
clazz.getMethods();
或clazz.getMethod(String name, Class<?>... parameterTypes);
-
获取字段:
clazz.getFields();
或clazz.getField(String name);
3.3 操作对象
通过反射,可以动态地创建对象、调用方法、访问字段等。
-
动态创建对象:
Person person = (Person) clazz.newInstance(); // 已过时,推荐使用 Constructor
或者:
Constructor constructor = clazz.getConstructor(String.class, int.class); Person person = (Person) constructor.newInstance("John", 25);
-
动态调用方法:
Method method = person.getClass().getMethod("sayHello", String.class); method.invoke(person, "World");
-
动态访问字段:
Field field = person.getClass().getField("name"); field.set(person, "Alice");
4. 反射的实际应用
4.1 动态加载和运行代码
例如,在一个插件系统中,可以通过反射动态加载并运行插件的代码:
Class<?> pluginClass = Class.forName("com.example.Plugin");
Object plugin = pluginClass.newInstance();
Method executeMethod = pluginClass.getMethod("execute");
executeMethod.invoke(plugin);
4.2 Java Bean 操作
通过反射可以动态地操作 Java Bean 的属性,如:
Person person = new Person("John", 30);
Field ageField = person.getClass().getField("age");
ageField.set(person, 35);
4.3 测试框架
许多测试框架(如 JUnit)都使用反射来运行测试方法:
TestRunner runner = new TestRunner(MyTest.class);
runner.run();
框架通过反射找到测试类中的测试方法,并调用它们。
4.4 ORM 框架
ORM 框架(如 Hibernate)使用反射将数据库表映射到 Java 类:
Session session = sessionFactory.openSession();
session.save(person);
Hibernate 通过反射操作 Person
类的字段,将其数据存储到数据库中。
5. 反射的优缺点
优点
-
灵活性:可以在运行时动态地操作类。
-
可扩展性:支持编写通用代码,适用于多种类和对象。
-
框架和工具:许多现代框架和工具都依赖反射来实现核心功能。
缺点
-
性能开销:反射操作的性能比直接调用稍差,因为它需要解析类信息。
-
安全性问题:可能破坏封装性,访问或修改私有成员。
-
复杂性:使用反射会使代码变得复杂,难以阅读和维护。
6. 示例代码
以下是个简单的反射示例,展示如何动态创建对象并调用方法:
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class ReflectionExample {
public static void main(String[] args) {
try {
// 获取类
Class<?> clazz = Class.forName("com.example.Person");
// 创建对象
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Person person = (Person) constructor.newInstance("John", 30);
// 调用方法
Method sayHelloMethod = person.getClass().getMethod("sayHello", String.class);
sayHelloMethod.invoke(person, "World");
// 访问字段
Field ageField = person.getClass().getField("age");
System.out.println("Age: " + ageField.get(person));
} catch (Exception e) {
e.printStackTrace();
}
}
}
类Person
如下:
public class Person {
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void sayHello(String target) {
System.out.println("Hello, " + target + "! My name is " + this.name);
}
}
在黑马Java下部中,反射通常的俩中作用:
6.1:保存任意对象数据
6.2:创建对象并运行方法
7.Skyakeout 中的应用
/**
* 功能字段自动填充切面
*/
@Component
@Aspect
@Slf4j
public class AutoFillAspect {
/**
* 切入点
*/
@Pointcut("execution(public * com.sky.mapper.*.*(..))&&@annotation(com.sky.annotation.AutoFill)")
public void autoFillPointCut(){}
/**
* 前置通知,公共字段自动填充
*/
@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint){
//通过 JoinPoint 获取方法参数和上下文信息。
log.info("开始公共字段自动填充...");
//获取当前被拦截的方法上的数据库操作类型
MethodSignature signature = (MethodSignature) joinPoint.getSignature();//方法签名对象
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获得方法上的注解对象
OperationType operationType = autoFill.value();//获得注解中的数据库操作类型
//获取当前被拦截的方法上的——实体对象
Object[] args = joinPoint.getArgs();//获得方法的参数
if(args.length == 0||args == null){
return;
}
Object entity = args[0];//获得实体对象
//准备赋值的数据
LocalDateTime now = LocalDateTime.now();
Long currentId = BaseContext.getCurrentId();
//根据不同的操作类型,通过反射赋予不同的值
if(operationType == OperationType.INSERT){
//为3个公共字段赋值
try {
//使用反射动态调用实体对象的 setter 方法。
Method setCreateTime = entity.getClass().getDeclaredMethod("setCreateTime",LocalDateTime.class);
Method setCreateUser = entity.getClass().getDeclaredMethod("setCreateUser",Long.class);
Method setUpdateTime = entity.getClass().getDeclaredMethod("setUpdateTime",LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod("setUpdateUser", Long.class);
//通过反射设置值
//invoke 是 Java 反射机制中的一个核心方法,用于动态调用对象的方法。
//invoke 方法允许你在运行时调用一个对象的任意方法
setCreateTime.invoke(entity,now);
setCreateUser.invoke(entity,currentId);
setUpdateTime.invoke(entity,now);
setUpdateUser.invoke(entity,currentId);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
if(operationType == OperationType.UPDATE){
//为2个公共字段赋值
try {
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME,LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
//通过反射设置值
setUpdateTime.invoke(entity,now);
setUpdateUser.invoke(entity,currentId);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}