Skip to content

动态代理调用的实际运用

动态代理在实际工作中很难用到,通常都是一些底层组件才会使用,比如SpringAOP。但由于业务正在做“降本增效”的多租户改造,因此正好有了使用机会。

我们的业务分布在6个国家,每个国家都有独立的服务器、数据库、中间件,然而每个国家的用户数、使用APP时间段、使用习惯等各种因素导致服务器资源的使用效率不高:有的服务可能CPU在1%~10%使用率,为了高可用却仍然需要至少4台服务器,资源会有浪费。现在要做的就是把业务、服务器、数据库均合并,通过全链路携带“租户标记TenantId”来区分请求来源,所有国家共用服务器和数据库资源。

实现的方式很简单,类似skywalking这种tracing组件,利用ThreadLocal等数据结构,将类似“业务_国家_语言_货币单位”这种请求标记全链路传递。

multi-tanant.png

由于时间非常紧张,集团大佬写的多租户中间件,调用上比较繁琐,每次RPC调用前,都得设置一次TenantId,因为同个线程可能会承载各个国家各个语言的用户,并不是固定的。这给业务带来一些不必要的代码,看着会很烦。于是我们组的大佬写出了一个代理类,在创建RPC consumer bean时直接代理,隐藏这些设置多租户的业务无关的代码。里面有一些精妙的思路,非常值得分析。

1、创建代理

java
    @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

java
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类,里面有对应的解释:

java
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
    String methodName = method.getName();  
    if (methodName.equals("equals")) {  
        // Only consider equal when proxies are identical.  
        return (proxy == args[0]);  
    }  
    else if (methodName.equals("hashCode")) {  
        // Use hashCode of proxy.  
        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();  
    }  
}

这也是代理类比较通用和经典的写法。

转载请注明出处https://bananaoven.com/articles/43070.html | 香蕉微波炉
分享许可方式知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议
重大发现:转载注明原文网址的同学刚买了彩票就中奖,刚写完代码就跑通,刚转身就遇到了真爱。