exe文件怎么看源码_Java 反射机制你还不会?那怎么看 Spring 源码?

5c591a90529a0589943b16037739b2f5.gif d9ebacbf569bb95f9635482345467c29.png

作者 | ewenll

责编 | 夕颜

出品 | CSDN博客

36c291c45808d6943e67504812b8c90d.png Java代码在计算机中经历的阶段:三个阶段 18296503f4859b524f328242bd7e02f8.png Person.java经过javac编译,变为Person.class文件(字节码文件),字节码文件中,主要有类的属性、构造函数、方法,当然还有类的其他信息,这个阶段称为源码阶段,通过类加载器进入内存,在内存中生成一个Class对象,这个阶段为Class类对象阶段,一个类的Class对象中存储了类的全部信息,使用这个类对象的阶段称为Runtime运行时阶段 600047d5a86b086373af0c33534dbad5.png Java识别类和对象信息的两种方式
  • 一种是传统的RTTI(Run-Time Type Identification),它假定我们在编译时已经知道了所有的类型信息;
  • 另一种是反射机制,它允许我们在运行时发现和使用类的信息。使用的前提条件:必须先得到代表的字节码的Class,Class类用于表示.class文件(字节码)
13663d710b2f30efd264820e48e108d9.png 什么是反射? Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。 要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法。所以先要获取到每一个字节码文件对应的Class类型的对象.。反射就是把Java类中的各种成分映射成一个个的Java对象。 312a766d814ec0f6a7fe5617713dd3ef.png 获取Class对象的方式
  1. 类名.class:该方法最为安全可靠,程序性能更高。这说明任何一个类都有一个隐含的静态成员变量 class。这种方法只适合在编译前就知道操作的 Class,多用于传递参数。
  2. Class.forName(“类的全路径名”) :当知道某类的全路径名时,可以使用此方法获取 Class 类对象。用的最多,但可能抛出 ClassNotFoundException 异常,多用于加载配置文件。
  3. 对象名.getClass():getClass()是Object中的方法,java中所有类都直接或间接继承了Object类,所以都有getClass()方法。
//ewen包下的Person类public class Person {private String name;private int age;public String a;protected String b;String c;private String d;//构造函数、get、set方法省略。public void sleep(){        System.out.println("sleep....");    }}
public class ReflectTest {public static void main(String[] args) throws Exception {//获取Person类的Class对象// 通过类名.class获取        Class clazz1 = Person.class;//通过Class.forName("类的全路径名")       Class clazz2 = Class.forName("ewen.Person");//通过对象名.getClass()方法获得        Person person = new Person();        Class clazz3 = person.getClass();        System.out.println(clazz1 == clazz2);  //true        System.out.println(clazz2 == clazz3);  //true    }}
一个类只有一个Class实例,无论通过哪种方式获取,得到的都是同一个Class对象。 c3df05f32defa465293099708f66aec8.png Class对象的功能 1)获取类的成员变量
  • Field[] getFields() :获取所有public修饰的成员变量,返回一个含有所有public修饰的成员变量对象的数组;
  • Field getField(String name):获取指定名称的 public修饰的成员变量,返回一个成员变量对象。
  • Field[] getDeclaredFields() :获取所有的成员变量,不考虑修饰符。
  • Field getDeclaredField(String name) :获取指定名称的成员变量对象,不考虑修饰符。
//获取所有pubic修饰的成员变量        Field[] fields = clazz1.getFields();for (Field field : fields) {            System.out.println(field); //public java.lang.String ewen.Person.a,只有a是public修饰的        }//获取指定的成员变量        Field field = clazz1.getField("a");         System.out.println("成员变量a:" + field);  /public java.lang.String ewen.Person.a//使用getField()方法获取非public修饰的方法,报NoSuchFieldException/* Field field2 = clazz1.getField("b");        System.out.println("protected修饰的b" + field2);*///获取所有成员变量       Field[] fields2 =  clazz1.getDeclaredFields();for (Field field1 : fields2) {            System.out.println(field1);  //所有成员变量        }//获取指定成员变量,不考虑修饰符        Field field3 = clazz1.getDeclaredField("d");        System.out.println(field3);    /public java.lang.String ewen.Person.d
2)获取类的构造方法
  • Constructor>[] getConstructors() :获取所有public 修饰的构造方法,返回一个含有所有public修饰的构造函数对象的数组。
  • Constructor getConstructor(类>… parameterTypes) :获取含有指定参数public修饰的构造函数。
  • Constructor>[] getDeclaredConstructors() :获取所有构造函数,不考虑修饰符。
  • Constructor getDeclaredConstructor(类>… parameterTypes) :获取所有构造函数,不考虑修饰符,参数是构造器中参数类型对应的Class对象。
