Java基础(017):反射初探

  本文主要就反射的基础概念进行说明,目录结构如下:

0、前言:为什么需要反射

  先说一说笔者整理后的一个大致的看法:我们知道,静态类型语言一般都是在编译时就确定类型,因此,对于某个接口、某个具体类型对象,在代码中可以调用哪些方法、哪些属性都是确定的,这也使得所有调用在经过编译器编译之后基本可以认为是类型安全的。这样,对于一个未知类型的 object 对象,我们是无法知道这个 object 对象到底有没有像 doSomething() 这样一个方法的,也肯定是无法直接调用的。静态类型的设计不支持这种不安全的处理,除非我们知道这个对象是属于某个已知的类型或者接口,然后进行转型,我们才能使用这种类型确定性。而反射则是针对此种场景而开的一个后门,反射让代码可以在运行时去获取对象类型相关的所有元信息,包括检测是否存在指定的属性、方法声明、注解声明等,或者获取所有的这些相关声明,同时还可以对此进行操作使用。也就是说,应用无需在编译时就提前知道一切,存在哪些实现类,它可以通过反射在运行时再根据指定配置去获取相关类型元信息进行操作(加载指定的类,按照指定的构造器实例化,调用指定的方法、处理指定的注解等),从而使得代码和框架应用等更具动态性、可扩展性,而基于此则衍生出很多框架及应用,包括注解、代理、JavaBean对象复制、AOP等,像通过properties文件配置、xml文件配置、注解配置等方式进行扩展等,这也是反射机制狠受欢迎的重要原因。当然,这种机制绕开了静态语言中编译器的类型安全检查,可能会带来一些潜在的安全问题。

  总结下,可以说这是由于静态语言本身存在着限制,需要在编译期完成类型确定(静态加载),而反射则是突破这种限制,将编译期该处理的事情,延迟到了运行期(动态加载),而且是毫无保留的,正是由于可以在运行时处理,才有了常见的各种基于配置、注解、动态代理的应用。

  本文旨在说明为什么需要反射、反射是什么、反射的基础API基本操作、反射可以做什么、有哪些应用等,好让大家对反射有一个基本的认识。至于为什么是这样,底层是怎么支持和体现的,这部分深入的知识,后续会再进一步分析。

1、反射是什么

  反射是什么??

  我们来看看官网对于反射的说明。根据官网 [1]Trail: The Reflection API 的描述:


Reflection is commonly used by programs which require the ability to examine or modify the runtime behavior of applications running in the Java virtual machine. This is a relatively advanced feature and should be used only by developers who have a strong grasp of the fundamentals of the language. With that caveat in mind, reflection is a powerful technique and can enable applications to perform operations which would otherwise be impossible.


  反射机制是 Java 语言提供的一种高级功能,它赋予程序在运行时自省(introspect)和修改运行时行为的能力。简单来说就是通过反射,程序可以检测或修改应用的运行时行为,对于任意一个类,都可以获取这个类的所有属性和方法、构造函数、实现的接口、父类等信息;对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态获取信息以及动态调用对象方法、修改运行时行为的功能称为java语言的反射机制。

  作为一门比较成熟的面向对象设计语言,Java 把类内部的所有属性、方法、构造函数等组成成分(元信息)都抽象成相应的Java类(对象)来进行操作,包括常见的 Class、Method、Constructor、Field 等,利用反射,我们就可以获取这些对象并进行操作,很多常见的框架和应用也都是基于此进行的扩展。

  而这一切其实都是来源于编译后的 class 字节码在加载后所表述的信息。类加载器将 .class 文件(或者其他符合 JVM 字节码规范的二进制流)加载到内存中,并为之生成对应的描述 Class 结构的元信息对象,也就是常说的 Class 对象,而通过该元信息对象可以获知 Class 的结构信息:如构造函数,属性和方法等。反射机制允许代码通过这个Class对象来操作所有功能。我们可以毫不夸张地说: Class 是反射的基础。

  由此我们知道,class 字节码在加载后既可以被我们直接利用来创建实例,实现直接操作处理(new),还可以被应用程序或者框架用于运行时加载和处理指定的配置类、方法等,甚至进行定制处理,做到灵活且可扩展性高。

  反射机制就像是为开发者开的一扇后门,提供了一种强大的机制来处理正常情况下不可能完成的事。反射机制把类的各种元信息当成是对象来进行处理,包括属性、方法、构造函数等,都会有对应的对象,通过这些,程序就可以在运行时动态地进行操作。在反射机制面前,类和对象是没有秘密的,可以被任意操作(所以需要特别注意)。

  另外,维基百科 wiki-Reflection 中提到:

  1. Introspection is the ability of a program to examine the type or properties of an object at runtime.
  2. Reflection is the ability of a program to examine and modify the structure and behavior of an object at runtime.

  从定义来看, Introspection (自省) 是反射 Reflection 的一个子集,有些编程语言支持 Introspection 但不支持 Reflection 。

  从官网的描述可以看出,Java的反射机制包括了这两层含义:

  • 获取或检测运行时的对象类型元信息;
  • 修改或者操作相关元信息。

