乐趣区

关于反射:反射和动态代理

作者:辞慾
链接:https://zhuanlan.zhihu.com/p/…
起源:知乎
著作权归作者所有。商业转载请分割作者取得受权,非商业转载请注明出处。

简介

什么是反射

反射 (Reflection) 是 Java 程序开发语言的特色之一,它容许运行中的 Java 程序获取本身的信息,并且能够操作类或对象的外部属性。

通过反射机制,能够在运行时拜访 Java 对象的属性,办法,构造方法等。

反射的利用场景

反射的次要利用场景有:

  • 开发通用框架 – 反射最重要的用处就是开发各种通用框架。很多框架(比方 Spring)都是配置化的(比方通过 XML 文件配置 JavaBean、Filter 等),为了保障框架的通用性,它们可能须要依据配置文件加载不同的对象或类,调用不同的办法,这个时候就必须用到反射——运行时动静加载须要加载的对象。
  • 动静代理 – 在切面编程(AOP)中,须要拦挡特定的办法,通常,会抉择动静代理形式。这时,就须要反射技术来实现了。
  • 注解 – 注解自身仅仅是起到标记作用,它须要利用反射机制,依据注解标记去调用注解解释器,执行行为。如果没有反射机制,注解并不比正文更有用。
  • 可扩展性性能 – 应用程序能够通过应用齐全限定名称创立可扩展性对象实例来应用内部的用户定义类。

反射的毛病

  • 性能开销 – 因为反射波及动静解析的类型,因而无奈执行某些 Java 虚拟机优化。因而,反射操作的性能要比非反射操作的性能要差,应该在性能敏感的应用程序中频繁调用的代码段中防止。
  • 毁坏封装性 – 反射调用办法时能够疏忽权限查看,因而可能会毁坏封装性而导致平安问题。
  • 外部曝光 – 因为反射容许代码执行在非反射代码中非法的操作,例如拜访公有字段和办法,所以反射的应用可能会导致意想不到的副作用,这可能会导致代码性能反常并可能毁坏可移植性。反射代码突破了形象,因而可能会随着平台的降级而扭转行为。

反射机制

类加载过程

类加载的残缺过程如下:

  1. 在编译时,Java 编译器编译好 .java 文件之后,在磁盘中产生 .class 文件。.class 文件是二进制文件,内容是只有 JVM 可能辨认的机器码。
  2. JVM 中的类加载器读取字节码文件,取出二进制数据,加载到内存中,解析.class 文件内的信息。类加载器会依据类的全限定名来获取此类的二进制字节流;而后,将字节流所代表的动态存储构造转化为办法区的运行时数据结构;接着,在内存中生成代表这个类的 java.lang.Class 对象。
  3. 加载完结后,JVM 开始进行连贯阶段(蕴含验证、筹备、初始化)。通过这一系列操作,类的变量会被初始化。

Class 对象

要想应用反射,首先须要取得待操作的类所对应的 Class 对象。Java 中,无论生成某个类的多少个对象,这些对象都会对应于同一个 Class 对象。这个 Class 对象是由 JVM 生成的,通过它可能获悉整个类的构造。所以,java.lang.Class 能够视为所有反射 API 的入口点。

反射的实质就是:在运行时,把 Java 类中的各种成分映射成一个个的 Java 对象。

举例来说,如果定义了以下代码:

User user = new User(); 

步骤阐明:

  1. JVM 加载办法的时候,遇到 new User(),JVM 会依据 User 的全限定名去加载 User.class。
  2. JVM 会去本地磁盘查找 User.class 文件并加载 JVM 内存中。
  3. JVM 通过调用类加载器主动创立这个类对应的 Class 对象,并且存储在 JVM 的办法区。留神:一个类有且只有一个 Class 对象。

应用反射

java.lang.reflect 包

Java 中的 java.lang.reflect 包提供了反射性能。java.lang.reflect 包中的类都没有 public 构造方法。

java.lang.reflect 包的外围接口和类如下:

  • Member 接口 – 反映对于单个成员 (字段或办法) 或构造函数的标识信息。
  • Field 类 – 提供一个类的域的信息以及拜访类的域的接口。
  • Method 类 – 提供一个类的办法的信息以及拜访类的办法的接口。
  • Constructor 类 – 提供一个类的构造函数的信息以及拜访类的构造函数的接口。
  • Array 类 – 该类提供动静地生成和拜访 JAVA 数组的办法。
  • Modifier 类 – 提供了 static 办法和常量,对类和成员拜访修饰符进行解码。
  • Proxy 类 – 提供动静地生成代理类和类实例的静态方法。

