Java 反射与动态代理

721 阅读8分钟

编程语言有多种分类角度,其中一种就是动态类型语言和静态类型语言,简单区分就是静态类型语言在编译期进行类型检查,而动态类型语言在运行时进行类型检查。注意,这和动态语言、静态语言区分角度不同。

一般来说,Java 属于静态类型语言,但因为提供了类似反射等机制,也具备了部分动态类型语言的能力。今天就来介绍一下反射机制。

什么是反射

反射机制指的是程序在运行时自省(introspect,即能够获取自身信息)的能力。在 Java 中,只要给定类的完全限定名,就可以通过反射直接操作类或对象。它主要有以下几个作用:

  • 在运行时判断任意一个对象所属的类;
  • 在运行时判断任意一个类具有的成员变量和方法;
  • 在运行时调用任意一个对象的方法;
  • 在运行时构造任意一个类的对象;

可以看到,反射提高了程序的灵活性和扩展性。但同时降低了代码的可读性和维护性;又因为反射涉及到动态类型,无法执行某些虚拟机优化,所以代码的执行性能也降低;另外,可以访问任意成员变量和方法,也破坏了封装性。一般来说,在业务代码中应尽量避免使用反射,但必须能理解中间件或框架中的反射代码。

反射的应用场景非常多。例如,对象序列化,动态代理,JDBCClass.forName()RPC 框架,SpringIOC/DI

Class 类

JavaClass 类是反射机制的基础,包含了被装入到 JVM 中的类(包括类和接口)的信息。每个类(型)都有一个 Class 对象,也就是说每当编写并编译了一个新类,就会产生一个 Class 对象,被保存在一个同名的 .class 文件中。

所有的类都是在对其第一次使用时,动态加载到 JVM 中的。当运行程序时,类加载器会首先检查这个类的 class 对象是否已经加载,如果没有加载,类加载器就会根据类名查找 .class 文件,并将其 Class 对象载入。

获取某一个类所对应的 Class 对象有三种方法:

1). 根据对象的 getClass() 方法获取:

User user = new User();
Class c = user.getClass();

2). 根据 Class 的静态方法 forName() 获取:

Class c = Class.forName("com.timber.User");

3). 根据类名 .class 获取:

Class c = User.class;

对于基本类型来说,它们的包装类型拥有一个名为 TYPEfinal 静态字段,指向该基本类型对应的 Class 对象。例如,Integer.TYPE 指向 int.class

对于数组类型来说,可以使用 类名 + [].class 来访问 Class 对象。例如,int[].class

使用反射创建对象

Java 中创建对象主要有四种方式:

  • 通过 new 关键字创建;
  • 使用反射;
  • 使用 clone 方法;
  • 使用反序列化;

使用反射

Java 中使用反射创建对象主要有两种方法:

1). 使用 Class 类的 newInstance 方法

方法一:
Class<?> userClass = Class.forName("com.timber.User");  // 给定类的完全限定名
User user = (User) userClass.newInstance();

方法二:
User user = User.class.newInstance();

2). 使用 Constructor 类的 newInstance 方法

Constructor<User> constructor = User.class.getConstructor();
User user = constructor.newInstance();

Constructor<?> constructor = User.class.getConstructor();
User user = (User) constructor.newInstance();

事实上,ClassnewInstance 方法内部就是调用 ConstructornewInstance 方法。如下为 Class 类的源码:

@CallerSensitive
public T newInstance() throws InstantiationException, IllegalAccessException {
    Constructor<T> tmpConstructor = cachedConstructor;
    try {
        return tmpConstructor.newInstance((Object[])null);
    } catch (InvocationTargetException e) {
        Unsafe.getUnsafe().throwException(e.getTargetException());
        // Not reached
        return null;
    }
}

使用反射功能

在得到 Class 对象后,可以正式使用反射功能了。除了使用 newInstance() 生成类的实例,还有以下几项:

1). 使用 isInstance(Object o) 来判断一个对象是否该类的实例,等同于 instanceof 关键字:

Class c = User.class.newInstance();
boolean b = c.isInstance(user);

2). 使用 Array.newInstance(Class c, int size) 来构造该类型的数组:

int[] arr = Array.newInstance(int[].class, 3);

3). 使用 getFields()/getConstructors()/getMethods() 来访问该类的成员。需要注意,方法名中带 Declared 的不会返回父类的成员,但是会返回私有成员;而不带 Declared 的则相反。

Method[] methods = c.getMethods();
Method[] methods = c.getDeclaredMethods();

当获得类成员之后,可以进一步做如下操作:

  • 使用 Constructor/Field/Method.setAccessible() 来修改访问限制
  • 使用 Constructor.newInstance(Object[]) 来生成该类的实例
  • 使用 Field.set/get(Object) 来访问字段值
  • 使用 Method.invoke(Object, Object[]) 来调用方法

实例:在泛型为 IntegerArrayList 中存放一个 String 类型的对象:

public void test() {
    try {
        List<Integer> list = new ArrayList<>();
        Class<?> listClass = list.getClass();
        Method method = listClass.getMethod("add", Object.class);
        method.invoke(list, "test");
        System.out.println(list.get(0));
    } catch (Exception e) {
        e.printStackTrace();
    }
}

反射调用的开销