2、反射的基础API

  前面提到,反射所用到的各种元信息对象,都来源于 Class ,接下来就先来看下反射的基础API中的基础 : java.lang.Class 。

2.1、反射的基础类:java.lang.Class

  java.lang.Class 类用于表示运行时类型信息,是所有类的类,是反射的基础,而 Class 对象则表示类对应的具体类型信息,它和加载的 class 文件所描述的信息是一致的。像最常用的 java.lang.String ,JVM 会为 String 创建一个对应的 Class 对象 String.class ,保存 String 类相关的类型信息,该对象保存在 jvm 堆中,作为访问方法区中 String 类型信息的接口,可以通过 String.class 访问。

  Class 本身是无法直接实例化的(私有的构造函数),Class 对象都是由 JVM 加载和创建的。我们可以通过已知的类或者对象来获取对应的Class对象。

  Java的基本类型 boolean、byte、char、short、int、long、float 和 double 和关键字 void 都有对应的 class 对象,例如 int.class(对应 Integer.TYPE 而不是 Integer.class ) 、void.class 等。

注:Void的API文档提到,The Void class is an uninstantiable placeholder class to hold a reference to the Class object representing the Java key word void.

  枚举是一种类([10]Enum Types)。

  注解是一种接口(继承 java.lang.annotation.Annotation ,[11]9.6. Annotation Types:An annotation type declaration specifies a new annotation type, a special kind of interface type. To distinguish an annotation type declaration from a normal interface declaration, the keyword interface is preceded by an at-sign (@).)

  即使是数组都有与之关联的Class类的对象,所有具有相同元素类型和维数的数组都共享该 Class 对象,而且与具体的元素的Class对象是不一样的,例如一维String数组 [Ljava.lang.String; 和 java.lang.String 是不一样的。

  在使用自定义类时,虚拟机会先检查这个类的Class对象是否已经加载,如果没有加载,默认的类加载器就会先根据类的全限定名查找对应的 .class 文件,加载到内存然后生成对应的Class对象。

  结合上面的阐述,我们大概可以知道,Class对象加载总体来源有2种:静态加载和动态加载。(或者其他类似说法,本质上是一样的,不必拘泥,区别在于编译时和运行时)

  静态加载其实就是编译器在编译时都一一确定的(前面提到的静态语言的编译期类型确定性),例如,我们直接写出来的 new 实例对象,对应的实例对象所代表的类肯定是已确定而且需要直接检查的,我们直接用的是啥就得在编译时检查啥;而动态加载,只有在运行时才知道,主要表现在我们可能是从别的地方获取到了一个引用配置(最常见的就是配置的方式,例如xml配置、properties配置等),然后动态的把这个未知类型所引用的对象的.class文件加载进jvm虚拟机里,实际上准确来说是class二进制字节流,只要是符合JVM规范的字节码就行,具体的字节码格式建议参考JVM规范 [9]Chapter 4. The class File Format 或者《深入理解Java虚拟机》第三版中的相关描述。class二进制字节流来源很多途径,如:

  • 从ZIP包获取,这是JAR、EAR、WAR等格式的基础
  • 从网络中获取,典型的应用是 Applet (基本不用了)
  • 运行时计算生成,这种场景使用最多的是动态代理技术,在 java.lang.reflect.Proxy 类中,就是用了 ProxyGenerator.generateProxyClass 来为特定接口生成形式为 *$Proxy 的代理类的二进制字节流
  • 由其它文件生成,典型应用是JSP,即由JSP文件生成对应的Class类
  • 从缓存、数据库中获取等等

  动态加载体现的是运行时配的啥再来加载啥(需要获取啥)。

注:记得在《Java编程思想》有提到 RTTI ,即 Run-Time Type Identification 运行时类型识别,暂未发现 Java 文档中有提到的,这里就不多说了,可以自己搜索或者简要参考 Java RTTI和反射的区别?

2.2、获取Class对象的3种方式

  根据 [2]Retrieving Class Objects 提到的,可以通过下面3种方法获取Class对象:

  • ① 通过 Object.getClass() 实例方法进行获取
String str = "str";
Class<?> klass = str.getClass();
System.out.println(klass.getName());
  •  ② 通过 .class 语法进行获取
// Class<?> strClass = String.class;
Class<String> strClass = String.class;
System.out.println(strClass.getName());
Class<?> intClass = int.class;
Class<?> integerClass = Integer.class;
Class<?> integerTypeClass = Integer.TYPE;
// true
System.out.println(intClass == integerTypeClass);
// false
System.out.println(intClass == integerClass);
  •  ③通过 Class.forName() 进行获取
Class<?> cla = Class.forName("java.lang.String");
System.out.println(cla.getName());

2.3、Class对象核心接口方法

  Class对象是反射的基础,提供了获取类信息的各种方法,以下是部分方法及其说明的相关翻译,可以看到,这里包含了修饰符、包信息、父类、实现的接口、构造器、方法、属性变量、注解等信息,也都和我们写的Java代码一一对应。具体参考API链接的说明 [3]java.lang.Class<T> 或者直接参考JDK相关源码及其注释。

方法
说明
forName(String name)
返回指定类名name的Class对象
newInstance()
调用缺省构造器,返回Class对象的一个实例
getName()
返回此Class对象所表示的实体(类、接口、数组类或者void)的名称
getPackage()
包信息
getModifiers()
修饰符,如public, protected, private, final, static, abstract and interface; 等,
返回来的 int 值可以通过 java.lang.reflect.Modifier 提供的方法来判断
getSuperClass()
返回当前Class对象的父类的Class对象
getInterfaces()
返回当前Class对象的接口
getClassLoader()
返回该类的类加载器
getConstructor(Class<?>... parameterTypes)
返回指定参数Class类型的public构造器的Constructor对象
getConstructors()
返回代表该类所有public构造器的Constructor数组
getDeclaredConstructor(Class<?>... parameterTypes)
返回指定参数Class类型的构造器的Constructor对象
getDeclaredConstructors()
返回代表该类所有构造器的Constructor数组
getMethod(String name, Class<?>... parameterTypes)
返回指定方法名称和参数 Class 类型列表的public方法的 Method 对象,
包括从父类继承过来的public方法
getMethods()
返回所有public方法的 Method 数组,包括父类/接口的public方法
getDeclaredMethod(String name, Class... parameterTypes)
返回指定方法名称和参数 Class 类型列表的方法,不包含父类的方法
getDeclaredMethods()
返回所有方法的 Method 数组,包括 public、protected、默认(包私有)访问
和 private 方法,但是不包括父类的方法
getField(String name)
返回指定名称的public成员变量的 Field 对象
getFields()
返回所有可访问的public成员变量的 Field 对象数组
getDeclaredField(String name)
返回指定名称的变量的 Field 对象
getDeclaredFields()
返回代表该类所有声明的属性变量的 Field 数组
getAnnotation(Class<A> annotationClass)
返回指定类型的注解声明
getAnnotations()
返回所有的注解声明
getAnnotationsByType(Class<A> annotationClass)
返回指定类型的注解声明,区别在于可否重复,有些注解类型可以重复出现
getDeclaredAnnotation(Class<A> annotationClass)
返回指定类型的注解声明,只包含该类直接声明的
getDeclaredAnnotations()
返回该类所直接声明的注解,不包含继承的
getDeclaredAnnotationsByType(Class<A> annotationClass)
返回指定类型的注解声明,区别在于可否重复,有些注解类型可以重复出现,
但是只包含该类直接声明的
。。。。。。。。。。

  其中修饰符的判断:

/// java.lang.reflect.Modifier
Modifier.isAbstract(int modifiers);
Modifier.isFinal(int modifiers);
Modifier.isInterface(int modifiers);
Modifier.isNative(int modifiers);
Modifier.isPrivate(int modifiers);
Modifier.isProtected(int modifiers);
Modifier.isPublic(int modifiers);
Modifier.isStatic(int modifiers);
Modifier.isStrict(int modifiers);
Modifier.isSynchronized(int modifiers);
Modifier.isTransient(int modifiers);
Modifier.isVolatile(int modifiers);

  

2.4、反射常用的核心类

  java.lang.reflect 包提供了支持 Java 反射机制的核心类:

  • Field :代表类的成员变量(成员变量也称为类的属性)
  • Method :代表类的方法
  • Constructor :代表类的构造方法
  • Annotation :注解信息
  • Package :包信息
  • Array 类:提供了动态创建数组,以及访问数组的元素的静态方法
  • Proxy 类:动态代理核心类
  • 。。。

3、Java反射提供的功能和API基本使用

  java反射框架主要提供以下内容:

  • 在运行时判断任意一个对象所属的类;
  • 在运行时构造任意一个类的对象;
  • 在运行时获取任意一个类包含的成员变量、方法、继承的父类、实现的接口等信息;
  • 在运行时调用任意一个对象的任意方法和变量
  • 获取泛型信息
  • 获取注解和处理
  • 生成动态代理
  • 。。。

  关于前面提到的API的具体使用,建议参考官方文档进行学习,例如 [1]Trail: The Reflection API 、 [3]Java Reflection API ,也可以自行搜索,网上有很多的案例。本文就简单地列举一些示例(具体在 3.6 或者参考笔者 github )。

3.1、创建实例对象

  通过反射来创建实例对象主要有两种方式:

  • 用 Class 对象的 newInstance 方法,使用默认无参构造器或者自定义的无参构造器
  • 用 Constructor 对象的 newInstance 方法

3.2、通过Method调用方法

  包括以下几种:

  • 静态方法
  • 公有方法
  • 私有方法

  私有方法需要通过 getDeclaredMethod 获取,且需要 setAccessible(true) 。

3.3、通过Field操作属性

  私有属性同样需要 setAccessible(true) 。

3.4、判断是否为某个类的实例

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

  • 用 instanceof 关键字
  • 用 Class 对象的 isInstance 方法(它是一个 Native 方法)

  例如:

String.class.isInstance("str");

boolean isString = "str" instanceof String;

3.5、获取泛型信息

  参考 [12]Java Reflection - Generics 。有部分泛型信息是可以在运行时通过反射动态获取的。我们知道,泛型主要包括两部分:一部分是声明,另一部分则是使用。而具体使用的时候,我们是可以知道这部分泛型信息的。主要包括以下3部分:

  • 方法返回值的泛型
  • 方法参数的泛型
  • 成员变量的泛型

3.6、获取注解和处理

  注解只是一种标记,叫啥名字不重要,重要的是处理这个注解标记的代码,例如jdk本身的 @Override 会在编译时处理。

  下面看下相关的示例代码,可以参考笔者 github

package cn.wpbxin.javabasis.reflection.basis;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
    int order() default 0;
    String id() default "id";
}