//获取构造函数Constructor con =  clazz1.getDeclaredConstructor(String.class, int.class);System.out.println(con);  //private ewen.Person(java.lang.String,int)
3)获取类中的方法
  • Method[] getMethods() :获取所有public修饰的方法,返回一个含有所有public修饰的方法对象的数组。
  • Method getMethod(String name, 类>… parameterTypes) :获取public修饰的含有指定参数的方法。
  • Method[] getDeclaredMethods() :获取所有方法,不考虑修饰符
  • Method getDeclaredMethod(String name, 类>… parameterTypes) :获取指定方法,不考虑修饰符。
4)获取类的全路径名:String getName() 8e0f0cae51487c41da58a61788c63fed.png 通过反射操作类中的成员变量、构造函数、方法 1)Field:成员变量对象 操作:
  • 设置成员变量值:field.set(类对象名,value)
//获取指定的成员变量        Field field = clazz1.getField("a");        System.out.println("成员变量a:" + field);        Person person2 = new Person();//设置值        field.set(person2,"aaa");        System.out.println("a的值:" + person2.getA());    //a的值:aaa
  • 获取成员变量值:field.get(类对象名)
//获取指定的成员变量        Field field = clazz1.getField("a");        System.out.println("成员变量a:" + field);        Person person2 = new Person();//获取person2中的a的值        Object value = field.get(person2);        System.out.println("a的值:" + value);  //a的值为null,String类型的成员变量默认为null。
  • 忽略访问权限修饰符的安全检查:field.setAccessible(true)
//获取指定成员变量,不考虑修饰符        Field field3 = clazz1.getDeclaredField("d");        System.out.println(field3);//设置值        Person person1 = new Person();/*        忽略访问权限修饰符的安全检查,d是private修饰的,如果不忽略        访问权限修饰符的安全检查,会报IllegalAccessException异常。         */        field3.setAccessible(true);        field3.set(person1,"111");        System.out.println("d的值:" + person1.getD());    //d的值:111
2)Constructor:构造方法对象
  • 创建对象:T newInstance(Object… initargs) ,里面为可变参数,变量的值。
/获取构造函数        Constructor con1 = clazz1.getConstructor(String.class);//创建对象        Object person3 = con1.newInstance("张三");
如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法。 如果构造函数是private修饰的,也可以调用setAccessible(true)来忽略访问权限的安全检查。
Object person4 = clazz1.newInstance();
3)Method:方法对象
  • 执行方法:Object invoke(类对象名, Object… args) ,参数必须得有类对象,变量参数根据方法参数来定
如果方法不是public修饰的,调用method.setAccessible(true)来忽略访问权限的安全检查。
//获取方法对象 Method method = clazz1.getMethod("sleep");//执行方法 method.invoke(person1);
Method 调用 invoke() 的时候,存在许多细节: invoke() 方法中第一个参数 Object 实质上是 Method 所依附的 Class 对应的类的实例,如果这个方法是一个静态方法,那么 obj 为 null,后面的可变参数 Object 对应的自然就是参数。 invoke() 返回的对象是 Object,所以实际上执行的时候要进行强制转换。 在对Method调用invoke()的时候,如果方法本身会抛出异常,那么这个异常就会经过包装,由Method统一抛InvocationTargetException。而通过InvocationTargetException.getCause() 可以获取真正的异常。
  • 获取方法名称 String getName()
