反射原理及动态代理模式(一)

背景

做Java或者Android开发的攻城狮们应该都听过反射(别想歪了,别开车,兄得)。或者根本就没听过,当有前辈提起说某个功能可以用反射实现,你试一下的时候,很多人和我一样可能就一脸懵逼了。这里我就把自己学习到了关于反射的知识,和大家分享一下。

 一、什么是反射

首先,什么是反射,我们为什么要用反射或者什么时候需要用反射呢?反射反射,关键就在于它那个反字上(不是射哦。sorry,又开车啦!),要了解反字,我们需要先知道什么是正,在Android开发中,我们正常的使用某个类时都必然是先知道这是什么类,他能做哪些事。然后我们再通过New的方式实例化一个该类的对象,然后再调用它的内部方法。而反射就完全相反,反射就是从一开始我们就不知道我们需要调用的对象是什么类,甚至都不知道它内部有哪些方法,所以也就不能够通过New的方式来创建一个实例对象了。这时候,我们就只能使用JDK提供给我们的反射API来进行反射调用。通俗的讲就是:反射是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整结构,并能够调用对应的方法。

Java是一个面向对象的语言,我们创建的每一个类都可以看作是一个Class对象。但是我们创建类时并没有显示的去写这个Class对象,那么这个Class对象是放在哪里的呢?当我们创建一个类后,会进行编译,编译器会将我们的类编译成Class文件,而这个Class文件的末尾就保存了这个Class对象,里面保存了这个类的原数据信息(如:类名、方法、变量、修饰符等等)。而我们所调用的方法、变量属性等都在这个Class对象中。反射中最关键的环节之一就是获取这个Class对象,那么Java中如何获取一个类的Class对象呢,获取到这个Class对象后我们又能干什么呢?

二、反射的基本使用

先来讲讲如何使用反射,然后我们再详细剖析为什么要使用反射。

(1)什么是 :正

一般情况下,我们开发时都是这样去创建某个实例对象,然后调用对象的方法完成某种功能,这种直接通过New的方式创建对象的方式就是前面我们说的正:已经知道这是个什么类了,而且对其内部构造一清二楚的情况下直接new对象,然后调用方法就可以完成功能了。

 //通常情况下我们这样去创建一个实例
 ReflectBean bean = new ReflectBean();
 bean.setName("大家好,我是韦小宝");

(2)什么是:反

对于攻城狮们来说,有一般情况就必然存在特殊情况。那么者个特殊情况就是,我们在代码编译期完全不知道将要实例化的对象是属于那个类,更不知道它的内部构造,所以也就不能够直接通过new的方式来创建对象,更不能像正常情况那样来调用对象的内部方法了。那么这时候贴心的JDK为我们提供了反射这种方式,来完成我们的工作。

(3)使用反射的前提

前面我们也说了,每一个类都可以看成是一个Class对象,而使用反射的前提就是获取这个Class对象。那么Java如何去获取一个类的Class对象呢?JDK为我们提供了三种方式。

 Class beanClass1 = ReflectBean.class;
 Class beanClass2 = bean.getClass();
 Class beanClass3 =  Class.forName("com.demo.singlecode.reflectdemo.Bean.ReflectBean");

 需要特别注意第三种获取Class对象的方式,这也是目前很多框架正在使用的方式,如果研究过Android源码的同学,应该也能经常看到这种用法。

如果只拿到Class对象对于调用者来说没有什么实质性意义,我们需要通过Class对象来获取到类对象才能够调用方法修改属性等。有人是不是又懵了,咋又是Class对象,又是类对象呢?大家可以这样来理解,Class对象只是用来保存这个类的信息,供虚拟机使用,同时我们可以通过这个Class对象创建类对象。而类对象可以看成是一个实体,能够实现某种功能,供调用者直接使用完成任务。

JDK为我们提供了两种通过Class对象来创建类对象的方式

扫描二维码关注公众号,回复: 9459059 查看本文章

1、通过Class对象的newInstance来获取类对象

 ReflectBean newBean = (ReflectBean) beanClass1.newInstance();
 newBean.setName("大家好,才是真的好");

2、通过Class对象获取到类的构造器来创建类对象

 /**
  * 获取这个Class对象所在类的所有构造函数的构造器
  */
