前面两篇反射,分别介绍了如何封装参数和定位方法,对于最终的反射调用,还缺少的是目标类的确定和方法执行;本篇博文将目标集中在这最后一块
链上上两篇文章地址
I. 目标对象确定
对于找到我们的目标对象,这个就与我们最终的应用的运行方式有关系了。如果是一个Spring应用,我们知道所有的bean都会放在ApplicationContext
上下文中,可以通过beanName或者Class来找到目标对象;如果我们的应用就是一个单纯的jar包,没有引入第三方容器管理,这个要获取目标类就与具体的实现有关系了
下面我们将进行分别说到
1. 目标对象分类
分类是个啥意思,为什么要分类了?
- 这个主要是从我们的目标出发,我们最终的目的是通过反射调用我们的目标方法,那么方法的调用执行,通常分为两种,一个是静态类的调用;一个是实例的调用
这两个的区别在哪里?
- 最终的反射执行
java.lang.reflect.Method#invoke(object, args)
- 静态类调用方式,传入null
- 实例调用方式,传入实例对象s
从上面的区别可以看出,对于静态类方式,找到方法和参数就行了,不需要再额外的去找对应的实例了
2. 目标对象类型判断
同样我们可以通过反射的方式判断方法是否属于静态方法
1 2
| Modifier.isStatic(method.getModifiers())
|
在QuickFix(<=1.1)的实现中,并没有采用这种方式,而是直接选择了通过外部传参的方式来确定目标对象是否为静态类;原因在于实现简单,所以这里有个优化点,完全可以直接自动化判断
3. 获取目标对象
前面说到了,不同的运行环境,获取目标对象的方式不一样,所以让我们直接覆盖所有的场景时不太现实的。一个可选的方案就是预留接口,让接入方自己来选择,如何根据传入的参数,来选择对应的目标对象,所以我们定义了一个接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| @LoaderOrder public interface ServerLoader {
default int order() { try { return this.getClass().getAnnotation(LoaderOrder.class).order(); } catch (Exception e) { return 10; } }
boolean enable(FixReqDTO reqDTO);
ImmutablePair<Object, Class> getInvokeObject(FixReqDTO reqDTO); }
|
上面接口中,三个方法,先看第二个,因为我们前面进行了分类,所以我们必然会有一个StaticServerLoader
,专门用来加载静态目标对象,而这个loader对于普通对象获取就无法满足了
第二个需要注意的就是order()
方法,用来指定ServerLoader的优先级,特别是当我们的系统中存在多个ServerLoader可以返回我们想要的结构时,这个时候设置优先级就是一个较好的方案了
看到源码的同学会发现,我们的实现类并不是直接实现ServerLoader
接口,而是继承自模板类ServerLoaderTemplate
,抽象了公共的业务逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public abstract class ServerLoaderTemplate implements ServerLoader {
@Override public ImmutablePair<Object, Class> getInvokeObject(FixReqDTO reqDTO) { ImmutablePair<Object, Class> serverPair = loadServicePair(reqDTO.getService());
if (StringUtils.isEmpty(reqDTO.getField())) { return serverPair; }
return loadFieldPair(reqDTO, serverPair); }
public abstract ImmutablePair<Object, Class> loadServicePair(String service);
public ImmutablePair<Object, Class> loadFieldPair(FixReqDTO reqDTO, ImmutablePair<Object, Class> serverPair) { try { return ReflectUtil.getField(serverPair.getLeft(), serverPair.getRight(), reqDTO.getField()); } catch (Exception e) { throw new ServerNotFoundException("get server#filed error!", e); } } }
|
从模板类的中,可以发现一个有意思的地方,我们传入的Service可能并不是最终要执行的目标对象
怎么理解呢?举一个简单的例子
1 2 3 4 5 6 7
| public class A { private B b; }
public class B { public void print() {} }
|
我们现在希望执行的是A对象中成员b的print方法,所以这种case下我们的目标对象是b,因此上面的实现中,添加了方法 loadFieldPair
接下来给出两个具体获取目标对象的实现,一个是静态类的,一个是Spring容器的
StaticServerLoader
实现相对简单,直接使用ClassLoader.load()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class StaticServerLoader extends ServerLoaderTemplate { private static final String STATIC_TYPE = "static";
@Override public boolean enable(FixReqDTO reqDTO) { return STATIC_TYPE.equalsIgnoreCase(reqDTO.getType()); }
@Override public ImmutablePair<Object, Class> loadServicePair(String service) { try { Class clz = this.getClass().getClassLoader().loadClass(service); return ImmutablePair.of(null, clz); } catch (Exception e) { throw new ServerNotFoundException("parse " + service + " to bean error: " + e.getMessage()); } } }
|
spring
的获取方式,则主要是借助SprintContext
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| public class BeanServerLoader extends ServerLoaderTemplate { private static final String BEAN_TYPE = "bean";
private static ApplicationContext applicationContext;
public BeanServerLoader(ApplicationContext applicationContext) { BeanServerLoader.applicationContext = applicationContext; }
@Override public boolean enable(FixReqDTO reqDTO) { return StringUtils.isEmpty(reqDTO.getType()) || BEAN_TYPE.equalsIgnoreCase(reqDTO.getType().trim()); }
private boolean beanName(String server) { return !server.contains("."); }
@Override public ImmutablePair<Object, Class> loadServicePair(String server) { Object invokeBean = null; if (beanName(server)) { invokeBean = applicationContext.getBean(server.trim()); } else { try { Class clz = this.getClass().getClassLoader().loadClass(server.trim()); if (clz != null) { invokeBean = applicationContext.getBean(clz); } } catch (Exception e) { throw new ServerNotFoundException("Failed to load Server: " + server); } }
if (invokeBean == null) { throw new ServerNotFoundException("Server not found: " + server); }
return ImmutablePair.of(invokeBean, invokeBean.getClass()); }
public static BeanServerLoader getLoader() { return applicationContext.getBean(BeanServerLoader.class); } }
|
关于ServerLoader的更多设计理念,会放在QuickFix的后续博文中进行说明
4. 执行目标方法
当我们获取到了目标对象,目标方法,传参之后,调用就简单了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public static String execute(Object bean, Class clz, String method, Object[] args) { if (StringUtils.isEmpty(method)) { return JSON.toJSONString(bean); }
Method chooseMethod = getMethod(clz, method, args);
if (chooseMethod == null) { throw new ServerNotFoundException("can't find server's method: " + clz.getName() + "#" + method); }
try { chooseMethod.setAccessible(true); Object result = chooseMethod.invoke(bean, args); return JSON.toJSONString(result); } catch (Exception e) { throw new ServerInvokedException( "unexpected server invoked " + clz.getName() + "#" + method + " args: " + JSON.toJSONString(args), e); } }
|
5. other
至此,QuickFix项目中关于反射的相关技能点已经说完了,可以说QuickFix项目,整个都是依托于反射来玩耍的,如果希望了解下java反射相关知识点和使用姿势的话,这个项目也是一个很好的选择(简单、轻量)
QuickFix项目中另外一个我个人认为有意思的点在于支持扩展的设计理念,如何让这个简单的框架适用于各种不同的应用中,也是一个很有意思的挑战,后续博文将带来这方面的介绍
II. 其他
0. 项目相关
项目地址:
博文地址:
一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
2. 声明
尽信书则不如,已上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激
3. 扫描关注
一灰灰blog
知识星球