Java反射机制

一、预先需要掌握的知识(Java虚拟机

 

java虚拟机的方法区

java虚拟机有一个运行时数据区,这个数据区又被分为方法区,堆区和栈区,我们这里需要了解的主要是方法区。方法区的主要作用是存储被装载的类 的类型信息,当java虚拟机装载某个类型的时候,需要类装载器定位相应的class文件,然后将其读入到java虚拟机中,紧接着虚拟机提取class中的类型信息,将这些信息存储到方法区中。这些信息主要包括

 

1、这个类型的全限定名 


2、这个类型的直接超类的全限定名 

 

3、这个类型是类类型还是接口类型 

 

4、这个类型的访问修饰符 

 

5、任何直接超接口的全限定名的有序列表 

 

6、该类型的常量池 

 

7、字段信息 

 

8、方法信息 

 

9、除了常量以外的所有类变量 

 

10、一个到class类的引用 

 

等等(读者可以参考《深入java虚拟机》这本书的叙述

参阅博文:Java虚拟机运行时数据区域

 

Class

 

Class类是一个非常重要的java基础类,每当装载一个新的类型的时候,java虚拟机都会在java堆中创建一个对应于新类型的Class实例,该实例就代表此类型,通过该Class实例我们就可以访问该类型的基本信息。上面说到在方法区中会存储某个被装载类的类型信息,我们就可以通过Class实例来访问这些信息。比如,对于上面说到的信息Class中都有对应的方法,如下

 

1getName();这个类型的全限定名 

 

2getSuperClass();这个类型的直接超类的全限定名 

 

3isInterface();这个类型是类类型还是接口类型 

 

4getTypeParamters();这个类型的访问修饰符

 

5getInterfaces();任何直接超接口的全限定名的有序列表 

 

6getFields();字段信息 

 

7getMethods();方法信息 

 

等等(读者可以自己参看jdk帮助文档,得到更多的信息


二、java反射详解 

 

反射的概念:所谓的反射就是java语言在运行时拥有一项自观的能力,反射使您的程序代码能够得到装载到JVM中的类的内部信息,允许您执行程序时才得到需要类的内部信息,而不是在编写代码的时候就必须要知道所需类的内部信息,这使反射成为构建灵活的应用的主要工具。 

 

反射的常用类和函数:Java反射机制的实现要借助于4个类:ClassConstructorFieldMethod;其中class代 表的是类对象,Constructor-类的构造器对象,Field-类的属性对象,Method-类的方法对象,通过这四个对象我们可以粗略的看到一个类的各个组成部分。其中最核心的就是Class类,它是实现反射的基础,它包含的方法我们在第一部分已经进行了基本的阐述。应用反射时我们最关心的一般是一个类的构造器、属性和方法,下面我们主要介绍Class类中针对这三个元素的方法

 

1. 得到构造器的方法 

 

Constructor getConstructor(Class[] params) -- 获得使用特殊的参数类型的公共构造函数, 

 

Constructor[] getConstructors() -- 获得类的所有公共构造函数 

 

Constructor getDeclaredConstructor(Class[] params) -- 获得使用特定参数类型的构造函数(与接入级别无关

 

Constructor[] getDeclaredConstructors() -- 获得类的所有构造函数(与接入级别无关

 

2. 获得字段信息的方法 

 

Field getField(String name) -- 获得命名的公共字段 

 

Field[] getFields() -- 获得类的所有公共字段 

 

Field getDeclaredField(String name) -- 获得类声明的命名的字段 

 

Field[] getDeclaredFields() -- 获得类声明的所有字段 


3. 获得方法信息的方法 

 

Method getMethod(String name, Class[] params) -- 使用特定的参数类型,获得命名的公共方法 

 

Method[] getMethods() -- 获得类的所有公共方法 

 

Method getDeclaredMethod(String name, Class[] params) -- 使用特写的参数类型,获得类声明的命名的方法 

 

Method[] getDeclaredMethods() -- 获得类声明的所有方法 

 

4. 应用反射的基本步骤

 

1.获得你想操作的类的Class对象; 

 

 

     方法一:Class c=Class.forName("java.lang.String") //这种方式获得类的Class对象需要 包名.类名

 

     方法二:Class c = 对象.getClass(); //java语言中任何一个java对象都有getClass 方法

 

     方法三:Class c=类.class  //java中每个类型都有class 属性.

 

2.调用Class中的方法得到你想得到的信息集合,如调用getDeclaredFields()方法得到类的所有属性; 

 

3.处理第2步中得到的信息,然后进行你想做的实际操作。 

 

 

下面我将针对类的构造器、属性方法和对象分别举例说明,向大家演示一下反射的应用过程。 

 

1、构造器 

 

步骤为:通过反射机制得到某个类的构造器,然后调用该构造器创建该类的一个实例 

import java.lang.reflect.*;  
public class ConstructorDemo{ 
   public ConstructorDemo(){ } 
   public ConstructorDemo(int a, int b){ 
      System.out.println("a="+a+"b="+b); 
   } 

   public static void main(String args[]){ 
       try { 
           Class cls =Class.forName("包名.ConstructorDemo"); 
           Class partypes[] =new Class[2]; partypes[0] = Integer.TYPE;   
           partypes[1] =Integer.TYPE; 
           Constructor ct=cls.getConstructor(partypes); 
           Object arglist[] =new Object[2]; 
           arglist[0] = newInteger(37); 
           arglist[1] = newInteger(47); 
           Object retobj = ct.newInstance(arglist); 
         } catch (Throwable e) {
           System.err.println(e);} 
         } 
   } 

2、属性 

 

步骤为:通过反射机制得到某个类的某个属性,然后改变对应于这个类的某个实例的该属性值 

import java.lang.reflect.*; 
public class FieldDemo1{ 
  public double d; 

  public static void main(String args[]){ 
  try { 
    Class cls = Class.forName("FieldDemo1"); 
    Field fld = cls.getField("d"); 
    FieldDemo1 fobj = new FieldDemo1(); 
    System.out.println("d = " + fobj.d); 
    fld.setDouble(fobj, 12.34); 
    System.out.println("d = " + fobj.d); 
  } catch (Throwable e){ 
    System.err.println(e); } 
  } 
} 

a.获取所有的属性的写法:
//获取整个类
Class c = Class.forName("java.lang.Integer");
//获取所有的属性?
Field[] fs = c.getDeclaredFields();
	 
//定义可变长的字符串,用来存储属性
StringBuffer sb = new StringBuffer();
//通过追加的方法,将每个属性拼接到此字符串中
//最外边的public定义
sb.append(Modifier.toString(c.getModifiers()) + " class " + c.getSimpleName() +"{\n");
//里边的每一个属性
for(Field field:fs){
 sb.append("\t");//空格
 sb.append(Modifier.toString(field.getModifiers())+" ");//获得属性的修饰符,例如public,static等等
 sb.append(field.getType().getSimpleName() + " ");//属性的类型的名字
 sb.append(field.getName()+";\n");//属性的名字+回车
}
 sb.append("}");
System.out.println(sb);



b,获取特定的属性,对比着传统的方法来学习:

public static void main(String[] args) throws Exception{			
//以前的方式:
/*
User u = new User();
u.age = 12; //set
System.out.println(u.age); //get
*/
			
//获取类
Class c = Class.forName("User");
	//获取id属性
	Field idF = c.getDeclaredField("id");
	//实例化这个类赋给o
	Object o = c.newInstance();
	//打破封装
	idF.setAccessible(true); //使用反射机制可以打破封装性,导致了java对象的属性不安全。
	//给o对象的id属性赋值"110"
	idF.set(o, "110"); //set
	//get
	System.out.println(idF.get(o));
}

3、方法 

 

步骤为:通过反射机制得到某个类的某个方法,然后调用对应于这个类的某个实例的该方法 

//通过使用方法的名字调用方法 
import java.lang.reflect.*; 
public class MethodDemo1{ 
  public int add(int a, int b){ 
  return a + b; 
  } 

  public static void main(String args[]){ 
    try { 
      Class cls =Class.forName("MethodDemo1"); 
      Class partypes[] = new Class[2]; 
      partypes[0] = Integer.TYPE; 
      partypes[1] = Integer.TYPE; 
      Method meth = cls.getMethod("add",partypes); 
      MethodDemo1 methobj = new MethodDemo1(); 
      Object arglist[] = new Object[2]; 
      arglist[0] = new Integer(37); 
      arglist[1] = new Integer(47); 
      Object retobj= meth.invoke(methobj, arglist); 
      Integer retval = (Integer)retobj; 
      System.out.println(retval.intValue()); 
    } catch (Throwable e) { 
      System.err.println(e); 
    } 
  } 
} 

4对象


创建对象:获取类以后我们来创建它的对象,利用newInstance:
Class c =Class.forName("包.类");
//创建此Class 对象所表示的类的一个新实例
Objecto = c.newInstance(); //调用了Employee的无参数构造方法.


三、java反射的应用(hibernate) 

 

我们在第二部分中对java反射进行了比较系统的阐述,也举了几个简单的实例,下面我们就来讨论一下java反射的具体应用。前面我们已经知 道,Java反射机制提供了一种动态链接程序组件的多功能方法,它允许程序创建和控制任何类的对象(根据安全性限制)之前,无需提前硬编码目标类。这些特 性使得反射特别适用于创建以非常普通的方式与对象协作的库。例如,反射经常在持续存储对象为数据库、XML或其它外部格式的框架中使用。下面我们就已Hibernate框架为例像大家阐述一下反射的重要意义。 

 

Hibernate是一个屏蔽了JDBC,实现了ORMjava框架,利用该框架我们可以抛弃掉繁琐的sql语句而是利用HibernateSession类的save()方法直接将某个类的对象存到数据库中,也就是所涉及到sql语句的那些代码Hibernate帮我们做了。这时候就出现了 一个问题,Hibernate怎样知道他要存的某个对象都有什么属性呢?这些属性都是什么类型呢?如此,它在向数据库中存储该对象属性时的sql语句该怎 么构造呢?解决这个问题的利器就是我们的java反射! 

 

下面我们以一个例子来进行阐述,比如我们定义了一个User类,这个User类中有20个属性和这些属性的getset方法,相应的在数据库中 有一个User表,这个User表中对应着20个字段。假设我们从User表中提取了一条记录,现在需要将这条记录的20个字段的内容分别赋给一个User对象myUser20个属性,而Hibernate框架在编译的时候并不知道这个User类,他无法直接调用myUser.getXXX或者myUser.setXXX方法,此时就用到了反射,具体处理过程如下: 

 

1、根据查询条件构造PreparedStament语句,该语句返回20个字段的值; 

 

2Hibernate通过读取配置文件得到User类的属性列表list(是一个String数组)以及这些属性的类型; 

 

3、创建myUser所属类的Class对象cc=myUser.getClass() 

 

4、构造一个for循环,循环的次数为list列表的长度; 

 

     4.1、读取list[i]的值,然后构造对应该属性的set方法; 

 

     4.2、判断list[i]的类型XXX,调用PreparedStament语句中的getXXX(i),进而得到i出字段的值; 

 

     4.3、将4.2中得到的值作为4.1中得到的set方法的参数,这样就完成了一个字段像一个属性的赋值,如此循环即可; 

 

看到了吧,这就是反射的功劳,如果没有反射很难想象如果完成同样的功能会有多么难!但是反射也有缺点,比如性能比较低、安全性比较复杂等,这里就不在讨论这些东西,感兴趣的读者可以在网上找到答案,有很多的!

 

四、代码讲解

1.首先准备两个很简单的业务类


2. 非反射方式切换不同的业务方法调用


当需要从第一个业务方法切换到第二个业务方法的时候,使用非反射方式,必须修改代码,并且重新编译运行,才可以达到效果  

1. 反射方式

使用反射方式,首先准备一个配置文件,就叫做spring.txt,放在src目录下。 里面存放的是类的名称,和要调用的方法名。

在测试类Test中,首先取出类名称和方法名,然后通过反射去调用这个方法。

当需要从调用第一个业务方法,切换到调用第二个业务方法的时候,不需要修改一行代码,也不需要重新编译,只需要修改配置文件spring.txt,再运行即可。

                                                                                                                                                          

使用这个例子,可以较好得理解反射的一个应用场景。

这也是Spring框架的最基本的原理,只是它做的更丰富,安全,健壮。 


五、深入理解


首先我们了解一下JVM,什么是JVMJava的虚拟机,java之所以能跨平台就是因为这个东西,你可以理解成一个进程,程序,只不过他的作用是用来跑你的代码的。上图是java的内存模型,我们关注的点,一个方法区,一个栈,一个堆,初学的时候老师不深入的话只告诉你java的内存分为堆和栈,易懂点吧!

假如你写了一段代码:Object o=new Object();

运行了起来!

首先JVM会启动,你的代码会编译成一个.class文件,然后被类加载器加载进jvm的内存中,你的类Object加载到方法区中,创建了Object类的class对象到堆中,注意这个不是new出来的对象,而是类的类型对象,每个类只有一个class对象,作为方法区类的数据结构的接口。jvm创建对象前,会先检查类是否加载,寻找类对应的class对象,若加载好,则为你的对象分配内存,初始化也就是代码:new Object()

上面的流程就是你自己写好的代码扔给jvm去跑,跑完就over了,jvm关闭,你的程序也停止了。

为什么要讲这个呢?因为要理解反射必须知道它在什么场景下使用。

题主想想上面的程序对象是自己new的,程序相当于写死了给jvm去跑。假如一个服务器上突然遇到某个请求哦要用到某个类,哎呀但没加载进jvm,是不是要停下来自己写段代码,new一下,哦启动一下服务器,(脑残)!

反射是什么呢?当我们的程序在运行时,需要动态的加载一些类这些类可能之前用不到所以不用加载到jvm,举个例子我们的项目底层有时是用mysql,有时用oracle,需要动态地根据实际情况加载驱动类,这个时候反射就有用了,假设com.java.dbtest.myqlConnectioncom.java.dbtest.oracleConnection这两个类我们要用,这时候我们的程序就写得比较动态化,通过Class tc = Class.forName("com.java.dbtest.TestConnection");通过类的全类名让jvm在服务器中找到并加载这个类,而如果是oracle则传入的参数就变成另一个了。这时候就可以看到反射的好处了,这个动态性就体现出java的特性了!当然这里只是举了反射的一个应用,实际还有其他作用,只是这个例子能更好地理解!




参考文档:

Java反射机制 

JAVA中的反射机制

来自知乎问答


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值