Java 反射(reflect)

412 阅读2分钟

前言

反射是Java的一种独特的设定,它遵循了Java声明的万物皆对象的理念。反射就是面向类的一种工具,可以帮助程序员直接操作类本身。

Demo

public class ReflectSample {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        // 获得 Class 类对象,显示加载类
        Class rc = Class.forName("learn.macro.mall.jvm.reflect.Robbot");
        // 通过反射构建对象,构建出对象
        Robbot r = (Robbot) rc.newInstance();
        // getDeclaredMethod 获取类的所有方法
        Method sayHi = rc.getDeclaredMethod("sayHi", String.class);
        // 通过反射修改方法权限
        sayHi.setAccessible(true);
        // 执行修改
        Object str = sayHi.invoke(r, "qpm2");
    }

}

如上述代码,可以根据这些方法,通过反射的方法处理类信息。这种灵活的方式,也为Java多种框架提供的可能。

        // Demo Spring + 反射,轻易地就可以构建一个处理的转发框架
		// 从sping上下文取出所有消息处理器
		Map<String, IPacketProcessor> handlerMap = ctx.getBeansOfType(IPacketProcessor.class);
		for (Object obj : handlerMap.values()) {
			Class<?> clazz = AopTargetUtils.getTarget(obj).getClass();
			// 找到所有处理方法
			Method[] methods = clazz.getMethods();
			for (Method method : methods) {
				Packet c = method.getAnnotation(Packet.class);
				if (c == null) {
					continue;
				}
				Class<?>[] paramsTypes = method.getParameterTypes();
				if(paramsTypes.length != 2){
					throw new RuntimeException("packetId=" + c.id() + "\t Method : " +  method.getName() + " param length must 2.");
				}
				// 把处理方法加入缓存中
				PacketHolder holder = new PacketHolder((IPacketProcessor)obj, method);
				handlers.put((short)holder.getCmdId(), holder);
			}
		}

Spring MVC 中,应该也会使用类似的做法。

// doInvoke:190, InvocableHandlerMethod (org.springframework.web.method.support)
    /**
	 * Invoke the handler method with the given argument values.
	 */
	@Nullable
	protected Object doInvoke(Object... args) throws Exception {
		ReflectionUtils.makeAccessible(getBridgedMethod());
		try {
		// 通过反射调用
			return getBridgedMethod().invoke(getBean(), args);
		}
		catch (IllegalArgumentException ex) {
			assertTargetBean(getBridgedMethod(), getBean(), args);
			String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
			throw new IllegalStateException(formatInvokeError(text, args), ex);
		}
		catch (InvocationTargetException ex) {
			// Unwrap for HandlerExceptionResolvers ...
			Throwable targetException = ex.getTargetException();
			if (targetException instanceof RuntimeException) {
				throw (RuntimeException) targetException;
			}
			else if (targetException instanceof Error) {
				throw (Error) targetException;
			}
			else if (targetException instanceof Exception) {
				throw (Exception) targetException;
			}
			else {
				throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
			}
		}
	}

关于反射的性能问题

不少人在认知上,都知道反射是比较慢的,那如何解释里面的快慢问题呢?

public class slow {

    class B {}
    
    public static long timeDiff(long old) {
        return System.currentTimeMillis() - old;
    }

    public static void main(String args[]) throws Exception {
        long numTrials = (long) Math.pow(10, 8);
        long millis;
        millis = System.currentTimeMillis();
        for (int i=0; i<numTrials; i++) {
            new B();
        }
        System.out.println("Normal instaniation took: "
                + timeDiff(millis) + "ms");
        millis = System.currentTimeMillis();
        Class<B> c = B.class;
        for (int i=0; i<numTrials; i++) {
            c.newInstance();
        }
        System.out.println("Reflecting instantiation took:"
                + timeDiff(millis) + "ms");
    }
}

执行的结果,大概保持在1倍的差距。网上大量的说法都是100x差距,估计Java本身已经对反射不断地在做优化了。具体如何从100x缩短成2x,暂时不是很清楚。

Normal instaniation took: 563ms
Reflecting instantiation took:1119ms

原因找了一些博客查看,大概如下:

  • 反射类似解释型执行,作为静态语言的编译器,无法对其进行优化。JIT即时编译也同样;
  • 所有的调用,都需要先通过名字找到找到方法区的内存字节码位置;
  • 调用的时候还会进行一系列的检查(权限等等),包括box和unbox,参数数组化;

参考