Java反射是指在运行时动态地获取类的信息并操作类的成员(字段、方法、构造函数等)。它允许程序在运行时检查和操作类、对象、方法和属性,而无需事先在代码中明确地指定它们的类型。
Java反射提供了一组类(如Class、Method、Field等),用于获取并操作类的成员。以下是Java反射的主要概念和作用:
Class类:在Java反射中,Class类是所有类的元数据。它包含了类的属性、方法和构造函数的信息。通过Class类,可以获取类的名称、修饰符、父类、接口等信息。
获取Class对象:可以使用多种方式获取Class对象,包括使用类的.class语法、调用对象的getClass()方法、使用Class.forName()方法等。
实例化对象:使用反射可以实例化对象,即通过Class对象创建类的实例。可以调用Class对象的newInstance()方法或Constructor类的newInstance()方法来实现。
获取和操作字段:通过反射可以获取类的字段信息,包括字段的名称、类型、修饰符等。还可以设置和获取字段的值,即使字段是私有的也可以通过反射访问和修改它们。
调用方法:通过反射可以调用类的方法。可以获取方法的信息,包括方法的名称、参数类型、返回类型、修饰符等,并通过Method类的invoke()方法来动态调用方法。
构造函数操作:反射允许通过Class对象获取类的构造函数信息,并实例化对象。可以获取构造函数的参数类型、修饰符等,并通过Constructor类的newInstance()方法来创建对象。
动态代理:Java反射还可以实现动态代理,即在运行时生成代理对象来拦截对目标对象的访问,实现一些特定的处理逻辑。
Java反射的主要作用是在运行时动态地操作类和对象,使程序能够更灵活地处理类的成员。它广泛应用于很多框架和库中,例如ORM(对象关系映射)框架、依赖注入容器、单元测试等。反射也用于开发工具,如Java反编译器、代码生成器等。然而,由于反射涉及到动态生成和调用代码,使用不当可能会导致性能下降和安全隐患,因此在使用反射时需要谨慎权衡。
动态性和灵活性:Java反射提供了在运行时动态地获取和操作类的信息的能力。通过反射,可以在程序运行时探查和操作类的字段、方法和构造函数等元素,而不需要在编译时确定具体的类型。这使得程序可以根据运行时条件进行灵活的逻辑和行为。
框架和库的扩展性:许多Java框架和库使用反射来实现插件化、扩展性和自定义功能。通过反射,可以动态地加载和使用扩展模块或插件,从而使应用程序具备更大的灵活性和可扩展性。
库和框架的开发:当你需要开发通用的库、框架或工具时,反射是一个强大的工具。通过反射,你可以设计更通用、灵活的代码,允许用户在运行时动态地配置和定制库的行为。
测试和调试:反射在测试和调试过程中也发挥重要作用。通过反射,可以获取和修改私有字段的值,调用私有方法,以及检查和设置对象的状态,这对于测试和调试复杂的代码逻辑非常有用。
框架和类库的学习与分析:反射也是学习和分析第三方框架、类库或未知代码的有力工具。通过反射,你可以深入了解框架或类库的内部结构,探索其提供的功能和接口,并进行扩展和定制。
尽管反射功能强大,但需要注意的是,过度使用反射可能导致性能下降、代码可读性降低和安全风险增加。因此,在使用反射时应权衡利弊,并遵循最佳实践和安全原则。
类的加载和运行机制是Java虚拟机在执行Java程序时的关键步骤。回顾类的加载和运行机制,可以涵盖以下内容:
类加载器(Class Loader):
类加载的过程:
双亲委派模型(Delegation Model):
缓存机制:
类的唯一性:
了解类的加载和运行机制对于理解Java程序的执行过程至关重要。它不仅是Java虚拟机的核心功能,还对Java的安全性、类的隔离性和动态性提供了基础支持。
Class类和java.lang.reflect包是Java中用于支持反射机制的关键类和包。它们的作用和重要性如下:
Class类的作用和重要性:
newInstance()
、getMethod()
、getField()
等。java.lang.reflect包的作用和重要性:
Class类和java.lang.reflect包为Java反射机制的实现提供了必要的支持,它们使得开发者可以在运行时动态地获取和操作类的信息,实现灵活性和扩展性。通过使用这些类和包,开发者能够在设计通用的库、框架和工具时实现更高的灵活性和可扩展性。同时,它们也为测试、调试和分析第三方代码提供了强大的工具。
反射机制在Java中具有许多优点,但也存在一些缺点。下面是反射机制的优缺点:
优点:
动态性和灵活性:反射机制允许在运行时动态地获取和操作类的信息,可以实现动态创建对象、调用方法和访问字段等操作,从而使程序具备更大的灵活性和可扩展性。
通用性和可重用性:通过反射,可以编写通用的代码,可以处理不同类型的对象和类,而无需提前知道具体的类型。这样可以提高代码的可重用性和通用性。
框架和库的扩展性:许多框架和库使用反射来实现插件化、扩展性和自定义功能。通过反射,可以在运行时动态地加载和使用扩展模块或插件,从而使应用程序具备更大的灵活性和可扩展性。
深入了解和分析类库:反射提供了一种方式来深入了解和分析第三方类库的内部结构,包括类的字段、方法和构造函数等。这有助于学习和使用类库,并能够在需要时进行扩展和定制。
缺点:
性能影响:相对于直接调用,使用反射机制会导致性能上的损失。反射调用比直接调用方法更慢,因为它涉及到动态的方法查找和调用过程,而且还需要进行访问权限的检查。
安全性问题:反射机制可以绕过访问控制,可以访问和修改私有的字段和方法,这可能会破坏封装性和引入潜在的安全风险。使用反射时需要谨慎处理访问权限的问题,以避免潜在的安全漏洞。
可读性降低:由于反射可以动态地访问和操作类的成员,代码的可读性可能会降低。使用反射时,某些类型的错误可能只在运行时才会暴露出来,而不会在编译时被捕获,增加了调试和维护的复杂性。
综上所述,反射机制在提供灵活性、通用性和可扩展性方面具有重要优势。然而,开发者应权衡反射的优缺点,并在使用反射时注意性能影响、安全性问题和可读性降低的风险。反射应谨慎使用,遵循最佳实践,以确保代码的可靠性和安全性。
通过类名.class属性,这种方法在编译时加载类,不会初始化类。
public class A {
static {
System.out.println("A类初始化");
}
}
public class test {
@Test
// 类名.class 方式获取class
public void test01() throws ClassNotFoundException {
System.out.println("类.class");
Class<A> aClass = A.class;
}
}
通过对象的getClass()方法,这种方法需要先创建对象,然后获取对象的Class实例。
public class test {
@Test
// 对象getClass方法获取class
public void test02() throws ClassNotFoundException {
System.out.println("对象getClass");
Class<?> aClass1 = new A().getClass();
}
}
通过Class的静态方法forName(全类名),这种方法在运行时加载类,会初始化类。
public class test {
@Test
// 通过Class的静态方法forName(全类名)获取class
public void test03() throws ClassNotFoundException {
System.out.println("Class.forName");
Class<?> aClass1 = Class.forName("com.hzy.reflect.A");
}
}
通过类加载器的loadClass(全类名)方法,这种方法在运行时加载类,不会初始化类。
public class test {
@Test
// 通过类加载器的loadClass(全类名)方法
public void test04() throws ClassNotFoundException {
System.out.println("类加载器");
ClassLoader classLoader = A.class.getClassLoader();
Class<?> aClass = classLoader.loadClass("com.hzy.reflect.A");
}
}
public class A {
public String name;
private int age;
static {
System.out.println("A类初始化");
}
public A() {
}
public A(String name) {
this.name = name;
}
private A(int age) {
this.age= age;
}
}
获取A的class对象
Class<A> aClass = A.class;
直接调用class对象的newInstance(),调用了空构造函数实例化对象
A a = aClass.newInstance();
调用class对象的getConstructor(),返回空构造器对象
Constructor<A> constructor = aClass.getConstructor();
A a = constructor.newInstance();
调用class对象的getConstructor(),返回带参构造器对象
Constructor<A> constructor = aClass.getConstructor(String.class);
A a = constructor1.newInstance("zs");
调用class对象的getDeclaredConstructor(),返回带参构造器对象
Constructor<A> declaredConstructor = aClass.getDeclaredConstructor(int.class);
// 反射访问一个类的私有成员,可以使用setAccessible(true)方法来覆盖Java语言的访问控制检查
declaredConstructor.setAccessible(true);
A a = declaredConstructor.newInstance(10);
getConstructor()
和getDeclaredConstructor()
都是Java反射API中的方法,它们都可以用来获取类的构造器。它们之间的区别在于getConstructor()
方法只返回类的public构造器,而getDeclaredConstructor()
方法则返回类的所有构造器,包括public、protected、default (package) access和private构造器。
因此,如果只需要获取类的public构造器,可以使用getConstructor()
方法。如果您需要获取类的所有构造器,不管它们的访问修饰符是什么,可以使用getDeclaredConstructor()
方法。
getConstructors()
:可以获取全部public修饰的构造函数
getDeclaredConstructors()
:可以获取全部的构造函数
public class A {
public String name;
private int age;
protected int protectedTest;
public int publicTest;
}
可以使用Class
类的getField()
和getDeclaredField()
方法来获取类的字段。这两个方法都接受一个字符串参数,表示要获取的字段的名称。
@Test
// 获取指定字段
public void test02() throws Exception {
Field name = aClass.getField("name");
Field age = aClass.getDeclaredField("age");
System.out.println(name);
System.out.println(age);
}
也可以使用Class
类的getFields()
和getDeclaredFields()
方法来获取类的字段。这两个方法都获取一个字段集合。
// 获取public 字段
public void test01() {
Class<A> aClass = A.class;
Field[] fields = aClass.getFields();
System.out.println("public 字段");
for(var field : fields){
System.out.println(field);
}
}
// 全部字段
public void test02() {
Class<A> aClass = A.class;
Field[] declaredFields = aClass.getDeclaredFields();
System.out.println("所有字段");
for(var field : declaredFields){
System.out.println(field);
}
}
可以使用Field
类的get()
和set()
方法来分别获取和设置字段值。
get()
方法接受一个对象作为参数,表示要获取哪个对象的字段值。如果该字段是静态的,则可以传入null
作为参数。set()
方法接受两个参数,第一个参数表示要设置哪个对象的字段值,第二个参数表示要设置的值。如果该字段是静态的,则第一个参数可以传入null
。
// 获取字段值和操作字段值
public void test03() throws Exception {
Field name = aClass.getField("name");
Field staticTest = aClass.getDeclaredField("staticTest");
A a = new A();
name.set(a,"张三");
staticTest.set(null,12);
System.out.println(name.get(a));
System.out.println(staticTest.get(null));
}
可以使用Field
类的getModifiers()
和getType()
方法来分别获取字段的修饰符和类型。
getModifiers()
方法返回一个整数,表示字段的修饰符。可以使用java.lang.reflect.Modifier
类中的方法来解析这个整数,以获取字段的修饰符信息。例如,可以使用Modifier.isPublic()
方法来判断字段是否是public的。
getType()
方法返回一个Class
对象,表示字段的类型。
public void test01() throws Exception{
Field name = aClass.getField("name");
int modifiers = name.getModifiers();
Class<?> type = name.getType();
System.out.println(modifiers);
System.out.println(Modifier.isPublic(modifiers));
System.out.println(type);
}
查看Modifier
源码可以知道Modifier
类中定义了一些常量,表示不同的修饰符。例如,Modifier.PUBLIC
表示public修饰符,Modifier.PRIVATE
表示private修饰符。这些常量的值是不同的2的幂
操作私有字段需要覆盖访问控制权限
public void test04() throws Exception {
Field age = aClass.getDeclaredField("age");
A a = new A();
age.setAccessible(true);
System.out.println(age.get(a));
}
public class A {
public String printA(){
return "a";
}
public void printA(String a){
System.out.println("a:"+a);
}
private void printB(String b){
System.out.println("b:"+b);
}
public static void printB(){
System.out.println("bb");
}
}
可以使用反射来获取类的方法信息。可以使用Class
类的getMethod()
和getDeclaredMethod()
方法来获取类的方法,然后使用Method
类的getName()
、getReturnType()
、getParameterTypes()
和getModifiers()
方法来分别获取方法的名称、返回类型、参数类型和修饰符。
getMethod()
方法接受一个字符串参数和一个可变长度的Class
对象数组作为参数。字符串参数表示要获取的方法的名称,Class
对象数组表示要获取的方法的参数类型。getMethod()
方法只能获取类的public方法,包括其父类和接口中的public方法。如果要获取类的所有方法,不管它们的访问修饰符是什么,可以使用getDeclaredMethod()
方法。
public void test01() throws Exception{
Method printA1 = aClass.getMethod("printA", String.class);
System.out.println(printA1.getName());
System.out.println(printA1.getReturnType());
for (var ParameterType :printA1.getParameterTypes()){
System.out.println(ParameterType);
}
System.out.println(printA1.getModifiers());
}
使用Method
类的invoke()
方法来调用方法。
invoke()
方法接受一个对象和一个可变长度的对象数组作为参数。第一个参数表示要调用哪个对象的方法,第二个参数表示要传递给方法的参数。如果该方法是静态的,则第一个参数可以传入null
。
public void test02() throws Exception{
Method printA1 = aClass.getMethod("printA", String.class);
Method printA2 = aClass.getMethod("printA");
A a = new A();
printA1.invoke(a, "hahaha");
System.out.println(printA2.invoke(a));
}
// 调用静态方法
public void test03() throws Exception{
Method printB = aClass.getMethod("printB");
printB.invoke(null);
}
调用私有方法需要覆盖访问控制权限
public void test04() throws Exception{
Method printB = aClass.getDeclaredMethod("printB",String.class);
A a = new A();
printB.setAccessible(true);
printB.invoke(a,"bbdd");
}
不覆盖会报错
java.lang.IllegalAccessException: class com.hzy.reflect.MethodTest cannot access a member of class com.hzy.reflect.A with modifiers "private"
动态代理是一种设计模式,它允许在运行时动态地创建一个对象的代理,而无需手动编写代理类的代码。动态代理通常用于实现面向切面编程(AOP),可以在不修改原有代码的情况下,为对象添加额外的功能。
在Java中,可以使用java.lang.reflect.Proxy
类来创建动态代理。Proxy
类提供了一个静态方法newProxyInstance()
,用于创建动态代理对象。该方法接受三个参数:类加载器、接口数组和调用处理器。类加载器用于定义代理类,接口数组表示代理类需要实现的接口,调用处理器用于处理对代理对象的方法调用。
动态代理的应用场景很多,例如:
创建动态代理对象主要有两种方式:基于接口的动态代理和基于类的动态代理。
java.lang.reflect.Proxy
类和 java.lang.reflect.InvocationHandler
接口实现。基于java.lang.reflect.Proxy
类来创建动态代理对象创建原理是:假设创建的代理对象名为 $Proxy0,那么:
InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])
方法。在创建动态代理对象时,需要实现 InvocationHandler
接口,并重写 invoke
方法。在 invoke
方法中,可以对代理的方法进行拦截和增强。例如,可以在方法调用前后进行日志输出、权限检查、事务处理等操作。
public class test {
public static void main(String[] args) {
bookService bookService = new bookServiceImpl();
bookService proxy = ProxyUtil.getProxy(bookService);
proxy.save();
proxy.del("三国演义");
}
}
class ProxyUtil<T>{
public static <T> T getProxy(T obj){
return (T)Proxy.newProxyInstance(obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
StringBuffer sb = new StringBuffer();
if (args!=null){
for(var a: args){
sb.append(a).append("\t");
}
}
System.out.printf("Before method: 方法名:%s, 方法入参:%s\n", method.getName(), sb);
Object result = method.invoke(obj, args);
System.out.printf("After method: 方法出参:%s\n", result);
return result;
}
});
}
}
interface bookService{
void save();
boolean del(String name);
}
class bookServiceImpl implements bookService{
@Override
public void save() {
System.out.println("书籍保存成功");
}
@Override
public boolean del(String name) {
System.out.println(name+"成功删除");
return true;
}
}