欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

反射(一)----原理机制和基本运用

程序员文章站 2022-07-12 15:58:03
...

   JAVA 反射的应用还是比较多,这里会对反射的一些原理进行介绍,然后我们才知道如何使用和优化。至

 

于反射的使用介绍,这里就不在过多叙述了,API 上都介绍得有。

   要了解JAVA 反射的原理,我们还得对类在虚拟机中的一些知识做简要介绍...

 

一、类文件的结构:

   1.1 虚拟机加载Class文件过程:

       在JVM 类加载机制的博客里面我介绍过整体流程,这里仅仅介绍加载时相关部分。

       在我们启动一个类,或者其他方式加载一个类的时候,会通过类的全限定名获取该类的二进制流,然

 

后将字节流所代表的的静态存储结构转化成方法区的运行时数据结构,然后会生成一个代表该类的

 

java.lang.Class 对象,作为在方法区这个类的访问入口。也就是说只要完成了这一步骤,那么通过这个入

 

口我们就可以访问里面的存储好的数据结构信息了。而且动态加载的时候,会先进行查找,该类是否存在,

 

存在了就不会再加载了,保持一份。

 

    class 文件是一组以8位字节为基础单位的二进制流,各个数据项目按严格的顺序紧凑的排列在Class文

 

件中,里面的信息主要描述以下信息:

    1.版本号:主版本号和次版本号

 

    2.常量池:主要存放字面量(Literal)和符号引用(references)

      2.1 字面量:文本字符串、final 类型的常量值 等

      2.2 符号引用:

          a.类和接口的全限定名字

          b.字段描述和描述符

          c.方法的名称和描述

    

    3.访问标志:

      a.是类还是接口

      b.是否是public 等类型

      c.是否是abstract ,是否被声明为final 等标志

    

    4.类索引、父类索引和接口索引集合

      a.类索引:确定这个类的全限定名

      b.父类索引:确定父类的全限定名

      c.接口索引集合:作为入口,作为一个计数器

 

    5.字段表集合:

      包括信息有字段作用域(public,private等修饰符)、是实例变量还是类变量(static)、可变性 (final)、并发可见性(volatile)、可否被序列化(transient)等信息

    

    6.方法集合:

      包括访问标志、名称索引、描述符索引、属性表集合。

 

    7.其他:包括属性表集合、Code 属性(指令) 等其他这里暂时不过多介绍,详细请看虚拟机的书籍。

 

 

 

 

二、反射概念:

    通过上面简单的介绍,相信大家了解了我们的Class 文件在加载到JVM 里面之后,实际存放的信息有很

 

多,而且上面介绍的都是大家有一定了解的,比如 方法 、属性 等等,那么反射是什么呢?

    所谓反射是JAVA 语言允许在 运行时拥有一种自审的能力,也就是说JVM 允许代码在运行期间可以获得

 

类的内部信息,简单的说我们可以在程序运行期间获得刚才我们介绍的类里面的信息。

 

    2.1 反射的常用方法:

        a.forName(String className) : 

          返回与带有给定字符串名的类或接口相关联的 Class 对象。

 

        b.forName(String name, boolean initialize, ClassLoader loader) :

          使用给定的类加载器,返回与带有给定字符串名的类或接口相关联的 Class 对象。

 

        c.getAnnotation(Class<A> annotationClass) 

          如果存在该元素的指定类型的注释,则返回这些注释,否则返回 null。

 

        d.getAnnotations() 

          返回此元素上存在的所有注释。

 

        e.getConstructor(Class<?>... parameterTypes) 

          返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法。

 

        f.getDeclaredField(String name) 

          返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。

    

        g.getDeclaredMethod(String name, Class<?>... parameterTypes) 

          返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。

 

 

这里我就不累赘了,仅仅引入性的介绍几个,更多的可以去看看API

 

 

