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

Posted 咬定青松

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态代理在创建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

以上是关于动态代理在创建HiveMetaStoreClient上的运用的主要内容,如果未能解决你的问题,请参考以下文章

代理模式之JDK动态代理

类的加载机制和反射——使用反射生成JDK动态代理

性能优于JDK代理,CGLib如何实现动态代理

动态代理

代理模式(动态)

代理静态动态