ab36b6b53226e92995bc0d562bca5da3.png 案例 需求:写一个"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法 实现: 1. 配置文件 2. 反射 步骤: 1. 将需要创建的对象的全类名和需要执行的方法定义在配置文件中 2. 在程序中加载读取配置文件 3. 使用反射技术来加载类文件进内存 4. 创建对象 5. 执行方法
pro.properties文件:pro.className=ewen.Personpro.methodName=sleeppublic class UseReflect {public static void main(String[] args) throws Exception {//加载配置文件/*        创建Properties对象。         Properties是Java中jdk自带的一个对象        我们可以直接将后缀为properties的文件变为Properties对象,        然后通过Porperties对象中的方法对.properties文件对象进行操作         */        Properties pro = new Properties();//获得配置文件(pro.properties)的字节流        InputStream is = UseReflect.class.getClassLoader().getResourceAsStream("pro.properties");//从输入流中读取属性列表(键和元素对)。        pro.load(is);//获取配置文件中定义的数据        String className = pro.getProperty("pro.className");        String methodName = pro.getProperty("pro.methodName");//获取Class对象        Class clazz = Class.forName(className);//获取类对象        Object obj = clazz.newInstance();//获取方法对象        Method method = clazz.getMethod("sleep");//执行方法        method.invoke(obj);    }}
只需更改配置文件pro.properties文件就可以,在不改变代码的前提下,执行任意类的任意方法。 e9571961dde53eb132c6e17cbd29032f.png 反射的优缺点 优点
  1. 反射机制极大的提高了程序的灵活性和扩展性,降低模块的耦合性,提高自身的适应能力。
  2. 通过反射机制可以让程序创建和控制任何类的对象,无需提前硬编码目标类。
  3. 使用反射机制能够在运行时构造一个类的对象、判断一个类所具有的成员变量和方法、调用一个对象的方法。
反射机制是构建框架技术的基础所在,使用反射可以避免将代码写死在框架中。 正是反射有以上的特征,所以它能动态编译和创建对象,极大的激发了编程语言的灵活性,强化了多态的特性,进一步提升了面向对象编程的抽象能力,因而受到编程界的青睐。 缺点 尽管反射机制带来了极大的灵活性及方便性,但反射也有缺点。反射机制的功能非常强大,但不能滥用。在能不使用反射完成时,尽量不要使用,原因有以下几点: 1. 性能问题。 Java反射机制中包含了一些动态类型,所以Java虚拟机不能够对这些动态代码进行优化。因此,反射操作的效率要比正常操作效率低很多。我们应该避免在对性能要求很高的程序或经常被执行的代码中使用反射。而且,如何使用反射决定了性能的高低。如果它作为程序中较少运行的部分,性能将不会成为一个问题。 2. 安全限制 使用反射通常需要程序的运行没有安全方面的限制。如果一个程序对安全性提出要求,则最好不要使用反射。 3. 程序健壮性 反射允许代码执行一些通常不被允许的操作,所以使用反射有可能会导致意想不到的后果。反射代码破坏了Java程序结构的抽象性,所以当程序运行的平台发生变化的时候,由于抽象的逻辑结构不能被识别,代码产生的效果与之前会产生差异。 版权声明:本文为CSDN博主「ewenll」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/weixin_43691723/java/article/details/105874227

【END】

d9a04b05da53a55ace2f562a9ce3ab49.png

更多精彩推荐

☞突发!印度封禁抖音、微信、快手等 59 款中国 App

☞深度学习先驱 Yann LeCun 被骂到封推!AI 偏见真该甩锅数据集?

☞他被称为"中国第一程序员",一人之力单挑微软,如今拜入武当修道

☞高文、张钹、杨强隔空论道:AI精度与隐私的博弈

☞带你从零入门 Serverless | 一文详解 Serverless 架构模式

☞中央银行数字货币的总体框架

56f9d16f7e9d4010bb7053d2d588eafb.png欢迎“分享”、“点赞”、“在看”三连走起!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Spring 框架的生命周期是一种控制应用程序对象的方式,它有助于控制对象的创建、初始化、配置、装配、激活、使用、销毁等状态。生命周期的详细源码如下:// 创建bean的实例 public Object createBean(String beanName) { // 实例化bean Object beanInstance = instantiateBean(beanName); // 从配置文件获取bean的属性 populateBean(beanName, beanInstance); // 对bean进行初始化 initializeBean(beanName, beanInstance); // 将bean实例放入容器 registerDisposableBeanIfNecessary(beanName, beanInstance); return beanInstance; }// 初始化bean public void initializeBean(String beanName, Object beanInstance) { invokeAwareMethods(beanName, beanInstance); // 对bean进行属性注入 applyPropertyValues(beanName, beanInstance); // 调用初始化方法 invokeInitMethods(beanName, beanInstance); }// 销毁bean public void destroyBean(String beanName, Object beanInstance) { // 调用销毁方法 invokeDestroyMethods(beanName, beanInstance); // 移除bean实例 unregisterDisposableBeanIfNecessary(beanName, beanInstance); } ### 回答2: Spring框架是一个开源的Java应用程序框架,它提供了一种灵活的方式来开发企业级应用程序。Spring框架包含了一个核心容器,用于管理应用程序的对象的生命周期。 首先,Spring框架提供了BeanFactory接口和ApplicationContext接口,它们都是用来管理Bean对象的生命周期的,并且都有默认实现类DefaultListableBeanFactory和AnnotationConfigApplicationContext。这些类负责从配置文件或注解读取Bean定义,并创建和管理Bean对象。 在Spring框架,Bean的生命周期可以分为以下几个阶段:实例化、属性注入、初始化、销毁。 实例化阶段是指在容器创建Bean对象的过程。Spring框架通过使用反射机制实例化Bean对象,当容器启动时,它会根据配置文件或注解信息创建Bean对象。 属性注入阶段是指将配置文件或注解的属性值注入到Bean对象的过程。Spring框架提供了多种方式来实现属性注入,包括构造器注入、Setter方法注入和注解注入。 初始化阶段是指在Bean对象创建完成后,进行一些初始化操作的过程。Spring框架提供了InitializingBean接口和@PostConstruct注解,可以在Bean对象创建完成后执行自定义的初始化方法。 销毁阶段是指在容器关闭或Bean对象不再被使用时,进行销毁操作的过程。Spring框架提供了DisposableBean接口和@PreDestroy注解,可以在Bean对象销毁前执行自定义的销毁方法。 总的来说,Spring框架的生命周期源码主要涉及到Bean的创建、属性注入、初始化和销毁这些过程。这些源码通过反射机制、配置文件和注解来实现,提供了灵活性和可扩展性,使得开发者可以方便地管理和控制应用程序的对象的生命周期。 ### 回答3: Spring框架的Bean生命周期源码分为以下几个步骤: 1. 加载Bean定义:Spring容器通过读取配置文件或注解等方式,将Bean的定义加载到内存。这一步骤常用的类是BeanDefinitionReader,它会解析配置文件并创建BeanDefinition对象。 2. 创建Bean实例:Spring容器根据Bean的定义信息使用反射机制实例化Bean对象。这一步骤通常由BeanFactory或ApplicationContext负责。 3. 设置Bean属性:容器通过Bean的Setter方法设置Bean的属性,包括依赖注入和属性值注入。 4. Aware接口回调:如果Bean实现了某些Aware接口,容器将会回调相应的方法,用于提供一些Spring框架相关的资源。 5. BeanPostProcessor前置处理:如果有注册的BeanPostProcessor对象,容器将按照顺序依次调用其postProcessBeforeInitialization方法,用于在Bean初始化之前对Bean进行处理。 6. 初始化Bean:容器调用Bean的初始化方法,可以通过@Bean注解指定,或实现InitializingBean接口。 7. BeanPostProcessor后置处理:容器将会按照顺序依次调用BeanPostProcessor的postProcessAfterInitialization方法,用于在Bean初始化之后对Bean进行处理。 8. Bean销毁前处理:如果Bean实现了DisposableBean接口或配置了destroy-method方法,容器将调用其相关方法进行销毁前的处理。 9. 销毁Bean:容器销毁Bean,可以通过@PreDestroy注解指定,或实现DisposableBean接口。 以上是Spring框架Bean生命周期的详细源码过程。通过这些步骤,Spring容器可以完美地管理Bean的生命周期,提供各种功能和扩展点,使得开发者更加方便地使用和管理Bean。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值