@IT·互联网Java学习笔记

深入剖析JAVA的反射机制

2017-05-06  本文已影响156人  起个名忒难
定义

在Java 的运行时环境中,对于任意的一个类,都能够知道这个类的所有属性和方法,对于任意一个对象都能够调用任意的方法和属性,这种动态的获取类的信息以及动态的调用对象方法的功能,称之为反射(注意是运行状态,非编译)。

Java反射提供的主要功能:
Java反射机制实现类:
反射的使用

在JAVA中,无论一个类生成多少个对象,这些对象都会对应同一个Class对象 。要想使用反射,首先需要获得待处理的类或者对象所对应的Class对象。该Class对象是在类被加载之后,由系统自动生成的。获取该Class对象有三种方式:

  1. 使用Class类的静态方法forName: Class.forName("java.lang.String") ,参数必须添加完整的路径,包括包名。
  2. 使用类的.class 语法: String.class
  3. 使用对象的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对象。

动态代理的使用步骤:

至此,End!



少年听雨歌楼上,红烛昏罗帐。  
壮年听雨客舟中,江阔云低,断雁叫西风。
感谢支持!
                                        ---起个名忒难

上一篇下一篇

猜你喜欢

热点阅读