三、反射的基本使用:这里我们写一个简单类,模拟常见的功能。

   

 public class Test {
// 构造方法
public Test() {
System.out.println("无参数构造");
}
public Test(String str) {
System.out.println("有参构造:"+str);
}
public  void test(){
System.out.println("普通测试方法:");
}
public static void staticTest(){
System.out.println("静态测试方法");
}
// 基本属性
private Integer number;
public String name = "张三";
    }

 

 

  

    1.类加载:

 

    public static void main(String[] args) throws Exception {
Class<?> c = Class.forName("com.Test");
// 这里加载类,会调toString 方法, 打印类型,和getName 方法
// 同时发现类已经存在,说明加载成功
// 这里常常我们用来动态加载一个类,比如再获得JDBC 连接的时候,
// 我们可以动态的获得不通厂商的连接:Class.forName("xxx.oracle/mysql")
System.out.println("类信息:"+c);
// 同时这里可以直接这只加载器实现:Class.forName(name, initialize, loader) 
}

 

 

    2 获得方法并且调用:

 

     public static void main(String[] args) throws Exception {
Class<?> c = Class.forName("com.Test");
// 这个是获得指定方法,我们先获得普通方法
Method m = c.getMethod("test");
// 这里执行会失败了,因为我们们的类没进行实例化
// m.invoke(c);
// 这样就不会,这里使用了默认的构造
m.invoke(c.newInstance());
// 但是静态方法不用实例化就可以
Method m2 = c.getMethod("staticTest");
m2.invoke(c);
// 当然我们还能获得所有的方法信息
// 获得该类所有方法
c.getDeclaredMethods();
// 获得包括继承的类的所有方法
c.getMethods();
}

 

  

      3.获得字段信息:

public static void main(String[] args) throws Exception {
Class<?> c = Class.forName("com.Test");
// 私有字段是无法通过公有方式访问
// Field f1 = c.getField("number");
// public 字段是可以直接访问
Field f = c.getField("name");
System.out.println(f);


// 这是赋值,必须先获得对象实例,可以为字段赋值
Object o = c.newInstance();
f.set(o, "2");
System.out.println(f.get(o));
// 其他方法,和获取method 差不多
}

 

关于构造器,权限等这些就可以看API,这里仅仅介绍。

     

      4.其他应用:

        反射运用得很多,比如我们熟悉的hibernate、spring、以及其他的orm 框架都需要用到。

        这里我们模拟hibernate 的实现,来完成对象的保存操作。

        // 这是我们模拟的实体bean

// 这是我们模拟的实体bean
public class Bean {
	private Integer id;
	private String name;
	private String password;
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
}

 

 

 

 

import java.lang.reflect.Field;
import java.lang.reflect.Method;

// 这是我们的测试
public class Test {
	public static void main(String[] args) {
		// 1. 首先我们创建一个bean,模拟从前端获取的数据
		Test t = new Test();
		Bean bean = t.getBean();
		// 2.生成我们需要的SQL 并设值
		t.save(bean);
	}
	
	private Bean getBean(){
		// 模拟用反射实现
		Bean bean = null;
		try {
			Class c = Class.forName("Bean");
			bean = (Bean) c.newInstance();
			// 私有字段无法访问,我们通过方法赋值
			Method m1 = c.getDeclaredMethod("setId",Integer.class);
			Method m2 = c.getDeclaredMethod("setName",String.class);
			Method m3 = c.getDeclaredMethod("setPassword",String.class);
			m1.invoke(bean, 1);
			m2.invoke(bean, "admin");
			m3.invoke(bean, "123456");
		} catch (Exception e) {
			e.printStackTrace();
		}
		return bean;
	}
	
	// 假设我们的表  就是 BEAN
	private void save(Bean bean){
		Field[] fields = Bean.class.getDeclaredFields();
		StringBuffer sb = new StringBuffer("INSERT INTO BEAN VALUES");
		sb.append(getInsertStr(fields.length));
		// 这里我们可以看到SQL 已经生成
		System.out.println(sb);
		// 这里就是我们的JDBC 根据字段名字赋值 的操作了。
		// 当然hibernate 写得肯定会复杂很多,但是基本原理不变
		
	}
	
	private String getInsertStr(int fields){
		StringBuffer sb = new StringBuffer("(");
		for(int i = 0;i<fields;i++){
			sb.append("?,");
		}
		sb.delete(sb.length()-1,sb.length());
		sb.append(")");
		return sb.toString();
	}
}

 

在spring 里面,在介绍过生成代理类,也就是AOP 的地方也用过,这里也就不多说了。

 

小结:

     1.这里介绍了反射的基本运用,以及一些相关原理的东西,还没真正深入

     2.反射给了我们很大的灵活,但是同时很多错误只能到运行期间才能发现,使用要多注意。

     3.反射提供灵活的同时,也牺牲了性能,在JDK1.6+ 版本,反射的一般调用,比直接调用慢2倍左右。

       性能这一块后面再研究,再招优化的方案。