1. 概述

Java 反射机制与动态署理我们平时写代码可能用得比较少,但在种种常见的框架(Spring、MyBatis 等)中却习以为常。有句话叫“无反射,不框架;无署理,不框架”。

由于以后计划阅读和学习框架的源码,这里先简朴回首反射机制和动态署理(暂不深入剖析实现原理),为后面做些准备。

2. 反射机制

Java 反射机制是在 Java 程序运行状态中,对于随便一个类,都能够知道这个类的所有属性和方式;对于随便一个工具,都能够挪用它的随便方式和属性。这种动态获取信息以及动态挪用工具方式的功效称为 Java 语言的反射机制。

从面向工具的角度来看,我们平时用到的"类"、"组织器"、"属性"、"方式"实在也是一个"类",它们在 JDK 中划分对应 Class、Constructor、Field、Method 类。其中 Class 相当于"类的类",可称为"元类",从这个角度看,我们平时自界说的"类"可以明白为 Class 的一个工具。

简朴来说(小我私家明白),反射机制就是通过 Class、Constructor 等"元类"来操作其他的通俗类(建立工具、挪用方式等)。下面以简朴代码示例。

2.1 准备代码

界说一个通俗的 Java 类 Human,如下:

package com.jaxer.example.reflection;

public class Human {
  public String gender;

  private int age;

  protected void hello() {
  }

  public void hi() {
  }
}

界说一个通俗的 Person 类,继续自 Human,如下:

public class Person extends Human {
  // 两个属性
  public String name;

  private int age;

  // 种种修饰符的组织器
  public Person() {
  }

  private Person(String name) {
    this.name = name;
  }

  protected Person(int age) {
    this.age = age;
  }

  Person(String name, int age) {
    this.name = name;
    this.age = age;
  }

  // getter, setter 方式
  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public int getAge() {
    return age;
  }

  public void setAge(int age) {
    this.age = age;
  }

  // 自界说方式
  private void test() {
    System.out.println("test is invoked.");
  }

  @Override
  public String toString() {
    return "Person{" +
        "name='" + name + '\'' +
        ", age=" + age +
        '}';
  }
}

该 Person 类界说了 name 和 age 两个成员变量及其 getter/setter 方式,另有四个组织器,划分使用差别的修饰符,自界说了一个 test 方式,重写了 toString 方式。

PS: 这两个类仅供参考,只是为了演示。

2.2 获取 Class

获取 Person 的 Class 工具通常有下面三种方式:

// 方式一:从工具获取
Person person = new Person();
person.getClass();

// 方式二:类名.class
Person.class;

// 方式三:Class.forName
Class<?> aClass = Class.forName("com.jaxer.example.reflection.Person");

/*
  这三种方式的获取到的都是:
    class com.jaxer.example.reflection.Person
  而且可以证实,这三种方式获得的同一个 Class 工具,即同一个 Person 类
*/

获取到了 Class 工具,就可以使用 Class 工具来建立 Person 工具或者做一些其他操作,例如:

// 获取 Class 工具
Class<?> aClass = Class.forName("com.jaxer.example.reflection.Person");

// 使用 Class 建立 Person 工具
System.out.println(aClass.newInstance()); // 建立 Person 工具

// 获取 Person 的父类(Human)
System.out.println("superClass-->" + aClass.getSuperclass());

/*  输出效果:
 *  Person{name='null', age=0}
 *  superClass-->class com.jaxer.example.reflection.Human
 */

2.3 获取 Constructor

Class 中获取组织器(Constructor)的方式如下:

  1. getConstructors: 获取所有 public 组织器;

  2. getDeclaredConstructors: 获取所有组织器(包罗 private 类型);

  3. getConstructor(Class… parameterTypes): 获取指定参数的 public 组织器;

  4. getDeclaredConstructor(Class… parameterTypes): 获取指定参数的组织器(包罗 private 类型)。

测试代码:

private static void testConstructors(Class<?> aClass) throws Exception {
    // 1. 获取所有public组织器
    Constructor<?>[] constructors = aClass.getConstructors();
    for (Constructor<?> constructor : constructors) {
        System.out.println("constructor-->" + constructor);
    }

    // 2. 获取所有组织器
    Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();
    for (Constructor<?> declaredConstructor : declaredConstructors) {
        System.out.println("declaredConstructor-->" + declaredConstructor);
    }

    // 4. 获取指定的组织器(凭据参数类型)
    Constructor<?> constructor = aClass.getDeclaredConstructor(String.class);
    // 修改接见权限(用这种方式可以使用 private 类型组织器建立工具)
    constructor.setAccessible(true);
    // 使用该组织器建立 Person 工具
    Object instance = constructor.newInstance("Ace");
    System.out.println(instance);
}

输出效果如下:

constructor-->public com.jaxer.example.reflection.Person()

declaredConstructor-->com.jaxer.example.reflection.Person(java.lang.String,int)
declaredConstructor-->protected com.jaxer.example.reflection.Person(int)
declaredConstructor-->private com.jaxer.example.reflection.Person(java.lang.String)
declaredConstructor-->public com.jaxer.example.reflection.Person()

Person{name='Ace', age=0}

2.4 获取 Field

获取 Class 属性(Field)的方式如下:

  1. getFields(): 获取 public 属性(包罗父类);

  2. getDeclaredFields(): 获取所有属性(包罗 private 类型);

  3. getField(String name): 获取指定名称的 public 属性;

  4. getDeclaredField(String name): 获取指定名称的属性(包罗 private 类型)。

测试代码:

private static void testFields(Class<?> aClass) throws Exception {
    // 1. 获取 public 属性(包罗父类的 public 属性)
    Field[] fields = aClass.getFields();
    for (Field field : fields) {
        System.out.println("field-->" + field);
    }

    // 2. 获取所有属性
    Field[] declaredFields = aClass.getDeclaredFields();
    for (Field declaredField : declaredFields) {
        System.out.println("declaredField-->" + declaredField);
    }

    // 4. 获取指定的属性(这里获取 age 属性)
    Field age = aClass.getDeclaredField("age");
    System.out.println("age-->" + age);

    // 给指定工具的属性赋值(private 类型不能赋值,需要修改接见权限)
    Object obj = aClass.newInstance();
    age.setAccessible(true); // 修改接见权限
    age.set(obj, 18); // 设置新值
    System.out.println("obj-->" + obj);
}

输出效果:

field-->public java.lang.String com.jaxer.example.reflection.Person.name
field-->public java.lang.String com.jaxer.example.reflection.Human.gender

declaredField-->public java.lang.String com.jaxer.example.reflection.Person.name
declaredField-->private int com.jaxer.example.reflection.Person.age

age-->private int com.jaxer.example.reflection.Person.age
obj-->Person{name='null', age=18}

2.5 获取 Method

获取 Class 方式(Method)的方式如下:

  1. getMethods(): 获取所有 pubic 方式(包罗父类及 Object 类);

  2. getDeclaredMethods(): 获取所有方式(包罗 private 方式);

  3. getMethod(String name, Class… parameterTypes): 获取指定名称和参数的 public 方式;

    ,

    联博统计

    www.caibao.it采用以太坊区块链高度哈希值作为统计数据,联博以太坊统计数据开源、公平、无任何作弊可能性。联博统计免费提供API接口,支持多语言接入。

    ,
  4. getDeclaredMethod(String name, Class… parameterTypes): 获取指定名称和参数的方式(包罗 private 方式)。

测试代码:

private static void testMethods(Class<?> aClass) throws Exception {
  // 1. 获取所有 public 方式(包罗父类及 Object 类)
  Method[] methods = aClass.getMethods();
  for (Method method : methods) {
    System.out.println("method-->" + method);
  }

  // 2. 获取该类声明的所有方式
  Method[] declaredMethods = aClass.getDeclaredMethods();
  for (Method declaredMethod : declaredMethods) {
    System.out.println("declaredMethod-->" + declaredMethod);
  }

  // 4. 获取指定的方式并挪用
  Method method = aClass.getDeclaredMethod("test");
  method.setAccessible(true); // 修改接见权限
  System.out.println("方式名:" + method.getName());
  System.out.println("方式修饰符:" + Modifier.toString(method.getModifiers()));
  Object instance = aClass.newInstance();
  System.out.println(method.invoke(instance));
}

输出效果:

// getMethods 方式返回效果:
  // Person 类的 public 方式:
method-->public java.lang.String com.jaxer.example.reflection.Person.toString()
method-->public java.lang.String com.jaxer.example.reflection.Person.getName()
method-->public void com.jaxer.example.reflection.Person.setName(java.lang.String)
method-->public void com.jaxer.example.reflection.Person.setAge(int)
method-->public int com.jaxer.example.reflection.Person.getAge()
  // Human 类的 public 方式:
method-->public void com.jaxer.example.reflection.Human.hi()
  // Object 类的 public 方式:
method-->public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
method-->public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
method-->public final void java.lang.Object.wait() throws java.lang.InterruptedException
method-->public boolean java.lang.Object.equals(java.lang.Object)
method-->public native int java.lang.Object.hashCode()
method-->public final native java.lang.Class java.lang.Object.getClass()
method-->public final native void java.lang.Object.notify()
method-->public final native void java.lang.Object.notifyAll()

// getDeclaredMethods 方式返回效果:
declaredMethod-->public java.lang.String com.jaxer.example.reflection.Person.toString()
declaredMethod-->public java.lang.String com.jaxer.example.reflection.Person.getName()
declaredMethod-->public void com.jaxer.example.reflection.Person.setName(java.lang.String)
declaredMethod-->private void com.jaxer.example.reflection.Person.test()
declaredMethod-->public void com.jaxer.example.reflection.Person.setAge(int)
declaredMethod-->public int com.jaxer.example.reflection.Person.getAge()

// 挪用 test 方式:
方式名:private void com.jaxer.example.reflection.Person.test()
方式修饰符:private
test is invoked.
null

框架中常用的反射机制主要是以上那些,这里暂不深究实在现原理,以后有需要再行弥补。下面简要剖析署理相关的内容。

3. 署理

使用署理的主要目的:对目的工具(的方式)举行功效增强,例如 Spring 的 AOP。

既然是对目的工具的方式举行增强,署理工具的方式中一定会挪用目的工具的方式。而且一样平常会在目的工具的方式挪用前后(或者其他时机)做一些其他的处置以到达增强的效果。

署理模式通常分为「静态署理」和「动态署理」,静态署理使用较少;而动态署理常用的有 JDK 动态署理和 CGLib 动态署理。下面简要剖析。

3.1 准备代码

一个通俗的 Java 接口及实在现类如下:

// 接口
public interface UserService {
    void save(String name);
}

// 实现类
public class UserServiceImpl implements UserService {
    @Override
    public void save(String name) {
        System.out.println("保留用户名:" + name);
    }
}

无论静态署理照样动态署理,都是对该类的 save 方式举行增强。

此处以在该方式执行前后各打印一句话来演示。

3.2 静态署理

主要头脑:建立一个 UserService 接口的实现类 UserServiceProxy(署理工具),而且该类持有 UserServiceImpl 工具(目的工具),在 UserServiceProxy 类的 save 方式中挪用 UserServiceImpl 的 save 方式,示例代码如下:

public class UserServiceProxy implements UserService {
  private UserService userService;

  public UserServiceProxy(UserService userService) {
    this.userService = userService;
  }

  @Override
  public void save(String name) {
    System.out.println("---静态署理:方式执行前---");
    userService.save(name); // 挪用目的类的方式
    System.out.println("---静态署理:方式执行后---");
  }
}

测试代码:

private static void testStaticProxy() {
  UserService userService = new UserServiceImpl();
  UserService proxy = new UserServiceProxy(userService);
  proxy.save("jack");
}

/* 运行效果:
  ---静态署理:方式执行前---
  保留用户:jack
  ---静态署理:方式执行后---
*/

显而易见,使用 UserServiceProxy 可以增强 UserServiceImpl 的 save 方式。但由于代码是牢固的(编码时代写好的),不够天真,因此静态署理使用较少,通常使用动态署理。

PS: 此处代码实现形式可能不尽相同,但思绪相近。

3.3 动态署理

与静态署理相比,动态署理则是在运行时动态天生一个署理类,该类可以对目的工具的方式举行功效增强。动态署理常用的有 JDK 动态署理和 CGLib 动态署理,下面简要剖析。