取得 Class 对象

取得 Class 的三种办法:

(1)应用 Class 类的 forName 静态方法

示例:

package io.github.dunwu.javacore.reflect;
public class ReflectClassDemo01 {public static void main(String[] args) throws ClassNotFoundException {Class c1 = Class.forName("io.github.dunwu.javacore.reflect.ReflectClassDemo01");
        System.out.println(c1.getCanonicalName());
        Class c2 = Class.forName("[D");
        System.out.println(c2.getCanonicalName());
        Class c3 = Class.forName("[[Ljava.lang.String;");
        System.out.println(c3.getCanonicalName());
    }
}
//Output:
//io.github.dunwu.javacore.reflect.ReflectClassDemo01
//double[]
//java.lang.String[][]

应用类的齐全限定名来反射对象的类。常见的利用场景为:在 JDBC 开发中罕用此办法加载数据库驱动。

(2)间接获取某一个对象的 class

示例:

public class ReflectClassDemo02 {public static void main(String[] args) {
        Boolean b;
        // Class c = b.getClass(); // 编译谬误
        Class c1 = Boolean.class;
        System.out.println(c1.getCanonicalName());
        Class c2 = java.io.PrintStream.class;
        System.out.println(c2.getCanonicalName());
        Class c3 = int[][][].class;
        System.out.println(c3.getCanonicalName());
    }
}
//Output:
//boolean
//java.io.PrintStream
//int[][][]

(3)调用 Object 的 getClass 办法,示例:

Object 类中有 getClass 办法,因为所有类都继承 Object 类。从而调用 Object 类来获取

示例:

package io.github.dunwu.javacore.reflect;
import java.util.HashSet;
import java.util.Set;
public class ReflectClassDemo03 {
    enum E {A, B}
    public static void main(String[] args) {Class c = "foo".getClass();
        System.out.println(c.getCanonicalName());
        Class c2 = ReflectClassDemo03.E.A.getClass();
        System.out.println(c2.getCanonicalName());
        byte[] bytes = new byte[1024];
        Class c3 = bytes.getClass();
        System.out.println(c3.getCanonicalName());
        Set<String> set = new HashSet<>();
        Class c4 = set.getClass();
        System.out.println(c4.getCanonicalName());
    }
}
//Output:
//java.lang.String
//io.github.dunwu.javacore.reflect.ReflectClassDemo.E
//byte[]
//java.util.HashSet

判断是否为某个类的实例

判断是否为某个类的实例有两种形式:

  1. 用 instanceof 关键字
  2. 用 Class 对象的 isInstance 办法(它是一个 Native 办法)

示例:

public class InstanceofDemo {public static void main(String[] args) {ArrayList arrayList = new ArrayList();
        if (arrayList instanceof List) {System.out.println("ArrayList is List");
        }
        if (List.class.isInstance(arrayList)) {System.out.println("ArrayList is List");
        }
    }
}
//Output:
//ArrayList is List
//ArrayList is List

创立实例

通过反射来创立实例对象次要有两种形式:

  1. 用 Class 对象的 newInstance 办法。
  2. 用 Constructor 对象的 newInstance 办法。

示例:

public class NewInstanceDemo {public static void main(String[] args)
            throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Class<?> c1 = StringBuilder.class;
        StringBuilder sb = (StringBuilder) c1.newInstance();
        sb.append("aaa");
        System.out.println(sb.toString());
        // 获取 String 所对应的 Class 对象
        Class<?> c2 = String.class;
        // 获取 String 类带一个 String 参数的结构器
        Constructor constructor = c2.getConstructor(String.class);
        // 依据结构器创立实例
        String str2 = (String) constructor.newInstance("bbb");
        System.out.println(str2);
    }
}
//Output:
//aaa
//bbb

Field

Class 对象提供以下办法获取对象的成员(Field):

  1. getFiled – 依据名称获取私有的(public)类成员。
  2. getDeclaredField – 依据名称获取已申明的类成员。但不能失去其父类的类成员。
  3. getFields – 获取所有私有的(public)类成员。
  4. getDeclaredFields – 获取所有已申明的类成员。

示例如下:

public class ReflectFieldDemo {
    class FieldSpy<T> {public Boolean[][] b = {{false, false}, {true, true}};
        public String name = "Alice";
        public List<Integer> list;
        public T val;
    }
    public static void main(String[] args) throws NoSuchFieldException {Field f1 = FieldSpy.class.getField("b");
        System.out.format("Type: %s%n", f1.getType());
        Field f2 = FieldSpy.class.getField("name");
        System.out.format("Type: %s%n", f2.getType());
        Field f3 = FieldSpy.class.getField("list");
        System.out.format("Type: %s%n", f3.getType());
        Field f4 = FieldSpy.class.getField("val");
        System.out.format("Type: %s%n", f4.getType());
    }
}
//Output:
//Type: class [[Z
//Type: class java.lang.String
//Type: interface java.util.List
//Type: class java.lang.Object

