19.反射

本文深入讲解Java反射机制的基础概念及应用场景,包括如何获取类的信息、构造方法和成员方法等内容,并提供了一个通过配置文件动态执行类和方法的示例。

反射

1.反射定义

  • 反射是框架的灵魂,

  • 什么是框架?

    • 我们之前写一些功能都是通过最底层的代码去实现这些功能,为了提高开发效率,公司里面都是需要用到框架去开发,所谓java框架就是通过java语言给你封装了一个半成品,那你基于这个框架平台去开发能够节省很多的代码提高工作的效率。基本上所有的开发语言都会有不同的框架,比如学习前端我们最底层javascript,基于javascript的框架vue、jquery、bootstrap等等,java的框架比如现在流行的ssm、springboot、springcloud等等。
  • java的框架基本上都是通过反射实现的

  • 反射:将类的各个组成部分封装为其他对象.这就是反射机制.

    • 在运行状态中,对于任意一个类,都能够获取到这个类的所有属性和方法,对于任意一个对象都能够调用它的任意一个方法和属性(包括私有的方法和属性);这种动态获取信息以及动态调用对象的方法的功能就称为java的反射机制。通俗点讲,我们可以通过java反射技术,所有的类对我们来讲都是透明的,我们想要获取任意类的属性和方法都可以!
    • 要想实现反射机制,就必须先获取该类的字节码文件对象(.class),通过字节码对象,就能够通过字节码的类中的方法获取到我们想要的信息(方法、属性、类名、父类名、所有实现的接口、构造方法等等);
    • 每一个类对应着一个字节码文件也就对应着一个class类的对象,也就是字节码文件对象。
  • java代码在计算机中经历的三个阶段,分别是:

    1. 源代码阶段(编写代码时期)
    2. class类对象阶段(保存运行的时候首先类加载器加载编译源代码后加载到内存)
    3. runtime运行时阶段(运行到需要创建对象的代码的时候才会在内存开辟创建对象空间)

    如图

在这里插入图片描述

  • 反射的好处:
    1. 我们可以通过反射封装自己所需的框架
    2. 可以在程序运行的过程中操作这个对象(操作属性、方法等)
    3. 可以解耦合(不要让代码堆积在一块),提高程序的扩展性。

2.获取class对象(字节码对象)