3.3.1 JDK 动态署理

JDK 的动态署理实现方式:

使用 Proxy 类的 newProxyInstance 方式建立署理工具,使用 InvocationHandler 来实现增强的逻辑(通常建立一个 InvocationHandler 接口的实现类,在其 invoke 方式中实现增强的逻辑)。示例代码如下(仅供参考):

// JDK 署理工厂,作用是天生署理工具
public class JDKProxyFactory {
    // 获取 target 的署理工具(其中 target 为目的工具)
    public Object getProxy(Object target) {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), new MyInvocationHandler(target));
    }

    // 自界说 InvocationHandler
    private static class MyInvocationHandler implements InvocationHandler {
        // 目的工具
        private Object target;

        public MyInvocationHandler(Object target) {
            this.target = target;
        }

        // 实现增强逻辑
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("目的类方式执行前-----");
            Object result = method.invoke(target, args); // 挪用目的工具的方式
            System.out.println("目的类方式执行后-----");
            return result;
        }
    }
}

测试代码:

private static void testJdkProxy() {
    UserService userService = new UserServiceImpl(); // 目的工具
    JDKProxyFactory jdkProxyFactory = new JDKProxyFactory(); // 署理工厂
    // 使用署理工厂天生署理工具
    UserService proxy = (UserService) jdkProxyFactory.getProxy(userService);
    proxy.save("jack"); // 挪用署理工具的方式(已对目的工具举行增强)
}

/* 运行效果:
    目的类方式执行前-----
    保留用户:jack
    目的类方式执行后-----
*/
实现原理可参考:https://blog.csdn.net/yhl_jxy/article/details/80586785

4.3.3.2 CGLib 动态署理

主要思绪:

建立一个目的类的子类 Enhancer,在子类中设置回调工具(MethodInterceptor),并在回调方式(intercept)中实现对目的工具的增强功效逻辑。示例代码如下:

// CGLIB 工厂,用于天生署理工具
public class CglibProxyFactory {
    // 获取署理工具(对目的工具举行增强)
    public Object getProxy(Object target) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(new MyMethodInterceptor()); // 设置回调
        return enhancer.create();
    }

    // 自界说方式阻挡 MethodInterceptor
    private static class MyMethodInterceptor implements MethodInterceptor {
        @Override
        public Object intercept(Object obj, Method method, Object[] args, 
                    MethodProxy proxy) throws Throwable {
            System.out.println("cglib方式挪用前");
            Object o = proxy.invokeSuper(obj, args); // 挪用目的工具的方式
            System.out.println("cglib方式挪用后");
            return o;
        }
    }
}

测试代码:

private static void testCglibProxy() {
  UserService userService = new UserServiceImpl();
  CglibProxyFactory cglibProxyFactory = new CglibProxyFactory();
  UserService proxy = (UserService) cglibProxyFactory.getProxy(userService);
  proxy.save("jack");
}

/* 运行效果:
  cglib方式挪用前
  保留用户:jack
  cglib方式挪用后
*/

PS: 使用 CGLib 需要导入第三方 jar 包(cglib 和 asm)。

实现原理可参考:https://blog.csdn.net/yhl_jxy/article/details/80633194

二者主要区别:

JDK 动态署理不依赖第三方库,CGLib 需要依赖第三方库;
若目的工具实现了接口,两种方式都可以使用;若未实现接口,则只能使用 CGLib。

4. 小结

反射机制:简朴来说,反射机制主要是通过 Class、Constructor 等"元类"来操作其他的通俗类,以到达在运行时代动态建立工具、动态挪用方式等目的。

静态署理&动态署理:二者主要区别在于时机,静态署理在编码期已写好署理类,而动态署理则是在运行时代动态天生署理类。

JDK 动态署理&CGLib 动态署理的主要区别:

  • JDK 动态署理不依赖第三方库,CGLib 则要依赖第三方库;
    JDK 使用 InvocationHandler 接口实现增强逻辑,使用 Proxy.newProxyInstance 天生署理工具;而 CGLib 使用 MethodInterceptor 接口实现增强逻辑,使用 Enhancer 天生署理工具;
  • 若目的工具实现了接口,两种方式都可以使用;若未实现接口,则只能使用 CGLib。