Java 反射机制详解

时间:2022-04-28
本文章向大家介绍Java 反射机制详解,主要内容包括Class类简介:、1.    Java的反射机制、2.    Java反射的应用场景、3.    反射机制的典型应用---Tomcat服务器、4.    总结、5、实例、6、应用情景:、Refer:、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

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#