/// 
package cn.wpbxin.javabasis.reflection.basis;

import java.util.HashMap;
import java.util.Map;

/**
 * 反射基础API测试实例类
 * @author wpbxin
 *
 */
@Service(id = "reflectionService")
public class ReflectionService {
    
    private String msg = "default msg";
    private int type = 0;

    public ReflectionService(){}
    
    public ReflectionService(String msg,int type) {
        super();
        this.msg = msg;
        this.type = type;
    }
    
    @Override
    public String toString() {
        return "ReflectionService [msg=" + msg + ", type=" + type + "]";
    }
    
    private void privatePrintMsg(String msg) {
        System.out.println("private instance method:" + msg);
    }
    
    void defaultPrintMsg(String msg) {
        System.out.println("package-default instance method:" + msg);
    }
    
    protected void protectedPrintMsg(String msg) {
        System.out.println("protected instance method:" + msg);
    }
    
    public void publicPrintMsg(String msg) {
        System.out.println("public instance printMsg:" + msg);
    }
    
    public void throwException () {
        if (0 == type) {
            throw new NullPointerException();
        }
    }
    
    private void privatePrint(String msg) {
        System.out.println("private instance method:" + msg);
    }
    
    void defaultPrint(String msg) {
        System.out.println("package-default instance method:" + msg);
    }
    
