文档章节

Java 反射机制详解

大数据之路
 大数据之路
发布于 2012/08/30 18:49
字数 3873
阅读 59
收藏 3
点赞 0
评论 0

0. Class类简介:

 

Class对象

虚拟机在class文件的加载阶段,把类信息保存在方法区数据结构中,并在Java堆中生成一个Class对象,作为类信息的入口。

声明两个类,Cat.java 和 Dog.java

class Cat {
    private String name;
    private int age;
    static {
        System.out.println("Cat is load");
    }
}

class Dog {
    private String name;
    private int age;
    static {
        System.out.println("Dog is load");
    }
}

获取Class对象一般有三种方式:

  1. 通过实例变量方式

    public class test {
       public static void main(String[] args) {
          Dog dog = new Dog();
          Class clazz = dog.getClass();
       }
    }
  2. 通过类名方式

    public class test {
       public static void main(String[] args) {
            Class clazz = Dog.class;
       }
    }

    通过这种方式时,只会加载Dog类,并不会触发其类构造器的初始化。

  3. 通过Class.forName(String classname)方式
    public class ClassTest {
       public static void main(String[] args) {
         try {
             Class clazz = Class.forName("zzzzzz.Dog");
         } catch (ClassNotFoundException e) {}
       }
    }
    在JDK源码实现中,forName方法会调用Native方法forName0(),它在JVM中调用findClassFromClassLoader()加载Dog类,其原理和ClassLoader一样,将会触发Dog类的类构造器初始化,forName0方法声明如下:
    private static native Class<?> forName0(String name, boolean initialize, ClassLoader loader, Class<?> caller)
    其中initialize参数,用来告诉虚拟机是否需要对加载的类进行初始化,如果initialize为false,则不会进行初始化Dog类。
    Class clazz = Class.forName("zzzzzz.Dog", false, Dog.class.getClassLoader());

反射机制

反射机制reflect可以在运行期间获取类的字段、方法、父类和接口等信息。
1、获取类字段

Class class_dog = Dog.class;
Field[] fields = class_dog.getDeclaredFields();
for (Field field : fields) {
    System.out.println(field.getName());
}

2、获取类方法

Class class_dog = Dog.class;
Method[] methods = class_dog.getDeclaredMethods();
for (Method method : methods) {
    System.out.println(method);
}

通过method.invoke(obj, ...args)可以调用obj实例的method方法。

3、获取对应的实例构造器,并生成类实例

public class ClassTest {
    public static void main(String[] args) throws NoSuchMethodException {
        Class class_dog = Dog.class;
        Constructor constructor = class_dog.getConstructor(String.class, int.class);
        constructor.newInstance("Tom", 10);
    }
}

class Dog {
    private String name;
    private int age;

    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

如果没有显示的声明默认构造器,class_dog.getConstructor()会抛出NoSuchMethodException异常。

4、通过newInstance()方法生成类实例

Class class_dog = Dog.class;
Dog dog = class_dog.newInstance();

5、设置私有变量

Class class_dog = Dog.class;
Field name = class_dog.getDeclaredField("name");
name.setAccessible(true);
Dog dog = (Dog) class_dog.newInstance();
name.set(dog, "Tom");

6、获取私有变量

Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
return (Unsafe)f.get(null);

这种方式在使用Unsafe类进行黑魔法时经常用到。

反射的性能问题

Stackoverflow上,很多人觉得使用反射reflect会影响系统性能,主要有以下几点看法:
1、代码的验证防御逻辑过于复杂,本来这块验证时在链接阶段实现的,使用反射reflect时需要在运行时进行;
2、产生过多的临时对象,影响GC的消耗;
3、由于缺少上下文,导致不能进行更多的优化,如JIT;

不过现代JVM已经运行的足够快,我们应该把主要重心放在复杂的代码逻辑上,而不是一开始就进行各种性能优化。

1.    Java的反射机制

(1). 反射和类的关系

在程序运行状态中,对任意一个类 (指的是.class文件),都能够知道这个类所有的属性和方法

(2). 反射和类对象的关系