Constructor[] constructors = beanClass3.getConstructors();
for(Constructor constructor :constructors){
    System.out.println(constructor);
}

 /**
  * 获取某个带参数的构造函数的构造器
  */
 try {
     Constructor<ReflectBean> constructor = beanClass3.getConstructor(String.class);
     System.out.println(constructor);
     //这里需要注意,构造函数里面的参数类型是什么类型,我们就传什么类型的Class,不需要进行转型等操作
     Constructor<ReflectBean> constructor2 = beanClass3.getConstructor(String.class,int.class);
     System.out.println(constructor2);
     /*
      *获取到构造器后,如何创建一个对象呢,Class对象可以通过newInstance的方式来创建类对象,构造器同样有类似的方法
      */
    ReflectBean bean1 = constructor2.newInstance("爱你呀",18);//需要传入这个构造器所对应的构造函数的实参
    System.out.println(bean1.getName());
 } catch (NoSuchMethodException e) {
     e.printStackTrace();
 } catch (InvocationTargetException e) {
     e.printStackTrace();
 }

//有人会说这句有问题啊,不是说不知道具体类对象是什么类型吗,怎么还直接强转上了呢?而且还直接调用了内部方法,那我还不如直接去new一个对象来的方便呢。有这想法的同学很棒,因为我这种写法是本末倒置的,只是为了演示如何去获取一个对象。
ReflectBean bean1 = constructor2.newInstance("爱你呀",18)

//反射到了这一步正确的应该是这样的,我们只知道创建出来的是个Object对象。
Object ocject = constructor2.newInstance("爱你呀",18)

  现在我们构造器也取到了,也能创建类对象了,我们要如何去调用其内部方法或者修改其内部属性呢?JDK同样为我们提供了API。

 /**
  *获取所有非私有方法
  */
 Method[] methods = beanClass3.getMethods();
 for(Method method :methods){
     System.out.println(" "+method.getName());
 }

 /**
  * 那么我想获取所有方法,包括私有方法怎么办呢?
  */
 Method[]  methods1 = beanClass3.getDeclaredMethods();
 for(Method method:methods1){
     System.out.println(" declare Method :"+method.getName());
 }

/*
  * 上面都是获取所有方法,那么想获取某个指定方法怎么办呢?

  * 第一个参数是,将要获取的方法的方法名,后面是这个方法所对应的参数列表中各个参数所对应的参数类型的Class
  */
 try {
     Method method = beanClass3.getMethod("setName", String.class);//指定的方法只能是非私有的方法
     System.out.println("single public method :"+method.getName());
     Method method1 = beanClass3.getDeclaredMethod("setCode");//指定的方法可以是私有方法
     System.out.println("single private method :"+method1.getName());

        现在方法我们也取到了,Object我们也取到了,那么如何调用这个方法呢,看过源码的应该知道,Method中有一个invoke方法,调用这个invoke方法我们就可以实现对当前Class所在的类中的方法的调用,这也是通过反射调用方法的最后一步

  
    Object object = beanClass3.newInstance();
     /**
      * 第一个参数是这个类对象的实例,后面是该方法所对应的实参
      */
     method.invoke(object,"求包养");
     /**
      * 如果该方法是私有方法我们必须加上setAccessible(true)打开访问权限
      */
     method1.setAccessible(true);
     method1.invoke(object);
 } catch (NoSuchMethodException e) {
     e.printStackTrace();
 } catch (InvocationTargetException e) {
     e.printStackTrace();
 }

 Java类对象中除了方法,还有字段(如变量、常量等)。如果我想访问或修改这些属性怎么办呢

 /**
  * 同理,这里只能够获取非私有的字段
  */
 Field[] fields = beanClass3.getFields();

 /**
  * 获取当前类对象的所有字段包括私有字段
  */
 Field[] fields1 = beanClass3.getDeclaredFields();

 try {
     /**
      * 获取指定的非私有字段,参数为字段名称
      */
     Field field = beanClass3.getField("name");

     /**
      * 获取指定的字段,包括私有字段,参数为字段名称
      */
     Field field1 = beanClass3.getDeclaredField("code");

     /**
      * 如果我想修改某个字段的值怎么办呢
      */
     Object object = beanClass3.newInstance();

     /**
      * 访问字段
      */
     field.set(object,"小慈");
     Object value = field.get(object);
     System.out.println("public field :"+value);
     /**
      * 如果该字段是私有字段,我们必须加上setAccessible(true)打开访问权限
      */
     field1.setAccessible(true);
     field1.set(object,18);
     Object value1= field1.get(object);
     System.out.println("private field :"+value1);
 } catch (NoSuchFieldException e) {
     e.printStackTrace();
 }

需要代码的小伙伴们可以关顾本人github

发布了29 篇原创文章 · 获赞 3 · 访问量 906

猜你喜欢

转载自blog.csdn.net/LVEfrist/article/details/90545567