    protected void protectedPrint(String msg) {
        System.out.println("protected instance method:" + msg);
    }
    
    public void publicPrint(String msg) {
        System.out.println("public instance printMsg:" + msg);
    }
    
    private static void privateStaticPrint(String msg) {
        System.out.println("privateStaticPrint:" + msg);
    }
    
    static void defaultStaticPrint(String msg) {
        System.out.println("defaultStaticPrint:" + msg);
    }
    
    protected static void protectedStaticPrint(String msg) {
        System.out.println("protectedStaticPrint:" + msg);
    }
    
    public static void publicStaticPrint(String msg) {
        System.out.println("publicStaticPrint:" + msg);
    }
    
    Map<String, Integer> map = new HashMap<>();
    
    public Map<String, Integer> getMap() {
        return this.map;
    }
    
    public void setMap(Map<String, Integer> map) {
        this.map = map;
    }
}

///
package cn.wpbxin.javabasis.reflection.basis;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Map;

/**
 * 反射基础API测试类
 * 
 * @author wpbxin
 *
 */
public class ReflectionTest {
    public static void main(String[] args) throws Exception {

        // 获取 ReflectionService 对应的Class对象
        Class<ReflectionService> klass = ReflectionService.class;

        /// 1、创建实例对象

        // 1.1、通过 Class.newInstance 构造对象,默认使用无参构造器
        ReflectionService rs1 = klass.newInstance();
        System.out.println(rs1.toString());
        System.out.println();

        // 1.2、通过 Constructor.newInstance 构造对象,指定参数Class类型
        // 获取 ReflectionService 类中带有 String、int 参数的构造器
        Constructor<ReflectionService> rsConstructor = klass.getConstructor(String.class, int.class);
        // 根据构造器创建实例
        ReflectionService rs2 = rsConstructor.newInstance("constructor newInstance", 1);
        rs2.publicPrintMsg("调用公有方法");
        System.out.println(rs2.toString());
        System.out.println();

        /// 2、操作方法 Method

        // 返回类或者接口中声明的所有方法,包括私有的,但是不包含继承的方法
        Method[] declaredMethods = klass.getDeclaredMethods();
        for (Method method : declaredMethods) {
            System.out.println(method);
        }
        System.out.println();

        // 2.1、静态方法
        Method staticMethod = klass.getMethod("publicStaticPrint", String.class);
        staticMethod.invoke(null, "公有类方法调用");
        System.out.println();

        // 2.2、公有实例方法和私有实例方法
        // 返回类或者接口中声明的所有public方法,包括从超类或者接口中继承过来的
        Method[] methods = klass.getMethods();
        // 实例对象
        ReflectionService rfts = klass.newInstance();
        
        Method publicPrintMsg = klass.getMethod("publicPrintMsg", String.class);
        publicPrintMsg.invoke(rfts, "公有实例方法");
        System.out.println();
        
        // 私有方法必须使用 getDeclaredMethod
        Method privatePrintMsg = klass.getDeclaredMethod("privatePrintMsg", String.class);
        // 设置可见性
        privatePrintMsg.setAccessible(true);
        privatePrintMsg.invoke(rfts, "私有实例方法");
        System.out.println();

        /// 3、操作属性 Field
        // 获取所有属性,包括私有的,但不包括继承的
        Field[] declaredFields = klass.getDeclaredFields();
        for (Field field : declaredFields) {
            System.out.println(field);
        }
        System.out.println();
        
        // 私有属性
        Field privateField = klass.getDeclaredField("type");
        privateField.setAccessible(true);
        privateField.setInt(rfts, 20);
        System.out.println(rfts.toString());
        System.out.println();
        
        /// 4、判断是否为某个类的实例对象
        System.out.println(String.class.isInstance("str"));
        System.out.println();

        /// 5、获取泛型信息,参考 http://tutorials.jenkov.com/java-reflection/generics.html
        // 5.1、方法返回值的泛型
        Method rMethod = klass.getDeclaredMethod("getMap");
        Type returnType = rMethod.getGenericReturnType();
        if (returnType instanceof ParameterizedType) {
            ParameterizedType type = (ParameterizedType) returnType;
            Type[] typeArguments = type.getActualTypeArguments();
            for (Type typeArgument : typeArguments) {
                Class<?> typeArgClass = (Class<?>) typeArgument;
                System.out.println("typeArgClass=" + typeArgClass);
            }
        }
        System.out.println();
        
        // 方法参数的泛型
        Method pMethod = klass.getDeclaredMethod("setMap", Map.class);
        Type[] genericParameterTypes = pMethod.getGenericParameterTypes();
        for (Type genericParameterType : genericParameterTypes) {
            if (genericParameterType instanceof ParameterizedType) {
                ParameterizedType aType = (ParameterizedType) genericParameterType;
                Type[] parameterArgTypes = aType.getActualTypeArguments();
                for (Type parameterArgType : parameterArgTypes) {
                    Class<?> parameterArgClass = (Class<?>) parameterArgType;
                    System.out.println("parameterArgClass=" + parameterArgClass);
                }
            }
        }
        System.out.println();
        
        // 成员变量的泛型
        Field mapField = klass.getDeclaredField("map");
        Type genericFieldType = mapField.getGenericType();
        if(genericFieldType instanceof ParameterizedType){
            ParameterizedType aType = (ParameterizedType) genericFieldType;
            Type[] fieldArgTypes = aType.getActualTypeArguments();
            for(Type fieldArgType : fieldArgTypes){
                Class<?> fieldArgClass = (Class<?>) fieldArgType;
                System.out.println("fieldArgClass = " + fieldArgClass);
            }
        }
        System.out.println();
    
        /// 6、获取注解和处理
        // 简单示例
        Annotation[] declaredAnnotations = klass.getDeclaredAnnotations();
        for (Annotation annotation : declaredAnnotations) {
            if (annotation instanceof Service) {
                System.out.println(annotation);
                // do something else
            }
        }
        
    }
}