Method

Class 对象提供以下办法获取对象的办法(Method):

  • getMethod – 返回类或接口的特定办法。其中第一个参数为办法名称,前面的参数为办法参数对应 Class 的对象。
  • getDeclaredMethod – 返回类或接口的特定申明办法。其中第一个参数为办法名称,前面的参数为办法参数对应 Class 的对象。
  • getMethods – 返回类或接口的所有 public 办法,包含其父类的 public 办法。
  • getDeclaredMethods – 返回类或接口申明的所有办法,包含 public、protected、默认(包)拜访和 private 办法,但不包含继承的办法。

获取一个 Method 对象后,能够用 invoke 办法来调用这个办法。

invoke 办法的原型为:

public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException

示例:

public class ReflectMethodDemo {public static void main(String[] args)
            throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        // 返回所有办法
        Method[] methods1 = System.class.getDeclaredMethods();
        System.out.println("System getDeclaredMethods 清单(数量 =" + methods1.length + "):");
        for (Method m : methods1) {System.out.println(m);
        }
        // 返回所有 public 办法
        Method[] methods2 = System.class.getMethods();
        System.out.println("System getMethods 清单(数量 =" + methods2.length + "):");
        for (Method m : methods2) {System.out.println(m);
        }
        // 利用 Method 的 invoke 办法调用 System.currentTimeMillis()
        Method method = System.class.getMethod("currentTimeMillis");
        System.out.println(method);
        System.out.println(method.invoke(null));
    }
}

Constructor

Class 对象提供以下办法获取对象的构造方法(Constructor):

  • getConstructor – 返回类的特定 public 构造方法。参数为办法参数对应 Class 的对象。
  • getDeclaredConstructor – 返回类的特定构造方法。参数为办法参数对应 Class 的对象。
  • getConstructors – 返回类的所有 public 构造方法。
  • getDeclaredConstructors – 返回类的所有构造方法。

获取一个 Constructor 对象后,能够用 newInstance 办法来创立类实例。

示例:

public class ReflectMethodConstructorDemo {public static void main(String[] args)
            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {Constructor<?>[] constructors1 = String.class.getDeclaredConstructors();
        System.out.println("String getDeclaredConstructors 清单(数量 =" + constructors1.length + "):");
        for (Constructor c : constructors1) {System.out.println(c);
        }
        Constructor<?>[] constructors2 = String.class.getConstructors();
        System.out.println("String getConstructors 清单(数量 =" + constructors2.length + "):");
        for (Constructor c : constructors2) {System.out.println(c);
        }
        System.out.println("====================");
        Constructor constructor = String.class.getConstructor(String.class);
        System.out.println(constructor);
        String str = (String) constructor.newInstance("bbb");
        System.out.println(str);
    }
}

Array

数组在 Java 里是比拟非凡的一种类型,它能够赋值给一个对象援用。上面咱们看一看利用反射创立数组的例子:

public class ReflectArrayDemo {public static void main(String[] args) throws ClassNotFoundException {Class<?> cls = Class.forName("java.lang.String");
        Object array = Array.newInstance(cls, 25);
        // 往数组里增加内容
        Array.set(array, 0, "Scala");
        Array.set(array, 1, "Java");
        Array.set(array, 2, "Groovy");
        Array.set(array, 3, "Scala");
        Array.set(array, 4, "Clojure");
        // 获取某一项的内容
        System.out.println(Array.get(array, 3));
    }
}
//Output:
//Scala

其中的 Array 类为 java.lang.reflect.Array 类。咱们通过 Array.newInstance 创立数组对象,它的原型是:

public static Object newInstance(Class<?> componentType, int length)
    throws NegativeArraySizeException {return newArray(componentType, length);
}

动静代理

动静代理是反射的一个十分重要的利用场景。动静代理常被用于一些 Java 框架中。例如 Spring 的 AOP,Dubbo 的 SPI 接口,就是基于 Java 动静代理实现的。

动态代理

动态代理其实就是指设计模式中的代理模式。
代理模式为其余对象提供一种代理以管制对这个对象的拜访。

Subject 定义了 RealSubject 和 Proxy 的公共接口,这样就在任何应用 RealSubject 的中央都能够应用 Proxy。

abstract class Subject {public abstract void Request();
}

RealSubject 定义 Proxy 所代表的实在实体。

class RealSubject extends Subject {
    @Override
        public void Request() {System.out.println("实在的申请");
    }
}

Proxy 保留一个援用使得代理能够拜访实体,并提供一个与 Subject 的接口雷同的接口,这样代理就能够用来代替实体。

class Proxy extends Subject {
    private RealSubject real;
    @Override
        public void Request() {if (null == real) {real = new RealSubject();
        }
        real.Request();}
}

阐明:
动态代理模式诚然在拜访无法访问的资源,加强现有的接口业务性能方面有很大的长处,然而大量应用这种动态代理,会使咱们零碎内的类的规模增大,并且不易保护;并且因为 Proxy 和 RealSubject 的性能实质上是雷同的,Proxy 只是起到了中介的作用,这种代理在零碎中的存在,导致系统结构比拟臃肿和涣散。

动静代理

为了解决动态代理的问题,就有了创立动静代理的想法:

在运行状态中,须要代理的中央,依据 Subject 和 RealSubject,动静地创立一个 Proxy,用完之后,就会销毁,这样就能够防止了 Proxy 角色的 class 在零碎中繁杂的问题了。

Java 动静代理基于经典代理模式,引入了一个 InvocationHandler,InvocationHandler 负责对立治理所有的办法调用。

动静代理步骤:

  1. 获取 RealSubject 上的所有接口列表;
  2. 确定要生成的代理类的类名,默认为:com.sun.proxy.$ProxyXXXX;
  3. 依据须要实现的接口信息,在代码中动态创建 该 Proxy 类的字节码;
  4. 将对应的字节码转换为对应的 class 对象;
  5. 创立 InvocationHandler 实例 handler,用来解决 Proxy 所有办法调用;
  6. Proxy 的 class 对象 以创立的 handler 对象为参数,实例化一个 proxy 对象。

从下面能够看出,JDK 动静代理的实现是基于实现接口的形式,使得 Proxy 和 RealSubject 具备雷同的性能。

但其实还有一种思路:通过继承。即:让 Proxy 继承 RealSubject,这样二者同样具备雷同的性能,Proxy 还能够通过重写 RealSubject 中的办法,来实现多态。CGLIB 就是基于这种思路设计的。

在 Java 的动静代理机制中,有两个重要的类(接口),一个是 InvocationHandler 接口、另一个则是 Proxy 类,这一个类和一个接口是实现咱们动静代理所必须用到的。

InvocationHandler 接口

InvocationHandler 接口定义:

public interface InvocationHandler {public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable;
}

每一个动静代理类都必须要实现 InvocationHandler 这个接口,并且每个代理类的实例都关联到了一个 Handler,当咱们通过代理对象调用一个办法的时候,这个办法的调用就会被转发为由 InvocationHandler 这个接口的 invoke 办法来进行调用。

咱们来看看 InvocationHandler 这个接口的惟一一个办法 invoke 办法:

Object invoke(Object proxy, Method method, Object[] args)throws Throwable 

参数阐明:

  • proxy – 代理的实在对象。
  • method – 所要调用实在对象的某个办法的 Method 对象
  • args – 所要调用实在对象某个办法时承受的参数

如果不是很明确,等下通过一个实例会对这几个参数进行更深的解说。

Proxy 类

Proxy 这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的办法,然而咱们用的最多的就是 newProxyInstance 这个办法:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,  InvocationHandler h)  throws IllegalArgumentException

这个办法的作用就是失去一个动静的代理对象。

参数阐明:

  • loader – 一个 ClassLoader 对象,定义了由哪个 ClassLoader 对象来对生成的代理对象进行加载。
  • interfaces – 一个 Interface 对象的数组,示意的是我将要给我须要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就声称实现了该接口(多态),这样我就能调用这组接口中的办法了
  • h – 一个 InvocationHandler 对象,示意的是当我这个动静代理对象在调用办法的时候,会关联到哪一个 InvocationHandler 对象上

动静代理实例

下面的内容介绍完这两个接口 (类) 当前,咱们来通过一个实例来看看咱们的动静代理模式是什么样的:

首先咱们定义了一个 Subject 类型的接口,为其申明了两个办法:

public interface Subject {void hello(String str);
    String bye();}

接着,定义了一个类来实现这个接口,这个类就是咱们的实在对象,RealSubject 类:

public class RealSubject implements Subject {
    @Override
        public void hello(String str) {System.out.println("Hello" + str);
    }
    @Override
        public String bye() {System.out.println("Goodbye");
        return "Over";
    }
}