       反射对于某个类的一个对象,都能够调用它的任意一个方法属性

(3). Java反射机制 (基于 (1) 和(2))

[1]. 这种动态获取类的信息以及动态地调用类对象的方法或者属性功能称为Java语言的反射机制。

[2]. 通俗描述反射机制能动态获取对象的信息就称为反射

(4). Java反射机制的好处

极大地提高了应用程序扩展性

[1]. 通过多态提高程序的扩展性弊端

反射以前提高程序的扩展性是通过多态:将子类对象传递给父类引用来实现的

e.g. Animal ani=new Cat();

【缺点】必须要通过new来建立子类对象。子类对象必须写死在代码中。

[2]. 有了反射之后,可以通过反射技术省略掉new子类对象的一步

直接将子类对象类名以字符串的形式传递给反射技术框架由反射技术框架创建这个字符串代表的类的实例

(5). 反射的另类理解

[1]. 反射就是把Java中的类中的各个成分映射成相应的类

[2]. 一个Java类组成

成员变量成员方法构造方法修饰符、包等。

[3]. Java中的一个指定的类中的每一个成员都可以用相应的Java反射类API一个实例来表示

2.    Java反射的应用场景

1). 场景I

(1). 有应用程序,但没有源码

一个做好应用程序没有源代码。但是现在客户端想为这个应用程序添加自己的新功能,怎么办?存在以下两个需要解决的问题:

问题[1]. 也就是已经独立运行的App怎么识别客户端自定义的类?(因为App在开发的时候,并不知道客户端的自定义类是什么样子)

问题[2]. 如果App有办法识别这个了客户端自定义的类,如何使用这个类的对象呢?(因为App的源码不能改变)

(2). 问题I的解决办法

[1]. 通常一个应用程序为了扩展性,都会对外暴露一个接口

[2]. 这个接口由想扩展软件功能的客户端进行实现,之后,该应用软件可以使用符合自己条件的接口的子类对象

[3]. 以一个图的形式表现出来:

 

[4]. 解决完问题I,问题II产生了

{1}. 客户端创建了实现了App提供的对外接口Inter的实现子类DemoImpl

{2}. 出现的问题

此时:客户端想把Interin =new DemoImpl();加入到App中去。但是不能修改源码,那怎么能让App使用客户端建立的子对象呢?

(3). 问题II的解决办法

[1]. 通常应用程序在对外暴露接口之外,还对外提供配置文件。提供了配置文件之后,便可以把客户端自己建立的并且符合标准的类告知应用程序的App,而不用了解应用程序App里面是如何建立这个类的对象的。(从客户端的角度)

[2]{1}. 应用程序App应该做的就是采用IO流技术读取配置文件。这样,应用程序App便了解到了客户端自定义的类是什么。

[2]{2}. 然后应用程序App根据从IO流获取到的客户端自定义的类名字符串去寻找相应的.class类文件

如图:

 

[2]{3}一旦App找到客户端自定义的类文件DemoImpl.class,App可以将其加载到内存中,并通过字节码文件建立对象【这一步使用到了Java的反射技术!!!】

【分析为什么是反射机制】由于App一定是在客户端使用它之前编写成功,所以,App本身并不能知道未来客户端定义的类的名字,所以,一定是在运行时获取一个类的信息、获取这个类的对象并调用这个对象的有关方法,这便是Java的反射机制的定义

(4). 如何为自行开发的软件提高可扩展性

[1]. 编写软件的时候,要先通过反射技术去实现如何通过类名字符串获取该对象的实例,并且能进行方法调用

[2]. 然后对外暴露符合自身软件规范接口

[3]. 提供符合读写规则配置文件

【知识点回顾】

hibernate如何识别用户自定义的实体文件?Struts2如何识别用户自定义的Action的子类文件?【都是通过反射技术来实现的】

3.    反射机制的典型应用---Tomcat服务器

1). Tomcat服务器应用到的Java的三大技术

IO技术ServerSocket技术反射技术

2). Tomcat服务器大致处理用户应答的思路

(1). 对外暴露接口---->著名的Servlet (服务器脚本片段)

[1]. 对外提供接口的原因具体处理客户端应答请求的方式不一样的。应该根据具体的请求来进行具体的处理。向上抽取形成Servlet接口并提供给客户端使用。

[2]. 由开发者来实现Servlet接口定义的具体应答请求的处理方式

(2). 提供配置文件---->web.xml(WEB宏观部署描述文件)

每个Web应用程序都有自己的配置文件web.xml告知Tomcat服务器(App)有哪些用户自定义的Servlet实现类

3). Tomcat具体加载处理细节

(1). Tomcat (App)首先读取配置文件web.xml中配置好的Servlet的子类名称

(2). Tomcat根据读取到的客户端实现的Servlet子类的类名字符串去寻找对应的字节码文件。如果找到就将其加载到内存。

(3). Tomcat通过预先设置好的Java反射处理机制解析字节码文件并创建相应的实例对象。之后调用所需要的方法。