3.7、动态代理

  动态代理是一种方便运行时动态构建代理、动态处理代理方法调用的机制,很多场景都是利用类似机制做到的,比如用来包装 RPC 调用、面向切面编程(AOP)等。所谓的动态代理,首先它还是代理,不一样的地方是体现在“动态”,也就是说它不是静态的,不是原来就有的(编译期已有),是需要在运行过程中生成的,存在着变化,这就体现出了动态性。

  Java动态代理的底层是通过反射机制来实现的,反射是实现Java动态代理的基础,动态代理核心类 java.lang.reflect.Proxy 中有相关反射代码。

  Spring 动态代理的实现方式有两种:cglib(基于ASM) 和 JDK 原生动态代理。代理是一个比较大的话题,具体分析后面会再详细写,本文不再深究。

4、反射的重要应用

  有人可能会有这样的疑问:反射有什么作用?这么神吗?我明明已经知道要使用的具体实现类了,还需要反射吗?和直接 new 一个对象有什么差别?反正最后都是调用实际的对象,非得要绕来绕去的。

  如果实现类已知且完全可控,直接 new 出来也可以,甚至说是最简单高效的。但是有很多场景,实现类是未知的,有可能经常变化,只有在运行时才会根据指定的配置去确定实现类,甚至有的时候需要在运行中接收相应指令配置来加载的(例如查询数据库配置、配置中心推送等等);你也永远无法预测谁写的、写了什么实现类,也完全不可控,还有可能你完全接触不到实现类(RPC调用),这个时候就 new 不出来了。这也算是隔离变化、封装变化。

  有了反射,我们就无需提前知道有哪些需要用到的实现类,例如JDBC实现类,比较难去预测会有哪些新类型的数据库,MySQL、Oracle、SQL server 等,而且版本不一样,驱动包中的实现类还可能不一样,每次都改代码太要命了,修改约定的配置文件或者配置中心是最直接省事的;还有像云应用接口,人脸识别等,可能有好多家厂商提供的接口。另外就是像RPC调用、AOP之类的,也不太可能去直接写出每个代理类。

  反射是高扩展模块化、框架设计的灵魂,我们可以根据指定的配置来进行按需加载,设计出更加通用和灵活的架构。反射对于模块化的设计开发、通用基础框架等有很大的作用,日常使用到的 Spring 、Mybatis 等框架都会用到反射机制。

  下面简单列举下反射在实际开发中的一些重要应用场景。

