动态代理在实际工作中很难用到,通常都是一些底层组件才会使用,比如SpringAOP。但由于业务正在做“降本增笑效”的多租户改造,因此正好有了使用机会。
我们的业务分布在6个国家,每个国家都有独立的服务器、数据库、中间件,然而每个国家的用户数、使用APP时间段、使用习惯等各种因素导致服务器资源的使用效率不高:有的服务可能CPU在1%~10%使用率,为了高可用却仍然需要至少4台服务器,资源会有浪费。现在要做的就是把业务、服务器、数据库均合并,通过全链路携带“租户标记TenantId”来区分请求来源,所有国家共用服务器和数据库资源。
实现的方式很简单,类似skywalking这种tracing组件,利用ThreadLocal等数据结构,将类似“业务_国家_语言_货币单位”这种请求标记全链路传递。
由于时间非常紧张,集团大佬写的多租户中间件,调用上比较繁琐,每次RPC调用前,都得设置一次TenantId,因为同个线程可能会承载各个国家各个语言的用户,并不是固定的。这给业务带来一些不必要的代码,看着会很烦。于是我们组的大佬写出了一个代理类,在创建RPC consumer bean时直接代理,隐藏这些设置多租户的业务无关的代码。里面有一些精妙的思路,非常值得分析。
1、创建代理
1 2 3 4 5
| @SuppressWarnings("unchecked") public static <T> T createHsfConsumer(Class<T> hsfInterface, String version, String... targetUnits) { InvocationHandler h = new LandlordHsfInvocationHandler(hsfInterface, version, targetUnits); return (T) Proxy.newProxyInstance(hsfInterface.getClassLoader(), new Class[]{hsfInterface}, h); }
|
创建出来的bean会被spring容器管理,忽略使用细节,这里重点分析动态代理。
动态代理分JDK动态代理和CGLIB动态代理。JDK动态代理基于接口创建代理对象,利用反射机制生成包含被代理对象所有接口的代理类,覆盖接口中的所有方法。而CGLIB代理则是基于继承的方式生成子类,添加代理逻辑,不受限于接口。这里是RPC调用,很适合JDK动态代理。这里的细节是,使用的是hsfInterface的classloader,因为hsf是被pandora管理的(pandora是一个利用不同类加载器来加载完全相同的两个类,从而避免依赖的各个中间件有冲突的组件)。
2、handler
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
| private static class LandlordHsfInvocationHandler implements InvocationHandler { private final Class<?> hsfInterface; private final String version; private final List<String> targetUnits; private final Object hsfObject;
LandlordHsfInvocationHandler(Class<?> hsfInterface, String version, String... targetUnits) { this.hsfInterface = hsfInterface; this.version = version; this.targetUnits = Arrays.asList(targetUnits); try { HSFApiConsumerBean consumer = new HSFApiConsumerBean(); consumer.setInterfaceClass(hsfInterface); consumer.setIncludeFilters(Collections.singletonList("cluster-filter")); consumer.setIncludeRouters(Collections.singletonList("cluster-router")); consumer.setConfigserverCenter(this.targetUnits); consumer.setGroup("HSF"); consumer.setVersion(version); consumer.init(); this.hsfObject = consumer.getObject(); } catch (Exception e) { throw new RuntimeException(e); } }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { switch (method.getName()) { case "equals": return proxy == args[0]; case "hashCode": return System.identityHashCode(proxy); case "toString": return "Cross Unit HSF proxy, interface: " + hsfInterface.getName() + ", version: " + version; default: RequestCtxUtil.setTargetCluster(targetUnits.get(RandomUtils.nextInt(0, targetUnits.size()))); LandlordHSFUtils.setTenantIdForNextInvoke(LandlordContext.getCurrentTenantId()); return method.invoke(hsfObject, args); } } }
|
代码很容易理解,反射调用invoke之前,做一些“配置多租户、选择服务器可用区”等业务无关的操作。
亮点在invoke重写了equals、hashcode、toString方法的代理,这一段其实抄的是spring源码ObjectFactoryDelegatingInvocationHandler
类,里面有对应的解释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); if (methodName.equals("equals")) { return (proxy == args[0]); } else if (methodName.equals("hashCode")) { return System.identityHashCode(proxy); } else if (methodName.equals("toString")) { return this.objectFactory.toString(); } try { return method.invoke(this.objectFactory.getObject(), args); } catch (InvocationTargetException ex) { throw ex.getTargetException(); } }
|
这也是代理类比较通用和经典的写法。