【最后】Tomcat一启动,用户自定义的Servlet的子类通过Tomcat内部的反射框架也随之运行。

4.    总结

(1). 反射技术提高了应用程序的可扩展性

(2). 反射技术应用起来非常简单。为用户和App之间提供可以交互的配置文件接口

【用户面对配置文件的难度<<面对源代码的难度】

(3). 反射一般是“接口+配置文件”这种开发形式十分常见

(4). 学习框架技术的要领:学习框架的用途配置文件

5、实例

(1)一个简单例子,在系统开发中,调试程序时我们需要知道类对象中各个成员的值,这明显便是toString()函数的功能,但在大型系统中,不可能每个实体类中都去重写toString(),这个时候“反射”便派上用场了,我们可以实现一个辅助类StringSupport,在StringSupport中通过反射获取所有成员值。

         一个简单得不敢直视的例子:

public String toString() {
 
		Field fields[] = this.getClass().getDeclaredFields();
 
		StringBuffer buf = new StringBuffer();
		int index = 0;
		for (Field field : fields) {
			if (!field.isAccessible()) {
				System.out.println("false");
				field.setAccessible(true);
			}
			try {
				if (index++ != 0)
					buf.append(",");
				buf.append(field.get(this));
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
 
		}
		return buf.toString();
}

纯示例,在实际实现时,要考虑集合对象等各种东西。不过,如果你碰巧有这种需求,只需借助apache common库中的lang即可,其已经有很好的实现。

(2)第二个小例子自然是动态代理了,在介绍Java的动态代理前,先来看看设计模式中的代理模式,代理模式的UML图如下,其思想是为目标对象提供一种代理以控制对这个对象的访问。主要应用于Client端不能直接访问目标对象的情形,通过在客户端和目标对象之间充当中介的代理对象,可以控制Client对目标对象访问,亦或是添加额外的处理逻辑,论坛中用户权限的控制就是一个典型的例子。

        Java的代理机制与其思想是一致的。只不过这里Proxy是Java为你动态生成的,这个过程借助java.lang.reflect.Proxy类与java.lang.reflect.InvocationHandler接口进行实现,InvocationHandler接口中invoke函数便是调用目标类实现的功能接口的地方,可以在这个地方进行访问控制,添加额外的处理逻辑。

        使用Java动态代理的方式有两种:

第一种:

(1)通过实现InvocationHandler接口创建自己的调用处理器(InvocationHandler);

(2)通过为Proxy类指定ClassLoader对象和一组interface(也即客户端要使用的功能接口)来创建动态代理类;

(3)通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;

(4)通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。

第二种:

通过Proxy类的newProxyInstance函数来简化第一种的流程,在newProxyInstance中包含了上面(2)(3)(4)三步所做的内容。

接下来,又是一个简单的例子。

功能接口:

public interface Chatroom {
	abstract public void createChat();
}

目标类:

public class ChatroomImpl implements Chatroom {
 
	public void createChat(){
		System.out.println("创建一个聊天室");
	}
}

InvocationHandler实现:

public class ChatInvocationHandler implements InvocationHandler {
 
	private static int count = 0;
 
	private Object target;
 
	public ChatInvocationHandler(Object target){
		this.target = target;
	}
 
	public void setTarget(Object target){
		this.target = target;
	}
 
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
 
		if(count++%2==0){
			return method.invoke(target, args);
		}
 
		System.out.println("屌丝不能创建聊天室");
 
		return null;
	}
 
}

两种调用方式:

(1)

ChatInvocationHandler chatHandler = new ChatInvocationHandler(
					new ChatroomImpl());
 
			Class pClass = Proxy.getProxyClass(Chatroom.class.getClassLoader(),
					new Class[] { Chatroom.class });
 
			Constructor constructor = pClass
					.getConstructor(new Class[] { InvocationHandler.class });
 
			Chatroom proxy = (Chatroom) constructor
					.newInstance(new Object[] { chatHandler });
 
			for (int i = 0; i &lt; 10; i++) {
				if (proxy != null)
					proxy.createChat();
			}

 (2)

ChatInvocationHandler chatHandler = new ChatInvocationHandler(
					new ChatroomImpl());
 
			Chatroom proxy = (Chatroom) Proxy.newProxyInstance(Chatroom.class
					.getClassLoader(), new Class[] { Chatroom.class },
					chatHandler);
 
			for (int i = 0; i &lt; 10; i++) {
				if (proxy != null)
					proxy.createChat();
			}