1、注解

  注解本身仅仅是起到标记作用,它需要利用反射机制,根据注解标记去调用注解处理器,执行相应的行为。正是有了反射,才可能获取注解信息并进行相关处理。现在有很多的框架都是优先建议使用注解配置了。

2、JUnit

  著名的单元测试框架 JUnit使用反射来检测所有被 @Test 注解的方法并进行执行。

3、JSON与对象的转换

  通过反射获取属性的 getter 和 setter

4、反射与javaBean

  JavaBean读取属性x的值的大致流程:变大写、补前缀、获取方法。

"x"-->"X"-->"getX"-->"MethodGetX"

   简单实现:使用 java.beans.PropertyDescriptor 类

  麻烦实现: 使用 java.beans.Introspector 类,遍历 getBeanInfo 方法的返回值

  JavaBean必须有一个不带参数的构造函数,不然无法使用默认的实例化。

  使用BeanUtils工具包进行复制(BeanUtils.copyProperties),当然,这些工具包还提供了它一些功能:

  • 字符串和整数转换(对比(PropertyUtils)
  • 属性级联操作
  • 操作map

5、Mock

  用于单元测试的动态模拟对象。mock 库使用反射创建一个代理对象,完成类方法的 mocking

6、Servlet配置

  web应用中的 servlet 配置,当然,后面可能用注解进行替换了。

<servlet>
    <servlet-name>dipatcherServlet</servlet-name>
    <servlet-class>SpringDipatcherServlet</servlet-class>
<servlet>

   还有像 Spring WebMVC 的过滤器、拦截器、监听器等等

7、IDE的代码补齐提示

  像 IntelliJ IDEA 、Eclipse 等开发工具,都会有代码补齐提示,在写代码时会有代码(属性或方法名)提示,就是因为使用了反射。

8、Spring框架的使用

  Java的反射机制在做基础框架的时候非常有用。行内流传着这样一句话:反射机制是Java框架的基石。像大名鼎鼎的Spring,好些地方都用到了反射机制,最经典的就是基于xml的配置模式。只需要通过修改配置就可以加载不同的对象、调用不同的方法。

<bean id="person" class="com.spring.beans.Person" />

  Spring 就是依靠 class 属性进行 Class 对象的加载的。

  Spring 通过 XML 配置模式装载 Bean 的大致过程:

  • 将程序内所有 XML 或 Properties 配置文件加载入内存中
  • Java类里面解析xml或properties里面的内容,得到对应实体类的全限定名字符串以及相关的属性信息
  • 使用反射机制,根据这个字符串获得某个类的Class实例
  • 动态配置实例的属性

  Spring这样做的好处是:

  • 不用每一次都要在代码里面去new或者做其他的事情
  • 以后要改的话直接改配置文件,代码维护起来就很方便了
  • 有时为了适应某些需求,Java类里面不一定能直接调用另外的方法,可以通过反射机制来实现

  像 SpringMVC中controller接口入参转换、Java注解对象获取、用户鉴权、全局异常处理、全局日志处理、事务处理等等,都与反射有关。

9、JDBC的数据库连接

  在 JDBC 的操作中,如果要想进行数据库的连接,则必须按照以下几个步骤:

  • 通过Class.forName()加载数据库的驱动程序 (这里是通过反射进行加载,需要引入相关驱动Jar包)
  • 通过 DriverManager 类进行数据库的连接,连接的时候要将数据库的连接地址、用户名、密码等作为参数
  • 通过 Connection 接口接收连接

  其中第一步就是需要通过反射来获取并加载对应的驱动类。

10、JDBC ResultSet和JavaBean

11、数据库连接与事务管理

  Spring框架有一个事务代理,可以开启、提交/回滚事务,可以参考 Advanced Connection and Transaction Demarcation and Propagation http://tutorials.jenkov.com/java-persistence/advanced-connection-and-transaction-demarcation-and-propagation.html 中的详细讲解。

12、RPC远程调用

  

13、反射与工厂模式

  配置文件的形式,例如 config.properties、XML 文件,还有很常见的约定在 META-INF 文件下的配置,然后可以通过 Class.forName() 进行实例化,如果复用,可以通过 map 将进行缓存。

  等等很多应用都与反射有关,而且细讲起来都可以说是篇幅较长。鉴于笔者能力有限,这里就不细讲了。有兴趣的可以自己进行相关搜索。

5、反射的优点和缺点

  根据官网 [1]Trail: The Reflection API 中提到的,再加上部分总结:

优点:

  • 代码灵活,可扩展性强
  • IDE开发工具、测试与调试工具利用发射提供了各种便利
  • 依赖少,JDK原生支持(当然也有可能后面哪个版本就禁止掉了)

缺点:

  • 性能问题:反射涉及到运行时类型信息的动态解析获取,JNI,JNA都涉及加载动态库,需要大量资源,当然 jni,jna加载动态库只加载一次就够了,一般都会做缓存。而且由于动态解析,某些JVM优化可能就无法执行。
  • 安全问题:反射的使用基本上没有什么权限限制,使得程序运行在一个没有安全限制的环境之下,可能会导致安全问题。
  • 破坏封装和可移植性:反射可以修改访问权限,破坏了封装和抽象,随着迭代,反射代码可能会随着平台的升级而改变行为。

应用层的优化手段如下:

  • 获取反射元数据的时候避免使用遍历的方法(getMethods),尽量精准获取(getMethod)。
  • 使用缓存机制缓存反射时候操作的相关元数据(整个反射过程中最耗时的就是获取元数据阶段,如Class.forName())。

6、参考

原文地址:https://www.cnblogs.com/wpbxin/p/14886861.html