美文网首页@IT·互联网Java学习笔记
深入剖析JAVA的反射机制

深入剖析JAVA的反射机制

作者: 起个名忒难 | 来源:发表于2017-05-06 15:33 被阅读156次
    定义

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

    Java反射提供的主要功能:
    • 在运行时判断任意一个对象所属的类
    • 在运行时构造任意一个类的对象
    • 在运行时判断任意一个类所具有的成员变量和方法
    • 在运行时调用任意一个对象的方法
    Java反射机制实现类:
    • Field类:代表类的成员变量
    • Method类:代表类的方法
    • Constructor类:代表类的构造方法
    • Array类:提供动态的创建数组
      • 以上四个类都位于java.lang.reflect 包中
    • Class类:代表一个类 , 位于java.lang 包中
    反射的使用

    在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对象。

    动态代理的使用步骤:

    • 创建被代理类的接口和实现类,因为动态代理是基于接口的。这也是动态代理的一个小小缺憾。
    • 创建实现InvocationHandler接口的类,并实现invoke()方法
    • 通过Proxy的静态方法newProxyInstance(ClassLoader loader ,Class[] interfaces ,InvocationHandler handler) 创建一个代理实例。
    • 通过上面创建的代理实例,调用方法

    至此,End!



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

    相关文章

      网友评论

        本文标题:深入剖析JAVA的反射机制

        本文链接:https://www.haomeiwen.com/subject/lyxjtxtx.html