运行后,便可看到交错的“创建聊天室”与“屌丝不能创建聊天室”。

         暂到此,后续打算针对Spring AOP中动态代理的使用进行分析,此过程还会涉及Java字节码操纵利器”ASM”。Java的Proxy只支持目标类实现了接口的情况,对于没有接口的情况,Spring通过cglib库来生成动态代理类,cglib使用了asm的字节码操纵能力。

6、应用情景:

情景一:加载数据库驱动的时候

Class.forName的一个很常见的用法是在加载数据库驱动的时候。

如:

Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");  
Connection con=DriverManager.getConnection("jdbc:sqlserver://localhost:1433;DatabaseName==JSP","jph","jph");      

为什么在我们加载数据库驱动包的时候有的却没有调用newInstance( )方法呢?

即有的jdbc连接数据库的写法里是Class.forName(xxx.xx.xx);而有一些:Class.forName(xxx.xx.xx).newInstance(),为什么会有这两种写法呢? 

刚才提到,Class.forName("");的作用是要求JVM查找并加载指定的类,如果在类中有静态初始化器的话,JVM必然会执行该类的静态代码段。

而在JDBC规范中明确要求这个Driver类必须向DriverManager注册自己,即任何一个JDBCDriver的Driver类的代码都必须类似如下: 
public classMyJDBCDriver implements Driver {

          static{

                DriverManager.registerDriver(new MyJDBCDriver());

           }

 } 

  既然在静态初始化器的中已经进行了注册,所以我们在使用JDBC时只需要Class.forName(XXX.XXX);就可以了。

情景二:使用AIDL与电话管理Servic进行通信

Method method =Class.forName("Android.os.ServiceManager")

         .getMethod("getService",String.class);

// 获取远程TELEPHONY_SERVICE的IBinder对象的代理

IBinder binder =(IBinder) method.invoke(null, new Object[] { TELEPHONY_SERVICE});

// 将IBinder对象的代理转换为ITelephony对象

ITelephonytelephony = ITelephony.Stub.asInterface(binder);

// 挂断电话

telephony.endCall();

Refer:

[0] Class.forName()的作用与使用总结

http://blog.csdn.net/fengyuzhengfan/article/details/38086743

[1] 反射--01【反射机制】【反射的应用场景】【Tomcat服务器】

http://blog.csdn.net/benjaminzhang666/article/details/9408611

[2] Java那点事:反射机制杂谈

http://www.jmatrix.org/java/240.html

[3] Java反射机制的适用场景及其利与弊

http://blog.csdn.net/zolalad/article/details/29370565

[4] Class对象和Java反射机制

http://www.importnew.com/21235.html

[5] Class.forName 介绍

http://ludaojuan21.iteye.com/blog/243528

[6] Java深度历险(七)——Java反射与动态代理

http://www.infoq.com/cn/articles/cf-java-reflection-dynamic-proxy

[7] 说说Java反射机制

http://www.jianshu.com/p/1a21a9cb5bea#

© 著作权归作者所有

共有 人打赏支持
大数据之路
粉丝 1484
博文 516
码字总数 343984
作品 0
武汉
架构师
Android 4.4(KK)中利用APP打开关闭数据流量

在Android 4.4中,在app中打开或关闭数据流量 如果有这方面需求可以参考。 思路 利用JAVA的反射机制(Reflection),来调用CONNECTIVITY_SERVICE完成相关操作。 关于JAVA的反射机制,可以参考...

W_X
2014/12/17
0
4
Java基础之反射(非常重要)

反射是框架设计的灵魂 (使用的前提条件:必须先得到代表的字节码的Class,Class类用于表示.class文件(字节码)) 一、反射的概述 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道...

Java-老刘
05/15
0
0
【目录导航】JAVA零基础进阶之路

【JAVA零基础入门系列】(已完结)导航目录 Day1 开发环境搭建 Day2 Java集成开发环境IDEA Day3 Java基本数据类型 Day4 变量与常量 Day5 Java中的运算符 Day6 Java字符串 Day7 Java输入与输出...

MFrank
06/21
0
0
动态代理机制详解(JDK 和CGLIB,Javassist,ASM)