要想获取到Person这个类里面所有的属性、方法、构造方法等首先要获取它的字节码对象

  • 获取class有三种方式

    • 方式1:class.forName(“全类名”);

      Class cla1 = Class.forName("com.wu.entity.Person");
      System.out.println(cla1);
      

      多用于配置文件,将类名定义在配置文件中,读取文件加载类

    • 方式2:类名.class

      Class cla2 = Person.class;
      System.out.println(cla2);
      

      多用于对象的传参

      比如:Logger logger = Logger.getLogger(Person.class)

    • 方式3:对象.getClass

      Person p = new Person();
      Class cla3 = p.getClass();
      System.out.println(cla3);
      

      多用于对象的获取字节码的方式

  • 案例

    • Person类

      public class Person {
      	private String name;
      	private int age;
      	public String a;
      	public String b;
      }
      
    • 测试

      public class Test {
      	public static void main(String[] args) throws ClassNotFoundException {
      		//要想获取到Person这个类里面所有的属性、方法、构造方法等首先要获取它的字节码对象
      		//方式1:class.forName("全类名");
      		Class cla1 = Class.forName("com.wu.entity.Person");
      		System.out.println(cla1);
      		//方式2:Person.class
      		Class cla2 = Person.class;
      		System.out.println(cla2);
      		//方式3:对象.getClass
      		Person p = new Person();
      		Class cla3 = p.getClass();
      		System.out.println(cla3);
      		
      		//获取到的字节码对象(class对象)是同一个对象吗?
      		//结论:是,因为同一个字节码文件(.class)在一次程序运行过程中,只会被加载一次,
      		//不论通过哪一种方式获取的class对象都是同一个对象
      		System.out.println(cla1==cla2);
      		System.out.println(cla2==cla3);
      	}
      }
      
    • 结论

      字节码class对象只有一个!
      因为同一个字节码文件(.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的class对象都是同一个对象
      
    • 获取到了class字节码对象后,我们就可以通过class字节码对象获取到类的属性、方法、构造方法、父类等等。

3.通过Class对象获取类的信息

  • 测试

    import java.lang.reflect.Field;
    
    public class Test {
    	public static void main(String[] args) throws Exception {
    		Class<Person> personClass = Person.class;
    		//通过person类型的字节码对象调用getFields()获取到person类的所有public修饰的成员变量
    		Field[] fields = personClass.getFields();
    		for (Field field : fields) {
    			System.out.println(field);
    		}
    		System.out.println("===================");
    		//getField("a")获取Person类public修饰的属性a
    		Field a = personClass.getField("a");
    		System.out.println(a);
    		
    		//获取到这些属性有啥用?当然是获取或者修改的值。
    		Person per = new Person();
    		//获取到Person类属性a里面的值
    		Object aValue = a.get(per);
    		System.out.println(aValue);
    		
    		//修改Person类属性a里面的值
    		a.set(per, "张三");
    		System.out.println(per);
    		
    		System.out.println("==========================");
    		Field[] declareadFields = personClass.getDeclaredFields();
    		for (Field field : declareadFields) {
    			System.out.println(field);
    		}
    		
    		Field fieldName = personClass.getDeclaredField("name");
    		//报错:java.lang.IllegalAccessException是因此不能访问私有的属性
    		//解决办法,通过暴力反射
    		//访问之前先忽略访问修饰符的安全检查,就叫暴力反射
    		fieldName.setAccessible(true);
    		String name = (String)fieldName.get(per);
    		System.out.println("姓名是"+name);
    		
    		fieldName.set(per, "李四");
    		System.out.println(per);
    		
    	}
    }
    
  • 结果

    public java.lang.String com.wu2.entity.Person.a
    public java.lang.String com.wu2.entity.Person.b
    public java.lang.String com.wu2.entity.Person.c
    public java.lang.String com.wu2.entity.Person.d
    ===================
    public java.lang.String com.wu2.entity.Person.a
    吴
    Person [name=null, age=0, a=张三, b=null, c=null, d=null]
    ==========================
    private java.lang.String com.wu2.entity.Person.name
    private int com.wu2.entity.Person.age
    public java.lang.String com.wu2.entity.Person.a
    public java.lang.String com.wu2.entity.Person.b
    public java.lang.String com.wu2.entity.Person.c
    public java.lang.String com.wu2.entity.Person.d
    姓名是null
    Person [name=李四, age=0, a=张三, b=null, c=null, d=null]
    

4.通过class类对象获取构造方法

  • 获取构造方法的作用是创建对象

    public class Test {
    	public static void main(String[] args) throws Exception {
    		//获取Class对象
    		Class personClass = Person.class;
    		//getConstructor(String.class,int.class)获取到参数为String,int的那个构造方法
    		Constructor constructor = personClass.getConstructor(String.class,int.class);
    		System.out.println(constructor);
    		
    		//获取构造方法作用:创建对象!
    		Person person = (Person)constructor.newInstance("张三",18);
    		System.out.println(person);
    		
    		//获取空对象
    		Constructor constructor2 = personClass.getConstructor();
    		Person person2 = (Person)constructor2.newInstance();
    		System.out.println(person2);
    		
    		//获取空对象,可以简化如下
    		Person person3 = (Person)personClass.newInstance();
    		System.out.println(person3);
    	}
    }
    

5.通过class类对象获取成员方法

  • 获取成员方法作用是执行方法

    import java.lang.reflect.Method;
    
    public class Test {
    	public static void main(String[] args) throws Exception {
    		//获取Class对象
    		Class<Person> personClass = Person.class;
    		//获取public修饰的方法
    		Method eat_method = personClass.getMethod("eat");
    		Person person = new Person();
    		//通过invoke()方法来执行获取到的方法
    		eat_method.invoke(person);
    		
    		Method eat_method2 = personClass.getMethod("eat",String.class);
    		eat_method2.invoke(person, "牛排");
    		
    		//getDeclaredMethod获取所有定义的方法
    		Method eat_method3 = personClass.getDeclaredMethod("sleep");
    		eat_method3.setAccessible(true);
    		eat_method3.invoke(person);
            
    		System.out.println("======================");
    		//获取Peraon以及父类的所有方法
    		Method[] methods = personClass.getMethods();
    		for (Method method : methods) {
    //			System.out.println(method);
    			String methodName = method.getName();
    			System.out.println("方法名"+methodName);
    		}
            
    		System.out.println("======================");
    		//获取所有定义的方法
    		Method[] methods2 = personClass.getDeclaredMethods();
    		for (Method method : methods2) {
    //			System.out.println(method);
    			String methodName = method.getName();
    			System.out.println("方法名"+methodName);
    		}
    		
    		//获取类名
    		String className = personClass.getName();
    		System.out.println(className);
    	}
    }
    

上机案例:

写一个"框架’,我们通过properties配置需要执行的类和方法,然后我们获取到配置信息中配置的信息通过反射机制执行。

  • 实体类

    public class Person {
    	private String name;
    	private int age;
    	public String a = "吴";
    	public String b;
    	public String c;
    	public String d;
    	
    	public Person() {
    		super();
    	}
    
    	public Person(String name, int age) {
    		super();
    		this.name = name;
    		this.age = age;
    	}
    
    	@Override
    	public String toString() {
    		return "Person [name=" + name + ", age=" + age + ", a=" + a + ", b=" + b + ", c=" + c + ", d=" + d + "]";
    	}
    	
    	public void eat() {
    		System.out.println("eat--");
    	}
    	
    	public void eat(String food) {
    		System.out.println("eat--"+food);
    	}
    	
    	private void sleep() {
    		System.out.println("睡觉zzzz");
    	}
    }
    
  • 创建pro.properties文件

    className=com.wu.entity.Person
    methodName=eat
    
  • 测试

    public class Test {
    	public static void main(String[] args) {
    		Properties pro = new Properties();
    		try {
    			//加载pro.properties配置文件
    			InputStream is = Test.class.getClassLoader().getResourceAsStream("pro.properties");
    			pro.load(is);
    			
    			//1.根据键获取到pro.properties配置的值
    			String className = pro.getProperty("className");
    			String methodName = pro.getProperty("methodName");
    			//2.创建class字节码对象
    			Class<?> cls = Class.forName(className);
    			//创建实例对象
    			Object obj = cls.newInstance();
    			//得到对象方法
    			Method method = cls.getMethod(methodName,String.class);
    			method.invoke(obj, "牛排");
    		} catch (Exception e) {
    			e.printStackTrace();
    		} 
    	}
    }
    
<think>嗯,用户想了解Java Spring框架中反射的使用方法和常见问题。首先,我需要回忆一下Spring框架中哪些地方用到了反射机制。比如,依赖注入、动态代理、AOP这些功能都可能依赖反射。用户提供的引用里提到Spring通过反射自动扫描和装配Bean,还有Field和Method的使用,这些应该是关键点。 接下来,我应该分步骤解释反射在Spring中的应用。比如,Bean的实例化过程,Spring如何通过反射调用构造方法创建对象。然后,依赖注入时如何通过反射设置私有字段或调用setter方法。动态代理部分,可能需要提到JDK动态代理利用反射生成代理。AOP的切面逻辑也是通过反射来调用目标方法的。 关于使用方法,需要结合具体的例子,比如XML配置和注解配置下Spring如何处理。比如引用3中的XML配置,Spring会解析XML文件,通过反射创建Bean实例。注解方式如@Autowired,Spring会查找对应的Bean并注入。 常见问题部分,用户可能遇到性能问题,因为反射比直接调用慢。权限问题,比如访问私有成员时需要setAccessible(true),但可能存在安全限制。还有兼容性问题,比如反射Java版本升级后的变化,或者混淆代码导致的反射失败。此外,循环依赖的问题,Spring虽然有解决机制,但在某些情况下仍可能导致异常。 需要确保提到的每个点都有引用支持,比如引用2提到反射的通用性和框架中的应用,引用4提到Field和Method的使用。同时,注意用户提供的引用中有关于XML配置和依赖注入的内容,需要整合进去。 可能还需要考虑用户的实际场景,比如开发者在使用Spring时遇到反射相关的问题,如何排查和解决。比如性能优化部分,可以建议减少反射调用,使用缓存。安全方面,提醒正确处理Accessible权限。 最后,生成的相关问题要围绕Spring和反射的深入应用,比如原理、优化、安全问题、异常处理等,帮助用户进一步学习。</think>### Java Spring框架中反射的使用方法与常见问题 #### 一、反射在Spring框架中的核心作用 1. **Bean的实例化与依赖注入** Spring通过反射动态加载并调用构造方法创建对象。例如,XML配置中定义的`<bean class="com.example.UserService">`会被解析为`Class.forName("com.example.UserService").newInstance()`[^3]。依赖注入时,Spring通过反射访问私有字段或调用setter方法,例如`Field.set(obj, value)`或`Method.invoke()`[^4]。 2. **动态代理与AOP** Spring AOP使用JDK动态代理或CGLIB生成代理,其中JDK代理基于`Proxy.newProxyInstance()`和反射机制调用目标方法[^2]。 3. **注解解析与组件扫描** 如`@Autowired`、`@Component`等注解的处理依赖反射。Spring通过`Class.getAnnotations()`扫描路径,识别并注册Bean[^3]。 #### 二、具体使用方法 1. **基于XML配置的反射调用** ```xml <!-- 配置Bean --> <bean id="userDao" class="com.example.UserDaoImpl"/> <bean id="userService" class="com.example.UserService"> <property name="userDao" ref="userDao"/> </bean> ``` Spring解析XML时,通过反射调用`UserService`的`setUserDao`方法完成依赖注入[^3]。 2. **注解驱动的反射应用** ```java @Service public class OrderService { @Autowired private PaymentService paymentService; // 反射注入私有字段 } ``` Spring容器启动时,通过反射遍历字段并注入实例。 3. **方法级反射调用示例** ```java Method method = MyService.class.getDeclaredMethod("calculate", int.class, int.class); Object result = method.invoke(beanInstance, 10, 20); // 调用带参数的方法 ``` 这种机制常用于框架内部的事件处理或动态路由。 #### 三、常见问题与解决方案 1. **性能问题** - **现象**:频繁反射调用导致启动速度慢或运行时延迟。 - **优化**: - 使用缓存(如Spring的`ReflectionUtils`) - 优先采用CGLIB字节码增强代替JDK动态代理 2. **权限限制** - **案例**:注入私有字段时报`IllegalAccessException` - **解决**: ```java Field field = target.getClass().getDeclaredField("secretKey"); field.setAccessible(true); // 突破私有权限限制 field.set(target, value); ``` 3. **兼容性问题** - **场景**:Java模块化系统(JPMS)限制反射访问 - **处理**:在`module-info.java`中添加`opens`语句开放包访问权限。 4. **循环依赖陷阱** - **表现**:Bean A依赖Bean B,Bean B又依赖Bean A,导致`BeanCurrentlyInCreationException` - **方案**: - 使用`@Lazy`延迟加载 - 重构代码解耦依赖 #### 四、最佳实践 1. **优先使用Spring工具** 如`BeanUtils.copyProperties()`替代手动反射操作: ```java BeanUtils.copyProperties(source, target); // 安全高效的属性拷贝 ``` 2. **谨慎使用`MethodHandle`** 对于高频调用的方法,可考虑Java 7+的`MethodHandle`提升性能: ```java MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodHandle handle = lookup.findVirtual(MyClass.class, "method", MethodType.methodType(void.class)); handle.invokeExact(instance); ``` 3. **结合泛型型检查** 反射获取泛型型信息: ```java Type genericType = field.getGenericType(); // 获取带泛型的型 if (genericType instanceof ParameterizedType) { Type actualType = ((ParameterizedType)genericType).getActualTypeArguments()[0]; } ``` ####
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值