转载:https://blog.csdn.net/CSDN2497242041/article/details/101886097
反射是框架设计的灵魂
(使用的前提条件:必须先得到代表的字节码的Class,Class类用于表示.class文件(字节码))
前言
反射是Java Web框架设计的灵魂。作为Java Web框架中必不可少的反射机制,比如Spring的IOC控制反转(通过第三方配置文件实现对象的控制)就会经常用到。反射是Java中一种强大技术,能够使我们很方便的创建灵活的代码,通过获取配置文件的class名,这些代码可以在运行时装配,无需在组件之间进行源代码链接,降低了代码的耦合度。但是要注意反射使用不当的话会成本很高。
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.
以上的总结就是什么是反射
反射就是把java类中的各种成分映射成一个个的Java对象
例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象。
(其实:一个类中这些成员方法、构造方法、在加入类中都有一个类来描述)
如图是类的正常加载过程:反射的原理在与class对象。
熟悉一下加载的时候:Class对象的由来是将class文件读入内存,并为之创建一个Class对象。

查看Class类在java中的api详解(1.7的API)

Class 类的实例表示正在运行的 Java 应用程序中的类和接口。也就是jvm中有N多的实例每个类都有该Class对象。(包括基本数据类型)
Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的defineClass 方法自动构造的。也就是这不需要我们自己去处理创建,JVM已经帮我们创建好了。
没有公共的构造方法,方法共有64个太多了。
Java反射机制的概念
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。
反射的概念:
比如我们想看看我们自己长什么样子,我们自己肯定看不到自己长什么样子,所以,我们借助镜子,通过镜子的反射看到我们的样子,可以看清我们自己的五官;
同理,Java中运行的类,也有这么一面镜子,可以反射该类的一些行为和属性,而这个反射就体现在java.lang.Class中。通过Class对象,可以得到某个类的一些行为和属性,甚至我们通过反射可以操作这个对象的行为和属性,这就是反射机制。
我们对反射的最初接触是学习jdbc时,加载数据库驱动时会这样写:Class.forName(“com.mysql.jdbc.Driver”),当时似懂非懂的也不知道是什么意思,随着自己的不断学习,越来越感觉反射的神奇,让我们一起来揭开它的神秘面纱吧
- 2、Java是跨平台的语言:Java编写的程序,一次编译,到处运行。因为Java的源代码会被编译成.class文件字节码,只要装有Java虚拟机JVM的地方,.class文件畅通无阻。
Java的反射机制,操作的就是这个.class文件,首先加载相应类的字节码,随后解剖(反射 reflect)出字节码中的构造方法、方法以及变量。所以,要想解剖一个类,必须先要获取到该类的字节码文件对象,因为解剖使用的就是Class类中的方法,所以先要获取到每一个字节码文件对应的Class类型的对象。
- 3、反射主要通过Java的java.lang.Class类来完成。
Class类存放着对应类型的运行时信息。
Java运行时虚拟机为所有的类型维护一个java.lang.Class对象,该对象保存着该对象的运行时信息。泛型的的class为Class<T>
每个类型的Class对象只有一个,也就是说地址只有一个。
- 4、反射的步骤
1.获取目标对象的class,一般使用Class.forName(String clazzName);
2.通过class对象分别获得该类型的构造函数、属性和方法;
3.通过获得的属性和方法,进行进一步操作。
二、反射涉及的Class类和java.lang.reflect
反射涉及的类,除了Class类之外,基本上都在java.lang.reflect包里面,常用的类有Constructor,Field,Method类等,AccessibleObject类是前面三个类的基类,主要包含设置安全性检查等方法。
1、反射能用来做什么?
我们知道反射机制允许程序在运行时取得任何一个已知名称的class的内部信息,包括包括其modifiers(修饰符),fields(属性),methods(方法)等,并可于运行时改变fields内容或调用methods。那么我们便可以更灵活的编写代码,代码可以在运行时装配,无需在组件之间进行源代码链接,降低代码的耦合度;还有动态代理的实现等等;但是需要注意的是反射使用不当会造成很高的资源消耗!
2、反射的具体实现
下面是一个基本的类 Person
public class Person { //私有属性 private String name = "AME"; //公有属性 public int age = 18; //构造方法 public Person() { } //私有方法 private void eat(){ System.out.println("private eat()..."); } //公有方法 public void play(){ System.out.println("public play()..."); } }
3、Java获取Class的三种方式
a、通过给定类的全限定(包名+类名)字符串名称就可以获取该类的字节码对象,通过 Class.forName() 方法完成。 b、通过 Object 类中的 getClass() 方法,想要用这种方法必须要明确具体的类并且创建该类的对象。 c、通过静态属性.class 来获取对应的 Class 对象。需要要明确到类才能调用类中的静态成员。
反射中,我们常用用的是第一种方法,该方法会抛出ClassNotFoundException异常。
Class.forName(“类的全限定名”)为什么说是最常用的方法,相信大家都用过Spring或者MyBatis等等这类框架,在使用这类框架的时候,免不了与该框架的XML配置文件打交道,在很多配置的地方都会填写一个全类名;看过源码的同学应该就知道,因为这些框架会先解析XML配置文件得到这个全类名,然后通过这个全类名来得到Class对象,完成后面的反射调用的动作。
//1、通过对象调用getClass()方法来获取,通常应用在:比如你传过来一个Object类型的对象, //而我不知道你具体是什么类,用这种方法 Person person1= new Person(); Class c1 = person1.getClass(); //2、直接通过 类名.class 的方式得到,该方法最为安全可靠,程序性能更高 // 这说明任何一个类都有一个隐含的静态成员变量 class Class c2 = Person.class; //3、通过 Class 对象的 forName() 静态方法来获取,用的最多。会抛出 ClassNotFoundException 异常 Class c3 = Class.forName("com.ys.reflex.Person");
4、通过 Class 类获取成员变量、成员方法、接口、超类、构造方法等
- a、通过class对象获得Constructor,Field,Method对象的API
注意:getMethods()该方法是获取本类以及父类或者父接口中所有的公共方法(public修饰符修饰的)
getDeclaredMethods()该方法是获取本类中的所有方法,包括私有的(private、protected、默认以及public)的方法。
同理,其他关于属性、方法和构造器的“get…”和”getDeclared…“都是一样的意思。
/** * 1、Constructor(类构造器的获取) * 带“declared”的方法可以获取包括继承,public不包括private的构造函数 * 不带的无法获取继承,可以获取public,protected,private和默认包访问的构造函数。 */ //获取所有的构造函数(公共/继承) Constructor<?>[] getConstructors(); //指定参数获取类的特定构造函数(传入构造函数的参数类型) Constructor<T> getConstructors(Class<?>... parameterTypes) //获取指定的构造函数不包括继承 Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes); //获取所有的构造函数不包括继承的 Constructor<?> getDeclaredConstructor(); /** * 2、获取类属性 */ //获取指定属性,传入属性名(公共和继承) Field getField(String name); //获取所有属性(公共和继承) Field[] getFields(); //获取指定的属性(不包括继承) Field getDeclaredField(String name); //获取所有属性(不包括继承) Field[] getDeclaredFields(); /** * 3、获取类的方法 */ //获取指定的方法(公共/继承),传入方法名,方法参数 Method getMethod(String name, Class<?>... parameterTypes); //该方法是获取本类以及父类或者父接口中所有的公共方法(public修饰符修饰的) Method getMethods(); //获取指定的方法(不包括继承) Method getDeclaredMethod(String name, Class<?>... parameterTypes); //该方法是获取本类中的所有方法,包括私有的(private、protected、默认以及public)的方法。 Method[] getDeclaredMethods();
- b、通过获取的各个类信息进行进一步操作的API
/** * 1、通过Constructor类对象获取构造函数信息 */ String getName();//获取构造器名 Class getDeclaringClass();//获取一个用于描述类中定义的构造函数的class对象。 int getModifiers(); //返回int, 获取构造函数的修饰符等级。 Class[] getExceptionTypes();//获取描述方法抛出的异常类型的Class对象数组。 Class[] getParameterTypes();//获取描述参数类型的Class对象数组。 constructor.newInstance(Class<?>... parameterTypes);//通过获取的构造函数类,通过指定 //参数类调用该来的指定构造函数,创建该类型的实例对象。 /** * 2、通过Field类对象获取构造函数信息 */ String getName(); //获取属性名 Class getDeclaringClass(); //获取Class对象,一般为声明该属性的类的类型class Class getType(); //获取属性类型的Class对象。 int getModifiers(); //返回int, 获取构造函数的修饰符等级。 Object get(Object obj);//返回该类型的对象上该属性的值。 void set(Object obj,Object Value);//为该类型Class的对象的指定对象,的指定属性赋值。 /** * 3、通过Method类对象获取构造函数信息 */ String getName();//获取方法名 Class getDeclaringClass(); //获取Class对象,一般为声明该方法的类的类型class int getModifiers(); //返回int, 获取函数的修饰符等级。 Class[] getExceptionTypes();//获取描述方法抛出的异常类型的Class对象数组。 Class[] getParameterTypes();//获取描述参数类型的Class对象数组。 Object invoke(Object obj,Class<?>... parameterTypes);//通过反射结合类的实例对象调用函数。
- c、总结,Class类常用方法:
getSimpleName():获得类名。 getName():获得类的完整名字。 getFields():获得类的public类型的属性。 getDeclaredFields():获得类的所有属性。包括private 声明的和继承类 getMethods():获得类的public类型的方法。 getDeclaredMethods():获得类的所有方法。包括private 声明的和继承类 getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes 参数指定方法的参数类型。 getConstructors():获得类的public类型的构造方法。 getConstructor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes 参数指定构造方法的参数类型。 newInstance():通过类的不带参数的构造方法创建这个类的一个对象。
String className = c2.getName(); System.out.println(className);//输出com.qf.cdxt.Person //获得类的public类型的属性。 Field[] fields = c2.getFields(); for(Field field : fields){ System.out.println(field.getName());//age } //获得类的所有属性。包括私有的 Field [] allFields = c2.getDeclaredFields(); for(Field field : allFields){ System.out.println(field.getName());//name age } //获得类的public类型的方法。这里包括 Object 类的一些方法 Method [] methods = c2.getMethods(); for(Method method : methods){ System.out.println(method.getName());//play waid equls toString hashCode等 } //获得类的所有方法。 Method [] allMethods = c2.getDeclaredMethods(); for(Method method : allMethods){ System.out.println(method.getName());//play eat } //获得指定的属性 Field f1 = c2.getField("age"); System.out.println(f1); //获得指定的私有属性 Field f2 = c2.getDeclaredField("name"); //启用和禁用访问安全检查的开关,值为 true,则表示反射的对象在使用时应该取消 java 语言的访问检查;反之不取消 f2.setAccessible(true); System.out.println(f2); //创建这个类的一个对象 Object p2 = c2.newInstance(); //将 p2 对象的 f2 属性赋值为 Fy,f2 属性即为 私有属性 name f2.set(p2,"Fy"); //使用反射机制可以打破封装性,导致了java对象的属性不安全。 System.out.println(f2.get(p2)); //Fy //获取构造方法 Constructor [] constructors = c2.getConstructors(); for(Constructor constructor : constructors){ System.out.println(constructor.toString());//public com.ys.reflex.Person() }
上面就是一个反射机制的基本实现了。
三、反射机制的应用
1、通过反射运行配置文件内容
Student类:
public class Student { public void show(){ System.out.println("is show()"); } }
配置文件以txt文件为例子(pro.txt):
className = cn.fanshe.Student methodName = show
import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.lang.reflect.Method; import java.util.Properties; /* * 我们利用反射和配置文件,可以使:应用程序更新时,对源码无需进行任何修改 * 我们只需要将新类发送给客户端,并修改配置文件即可 */ public class Demo { public static void main(String[] args) throws Exception { //通过反射获取Class对象 Class stuClass = Class.forName(getValue("className"));//"cn.fanshe.Student" //2获取show()方法 Method m = stuClass.getMethod(getValue("methodName"));//show //3.调用show()方法 m.invoke(stuClass.getConstructor().newInstance()); } //此方法接收一个key,在配置文件中获取相应的value public static String getValue(String key) throws IOException{ Properties pro = new Properties();//获取配置文件的对象 FileReader in = new FileReader("pro.txt");//获取输入流 pro.load(in);//将流加载到配置文件对象中 in.close(); return pro.getProperty(key);//返回根据key获取的value值 } }
控制台输出: is show()
需求:
当我们升级这个系统时,不要Student类,而需要新写一个Student2的类时,这时只需要更改pro.txt的文件内容就可以了。代码就一点不用改动
要替换的student2类:
public class Student2 { public void show2(){ System.out.println("is show2()"); } }
配置文件更改为:
className = cn.fanshe.Student2 methodName = show2
控制台输出: is show2();
2、通过反射越过泛型检查
泛型用在编译期,编译过后泛型擦除(消失掉)。所以是可以通过反射越过泛型检查的。
测试类:
import java.lang.reflect.Method; import java.util.ArrayList; /* * 通过反射越过泛型检查 * * 例如:有一个String泛型的集合,怎样能向这个集合中添加一个Integer类型的值? */ public class Demo { public static void main(String[] args) throws Exception{ ArrayList<String> strList = new ArrayList<>(); strList.add("aaa"); strList.add("bbb"); // strList.add(100); //获取ArrayList的Class对象,反向的调用add()方法,添加数据 Class listClass = strList.getClass(); //得到 strList 对象的字节码 对象 //获取add()方法 Method m = listClass.getMethod("add", Object.class); //调用add()方法 m.invoke(strList, 100); //遍历集合 for(Object obj : strList){ System.out.println(obj); } } }
控制台输出: aaa bbb 100
注意:Java web框架中,里面就用到了反射机制,比如Spring中,通过第三方配置文件实现对象的控制。
只要在代码或配置文件中看到类的完全限定名(包名+类名),其底层原理基本上使用的就是Java反射机制。
四、反射机制应用于Java SSM框架
Java SSM框架里面经常用到反射机制,相信大家都用过Spring或者MyBatis等等这类框架,在使用这类框架的时候,免不了与该框架的XML配置文件打交道,在很多配置的地方都会填写一个全类名。看过源码的同学应该就知道,因为这些框架会先解析XML配置文得件得到这个全类名,然后通过这个全类名来得到Class对象,完成后面的反射调用的动作。比如Spring中,通过第三方配置文件实现对象的控制。只要在代码或配置文件中看到类的完全限定名(包名+类名),该Java Web框架底层原理基本上使用的就是Java反射机制。所以,为了更好学习Java后端框架,我们有必要理解这种Java反射机制。
反射机制概述
反射机制:无非就是先加载对应字节码中的类,然后根据加载类的信息,一点点的去解剖其中的内容。不管你是public的还是private的,亦或是本类的还是来自原继承关系或者实现接口中的方法和字段,我们Java的反射技术reflect,均可以将其从字节码中拉回到现实,不仅可以得到字段的名字,我们还可以获得字段的值和修改字段的值;不仅可以得到方法的申明我们还可以拿到方法的定义和唤起方法(执行方法)。
当然,我们会有这样的疑惑?
为什么, new一个对象那么简单,非要用反射技术中的newInstance()?
为什么,我可以直接对象a1. 变量访问变量,却非要用反射那么费劲的获得name字段呢?
为什么,我几行代码就能搞定的事情,非要用反射呢?
解密答案之前,我们先来思考一个问题?
假设我们定义了很多类,有Animal、Person、Car..... ,如果我想要一个Animal实例,那我就new Animal();如果另一个人想要一个Person实例,那么他需要new Person();当然,另一个说,我只要一个Car实例,于是它要new Car()......
这样就会导致,每个用户new的对象需求不相同,因此他们只能修改源代码,并重新编译才能生效。这种将new的对象写死在代码里的方法非常不灵活,因此,为了避免这种情况的方法,Java提供了反射机制。
反射是Java中一种强大的工具,能够使我们很方便的创建灵活的代码,通过反射运行配置文件内容,这些代码可以在运行时装配,无需在组件之间进行源代码链接,降低了代码的耦合度。但要注意反射使用不当的话会成本很高。
五、Spring中应用反射机制
new Animal();//我们必须指定Animal类来创建对象 new "Animal";//我们希望通过字符串就能承接一个Animal实例,而这个字符串我们可以放在配置文件中 典型的应用就是Spring,Spring就是将类的描述放在xx.xml配置文件中!!!
我们知道Spring的IOC吧,即“控制反转”(通过第三方配置文件实现对 对象的控制)。简单说是将我们设计好的对象交给容器控制,而不是直接交给程序内部进行对象的控制。
比如,在Spring中,我们经常看到:

针对上述的配置文件,我们Spring是怎么帮助我们实例化对象,并放到容器中去了呢? 没错,就是通过反射!!!!
我们看下下面的伪代码实现过程:
//解析<bean .../>元素的id属性得到该字符串值为"sqlSessionFactory" String idStr = "sqlSessionFactory"; //解析<bean .../>元素的class属性得到该字符串值 为"org.mybatis.spring.SqlSessionFactoryBean" String classStr = "org.mybatis.spring.SqlSessionFactoryBean"; //利用反射知识,通过classStr获取Class类对象 Class cls = Class.forName(classStr); //实例化对象 Object obj = cls.newInstance(); //container表示Spring容器 container.put(idStr, obj); //当一个类里面需要用另一类的对象时,我们继续下面的操作 //解析<property .../>元素的name属性得到该字符串值为“dataSource” String nameStr = "dataSource"; //解析<property .../>元素的ref属性得到该字符串值为“dataSource” String refStr = "dataSource"; //生成将要调用setter方法名 String setterName = "set" + nameStr.substring(0, 1).toUpperCase() + nameStr.substring(1); //获取spring容器中名为refStr的Bean,该Bean将会作为传入参数 Object paramBean = container.get(refStr); //获取setter方法的Method类,此处的cls是刚才反射代码得到的Class对象 Method setter = cls.getMethod(setterName, paramBean.getClass()); //调用invoke()方法,此处的obj是刚才反射代码得到的Object对象 setter.invoke(obj, paramBean);
是不是很熟悉,虽然是伪代码,但是和我们上篇讲的反射机制的使用是相同的,现在知道我们的反射机制用在哪了吧,没错就是我们经常提到的Java web框架中,里面就用到了反射机制,只要在代码或配置文件中看到类的完全限定名(包名+类名),其底层原理基本上使用的就是Java的反射机制。
所以,如果你不做框架的话,基本上是用不到反射机制的,我们大多时候是使用框架的一方,而反射机制都已经在底层实现过,我们就不必担心会写那么复杂的代码。但为了更好学习Java后端框架,我们还是有必要理了解一下Java反射机制。