前言:反射是Java Web框架设计的灵魂。作为Java Web框架中必不可少的反射机制,比如Spring的IOC控制反转(通过第三方配置文件实现对象的控制)就会经常用到。反射是Java中一种强大技术,能够使我们很方便的创建灵活的代码,通过获取配置文件的class名,这些代码可以在运行时装配,无需在组件之间进行源代码链接,降低了代码的耦合度。但是要注意反射使用不当的话会成本很高。

一、Java反射机制的概念

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。

1、反射机制允许Java程序在运行时取得任何一个已知名称的class的内部信息。可以动态的获取类的完整结构信息,调用对象的方法和属性。 

反射的概念:

比如我们想看看我们自己长什么样子,我们自己肯定看不到自己长什么样子,所以,我们借助镜子,通过镜子的反射看到我们的样子,可以看清我们自己的五官;

同理,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类来完成。

a>Class类存放着对应类型的运行时信息。
Java运行时虚拟机为所有的类型维护一个java.lang.Class对象,该对象保存着该对象的运行时信息。泛型的的class为Class<T>
b>每个类型的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()...");
    }
}

1>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");

2>通过 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()
}

上面就是一个反射机制的基本实现了。Java反射机制访问私有字段和私有方法

三、反射机制的应用

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后端框架中的应用:Java进阶知识3:反射机制应用于Java SSM框架

 

参考链接:

浅析java.lang.Class

Java反射机制的原理和用途

Java基础之—反射(非常重要)

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