深入剖析JAVA的反射机制
定义
在Java 的运行时环境中,对于任意的一个类,都能够知道这个类的所有属性和方法,对于任意一个对象都能够调用任意的方法和属性,这种动态的获取类的信息以及动态的调用对象方法的功能,称之为反射(注意是运行状态,非编译)。
Java反射提供的主要功能:
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的方法
Java反射机制实现类:
- Field类:代表类的成员变量
- Method类:代表类的方法
- Constructor类:代表类的构造方法
- Array类:提供动态的创建数组
- 以上四个类都位于java.lang.reflect 包中
- Class类:代表一个类 , 位于java.lang 包中
反射的使用
在JAVA中,无论一个类生成多少个对象,这些对象都会对应同一个Class对象 。要想使用反射,首先需要获得待处理的类或者对象所对应的Class对象。该Class对象是在类被加载之后,由系统自动生成的。获取该Class对象有三种方式:
- 使用Class类的静态方法forName: Class.forName("java.lang.String") ,参数必须添加完整的路径,包括包名。
- 使用类的.class 语法: String.class
- 使用对象的getClass()方法: String s = "reflect" ;Class<?> clazz = s.getClass()
以上便是获取Class对象的三种方式,要使用反射,必须先获取Class对象,一旦获取了Class对象之后,就可以调用Class对象的方法来获得对象和该类的真实信息。
反射相关API ,使用反射生成并操作对象
Class对象可以获得带操作类里的方法(Method),构造器(constructor),Field 。要操作方法需要获得Mehtod对象,要操作构造器需要获得Constructor对象,要操作属性需要获得Field对象。
下面将会结合具体的实例,来说明生成Class对象的三种方式是如何使用的:
反射初体验,通过第一种方式,获取Class对象,并打印java.lang.String的所有声明方法:
Class<?> aClass = Class.forName("java.lang.String"); //获取String类的Class对象
Method[] methdos = aClass.getDeclaredMethods(); //获取所有的声明方法
for(Method m : methdos) //循环遍历Method数组
System.out.println(m); //打印方法名称,包含了private 方法
运行结果:
部分运行结果截图通过反射调用某个类的方法
public class InvokeTest {
public int add(int a , int b){
return a +b ;
}
public String echo(String message){
return "Hello :" +message ;
}
public static void main(String[] args) throws Exception {
//通过new的方式调用
//InvokeTest invokeTest = new InvokeTest();
//System.out.println(invokeTest.add(1 , 2));
//System.out.println(invokeTest.echo("Tom"));
//通过反射调用方法
//通过内置语法,获取Class对象
Class<InvokeTest> invokeTestClass = InvokeTest.class;
//创建Class对象表示的类的实例 ,调用Class对象的newInstance()方法
InvokeTest invokeTest = invokeTestClass.newInstance();
//获取待操作类的Method对象。传入方法名字,和方法的参数类型
Method add = invokeTestClass.getDeclaredMethod("add", new Class[]{int.class, int.class});
//使用这种方式也是可以得,因为getDeclaredMethod()第二个参数是一个可变参数
//Method add = InvokeTestClass.getDeclaredMethod("add" , int.class , int.class)
Method echo = invokeTestClass.getDeclaredMethod("echo", new Class[]{String.class});
//调用Method对象的invoke方法,表示含义为,调用invokeTest对象的add/echo方法,并传出参数值
Object addResult = add.invoke(invokeTest, new Object[]{1, 2});
Object echoResult = echo.invoke(invokeTest, new Object[]{"Tome"});
//打印输出的结果
System.out.println((Integer) addResult);
System.out.println((String) echoResult);
//运行结果
//3
//Hello :Tome
}
}
通过反射调用某个类的构造方法 ,创建对象
public class ConstructorTest {
public static void main(String[] args) throws Exception {
User user = new User() ;
//使用第三种获取Class对象的方式。使用getClass()方法
Class<?> userClass = user.getClass();
//调用无参构造方法,创建对象实例,反射无法得知返回的类型,需要强制类型转换
Constructor<?> constructor = userClass.getConstructor();
User user1 = (User)constructor.newInstance();
//上面两行代码可以替换为下面一行代码
//User u = (User)userClass.newInstance();
user1.setAge(18);
user1.setName("zhangsan");
System.out.println(user1.getName() +"," + user1.getAge()); //zhangsan,18
System.out.println("--------------------------");
//调用有参构造方法,下面两行方法中的参数要一一对应
Constructor<?> constructor1 = userClass.getConstructor(new Class[]{String.class, int.class});
User user2 = (User)constructor1.newInstance(new Object[]{"lisi", 30});
System.out.println(user2.getName() +"," +user2.getAge());
}
}
class User{
private String name ;
private int age ;
public User(){}
public User(String name , int age ){
this.name = name ;
this.age = age ;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
通过反射生成对象有两种方式:
1.使用Class对象的newInstance()方法,这种方法要求对象的对应类有无参构造方法
2.先使用Class对象获得Constructor()对象,在调用Constructor对象的newInstance()方法。这种方式可以调用无参的构造方法,也可以调用有参的构造方法。但是Constructor()和newInstance()中的参数要一一对应。可参考上例。
通过反射操作类的属性
public class FieldTest {
public static void main(String[] args) throws Exception {
//获取Class对象,并生成实例对象
Class<?> personClass = Person.class;
Object p = personClass.newInstance();
//通过Class对象的getDeclaredField()方法,获取类的field
Field name = personClass.getDeclaredField("name");
Field age = personClass.getDeclaredField("age");
//name字段为private 修饰,使用setAccessible()方法,取消访问权限检查
name.setAccessible(true);
//调用set()方法,设置p对象的值
name.set(p , "zhangsan");
age.set(p , 18);
//打印值
System.out.println(p); //name: zhangsan, age: 18
}
}
class Person{
private String name ;
public int age ;
@Override
public String toString() {
return "name: "+name +", age: "+age ;
}
}
通过setAccessible()方法,可以通过反射访问类中的私有属性和私有方法,通常不建议这样使用,因为它打破了封装的特性。但是在一些特定情况中,还是需要使用这种方式的。
通过反射操作数组
public class ArrayTest {
public static void main(String[] args) {
//创建一个类型为String, 长度为10 的数组
Object o = Array.newInstance(String.class, 10);
//为数组赋值
Array.set(o , 2 ,"hello");
Array.set(o , 5 ,"world");
//获取数组的值,并打印
System.out.println(Array.get(o , 2)); //hello
System.out.println(Array.get(o , 5)); //world
}
}
上面简单介绍了几个常用的方法,旨在说明反射对方法,构造方法,字段,数组中的调用方式,除了上面的这些,还可以获取父类,接口,包 ,全部的方法声明,全部的field声明等与类相关的全部信息。想要了解更多内容,请自行查阅API。
通过反射生成动态代理
先看代码,稍后再解释动态代理的实现过程,这个地方可能有点难理解,多看几遍就可以了。
/*定义接口*/
public interface Subject {
public void sayHello(String name);
}
/*定义接口的实现类,也就是真实的代理调用对象*/
public class RealSubject implements Subject{
public void sayHello(String name ) {
System.out.println("hello : " + name );
}
}
/*定义动态代理类*/
public class DynamicProxy implements InvocationHandler{
private Object obj = null ; //此处定义了Object类型,表示可以代理任何对象
public DynamicProxy(Object obj){
this.obj = obj ;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
//调用obj对象的method方法,并传入参数args,本例中相当于调用obj对象的sayHello()方法,并传入Tom 参数
method.invoke(obj , args) ;
after();
return null ; //Subject接口中方法没有返回值,返回Null
}
//此方法表示代理的额外操作。
private void before(){
System.out.println("before proxy");
}
//此方法表示代理的额外操作
private void after(){
System.out.println("after proxy");
}
}
客户端调用:
public class Client {
public static void main(String[] args) {
//需要代理的真实对象
RealSubject realSubject = new RealSubject() ;
//创建代理类,并将真实的代理对象,传入构造方法中
InvocationHandler handler = new DynamicProxy(realSubject);
//运行时动态生成的代理实例,实现了RelSubject类所实现的接口,所以可以强制转换为Subject类型,第一个参数是类加载器,第二个参数是代理类实现的接口,第三个参数是处理实际工作的handler实例
Subject subject = (Subject)Proxy.newProxyInstance(realSubject.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), handler);
//调用接口中声明的方法 ,流程跳转到handler的invoke()方法中执行。
subject.sayHello("Tom");
}
}
总结:
Java的动态代理类位于java.lang.reflect包下,涉及到两个类:
interface InvocationHandler: 该接口中只定义了一个方法-invoke(),使用动态代理类时,必须实现这个接口。
Porxy:该类即为动态代理类,常用newProxyInstance()来生成代理实例,它不会做什么实质性的工作,实质性的工作是由与之关联的InvocationHandle来处理的。也就是说生成的代理对象都有一个与之关联的InvocationHandler对象。
动态代理的使用步骤:
- 创建被代理类的接口和实现类,因为动态代理是基于接口的。这也是动态代理的一个小小缺憾。
- 创建实现InvocationHandler接口的类,并实现invoke()方法
- 通过Proxy的静态方法newProxyInstance(ClassLoader loader ,Class[] interfaces ,InvocationHandler handler) 创建一个代理实例。
- 通过上面创建的代理实例,调用方法
至此,End!
少年听雨歌楼上,红烛昏罗帐。
壮年听雨客舟中,江阔云低,断雁叫西风。
感谢支持!
---起个名忒难