本文共 4464 字,大约阅读时间需要 14 分钟。
Java的反射(Reflection)机制是指在程序运行时动态获取程序信息和动态调用对象的功能。反射是Java实现动态性的关键,其主要作用如下:
在学习反射之前,我们先来了解反射的应用场景。
在项目的开发过程中使用多个服务(server)处理数据流(data flow),例如:server A、server B、server C;当项目上线之后,需要添加新的服务server D。
图示如下:
这时候该怎么办呢?此时,我们可利用插件化技术实现该需求。
方案如下:
示意代码如下:
public interface IServer { public void process(DataPoint point);}
示意代码如下:
public class ServerA implements IServer{ public void process(DataPoint point){ System.out.println("ServerA is processing"); }}public class ServerB implements IServer{ public void process(DataPoint point){ System.out.println("ServerB is processing"); }}public class ServerC implements IServer{ public void process(DataPoint point){ System.out.println("ServerC is processing"); }}
示意代码如下:
project.servers=servers.ServerA, servers.ServerB, servers.ServerC
在配置文件中配置所有的服务的全路径
示意代码如下:
public class Test{ public static void main(String[] args){ // 读取配置 final String configPath = "config.properties"; Properties properties = ConfigUtils.readProperties(configPath); String p = properties.getProperty("project.servers"); String []tmp = p.split(","); Listsvrlist = new ArrayList (); for(String t: tmp){ svrlist.add(t.trim()); } // 加载servers ArrayList servers = new ArrayList (); Iterator iter = svrlist.iterator(); while(iter.hasNext()){ String str = iter.next(); //生成server IServer server = (IServer) newInstance(str); if(server != null) servers.add(server); } DataPoint point = new DataPoint(); point.setName("point"); point.setValue(1); // servers处理数据 Iterator iter2 = servers.iterator(); while(iter2.hasNext()){ IServer server = iter2.next(); server.process(point); } } //实例化Server对象 private static Object newInstance(String classname){ try { Class clazz = Class.forName(classname); Object obj = clazz.newInstance(); return obj; } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return null; }}
要点概述:
至此,项目开发完毕。不过,没过多久又来了一个新的需求:在原来的基础上新增服务ServerD处理数据流。
方案如下:
第一步:ServerD实现IServer接口
示意代码如下:
public class ServerD implements IServer{ public void process(DataPoint point){ System.out.println("ServerD is processing"); }}
第二步:在配置文件中追加配置ServerD
示意代码如下:
project.servers=servers.ServerA, servers.ServerB, servers.ServerC, servers.ServerD
至此,我们通过插件的方式添加了服务ServerD而不需要修改以往的主程序。在整个插件化编程中最核心的技术就是反射,优势如下:
在开发过程中,不需要事先知道服务Server的名字(其实,很多情况下也无法事先知道);只需要在配置文件中追加配置即可。
看完了插件化编程,我们再来瞅瞅大家熟悉的Tomcat是如何调用Servlet的。在JavaWeb项目中存在众多的Servlet处理各种请求;比如,LoginServlet专门用于处理用户的登录请求;其流程概述如下:
在整个过程当中,其实现原理和插件化编程有着相似的思路:
在整个过程中,不需要事先知道自定义Servlet的名字(其实,很多情况下也无法事先知道);只需要在配置文件中添加相应的配置即可。
作为一个初学者,如果想理解反射的应用场景可以把自己想象成为一个Tomcat,尝试着反问自己一个问题并尝试作答:你知道有哪些项目会部署到Tomcat么?不知道!你根本不知道哪些项目会被部署到Tomcat,你也不知道项目中有哪些请求,你更不知道项目中有哪些Servlet用于处理请求。既然都无法知道有哪些Servlet,更不用说预先通过new的方式创建好Servlet实例用于以后处理各种请求了。所以,作为Tomcat它无法预知有哪些请求,它也无法预知有哪些Servlet处理请求;这些都是未知数。
其实,不管是插件化编程还是服务器编程都属于同一类情况:
在某些较为特殊的情况下,没有.java源文件只有.class字节码文件。此时,不能再使用原来new的方式创建对象而可利用.class通过反射方式的方式创建对象。
获取类中的所有信息。例如:属性、成员方法、构造函数、泛型等等。
普通情况下,我们无法访问对象的私有属性和私有方法。此时,反射是一个非常不错的选择。
当程序完成编译之后,类加载器(ClassLoader)将字节码文件加载进内存并在堆内存的方法区中产生一个Class类型的对象,该对象包含了类的所有信息。 我们可以通过该对象清晰而完整地看到类内部的结构与构成。换句话说, Class对象就像一面镜子映出了类的轮廓。所以, 我们形象地将此过程称之为反射。
其实,从反射的实现原理我们也可摸索出反射技术的学习路径: