目录

Java 反射机制

Reflect

在计算机学中,反射式编程(英语:reflective programming)或反射(英语:reflection),是指计算机程序在运行时(runtime)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。

要注意术语“反射”和“内省”(type introspection)的关系。内省(或称“自省”)机制仅指程序在运行时对自身信息(称为元数据)的检测;反射机制不仅包括要能在运行时对程序自身信息进行检测,还要求程序能进一步根据这些信息改变程序状态或结构。

  • 反射让开发人员可以通过外部类的全路径名创建对象,并使用这些类,实现一些扩展的功能。
  • 反射让开发人员可以枚举出类的全部成员,包括构造函数、属性、方法。以帮助开发者写出正确的代码。
  • 测试时可以利用反射 API 访问类的私有成员,以保证测试代码覆盖率。

也就是说,Oracle 希望开发者将反射作为一个工具,用来帮助程序员实现本不可能实现的功能(perform operations which would otherwise be impossible)。正如《人月神话》一书中所言:软件工程没有银弹。很多程序架构,尤其是三方框架,无法保证自己的封装是完美的。如果没有反射,对于外部类的私有成员,我们将一筹莫展,所以我们有了反射这一后门,为程序设计提供了更大的灵活性。工具本身并没有错,关键在于如何正确地使用。

Java 反射 API

  • Class反射对象描述类语义结构,可以从Class对象中获取构造函数、成员变量、方法类等类元素的反射对象,并以编程的方式通过这些反射对象对目标类对象进行操作。这些反射对象类在java.reflect包中定义

  • Constructor:类的构造函数反射类,通过Class#getConstructors()方法可以获得类的所有构造函数反射对象数组。在JDK5.0中,还可以通过getConstructor(Class… parameterTypes)获取拥有特定入参的构造函数反射对象。Constructor的一个主要方法是newInstance(Object[] initargs),通过该方法可以创建一个对象类的实例,相当于new关键字。在JDK5.0中该方法演化为更为灵活的形式:newInstance (Object… initargs)。

  • Method:类方法的反射类,通过Class#getDeclaredMethods()方法可以获取类的所有方法反射类对象数组Method[]。在JDK5.0中可以通过getDeclaredMethod(String name, Class… parameterTypes)获取特定签名的方法,name为方法名;Class…为方法入参类型列表。Method最主要的方法是invoke(Object obj, Object[] args),obj表示操作的目标对象;args为方法入参,代码清单3 10③处演示了这个反射类的使用方法。在JDK 5.0中,该方法的形式调整为invoke(Object obj, Object… args)。此外,Method还有很多用于获取类方法更多信息的方法:

  •   1)Class getReturnType():获取方法的返回值类型; 
    
  •   2)Class[] getParameterTypes():获取方法的入参类型数组; 
    
  •   3)Class[] getExceptionTypes():获取方法的异常类型数组; 
    
  •   4)Annotation[][] getParameterAnnotations():获取方法的注解信息,JDK 5.0中的新方法;
    
  • Field:类的成员变量的反射类,通过Class#getDeclaredFields()方法可以获取类的成员变量反射对象数组,通过Class#getDeclaredField(String name)则可获取某个特定名称的成员变量反射对象。Field类最主要的方法是set(Object obj, Object value),obj表示操作的目标对象,通过value为目标对象的成员变量设置值。如果成员变量为基础类型,用户可以使用Field类中提供的带类型名的值设置方法,如setBoolean(Object obj, boolean value)、setInt(Object obj, int value)等。

Class

除了 int 等基本类型外,Java 的其他类型全部都是 class(包括 interface)。 class(包括interface)的本质是数据类型(Type)。

每加载一种 class,JVM 就为其创建一个 Class 类型的实例,并关联起来。注意:这里的 Class 类型是一个名叫 Classclass

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public final class Class<T> implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement,
                              TypeDescriptor.OfField<Class<?>>,
                              Constable {
    /*
     * Private constructor. Only the Java Virtual Machine creates Class objects.
     * This constructor is not used and prevents the default constructor being
     * generated.
     */
    private Class(ClassLoader loader, Class<?> arrayComponentType) {
        // Initialize final field for classLoader.  The initialization value of non-null
        // prevents future JIT optimizations from assuming this final field is null.
        classLoader = loader;
        componentType = arrayComponentType;
    }
}

由于JVM为每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果获取了某个Class实例,我们就可以通过这个Class实例获取到该实例对应的class的所有信息。

获取 Class 类对象有三种方法

1
2
3
4
5
6
Class clz = Class.forName("java.lang.String");

Class clz = String.class;

String str = new String("Hello");
Class clz = str.getClass();
  • 使用 Class.forName 静态方法。当你知道该类的全路径名时,你可以使用该方法获取 Class 类对象。

  • 使用 .class 方法。这种方法只适合在编译前就知道操作的 Class。

  • 使用类对象的 getClass() 方法。

  • JVM为每个加载的class及interface创建了对应的Class实例来保存class及interface的所有信息;

  • 获取一个class对应的Class实例后,就可以获取该class的所有信息;

  • 通过Class实例获取class信息的方法称为反射(Reflection);

  • JVM总是动态加载class,可以在运行期根据条件来控制加载class。

访问字段

Class类提供了以下几个方法来获取字段:

  • Field getField(name):根据字段名获取某个public的field(包括父类)
  • Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
  • Field[] getFields():获取所有public的field(包括父类)
  • Field[] getDeclaredFields():获取当前类的所有field(不包括父类)

Java的反射API提供的Field类封装了字段的所有信息:

  • 通过Class实例的方法可以获取Field实例:getField(),getFields(),getDeclaredField(),getDeclaredFields();
  • 通过Field实例可以获取字段信息:getName(),getType(),getModifiers();
  • 通过Field实例可以读取或设置某个对象的字段,如果存在访问限制,要首先调用setAccessible(true)来访问非public字段。
  • 通过反射读写字段是一种非常规方法,它会破坏对象的封装。

调用方法

Class类提供了以下几个方法来获取Method:

  • Method getMethod(name, Class…):获取某个public的Method(包括父类)
  • Method getDeclaredMethod(name, Class…):获取当前类的某个Method(不包括父类)
  • Method[] getMethods():获取所有public的Method(包括父类)
  • Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)

Java的反射API提供的Method对象封装了方法的所有信息:

  • 通过Class实例的方法可以获取Method实例:getMethod(),getMethods(),getDeclaredMethod(),getDeclaredMethods();
  • 通过Method实例可以获取方法信息:getName(),getReturnType(),getParameterTypes(),getModifiers();
  • 通过Method实例可以调用某个对象的方法:Object invoke(Object instance, Object… parameters);
  • 通过设置setAccessible(true)来访问非public方法;
  • 通过反射调用方法时,仍然遵循多态原则。

调用构造函数

通常使用new操作符创建新的实例:

Person p = new Person();

如果通过反射来创建新的实例,可以调用Class提供的newInstance()方法:

Person p = Person.class.newInstance();

调用Class.newInstance()的局限是,它只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class Main {
    public static void main(String[] args) throws Exception {
        // 获取构造方法Integer(int):
        Constructor cons1 = Integer.class.getConstructor(int.class);
        // 调用构造方法:
        Integer n1 = (Integer) cons1.newInstance(123);
        System.out.println(n1);

        // 获取构造方法Integer(String)
        Constructor cons2 = Integer.class.getConstructor(String.class);
        Integer n2 = (Integer) cons2.newInstance("456");
        System.out.println(n2);
    }
}

Constructor对象封装了构造方法的所有信息;

  • 通过Class实例的方法可以获取Constructor实例:getConstructor(),getConstructors(),getDeclaredConstructor(),getDeclaredConstructors();
  • 通过Constructor实例可以创建一个实例对象:newInstance(Object… parameters); 通过设置setAccessible(true)来访问非public构造方法。

获取继承关系

获取父类的Class

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class Main {
    public static void main(String[] args) throws Exception {
        Class i = Integer.class;
        Class n = i.getSuperclass();
        System.out.println(n);
        Class o = n.getSuperclass();
        System.out.println(o);
        System.out.println(o.getSuperclass());
    }
}

Integer的父类类型是Number,Number的父类是Object,Object的父类是null。

获取 interface

1
2
3
4
5
6
7
8
9
public class Main {
    public static void main(String[] args) throws Exception {
        Class s = Integer.class;
        Class[] is = s.getInterfaces();
        for (Class i : is) {
            System.out.println(i);
        }
    }
}

继承关系

当我们判断一个实例是否是某个类型时,正常情况下,使用instanceof操作符:

1
2
3
4
5
Object n = Integer.valueOf(123);
boolean isDouble = n instanceof Double; // false
boolean isInteger = n instanceof Integer; // true
boolean isNumber = n instanceof Number; // true
boolean isSerializable = n instanceof java.io.Serializable; // true

如果是两个Class实例,要判断一个向上转型是否成立,可以调用isAssignableFrom():

1
2
3
4
5
6
7
8
// Integer i = ?
Integer.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Integer
// Number n = ?
Number.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Number
// Object o = ?
Object.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Object
// Integer i = ?
Integer.class.isAssignableFrom(Number.class); // false,因为Number不能赋值给Integer
  • 通过Class对象可以获取继承关系:

    • Class getSuperclass():获取父类类型;
    • Class[] getInterfaces():获取当前类实现的所有接口。
  • instanceof操作,判断一个实例是否是某个类型。

  • 通过Class对象的isAssignableFrom()方法可以判断一个向上转型是否可以实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Data
public class Car {  
    private String brand;  
    private String color;  
    private int maxSpeed;
    //未带参的方法  
    public void introduce() {   
       System.out.println("brand:"+brand+";color:"+color+";maxSpeed:" +maxSpeed);  
    }  
}

public class ReflectTest {  
    public static Car initByDefaultConst() throws Throwable  
    {  
        //①通过类装载器获取Car类对象  
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        Class clazz = loader.loadClass("com.ynthm.reflect.Car");   
          
        //②获取类的默认构造器对象并通过它实例化Car  
        Constructor cons = clazz.getDeclaredConstructor((Class[])null);   
        Car car = (Car)cons.newInstance();  
          
           
        //③通过反射方法设置属性  
        Method setBrand = clazz.getMethod("setBrand",String.class);          
        setBrand.invoke(car,"红旗CA72");        
        
        Field colorFld = clazz.getDeclaredField("color");  
        //取消Java语言访问检查以访问private变量  
       	colorFld.setAccessible(true);   
       	colorFld.set(pcar,"红色");  
        
        Method setMaxSpeed = clazz.getMethod("setMaxSpeed",int.class);  
        setMaxSpeed.invoke(car,200);          
        return car;  
    }  
  
    public static void main(String[] args) throws Throwable {  
        Car car = initByDefaultConst();  
        car.introduce();
    }  
}  

动态代理

有没有可能不编写实现类,直接在运行期创建某个interface的实例呢?

这是可能的,因为Java标准库提供了一种动态代理(Dynamic Proxy)的机制:可以在运行期动态创建某个interface的实例。

JDK的动态代理主要涉及到java.lang.reflect包中的两个类:Proxy和InvocationHandler。其中InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编织在一起。

而Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Main {
    public static void main(String[] args) {
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println(method);
                if (method.getName().equals("morning")) {
                    System.out.println("Good morning, " + args[0]);
                }
                return null;
            }
        };
        Hello hello = (Hello) Proxy.newProxyInstance(
            Hello.class.getClassLoader(), // 传入ClassLoader
            new Class[] { Hello.class }, // 传入要实现的接口
            handler); // 传入处理调用方法的InvocationHandler
        hello.morning("Bob");
    }
}

interface Hello {
    void morning(String name);
}

在运行期动态创建一个interface实例的方法如下:

  • 定义一个InvocationHandler实例,它负责实现接口的方法调用;
  • 通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:
    • 使用的ClassLoader,通常就是接口类的ClassLoader;
    • 需要实现的接口数组,至少需要传入一个接口进去;
    • 用来处理接口方法调用的InvocationHandler实例。
  • 将返回的Object强制转型为接口。

动态代理实际上是JVM在运行期动态创建class字节码并加载的过程。

Java标准库提供了动态代理功能,允许在运行期动态创建一个接口的实例;

动态代理是通过Proxy创建代理对象,然后将接口方法“代理”给InvocationHandler完成的。

Spring 中的反射

Java 反射性能优化

  • 对于method/field/constructor的invoke这类的反射,设置 “setAccessible(true)”

  • 当然了,将得到的method/field/constructor对象做缓存这是基本的

缓存访问的对象

接口优先于反射

属性访问器 map 存储 get set 方法的 lamda 表达式

使用字节码技术

魔法类:sun.misc.Unsafe