Java反射以及动态代理(上)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java反射以及动态代理(上)相关的知识,希望对你有一定的参考价值。

  在常用的各种框架中,反射与动态代理很常见,也很重要。本篇就对这一小节的内容基础性地总结。

  首先需要了解什么是类型信息,以及RTTI与反射的关系与区别。

  Java中,使用Class对象来表示所有类的对象。利用Class对象来获取类中的成员变量,构造函数以及方法,这些内容我们称之为类型信息。RTTI的含义是,在运行时识别一个对象的类型,但有一个前提,就是类型在编译时必须已知,这样才能用RTTI识别,并利用这些信息做一些有用的事情。但是如果在编译时,程序没有办法获知到这个对象所属的类,怎样才能使用这个类呢?答案是,我们可以在运行时,让JVM加载这个类的Class对象。由于没有经过编译期的检查,所以这里在使用之前,JVM也要简单地检查这个对象,看它属于哪个特定的类,而后加载这个类的Class对象。所以RTTI与反射的区别就在于,前者是在编译时打开和检查.class文件,运行时加载所需的类的Class对象,而后者则是在运行时打开和检查.class文件,并加载所需要类的Class对象。

  举个例子:

class Test{
    public static void main(String[] args){
        new Dev();
    }
}

在对它进行编译时,如果包下没有Dev这个类,那么编译会报错:

C:\Users\BruceChan\Desktop\test>javac Test.java
Test.java:3: 错误: 找不到符号
                new Dev();
                    ^
  符号:   类 Dev
  位置: 类 Test
1 个错误

但如果我们修改为反射的方式:

class Test{
    public static void main(String[] args){
        try{
            Class.forName("Dev");
        }catch(ClassNotFoundException e){
            System.out.print(e.toString());
        }    
    }
}

对其进行编译时会发现,即便在没有Dev这个类存在的情况下,编译还是会顺利进行,因为它是在运行时,用到这个Dev类时,才会加载这个类。

---------------------------------------------------------------------------------->

  接下来,这里用代码的形式小节一下反射技术中,常用的套路。反射中,有三种方法可以获得类型的Class对象:

try {
    Class cls1 = Class.forName("com.changjiang.test.RFP01.testReflect.MyClass");
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}
MyClass myClass
= new MyClass(); Class cls2 = myClass.getClass(); Class cls3 = MyClass.class;

通过该类的Class对象,可以获得它的属性,方法,构造函数,这里只说一下它的方法调用,首先给出原始类:

package com.changjiang.test.RFP01.testReflect;

import java.util.Date;

public class MyClass {
    private int myInt;
    private String myString;

    public MyClass(){

    }

    public MyClass(int a){
        this.myInt = a;
        System.out.println(a);
    }

    public void Method2Void(){

    }

    public int Method2Int(){
        System.out.println("Method2Int has run");
        return 0;
    }

    public String Method2String(){
        return "";
    }

    public Object Method2Object() {
        return new Date();
    }

    public void Method3Param(int a, String b){
        System.out.println("Method3Param has run with param-a:" + a + " and param-b:" + b);
    }
}

再给出几个方法的调用以及输出:

package com.changjiang.test.RFP01;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import com.changjiang.test.RFP01.testReflect.MyClass;

public class Main {
    public static void main(String[] args) {
        MyClass myClass = new MyClass();
        Class cls = myClass.getClass();
        try{  
             Method m = cls.getMethod("Method2Int", new Class[]{});//获取一个类的方法  
             m.invoke(myClass, new Object[]{});//精髓所在,调用这个类的方法  
             
             Method m2 = cls.getMethod("Method3Param", new Class[]{int.class, String.class});  
             m2.invoke(myClass, new Object[]{5, "fake"});//调用这个类的方法,带参数的  
             
             Constructor<MyClass> cons = cls.getConstructor(new Class[]{int.class});
             cons.newInstance(47);
         }catch(Exception e){  
             e.printStackTrace();  
         }  
         Method[] ms = cls.getMethods();  
         for(Method m:ms){  
             System.out.print(m.getName()+"\t");  
         }
    }
}

output:

Method2Int has run
Method3Param has run with param-a:5 and param-b:fake
47
Method2Int Method3Param Method2String Method2Object Method2Void wait wait wait equals toString hashCode getClass notify notifyAll

 ---------------------------------------------------------------------------------->

  OK,说到这里,我们该了解一下代理模式。清楚之后,我们再看动态代理。代理模式,简单地说,就是一个接口,两个实现。实现一中我们需要做一些事情,在实现二中,我们不仅要做实现一中的事情,而且可以在此之前与之后都添加我们想要做的别的事情。具体内容看看代码。

package com.changjiang.test.RFP01.testReflect;

public class SampleProxy {
    public static void main(String[] args) {
        new BaseSample1().doSomething();
        System.out.println("--------------------------------");
        new ProxySample(new BaseSample1()).doSomething();;
    }
}

interface BaseInterface{
    public void doSomething();
}

class BaseSample1 implements BaseInterface{
    public void doSomething() {
        System.out.println("BaseSample1");
    }
}

class ProxySample implements BaseInterface{
    private BaseInterface bi;
    public ProxySample(BaseInterface bi) {
        this.bi = bi;
    }
    public void doSomething() {
        System.out.println("before");
        bi.doSomething();
        System.out.println("after");
    }
}

这里的接口,被两个类实现,一个是原生类,另一个是代理类,而代理类中不但实现了这个接口,还需要将代理类通过构造器赋给自己的属性中,而后在接口实现方法中使用原始类的方法。

 ---------------------------------------------------------------------------------->

接下来看看动态代理,主要是如何实现动态代理以及核心类Proxy与其方法newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InovationHandler h)中几个参数的详细含义与作用剖析。(下篇)

以上是关于Java反射以及动态代理(上)的主要内容,如果未能解决你的问题,请参考以下文章

Java反射以及动态代理(下)-- 源码

反射真的慢么?动态代理会创建很多临时class?

Java高级之注解反射

java反射机制与动态代理

Java 动态代理是基于什么原理(还没整理完)

Java设计模式之动态代理