下一步,咱们就要定义一个动静代理类了,后面说个,每一个动静代理类都必须要实现 InvocationHandler 这个接口,因而咱们这个动静代理类也不例外:

public class InvocationHandlerDemo implements InvocationHandler {
    // 这个就是咱们要代理的实在对象
    private Object subject;
    // 构造方法,给咱们要代理的实在对象赋初值
    public InvocationHandlerDemo(Object subject) {this.subject = subject;}
    @Override
        public Object invoke(Object object, Method method, Object[] args)
            throws Throwable {
        // 在代理实在对象前咱们能够增加一些本人的操作
        System.out.println("Before method");
        System.out.println("Call Method:" + method);
        // 当代理对象调用实在对象的办法时,其会主动的跳转到代理对象关联的 handler 对象的 invoke 办法来进行调用
        Object obj = method.invoke(subject, args);
        // 在代理实在对象后咱们也能够增加一些本人的操作
        System.out.println("After method");
        System.out.println();
        return obj;
    }
}

最初,来看看咱们的 Client 类:

public class Client {public static void main(String[] args) {
        // 咱们要代理的实在对象
        Subject realSubject = new RealSubject();
        // 咱们要代理哪个实在对象,就将该对象传进去,最初是通过该实在对象来调用其办法的
        InvocationHandler handler = new InvocationHandlerDemo(realSubject);
        /*
         * 通过 Proxy 的 newProxyInstance 办法来创立咱们的代理对象,咱们来看看其三个参数
         * 第一个参数 handler.getClass().getClassLoader(),咱们这里应用 handler 这个类的 ClassLoader 对象来加载咱们的代理对象
         * 第二个参数 realSubject.getClass().getInterfaces(),咱们这里为代理对象提供的接口是实在对象所履行的接口,示意我要代理的是该实在对象,这样我就能调用这组接口中的办法了
         * 第三个参数 handler,咱们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
         */
        Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject
                        .getClass().getInterfaces(), handler);
        System.out.println(subject.getClass().getName());
        subject.hello("World");
        String result = subject.bye();
        System.out.println("Result is:" + result);
    }
}

咱们先来看看控制台的输入:

com.sun.proxy.$Proxy0
Before method
Call Method: public abstract void io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.hello(java.lang.String)
Hello  World
After method
Before method
Call Method: public abstract java.lang.String io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.bye()
Goodbye
After method
Result is: Over

咱们首先来看看 com.sun.proxy.$Proxy0 这货色,咱们看到,这个货色是由 System.out.println(subject.getClass().getName()); 这条语句打印进去的,那么为什么咱们返回的这个代理对象的类名是这样的呢?

Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject
                .getClass().getInterfaces(), handler);

可能我认为返回的这个代理对象会是 Subject 类型的对象,或者是 InvocationHandler 的对象,后果却不是,首先咱们解释一下 为什么咱们这里能够将其转化为 Subject 类型的对象

起因就是:在 newProxyInstance 这个办法的第二个参数上,咱们给这个代理对象提供了一组什么接口,那么我这个代理对象就会实现了这组接口,这个时候咱们当然能够将这个代理对象强制类型转化为这组接口中的任意一个,因为这里的接口是 Subject 类型,所以就能够将其转化为 Subject 类型了。

同时咱们肯定要记住,通过 Proxy.newProxyInstance 创立的代理对象是在 jvm 运行时动静生成的一个对象,它并不是咱们的 InvocationHandler 类型,也不是咱们定义的那组接口的类型,而是在运行是动静生成的一个对象,并且命名形式都是这样的模式,以 $ 结尾,proxy 为中,最初一个数字示意对象的标号。

接着咱们来看看这两句

subject.hello("World"); 
String result = subject.bye();

这里是通过代理对象来调用实现的那种接口中的办法,这个时候程序就会跳转到由这个代理对象关联到的 handler 中的 invoke 办法去执行,而咱们的这个 handler 对象又承受了一个 RealSubject 类型的参数,示意我要代理的就是这个实在对象,所以此时就会调用 handler 中的 invoke 办法去执行。

咱们看到,在真正通过代理对象来调用实在对象的办法的时候,咱们能够在该办法前后增加本人的一些操作,同时咱们看到咱们的这个 method 对象是这样的:

public abstract void io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.hello(java.lang.String)
public abstract java.lang.String io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.bye()

正好就是咱们的 Subject 接口中的两个办法,这也就证实了当我通过代理对象来调用办法的时候,起理论就是委托由其关联到的 handler 对象的 invoke 办法中来调用,并不是本人来实在调用,而是通过代理的形式来调用的。

小结

反射利用

退出移动版