在前面的方法中,Class.forName 会调用本地方法,Class.getMethod() 会遍历该类的共有方法,如果找不到,还会遍历父类的私有方法,所以它们的操作都很费时。另外,Method.getMethods() 等方法还会返回查找结果的一份拷贝。

在实践中,应该避免在热点代码中使用 Method.getMethods()Method.getDeclaredMethods() 方法。并且往往会缓存 Class.forName()Method.getMethod() 的结果。

在反射调用时会带来不少性能开销,主要原因有三个:

  • 由于 Method.invoke 是一个变长参数方法,在调用时会生成一个 Object 数组
  • 由于 Object 数组不能存储基本类型,所以会对基本类型进行自动装箱、拆箱
  • 如果拥有多个不同的反射调用,就会对应多个 GeneratedMethodAccessor,可能由于 JVM 调用点的类型 profile 无法同时记住多个类,而没有被内联

静态代理

代理是基本的设计模式之一。它可以通过访问代理对象来完成对目标对象的访问,在不修改原对象的情况下扩充其功能。可以分为静态代理和动态代理两种。

静态代理,就是代理类由程序员自己编写,代理模式中的所有对象在编译期就已经确定。下面是一个简单的例子,首先定义一个接口和其实现:

public interface UserService {
    void say();
}

public class UserServiceImpl implements UserService {
    @Override
    public void say() {
        System.out.println("目标对象");
    }
}

这就是代理模式中的目标对象和目标对象的接口,接下来定义代理对象:

public class UserServiceProxy implements UserService {
    private UserService target;
    
    public UserServiceProxy(UserService target) {
        this.target = target;
    }
    
    @Override
    public void say() {
        System.out.println("调用目标对象之前");
        target.say();
        System.out.println("调用目标对象之后");
    }
}

上面就是一个代理类,它同样实现了目标对象的接口,并重写了 say 方法。下面是一个测试类:

public class ProxyTest {
    public static void main(String[] args) {
        // 目标对象
        UserService target = new UserServiceImpl();
        // 代理对象
        UserService proxy = new UserServiceProxy(target);
        proxy.say();
    }
}
// 调用目标对象之前
// 目标对象
// 调用目标对象之后

静态代理也存在一些局限,例如,需要程序员手写很多代码,并且当需要代理的类中方法比较多,或者同时需要代理多个对象时,实现会很复杂。

动态代理

动态代理中的代理类是在运行期动态生成的。在 Java 中,动态代理有两种方式:

1). JDK 接口 + 反射方式

这种方式主要通过 java.lang.reflect 包中的 Proxy 类和 InvocationHandler 接口实现。由于是 JDK 本身支持,可能比 cglib 更加可靠,代码实现也比较简单。 但是它有一个限制,就是代理对象必须实现一个或多个接口。

2). cglib 继承 + asm 方式

cglib 是一个第三方代码生成类库,通过在运行时动态生成一个子类对象来实现,它底层通过一个小而快字节码处理框架 asm,转换字节码来生成新的类。这种方式无需实现接口,达到了代理类无侵入。但是无法代理 final 类和父类的 static/final 方法。

cglib 虽然性能较高,但是它需要对 JVM 内部结构包括 Class 文件格式和指令集很熟悉,所以不鼓励使用。

Java 的动态代理最主要的用途就是应用在各种框架中。例如,RPC 框架,Spring 中的 AOPServlet 的过滤器、拦截器。像 Mybatis 的分页插件,Spring AOP 中类似日志、事务、权限、性能监控等都用到了动态代理,以在不同模块的特定阶段实现某些功能。

两种实现方法

1). JDK 动态代理

目标对象的接口和实现和静态代理中一样,下面定义调用处理器类,它需要实现 IovacationHandler 接口:

public class JDKInvocationHandler implements InvocationHandler {
    private Object target;
    
    // 传入目标对象
    public JDKInvocationHandler(Object target) {
        super();
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("调用前:" + method.getName());
        // 将请求转发给目标对象,并传入相应的参数
        Object result = method.invoke(target, args);
        System.out.println("调用后:" + method.getName());
        return result;
    }

    public Object getProxy() {
        // 传入类加载器、希望该代理类实现的接口类数组、调用处理器,构造代理类
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                target.getClass().getInterfaces(),
                this);
    }

}

下面是测试类:

public class JDKProxyTest {
    public static void main(String[] args) {
        UserService target = new UserServiceImpl();
        JDKInvocationHandler handler = new JDKInvocationHandler(target);
        UserService proxy = (UserService) handler.getProxy();
        proxy.say();
    }
}
// 调用前:say
// 目标对象
// 调用后:say

2). Cglib 动态代理

目标对象和之前的一致,下面定义代理类:

public class CglibProxy implements MethodInterceptor {

    private Enhancer enhancer = new Enhancer();
    public Object getProxy(Class c) {
        enhancer.setSuperclass(c);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("打开事务");
        Object retVal = methodProxy.invokeSuper(o, objects);
        System.out.println("关闭事务");
        return retVal;
    }
}

下面是测试类:

public class CglibTest {
    public static void main(String[] args) {
        CglibProxy proxy = new CglibProxy();
        UserServiceImpl proxyImpl = (UserServiceImpl) proxy.getProxy(UserServiceImpl.class);
        proxyImpl.say();
    }
}
// 打开事务
// 目标对象
// 关闭事务