动态代理在创建HiveMetaStoreClient上的运用

本文首发微信公众号:码上观世界

Hive MetaStore 在版本2.x和3.x中有较大改变,包括常用接口类的参数,比如这个对外使用的HiveMetaStoreClient,其构造方法参数类型由之前的HiveConf类型改成了Configuration,由此出现了不同版本的兼容问题。

#HiveMetaStoreClient.class
public HiveMetaStoreClient(Configuration conf) throws MetaException {
  this(conf, null, true);
}

public HiveMetaStoreClient(Configuration conf, HiveMetaHookLoader hookLoader) throws MetaException {
  this(conf, hookLoader, true);
}

public HiveMetaStoreClient(Configuration conf, HiveMetaHookLoader hookLoader, Boolean allowEmbedded){
 ...   
}

不同引擎在适配不同版本的HiveMetaStoreClient采用了不同的办法,比如有的扩展原始的ThriftMetastoreClient,有的通过动态代理生成相应的HiveMetaStoreClient,本文来研究如何通过动态代理的方式创建HiveMetaStoreClient。

动态代理

Java Dynamic Proxy 是JDK提供的一种重要的代理机制,通过动态代理,可以通过一个方法服务多个方法调用,常用于门面(Facade)设计模式,是框架开发者的利器。

动态代理跟InvocationHandler相关联,用于Wapper实际方法执行之前的逻辑,比如:

public class DynamicInvocationHandler implements InvocationHandler {
    private static Logger LOGGER = LoggerFactory.getLogger(
    DynamicInvocationHandler.class);
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable {
        LOGGER.info("Invoked method: {}", method.getName());
        return 42;
    }
}

示例代码创建了一个InvocationHandler实例,只是打印将要执行的具体方法名称。其方法invoke在每次执行代理实例的方法时都会被调用,它有3个参数:

  • proxy:代理实例

  • method:实际执行的方法

  • args:实际执行的方法参数

返回值可以是调用被代理方法的返回值。

代理实例可以通过下面API来创建:

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)

其三个参数分别是:

loader:定义代理类的类加载器,可取自InvocationHandler实例所在类加载器

interfaces:代理类将要实现的接口集合

h:上文介绍的InvocationHandler实例

理论上,我们可以实现任意接口的代理类,比如我们创建Map类型的代理类:

Map proxyInstance = (Map) Proxy.newProxyInstance(
  DynamicProxyTest.class.getClassLoader(), new Class[] { Map.class }, new DynamicInvocationHandler());

可见,proxyInstance 是Map类的实例,一旦创建了代理实例,我们将可以按照通常的接口来调用:

proxyInstance.put("hello", "world");

在示例中DynamicInvocationHandler的invoke只是实现了代理方法的部分逻辑,并没有执行实际被代理类的方法,如果要调用实际的方法,还需要绑定实际方法所在类对象,我们将DynamicInvocationHandler略微修改,关联上实际类HashMap:

public static class DynamicInvocationHandler implements InvocationHandler {

    private static Logger LOGGER = LoggerFactory.getLogger(
            DynamicInvocationHandler.class);

    Map map;

    public DynamicInvocationHandler(Map map){
        this.map=map;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        LOGGER.info("Invoked method:{},args:{}", method.getName(),args);
        return method.invoke(this.map,args);
    }
}

示例中,在DynamicInvocationHandler构造方法中传入实际的类对象,然后在invoke方法中调用其Method。

此时再调用proxyInstance的put方法,才能让put发生“实际存储”的效果:

@Test
public void testDynamicProxy(){
    Map hashMap= Maps.newHashMap();
    Map proxyInstance = (Map) Proxy.newProxyInstance(
            DynamicInvocationHandler.class.getClassLoader(),
            new Class[] { Map.class },
            new DynamicInvocationHandler(hashMap));
    proxyInstance.put("hello","world");
}

HiveMetaStoreClient

HiveMetaStoreClient的接口类是IMetaStoreClient,创建该接口的代理类,我们首先需要关联一个InvocationHandler,同时在InvocationHandler中绑定实际被代理对象HiveMetaStoreClient,由于HiveMetaStoreClient存在版本兼容问题,这里我们没法直接实例化其对象,只好通过反射方式在运行期间生成,为此我们将HiveMetaStoreClient的构造方法参数提取出来,通过InvocationHandler实例的构造方法参数传入:

  • constructorArgTypes:构造方法参数类型

  • constructorArgs:构造方法参数值

  • msClientClass:HiveMetaStoreClient.class对象

然后通过反射动态创建IMetaStoreClient base实例对象:

public MetaStoreClientHandler(Class<?>[] constructorArgTypes,
                                   Object[] constructorArgs,
                                   Class<? extends IMetaStoreClient> msClientClass) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    Constructor meth = msClientClass.getDeclaredConstructor(constructorArgTypes);
	meth.setAccessible(true);
    //private final IMetaStoreClient base;
    this.base = (IMetaStoreClient) meth.newInstance(constructorArgs);
}

最后再在invoke中实现具体的代理逻辑,包括触发实际方法调用。

public static class MetaStoreClientHandler implements InvocationHandler {
    private final IMetaStoreClient base;
    ...
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //something before
        return method.invoke(this.base, args);
    }
}

有了MetaStoreClientHandler实例,我们再看如何调用:

1. 提取HiveMetaStoreClient构造方法参数类型和参数值(以三个参数的方法为例),并构造MetaStoreClientHandler对象:

//或者 HiveConf.class
Class[] constructorArgTypes = new Class[]{Configuration.class, HiveMetaHookLoader.class,  Boolean.class};
Object[] constructorArgs = new Object[]{this.hiveConf, (HiveMetaHookLoader) tbl -> null, true};

2. 通过Proxy.newProxyInstance创建代理

IMetaStoreClient client= (IMetaStoreClient) Proxy.newProxyInstance(
 MetaStoreClientHandler.class.getClassLoader(), HiveMetaStoreClient.class.getInterfaces(), handler);

3. 调用代理方法

Database database= new DatabaseBuilder().setCatalogName("hdfs202_299").setName("test_db").create(client,hiveConf);
List<String> list=client.getAllDatabases();

总结

本文简单回顾了Java动态代理技术,并介绍了如果通过动态代理来解决Hive Metastrore版本兼容问题,这也是一些引擎解决版本问题的基本思路。

参考

https://www.baeldung.com/java-dynamic-proxies

https://www.baeldung.com/java-8-lambda-expressions-tips

https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/proxy.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值