Java学习之==>反射

一、什么是反射

  大家都知道,要让 Java 程序能够运行,就得让类加载进 Java 虚拟机。如果类没有被加载进 Java 虚拟机,是不能正常运行的。现在我们运行的所有的程序都是在编译期的时候就已经知道了你所需要的那个类的已经被加载了。Java 的反射机制是在编译并不确定是哪个类被加载了,而是在程序运行的时候才加载、探知、自审,使用在编译期并不知道的类。

  Java 反射机制指的是在 Java 程序运行状态中,对于任何一个类,都可以获得这个类的所有属性和方法;对于给定的一个对象,都能够调用它的任意一个属性和方法。这种动态获取类的内容以及动态调用对象的方法称为反射机制。

  Java 的反射机制是Java特性之一,反射机制是构建框架技术的基础所在。灵活掌握Java反射机制,对学习框架技术有很大的帮助。

二、反射的作用

  • 首先,反射机制极大的提高了程序的灵活性和扩展性,降低模块的耦合性,提高自身的适应能力;
  • 其次,通过反射机制可以让程序创建和控制任何类的对象,无需提前硬编码目标类;
  • 再次,使用反射机制能够在运行时构造一个类的对象、判断一个类所具有的成员变量和方法、调用一个对象的方法;
  • 最后,反射机制是构建框架技术的基础所在,使用反射可以避免将代码写死在框架中;

  正是反射有以上的特征,所以它能动态编译和创建对象,极大的激发了编程语言的灵活性,强化了多态的特性,进一步提升了面向对象编程的抽象能力,因而受到编程界的青睐。

三、反射的原理

Java 类的执行需要经过以下几步:

  • 编译:将 .java 文件编译成 .class 字节码文件;
  • 加载:类加载器负责根据一个类的全限定名来读取此类的二进制字节流到JVM内部,并存储在运行时内存区的方法区,然后将其转换为一个与目标类型对应的java.lang.Class对象实例;
  • 连接
    • 验证:确保类文件的正确性,比如class文件的版本,class文件的魔术因子是否正确;
    • 准备:静态变量赋初值和内存空间,final修饰的内存空间直接赋原值;
    • 解析:把类中的符号引用转换为直接引用,分配地址;
  • 初始化:将static修饰代码执行一遍,如果是静态变量,则用用户指定值覆盖原有初值;如果是代码块,则执行一遍操作。

  Java 的反射就是利用上面第二步加载到 JVM 中的 .class 字节码文件转化的 Class 类对象来进行操作的。通过这个 Class 类对象我们就能获得加载到虚拟机当中这个Class对象对应的方法、成员以及构造方法的声明和定义等信息,然后进行各种操作。

四、反射常用API

1、获取反射中的 Class 类对象

在反射中,要获取一个类或调用一个类的方法,我们首先要获取到这个类的 Class 类对象。在 Java API 中,获取 Class 类对象有三种方法:

<1>、使用 Class.forName 静态方法。当我们知道该类的全路径名时,可以使用该方法获取 Class 类对象。

Class<?> clazz = Class.forName("code.reflect.MethodFuncs");

<2>、使用 类名+.class,这种方法适合在编译前就知道要操作的类。

Class<Father> fatherClass = Father.class;

<3>、使用类对象的 getClass() 方法

User user = new User();
Class<?> userClass = user.getClass();

2、通过反射获取类的 属性、方法、构造方法、注解等

<1>、属性

public void testField() throws Exception {
  // 基于Class找到类
  Class<?> clazz = Class.forName("code.reflect.Person");

  /**
   * 获取类的属性
   * getField只能获取到公有属性
   * getDeclaredField能获取到公有的也能获取到私有的
   */
  Field ageField = clazz.getField("age");
  Field infoField = clazz.getDeclaredField("info");

  // 看一下属性的名字
  String agefieldName = ageField.getName();
  String infoFieldName = infoField.getName();

  System.out.println(agefieldName);
  System.out.println(infoFieldName);

  System.out.println("------------------");

  /**
   * 获取Person类的所有属性,包括公有属性和私有属性,存放在一个数组内
   * 同样getFields只能获取到共有属性
   * getDeclaredFields能获取到公有的也能获取到私有的
   */
  Field[] fields = clazz.getDeclaredFields();
  for (Field field : fields) {
    System.out.println(field.getName());
  }
}

<2>、方法

