反射是指计算机程序在运行时访问、检测和修改它自己状态或行为的一种能力,是一种元编程语言特性,有不少语言都提供了对反射机制的支持,它使程序可以编写程序。Java的反射机制使得Java可以动态的获取类的信息和调用对象的方法。java
在Java中,Class(类类型)是反射编程的起点,表明运行时类型信息(RTTI,Run-Time Type Identification)。java.lang.reflect包含了Java支持反射的主要组件,如Constructor、Method和Field等,分别表示类的构造器、方法和域,它们的关系以下图所示。android
Constructor和Method与Field的区别在于前者继承自抽象类Executable,是能够在运行时动态调用的,而Field仅仅具有可访问的特性,且默认为不可访问。下面了解下它们的基本用法:编程
// 1. 采用Class.forName获取类的Class对象 Class clazz0 = Class.forName("com.yhthu.java.ClassTest"); System.out.println("clazz0:" + clazz0); // 2. 采用.class方法获取类的Class对象 Class clazz1 = ClassTest.class; System.out.println("clazz1:" + clazz1); // 3. 采用getClass方法获取类的Class对象 ClassTest classTest = new ClassTest(); Class clazz2 = classTest.getClass(); System.out.println("clazz2:" + clazz2); // 4. 判断Class对象是否相同 System.out.println("Class对象是否相同:" + ((clazz0.equals(clazz1)) && (clazz1.equals(clazz2))));
注意:三种方式获取的Class对象相同的前提是使用了相同的类加载器,好比上述代码中默认采用应用程序类加载器(sun.misc.Launcher$AppClassLoader)。不一样类加载器加载的同一个类,也会获取不一样的Class对象:后端
// 自定义类加载器 ClassLoader myLoader = new ClassLoader() { @Override public Class<?> loadClass(String name) throws ClassNotFoundException { try { String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class"; InputStream is = getClass().getResourceAsStream(fileName); if (is == null) { return super.loadClass(name); } byte[] b = new byte[is.available()]; is.read(b); return defineClass(name, b, 0, b.length); } catch (IOException e) { throw new ClassNotFoundException(name); } } }; // 采用自定义类加载器加载 Class clazz3 = Class.forName("com.yhthu.java.ClassTest", true, myLoader); // clazz0与clazz3并不相同 System.out.println("Class对象是否相同:" + clazz0.equals(clazz3));
// 返回申明为public的方法,包含从基类中继承的 for (Method method: String.class.getMethods()) { System.out.println(method.getName()); } // 返回当前类申明的全部方法,包含private的 for (Method method: String.class.getDeclaredMethods()) { System.out.println(method.getName()); }
注解(Annontation)是Java5引入的一种代码辅助工具,它的核心做用是对类、方法、变量、参数和包进行标注,经过反射来访问这些标注信息,以此在运行时改变所注解对象的行为。Java中的注解由内置注解和元注解组成。内置注解主要包括:设计模式
这里,咱们重点关注元注解,元注解位于java.lang.annotation包中,主要用于自定义注解。元注解包括:数组
自定义元注解需重点关注两点:1)注解的数据类型;2)反射获取注解的方法。首先,注解中的方法并不支持全部的数据类型,仅支持八种基本数据类型、String、Class、enum、Annotation和它们的数组。好比如下代码会产生编译时错误:缓存
@Documented @Inherited @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface AnnotationTest { // 1. 注解数据类型不能是Object;2. 默认值不能为null Object value() default null; // 支持的定义方式 String value() default ""; }
其次,上节中提到的反射相关类(Class、Constructor、Method和Field)和Package均实现了AnnotatedElement接口,该接口定义了访问反射信息的方法,主要以下:网络
// 获取指定注解类型 getAnnotation(Class<T>):T; // 获取全部注解,包括从父类继承的 getAnnotations():Annotation[]; // 获取指定注解类型,不包括从父类继承的 getDeclaredAnnotation(Class<T>):T // 获取全部注解,不包括从父类继承的 getDeclaredAnnotations():Annotation[]; // 判断是否存在指定注解 isAnnotationPresent(Class<? extends Annotation>:boolean
当使用上例中的AnnotationTest 标注某个类后,即可在运行时经过该类的反射方法访问注解信息了。框架
@AnnotationTest("yhthu") public class AnnotationReflection { public static void main(String[] args) { AnnotationReflection ar = new AnnotationReflection(); Class clazz = ar.getClass(); // 判断是否存在指定注解 if (clazz.isAnnotationPresent(AnnotationTest.class)) { // 获取指定注解类型 Annotation annotation = clazz.getAnnotation(AnnotationTest.class); // 获取该注解的值 System.out.println(((AnnotationTest) annotation).value()); } } }
当自定义注解只有一个方法value()时,使用注解可只写值,例如:@AnnotationTest("yhthu")编程语言
代理是一种结构型设计模式,当没法或不想直接访问某个对象,或者访问某个对象比较复杂的时候,能够经过一个代理对象来间接访问,代理对象向客户端提供和真实对象一样的接口功能。经典设计模式中,代理模式有四种角色:
在实现上,代理模式分为静态代理和动态代理,静态代理的代理类二进制文件是在编译时生成的,而动态代理的代理类二进制文件是在运行时生成并加载到虚拟机环境的。JDK提供了对动态代理接口的支持,开源的动态代理库(Cglib、Javassist和Byte Buddy)提供了对接口和类的代理支持,本节将简单比较JDK和Cglib实现动态代理的异同,后续章节会对Java字节码编程作详细分析。
JDK实现动态代理是经过Proxy类的newProxyInstance方法实现的,该方法的三个入参分别表示:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
一般的使用方法以下:
private Object getProxy() { return Proxy.newProxyInstance(JDKProxyTest.class.getClassLoader(), new Class<?>[]{Subject.class}, new MyInvocationHandler(new RealSubject())); } private static class MyInvocationHandler implements InvocationHandler { private Object realSubject; public MyInvocationHandler(Object realSubject) { this.realSubject = realSubject; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Some thing before method invoke"); Object result = method.invoke(realSubject, args); System.out.println("Some thing after method invoke"); return result; } }
类加载器采用当前类的加载器,默认为应用程序类加载器(sun.misc.Launcher$AppClassLoader);接口数组以Subject.class为例,调用方法处理类MyInvocationHandler实现InvocationHandler接口,并在构造器中传入Subject的真正的业务功能服务类RealSubject,在执行invoke方法时,能够在实际方法调用先后织入自定义的处理逻辑,这也就是AOP(面向切面编程)的原理。
关于JDK动态代理,有两个问题须要清楚:
// 1. 获取代理类的Class对象 Class<?> cl = getProxyClass0(loader, intfs); // 2. 利用Class获取Constructor,经过反射生成对象 cons.newInstance(new Object[]{h});
与反射获取Class对象时搜索classpath路径的.class文件不一样的是,这里的Class对象彻底是“无中生有”的。getProxyClass0根据类加载器和接口集合返回了Class对象,这里采用了缓存的处理。
// 缓存(key, sub-key) -> value,其中key为类加载器,sub-key为代理的接口,value为Class对象 private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory()); // 若是实现了代理接口的类已存在就返回缓存对象,不然就经过ProxyClassFactory生成 private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) { if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } return proxyClassCache.get(loader, interfaces); }
若是实现了代理接口的类已存在就返回缓存对象,不然就经过ProxyClassFactory生成。ProxyClassFactory又是经过下面的代码生成Class对象的。
// 生成代理类字节码文件 byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags); try { // defineClass0为native方法,生成Class对象 return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { throw new IllegalArgumentException(e.toString()); }
generateProxyClass方法是用来生成字节码文件的,根据生成的字节码文件,再在native层生成Class对象。
public final class $Proxy extends Proxy implements Subject { // m1 = Object的equals方法 private static Method m1; // m2 = Object的toString方法 private static Method m2; // Subject的request方法 private static Method m3; // Object的hashCode方法 private static Method m0; // 省略m1/m2/m0,此处只列出request方法实现 public final void request() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } }
因为生成的代理类继承自Proxy,super.h便是Prxoy的InvocationHandler,即代理类的request方法直接调用了InvocationHandler的实现,这就回答了InvocationHandler的invoke方法是如何被调用的了。
Cglib的动态代理是经过Enhancer类实现的,其create方法生成动态代理的对象,有五个重载方法:
create():Object create(Class, Callback):Object create(Class, Class[], Callback):Object create(Class, Class[], CallbackFilter, Callback):Object create(Class[], Object):Object
经常使用的是第二个和第三个方法,分别用于动态代理类和动态代理接口,其使用方法以下:
private Object getProxy() { // 1. 动态代理类 return Enhancer.create(RealSubject.class, new MyMethodInterceptor()); // 2. 动态代理接口 return Enhancer.create(Object.class, new Class<?>[]{Subject.class}, new MyMethodInterceptor()); } private static class MyMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("Some thing before method invoke"); Object result = proxy.invokeSuper(obj, args); System.out.println("Some thing after method invoke"); return result; } }
从上小节可知,JDK只能代理接口,代理生成的类实现了接口的方法;而Cglib是经过继承被代理的类、重写其方法来实现的,如:create方法入参的第一个参数就是被代理类的类型。固然,Cglib也能代理接口,好比getProxy()方法中的第二种方式。
Dubbo是一款高性能的Java RPC框架,是服务治理的重量级中间件。Dubbo采用dubbo:service描述服务提供者,dubbo:reference描述服务消费者,其共同必填属性为interface,即Java接口。Dubbo正是采用接口来做为服务提供者和消费者之间的“共同语言”的。
在移动网络中,Android做为服务消费者,通常经过HTTP网关调用后端服务。在国内的大型互联网公司中,Java后端大多采用了Dubbo及其变种做为服务治理、服务水平扩展的解决方案。所以,HTTP网关一般须要Android的网络请求中提供调用的服务名称、服务方法、服务版本、服务分组等信息,而后经过这些信息反射调用Java后端提供的RPC服务,实现从HTTP协议到RPC协议的转换。
关于Android访问网关请求,其分层结构可参考《基于Retrofit+RxJava的Android分层网络请求框架》。
那么,Android端可否以dubbo:reference化的方式申明须要访问的网络服务呢?如何这样,将极大提升Android开发人员和Java后端开发之间的沟通效率,以及Android端的代码效率。
首先,自定义服务的消费者注解Reference,经过该注解标记某个服务。
@Inherited @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Reference { // 服务接口名 String service() default ""; // 服务版本 String version() default ""; // 服务分组 String group() default ""; // 省略字段 }
其次,经过接口定义某个服务消费(若是能够直接引入后端接口,此步骤可省略),在注解中指明该服务对应的后端服务接口名、服务版本、服务分组等信息;
@Reference(service = "com.yhthu.java.ClassTestService", group = "yhthu", version = "v_test_0.1") public interface ClassTestService { // 实例方法 Response echo(String pin); }
这样就完成了服务的申明,接下来的问题是如何实现服务的调用呢?上述申明的服务接口如何定义实现呢?这里就涉及依赖注入和动态代理。咱们先定义一个标记注解@Service,标识须要被注入实现的服务申明。
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Service { } // 在须要使用服务的地方(好比Activity中)申明须要调用的服务 @Service private ClassTestService classTestService;
在调用classTestService的方法以前,须要注入该接口服务的实现,所以,该操做能够在调用组件初始化的时候进行。
// 接口与对应实现的缓存 private Map<Class<?>, Object> serviceContainer = new HashMap<>(); // 依赖注入 public void inject(Object obj) { // 1. 扫描该类中全部添加@Service注解的域 Field[] fields = obj.getClass().getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(Service.class)) { Class<?> clazz = field.getType(); if (clazz.getAnnotation(Reference.class) == null) { Log.e("ClassTestService", "接口地址未配置"); continue; } // 2. 从缓存中取出或生成接口类的实现(动态代理) Object impl = serviceContainer.get(clazz); if (impl == null) { impl = create(clazz); serviceContainer.put(clazz, impl); } // 3. 设置服务接口实现 try { field.setAccessible(true); field.set(obj, impl); } catch (IllegalAccessException e) { e.printStackTrace(); } } } }
inject方法的关键有三步:
private <T> T create(final Class<T> service) { return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]{service}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 1. 获取服务信息 Annotation reference = service.getAnnotation(Reference.class); String serviceName = ((Reference) reference).service(); String versionName = ((Reference) reference).version(); String groupName = ((Reference) reference).group(); // 2. 获取方法名 String methodName = method.getName(); // 3. 根据服务信息发起请求,返回调用结果 return Request.request(serviceName, versionName, groupName, methodName, param); } }); }
在HTTP网关获得服务名称、服务方法、服务版本、服务分组等信息以后,便可实现对后端服务的反射调用。总的来说,便可实现Android端dubbo:reference化的网络访问。
// 调用ClassTestService服务的方法 classTestService.echo("yhthu").callback(// ……);
上述代码实现均为伪代码,仅说明解决方案思路。
在该案例中,综合使用了自定义注解、反射以及动态代理,是对上述理论知识的一个具体应用。