在运行时期可以按照Java虚拟机规范对class文件的组织规则生成对应的二进制字节码。当前有很多开源框架可以完成这些功能,如ASM,Javassist。 动态代理机制详解(JDK 和CGLIB,Javassist,ASM...

素雷
2017/10/19
0
0
平日看到的JAVA相关技术链接

开源技术框架类: menagerie (ZooKeeper-based Java Concurrency Libraries) https://github.com/openUtility/menagerie#readme fastjson (Fast是一个JSON的Processor,功能强大、易用、快速......

qingtian
2011/02/24
0
0
阿里历年经典Java面试题汇总

Volatile的特征: A、禁止指令重排(有例外) B、可见性 Volatile的内存语义: 当写一个volatile变量时,JMM会把线程对应的本地内存中的共享变量值刷新到主内存。 当读一个volatile变量时,J...

Java团长17
07/11
0
0
Java反射改变Android属性

Java反射改变Android属性 在某些情况下,Android体系里面的某些对象没有对外提供针对某个属性或者类,方法公开的get或者set方法,但是项目需要对这些需要修改和调整。就需要使用Java的反射机...

zhangphil
04/28
0
0
Hibernate properties详解

Hibernate properties Hibernate配置属性 属性名 用途 hibernate.dialect ;一个Hibernate Dialect类名允许Hibernate针对特定的关系数据库生成优化的SQL. 取值 full.classname.of.Dialect hi...

7788
2014/04/02
0
0
JAVA反射机制 访问对象私有变量和方法

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

梁某某
2013/10/16
0
1
模拟spring IOC 实现

利用java的反射和动态代理实现IOC 在Java中,其反射和动态代理机制极其强大,我们可以通过其反射机制在运行时获取信息。而代理是一种基本的设计模式,它是一种为了提供额外的或不同的操作而插...

candies
2014/03/02
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Android 获取各大音乐平台的真实下载地址

废话 电脑使用谷歌浏览器或者QQ浏览器的时候。。。。。。。说不清楚,还是看图吧 大概意思就是,只要网页上需要播放,只要能播放并且开始播放,这个过程就肯定会请求到相关的音乐资源,然后就...

她叫我小渝
31分钟前
0
0
shell中的函数、shell中的数组、告警系统需求分析

shell中的函数 格式: 格式: function f_name() { command } 函数必须要放在最前面 示例1(用来打印参数) 示例2(用于定义加法) 示例3(用于显示IP) shell中的数组 shell中的数组1 定义数...

Zhouliang6
今天
2
0
用 Scikit-Learn 和 Pandas 学习线性回归

      对于想深入了解线性回归的童鞋,这里给出一个完整的例子,详细学完这个例子,对用scikit-learn来运行线性回归,评估模型不会有什么问题了。 1. 获取数据,定义问题     没有...

wangxuwei
今天
1
0
MAC安装MAVEN

一:下载maven压缩包(Zip或tar可选),解压压缩包 二:打开终端输入:vim ~/.bash_profile(如果找不到该文件新建一个:touch ./bash_profile) 三:输入i 四:输入maven环境变量配置 MAVEN_HO...

WALK_MAN
今天
0
0
33.iptables备份与恢复 firewalld的9个zone以及操作 service的操作

10.19 iptables规则备份和恢复 10.20 firewalld的9个zone 10.21 firewalld关于zone的操作 10.22 firewalld关于service的操作 10.19 iptables规则备份和恢复: ~1. 保存和备份iptables规则 ~2...

王鑫linux
今天
2
0
大数据教程(2.11):keeperalived+nginx高可用集群搭建教程

上一章节博主为大家介绍了目前大型互联网项目的系统架构体系,相信大家应该注意到其中很重要的一块知识nginx技术,在本节博主将为大家分享nginx的相关技术以及配置过程。 一、nginx相关概念 ...

em_aaron
今天
1
0
Apache Directory Studio连接Weblogic内置LDAP

OBIEE默认使用Weblogic内置LDAP管理用户及组。 要整理已存在的用户及组,此前办法是导出安全数据,文本编辑器打开认证文件,使用正则表达式获取用户及组的信息。 后来想到直接用Apache Dire...

wffger
今天
2
0
HFS

FS,它是一种上传文件的软件。 专为个人用户所设计的 HTTP 档案系统 - Http File Server,如果您觉得架设 FTP Server 太麻烦,那么这个软件可以提供您更方便的档案传输系统,下载后无须安装,...

garkey
今天
1
0
Java IO类库之BufferedInputStream

一、BufferedInputStream介绍 /** * A <code>BufferedInputStream</code> adds * functionality to another input stream-namely, * the ability to buffer the input and to * sup......

老韭菜
今天
0
0
STM 32 窗口看门狗

http://bbs.elecfans.com/jishu_805708_1_1.html https://blog.csdn.net/a1985831055/article/details/77404131...

whoisliang
昨天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部