public void testMethod() throws Exception {
  Class<?> clazz = Class.forName("code.reflect.MethodFuncs");
  /**
   * 获取类的方法
   * getMethod只能获取到公有方法
   * getDeclaredMethod能获取到公有的也能获取到私有的
   */
  Method sumMethod = clazz.getMethod("sum", Integer.class, Integer.class);
  Method sumMethod1 = clazz.getDeclaredMethod("sum1", int.class, int.class);

  // 打印方法名
  System.out.println(sumMethod.getName());
  System.out.println(sumMethod1.getName());
  /**
   * 获取MethodFuncs类的所有方法,包括公有方法和私有方法,存放在一个数组内
   * 同样getMethods只能获取到共有方法
   * getDeclaredMethods能获取到公有的也能获取到私有的
   */
  Method[] methods = clazz.getDeclaredMethods();
  for (Method method : methods) {
    System.out.println(method.getName());
  }
}

<3>、构造方法

public void testConsutrcutor() throws Exception {
  Class<?> clazz = Class.forName("code.reflect.Person");
  /**
   * 获取类的构造
   * getConstructor只能获取到公有方法
   * getDeclaredConstructor能获取到公有的也能获取到私有的
   */
  Constructor<?> publicConstructor1 = clazz.getConstructor(String.class);
  Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(String.class, int.class);
  System.out.println(publicConstructor1);
  System.out.println(declaredConstructor);
  /**
   * 获取Person类的所有构造方法,包括公有方法和私有方法,存放在一个数组内
   * 同样getConstructors只能获取到公有方法
   * getDeclaredConstructors能获取到公有的也能获取到私有的
   */
  Constructor[] constructors = clazz.getDeclaredConstructors();
  for (Constructor constructor : constructors) {
    System.out.println(constructor);
  }
}

<4>、注解 

public void testAnnotation() throws ClassNotFoundException, NoSuchMethodException {
  Class<?> clazz = Class.forName("code.reflect.Person");
  // 获取对类的指定注解NotNull
  NotNull notNullAnnotation = clazz.getAnnotation(NotNull.class);
  System.out.println(notNullAnnotation);
  /**
   * 获取类中方法的注解
   * 先获取到该方法,再获取方法的注解
   * 如果是获取属性的注解,则要先获取到属性
   */
  Method getNameMethod = clazz.getMethod("getName");
  NotNull methodAnnotation = getNameMethod.getAnnotation(NotNull.class);
  System.out.println(methodAnnotation);
  /**
   * 获取类的所有注解
   * 这里用的是getAnnotations,表示只能获取到共有的
   */
  Annotation[] annotations = clazz.getAnnotations();
  for (Annotation annotation : annotations) {
    System.out.println(annotation);
  }
  /**
   * 获取getname方法的所有注解
   * 这里用的是getDeclaredAnnotations,表示能获取到共有的和私有的
   */
  Annotation[] declaredAnnotations = getNameMethod.getDeclaredAnnotations();
  for (Annotation declaredAnnotation : declaredAnnotations) {
    System.out.println(declaredAnnotation);
  }
}

3、反射获取到方法后再执行方法

<1>、无参方法

public void testMethod() throws Exception {

  // 获取到Person类的Class类对象
  Class<?> clazz = Class.forName("code.reflect.Person");

  // 获取到sayHello方法
  Method helloMethod = clazz.getMethod("sayHello");

  // invoke执行通过反射获取的类的方法“sayHello”
  helloMethod.invoke(new Person());
  }

<2>、有参方法

public void testMethod2() throws Exception {
  Class<?> clazz = Class.forName("code.reflect.MethodFuncs");

  Method sumMethod = clazz.getMethod("sum", Integer.class, Integer.class);
  Method sumMethod1 = clazz.getDeclaredMethod("sum1", int.class, int.class);

  // 如果是私有方法需要对私有方法增加可访问的权限
  sumMethod1.setAccessible(true);
  Object result = sumMethod.invoke(new MethodFuncs(), 3, 5);
  Object result1 = sumMethod1.invoke(new MethodFuncs(), 3, 5);

  System.out.println(result);
  System.out.println(result1);
}

4、通过反射实例化对象

<1>、通过 Class 对象的 newInstance() 方法

public void testMethod4() throws Exception {
  // 获取MethodFuncs的Class类对象
  Class<?> clazz = Class.forName("code.reflect.MethodFuncs");

  // 通过newInstance实例化一个对象obj
  // newInstance方法的本质就是帮我们调了一遍MethodFuncs类的构造方法
  Object obj = clazz.newInstance();

  // 获取sum方法
  Method sumMethod = clazz.getDeclaredMethod("sum", Integer.class, Integer.class);

  // 执行sum方法
  Object result = sumMethod.invoke(obj, 3, 5);
  System.out.println(result);
}

<2>、通过 Constructor 对象的 newInstance() 方法

