调用方法的名称存储在字符串中而没有反射API?

时间:2014-05-10 10:10:06

标签: java reflection cglib

我知道,使用Reflection API,我们可以通过存储在字符串中的名称来调用方法。

但是,Reflection API不能用于高性能应用程序。 在我的应用程序中,将以非常高的速率调用方法。所以,我不能使用Reflection API。

那么,Reflection API有哪些替代方案?

我做了研究,发现可以使用cglib和其他代码生成库。

但是,我没有找到任何通过存储在字符串中的名称来调用方法的示例。

反射替代方案也是一个很好的例子。

更新 实际上我正在实现一些Master-Slave通信API。哪些从属设备将远程调用主方法。并且,方法调用将以非常高的速率(每秒约50个方法调用)。因为,主人不断轮询奴隶以获得任何回应。那么,我应该以这么高的调用率来反思吗?

5 个答案:

答案 0 :(得分:4)

的反射是。在排除它之前,我建议尝试一下,看看在过去几年的任何JVM上,你是否真的看到与之相关的任何性能问题。我怀疑你不会。

你唯一的其他真实选择 (实际上,有cglib; see this other answer for more, and why you may not want to use it是一种方法,你可以让人们打电话,传入要调用的方法的名称,然后调度到该方法(例如,使用大switch或调度表或类似方法)。 E.g:

public Object callMethod(String methodName, Object[] args) {
    switch (methodName) { // Using strings in `switch` requires a recent version of Java
        case "foo":
            return this.foo(args[0]);
        case "bar":
            this.bar(args[0], args[1]);
            return null;
        // ...and so on...
        default:
            throw new AppropriateException();
    }
}

答案 1 :(得分:1)

Cglib附带了一个名为FastMethod的类。该类的目的是通过使用非反射接口来调用给定方法。为此,FastMethod实现接口并生成用于调用指定方法的字节代码,从而避免了所谓的昂贵的反射调用。

但是,有两个原因可能导致您最不可能使用此课程。很久以前就写过Cglib。在这些日子里,反思仍然比今天更昂贵。但是,现代JVM know a concept called inflation。默认情况下,JVM将生成用于在第15次反射调用后调用方法的字节代码。这正是cglib为您提供的明确做法。

此外,反馈电话不是最昂贵的,而是它的查询。您仍需要将方法命名为FastMethod。因此,即使使用cglib,也无法避免这些成本。

因此,我建议您依靠反射,直到您真正将其视为性能瓶颈。至少,使用像JMH这样的工具来证明这种实现的合理性。另外,请考虑类消耗perm gen / meta空间可能导致trouble for your users

答案 2 :(得分:0)

反思很慢,这是一种常见的误解。这是在Java 1.3左右的时代。

但是在现代Java中,Reflection真的很好地优化了。它使用动态字节码生成。此外,JVM可以将这些调用直接内联到调用者中,因此使用Reflection调用方法几乎与直接调用一样快。

其他评论建议使用cglib的FastMethod。事实上,它并不比反射快得多。这是一个用着名的JMH框架编写的基准:

@State(Scope.Benchmark)
public class MethodInvoke {
    Method method;
    FastMethod fastMethod;

    @Setup
    public void init() throws NoSuchMethodException {
        method = getClass().getMethod("foo", int.class, long.class);
        method.setAccessible(true);
        fastMethod = FastClass.create(getClass()).getMethod("foo", new Class[] { int.class, long.class });
    }

    @GenerateMicroBenchmark
    public long fastMethod() throws Exception {
        return (Long) fastMethod.invoke(this, new Object[] {2, 3L});
    }

    @GenerateMicroBenchmark
    public long reflection() throws Exception {
        return (Long) method.invoke(this, 2, 3L);
    }

    public long foo(int a, long b) {
        return a + b;
    }
}

Java 7u51(64位)上的结果:

Benchmark                     Mode   Samples         Mean   Mean error    Units
b.MethodInvoke.fastMethod    thrpt         5    79248,583     3978,941   ops/ms
b.MethodInvoke.reflection    thrpt         5    76975,414     2844,730   ops/ms

您看,FastMethod.invoke仅比Method.invoke快3%,但与Reflection相反,FastMethod不会执行正确的参数验证等。

答案 3 :(得分:0)

通常,对方法的内省调用需要两个阶段:首先,您需要找到要调用的目标方法,然后在目标实例上调用该方法,为其提供参数。

在此过程中,最昂贵的操作是在类中定位目标方法(例如Method method = TargetClass.getMethod(Class[] signature ...)一旦获得Method对象,就在对象上调用方法,如:method.invoke(targetObj, param,...)是一个轻量级的操作,比直接方法调用稍微贵一点。

为了证明这一点,我只是做了一个快速的&三种方法的脏比较与以下结果(您需要相互比较它们):

  • 每次调用反射方法+调用:167ms
  • 来自类+调用的第一个缓存方法:36 ms
  • 直接调用:17ms

请注意,内省具有修复性能成本,因此该方法执行的计算越多,这些数字就越接近。

我在之前的项目中使用了这种方法缓存方法,其中性能非常重要。在实践中,您会发现实际的方法执行时间使得内省的成本难以理解。 (cfr。Amdahal's law

测试代码(承担快速和肮脏)

import java.lang.reflect.Method;
import java.util.Random;

/**
 * Created by maasg on 5/10/14.
 */
public class Instrospection {

    public static void main(String [] params) throws Exception {
        Random ran = new Random();
        String[] methods = new String[] {"method1", "method2", "method3"};
        Target target = new Target();

        // Warmup
        for (int i=0; i<1000; i++) {
            String methodName = methods[ran.nextInt(3)];
            String param = new Integer(ran.nextInt()).toString();
            Method method = Target.class.getMethod(methodName, String.class);
        }

        StringBuilder builder = new StringBuilder();
        long t0 = System.currentTimeMillis();
        for (int i=0; i<100000; i++) {
            String methodName = methods[ran.nextInt(3)];
            String param = new Integer(ran.nextInt()).toString();
            Method method = Target.class.getMethod(methodName, String.class);
            Object result = method.invoke(target, "param");
            builder.append(result.toString());
        }
        System.out.println("Elapsed 1: "+(System.currentTimeMillis()-t0));

        Method[] invokeMethods = new Method[] {
            Target.class.getMethod(methods[0], String.class),
            Target.class.getMethod(methods[1], String.class),
            Target.class.getMethod(methods[2], String.class),
        };

        builder = new StringBuilder();
        long t1 = System.currentTimeMillis();
        for (int i=0; i<100000; i++) {
            String param = new Integer(ran.nextInt()).toString();
            Method method = invokeMethods[ran.nextInt(3)];
            Object result = method.invoke(target, "param");
            builder.append(result.toString());
        }
        System.out.println("Elapsed 2: "+(System.currentTimeMillis()-t1));

        builder = new StringBuilder();
        long t2 = System.currentTimeMillis();
        for (int i=0; i<100000; i++) {
            Object result = null;
            String param = new Integer(ran.nextInt()).toString();
            switch (ran.nextInt(3)) {
                case 0: result = target.method1(param);
                case 1: result = target.method2(param);
                case 2: result = target.method3(param);
            }

            builder.append(result.toString());
        }
        System.out.println("Elapsed 3: "+(System.currentTimeMillis()-t2));

    }

}

答案 4 :(得分:0)

从Java 1.7开始,有一种从名称通过MethodHandle调用方法的新方法,它比反射快得多。

我在糟糕的笔记本电脑上做了一些基准测试:

  1. 每秒可以进行大约400万次调用
  2. MethodHandle每秒可以执行大约1.4亿次调用
  3. 要么是可接受的性能,但平均调用时间只有7纳秒,使用MethodHandle非常好。

    如果您根据名称在HashMap中存储了MethodHandle的引用,则可以将其重用于后续调用。