public void testMethod5() throws Exception {
  // 获取MethodFuncs的Class类对象
  Class<?> clazz = Class.forName("code.reflect.MethodFuncs");

  Constructor<?> constructor = clazz.getConstructor(String.class);
  Object obj = constructor.newInstance("小明");

  // 获取sum方法
  Method sumMethod = clazz.getDeclaredMethod("sum", Integer.class, Integer.class);

  // 执行sum方法
  Object result = sumMethod.invoke(obj, 3, 5);
  System.out.println(result);
}

通过 Constructor 对象创建类对象可以选择特定构造方法,而通过 Class 对象则只能使用默认的无参数构造方法。下面的代码就调用了一个有参数的构造方法进行了类对象的初始化。

5、对属性进行赋值

<1>、公有属性

public void testSetFields() throws Exception {

  Class<?> clazz = Class.forName("code.reflect.Person");

  Field nameField = clazz.getField("name");

  // 通过newInstance实例化对象
  Object instance = clazz.newInstance();

  System.out.println("instance before:" + instance);

  // 对属性进行赋值
  nameField.set(instance, "张三");

  System.out.println("instance after:" + instance);

  // 获取属性的值
  Object nameValue = instance.getClass().getField("name").get(instance);
  System.out.println("field get val:" + nameValue);
}

<2>、私有属性

public void testSetDeclareFields() throws Exception {
  Class<?> clazz = Class.forName("code.reflect.Person");

  // 获取私有属性-->getDeclaredField
  Field nameField = clazz.getDeclaredField("amount");

  // 对私有属性增加可访问的权限-->setAccessible
  nameField.setAccessible(true);

  // 通过newInstance实例化对象
  Object instance = clazz.newInstance();

  System.out.println("instance before:" + instance);

  // 给属性赋值
  nameField.set(instance, 1024);

  System.out.println("instance after:" + instance);
}

五、反射的缺点

尽管反射机制带来了极大的灵活性及方便性,但反射也有缺点。反射机制的功能非常强大,但不能滥用。在能不使用反射完成时,尽量不要使用,原因有以下几点:

1、性能问题

  Java 反射机制中包含了一些动态类型,所以Java虚拟机不能够对这些动态代码进行优化。因此,反射操作的效率要比正常操作效率低很多。我们应该避免在对性能要求很高的程序或经常被执行的代码中使用反射。而且,如何使用反射决定了性能的高低。如果它作为程序中较少运行的部分,性能将不会成为一个问题。

2、安全限制

  使用反射通常需要程序的运行没有安全方面的限制。如果一个程序对安全性提出要求,则最好不要使用反射。

3、程序健壮性

  反射允许代码执行一些通常不被允许的操作,所以使用反射有可能会导致意想不到的后果。反射代码破坏了Java程序结构的抽象性,所以当程序运行的平台发生变化的时候,由于抽象的逻辑结构不能被识别,代码产生的效果与之前会产生差异。

六、反射的应用

1、流行框架应用

我们经常使用的 Spring 配置中,经常会有相关 Bean 的配置:

当我们在 XML 文件中配置了上面这段配置之后,Spring 便会在启动的时候利用反射去加载对应的类。而当类不存在或发生启发异常时,异常堆栈便会将异常指向调用的 invoke 方法。从这里可以看出,我们平常很多框架都使用了反射,而反射中最最终的就是 Method 类的 invoke 方法了。

2、自动化框架的设计

我们可以把完整的包+类名称放到 properties 文件中,可以根据这个配置获取类的全路径,然后就可以干很多事。

下面来简单举个例子:

public interface OrderService {

  Response createOrder(Order order);

  Response queryOrder(String orderId, User user, Order order);

  Response updateOrder(Order order);
}
OrderService 接口

OrderService 接口中有三个方法,createOrder()、queryOrder() 和 updateOrder()。

public class OrderServiceImpl implements OrderService {

  @Override
  public Response createOrder(Order order) {
    System.out.println("createOrder execute..." + order);
    return new Response("200", "success");
  }

  @Override
  public Response queryOrder(@Params("orderId") String orderId, User user, Order order) {
    System.out.println("queryOrder execute... orderId:" + orderId);
    System.out.println("queryOrder execute...order:" + order);
    System.out.println("queryOrder execute...user:" + user);
    return new Response("200", "success");
  }

  @Override
  public Response updateOrder(Order order) {
    System.out.println("updateOrder execute..." + order);
    return new Response("200", "success");
  }
}
OrderServiceImpl 实现类

OrderServiceImpl 对 OrderService 接口中的方法有了具体实现。

iface=code.reflect.case1.impl.OrderServiceImpl
method=createOrder
iface.properties 配置文件

iface.properties 配置文件中配置了实现类的全路径和方法名

orderId=111
userId=111
amount=111
create_order1.properties 测试数据

create_order.properties 配置测试数据,这种文件有很多个,根据不同测试场景进行配置。

public class CaseOneArgHandler {

  private static final String IFACE_CONFIG_FILE = "iface.properties";

  private static final String CASE_BASE_PATH = "cases";

  public static void callCase(String caseTitle, String caseFile) throws Exception {
    // 1.获取接口的配置
    Properties ifaceConfigProp = new Properties();

    // 加载 cases/create_order/iface.properties
    ifaceConfigProp.load(CaseOneArgHandler.class.getClassLoader()
        .getResourceAsStream(CASE_BASE_PATH + "/" + caseTitle + "/" + IFACE_CONFIG_FILE));

    // 2.获取case数据的配置
    Properties caseDataProp = new Properties();

    // 加载 cases/create_order/create_order.properties
    caseDataProp.load(CaseOneArgHandler.class.getClassLoader()
        .getResourceAsStream(CASE_BASE_PATH + "/" + caseTitle + "/" + caseFile));

    // 获取每个case中的 接口类全名,比如: reflect.case1.impl.TradeServiceImpl
    String ifaceClassName = ifaceConfigProp.getProperty("iface");

    // 获取每个case中的 接口的方法名, 比如: createTrade
    String ifaceMethodName = ifaceConfigProp.getProperty("method");

    // 反射接口类,拿到接口的类对象
    Class<?> clazz = Class.forName(ifaceClassName);

    // 获取这个类对象的方法
    Method[] methods = clazz.getMethods();
    // clazz.getMethod("createOrder", Order.class);

    // 实例化接口的类,相当于是 new TradeServiceImpl()
    Object ifaceInstance = clazz.newInstance();

    // 遍历方法,找到我们需要的那个方法,createTrade
    for (Method method : methods) {
      // 遍历的方法后的判断
      if (!method.getName().equals(ifaceMethodName)) {
        continue;
      }

      // 获取方法的参数的类型的数组, [Order.class,Trade.class,User.class]
      Class<?>[] parameterTypes = method.getParameterTypes();

      // 具体的parameterType就是Order/Trade/User等等
      Class<?> parameterType = parameterTypes[0];

      // 还要进行反射, 目的是将我们的测试数据, 跟我们的参数类型的字段进行设置
      Field[] fields = parameterType.getDeclaredFields();

      Object paramInstance = parameterType.newInstance();

      // 相当于是对Order/Trade/User的字段进行遍历
      for (Field field : fields) {
        // 相当于是取到了 orderId
        String fieldName = field.getName();

        // 这时去properties文件中找数据, caseDataProp.getProperty("orderId");
        String propertyValue = caseDataProp.getProperty(fieldName);

        // 私有的属性,设置一下权限
        field.setAccessible(true);

        // 对属性进行赋值
        field.set(paramInstance, propertyValue);
      }

      // 真正的执行调用,相当于是 orderService.createOrder(order);
      method.invoke(ifaceInstance, paramInstance);
    }
  }
}
CaseOneArgHandler

CaseOneArgHandler 为框架的主业务逻辑处理,针对的是一个参数的情况。当中的处理思路是:读取 iface.properties 配置文件中的类和方法,通过反射获取到该类和方法的实体,再进一步通过反射得到该方法的参数类型和参数名称,然后把 create_order.properties 配置文件中的测试数据设置到 Order 对象中作为整个方法的参数传入,最后通过反射生成的实例化对象的 invoke 方法执行该方法,得到执行结果。

public class AppCreateOrder {

  public static void main(String[] args) throws Exception {
    // case 1
    CaseOneArgHandler.callCase("create_order", "create_order1.properties");
    // case 2
    CaseOneArgHandler.callCase("create_order", "create_order2.properties");
    // case 3
    CaseOneArgHandler.callCase("create_order", "create_order3.properties");
    // case 4
    CaseOneArgHandler.callCase("create_order", "create_order4.properties");
  }
}
AppCreateOrder

测试执行类,一个 case 调用一次 createOrder() 方法执行,结果如下:

当然,上面的 CaseOneArgHandler 只支持单个参数的 createOrder(Order order) 方法,如果需要支持像 queryOrder(String orderId, User user, Order order) 这样多个参数的方法,需要对 CaseOneArgHandler 进行优化才能支持,这里暂时不做介绍。

posted on 2019-09-19 18:12  破解孤独  阅读(395)  评论(0编辑  收藏  举报

导航