在前一篇文章Hadoop源码解析之YARN客户端作业提交流程中,介绍了创建客户端代理阶段用到Java中的动态代理:
protected void serviceStart() throws Exception {
try {
rmClient = ClientRMProxy.createRMProxy(getConfig(),
ApplicationClientProtocol.class);
if (historyServiceEnabled) {
historyClient.start();
}
if (timelineServiceEnabled) {
timelineClient.start();
}
} catch (IOException e) {
throw new YarnRuntimeException(e);
}
super.serviceStart();
}
可以看到rmClient是一个ClientRMProxy创建的代理,后续提交的代码就是rmClient.submitApplication(request)。
Hadoop的源码中,用到动态代理的地方很多,本篇博文就是对java的动态机制进行一个回顾。
在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface)、另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的。首先我们先来看看java的API帮助文档是怎么样对这两个类进行描述的:
InvocationHandler:
* {@code InvocationHandler} is the interface implemented by
* the <i>invocation handler</i> of a proxy instance.
*
* <p>Each proxy instance has an associated invocation handler.
* When a method is invoked on a proxy instance, the method
* invocation is encoded and dispatched to the {@code invoke}
* method of its invocation handler.
每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。我们来看看InvocationHandler这个接口的唯一一个方法 invoke 方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
我们看到这个方法一共接受三个参数,那么这三个参数分别代表什么呢?
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
proxy: 指代我们所代理的那个真实对象
method: 指代的是我们所要调用真实对象的某个方法的Method对象
args: 指代的是调用真实对象某个方法时接受的参数
如果不是很明白,等下通过一个实例会对这几个参数进行更深的讲解。
接下来我们来看看Proxy这个类:
* {@code Proxy} provides static methods for creating dynamic proxy
* classes and instances, and it is also the superclass of all
* dynamic proxy classes created by those methods.
Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法:
/* Returns an instance of a proxy class for the specified interfaces
* that dispatches method invocations to the specified invocation
* handler.*/
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
这个方法的作用就是得到一个动态的代理对象,其接收三个参数,我们来看看这三个参数所代表的含义:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
loader: 一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
interfaces: 一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
h: 一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
好了,在介绍完这两个接口(类)以后,我们来通过一个实例来看看我们的动态代理模式是什么样的:
首先我们定义了一个MyInterface类型的接口,为其声明了两个方法:
package DynamicProxyTest;
public interface MyInterface {
public void func1();
public void func2(String str);
}
接着,定义了一个类来实现这个接口,这个类就是我们的真实对象,MyClass类:
package DynamicProxyTest;
public class MyClass implements MyInterface{
public void func1()
{
System.out.println("I am func1.");
}
public void func2(String str)
{
System.out.println("I am func2. "+ str);
}
}
下一步,我们就要定义一个动态代理类了,前面说个,每一个动态代理类都必须要实现 InvocationHandler 这个接口,因此我们这个动态代理类也不例外:
package DynamicProxyTest;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyHandler implements InvocationHandler{
//这个就是我们要代理的真实对象
private Object subject;
//构造方法,给我们要代理的真实对象赋初值
public MyHandler(Object o)
{
this.subject = o;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在代理真实对象前我们可以添加一些自己的操作
System.out.println("before invoke:");
//打印调用的方法名
System.out.println("method: " +method);
//当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
method.invoke(subject, args);
//在代理真实对象后我们可以添加一些自己的操作
System.out.println("end invoke.");
return null;
}
}
最后,来看看我们的MyClient类:
package DynamicProxyTest;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class MyClient {
public static void main(String[] args) {
//我们要代理的真实对象
MyInterface myClass = new MyClass();
//我们要代理那个对象,就把该对象传进去,最后是通过真实对象来调用方法
InvocationHandler handler = new MyHandler(myClass);
/*
* 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
* 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
* 第二个参数myClass.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
* 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
*/
MyInterface intF = (MyInterface)Proxy.newProxyInstance(handler.getClass().getClassLoader(),
myClass.getClass().getInterfaces(), handler);
System.out.println(intF.getClass().getName());
intF.func1();
intF.func2(" hello world ");
}
}
我们先来看看控制台的输出:
com.sun.proxy.$Proxy0
before invoke:
method: public abstract void DynamicProxyTest.MyInterface.func1()
I am func1.
end invoke.
before invoke:
method: public abstract void DynamicProxyTest.MyInterface.func2(java.lang.String)
I am func2. hello world
end invoke.
我们首先来看看 com.sun.proxy.$Proxy0这东西,我们看到,这个东西是由 System.out.println(intF.getClass().getName()); 这条语句打印出来的,那么为什么我们返回的这个代理对象的类名是这样的呢?
可能我以为返回的这个代理对象会是MyInterface类型的对象,或者是InvocationHandler的对象,结果却不是,首先我们解释一下为什么我们这里可以将其转化为MyInterface类型的对象?原因就是在newProxyInstance这个方法的第二个参数上,我们给这个代理对象提供了一组什么接口,那么我这个代理对象就会实现了这组接口,这个时候我们当然可以将这个代理对象强制类型转化为这组接口中的任意一个,因为这里的接口是MyInterface类型,所以就可以将其转化为MyInterface类型了。
同时我们一定要记住,通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号。
接着我们来看看这两句
intF.func1();
intF.func2(" hello world ");
这里是通过代理对象来调用实现的那种接口中的方法,这个时候程序就会跳转到由这个代理对象关联到的 handler 中的invoke方法去执行,而我们的这个 handler 对象又接受了一个myClass类型的参数,表示我要代理的就是这个真实对象,所以此时就会调用 handler 中的invoke方法去执行:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在代理真实对象前我们可以添加一些自己的操作
System.out.println("before invoke:");
//打印调用的方法名
System.out.println("method: " +method);
//当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
method.invoke(subject, args);
//在代理真实对象后我们可以添加一些自己的操作
System.out.println("end invoke.");
return null;
}
我们看到,在真正通过代理对象来调用真实对象的方法的时候,我们可以在该方法前后添加自己的一些操作,同时我们看到我们的这个 method 对象是这样的:
method: public abstract void DynamicProxyTest.MyInterface.func1()
method: public abstract void DynamicProxyTest.MyInterface.func2(java.lang.String)
正好就是我们的MyInterface接口中的两个方法,这也就证明了当我通过代理对象来调用方法的时候,实际就是委托由其关联到的 handler 对象的invoke方法中来调用,并不是自己来真实调用,而是通过代理的方式来调用的。
接下来看一下动态代理在hadoop中的具体使用:
rmClient = ClientRMProxy.createRMProxy(getConfig(),ApplicationClientProtocol.class);
函数createRMProxy是ClientRMProxy类的一个静态方法:
public static <T> T createRMProxy(final Configuration configuration,
final Class<T> protocol) throws IOException {
return createRMProxy(configuration, protocol, INSTANCE);
}
而参数ApplicationClientProtocol.class就转换成了Class<T> protocol类型,到了最后调用Proxy.newProxyInstance的时候,就是传入的第二个参数Class<?>[] interfaces。
调用createRMProxy函数,进入父类RMProxy中:
protected static <T> T createRMProxy(final Configuration configuration,
final Class<T> protocol, RMProxy instance) throws IOException {
YarnConfiguration conf = (configuration instanceof YarnConfiguration)
? (YarnConfiguration) configuration
: new YarnConfiguration(configuration);
RetryPolicy retryPolicy = createRetryPolicy(conf);
if (HAUtil.isHAEnabled(conf)) {
RMFailoverProxyProvider<T> provider =
instance.createRMFailoverProxyProvider(conf, protocol);
return (T) RetryProxy.create(protocol, provider, retryPolicy);
} else {
InetSocketAddress rmAddress = instance.getRMAddress(conf, protocol);
LOG.info("Connecting to ResourceManager at " + rmAddress);
T proxy = RMProxy.<T>getProxy(conf, protocol, rmAddress);
return (T) RetryProxy.create(protocol, proxy, retryPolicy);
}
}
在HA功能启动时,返回了一个(T) RetryProxy.create(protocol, provider, retryPolicy)类,
第一个参数protocol就是最外面的ApplicationClientProtocol.class
第二个参数provider由instance.createRMFailoverProxyProvider(conf, protocol);产生:
private <T> RMFailoverProxyProvider<T> createRMFailoverProxyProvider(
Configuration conf, Class<T> protocol) {
Class<? extends RMFailoverProxyProvider<T>> defaultProviderClass;
try {
defaultProviderClass = (Class<? extends RMFailoverProxyProvider<T>>)
Class.forName(
YarnConfiguration.DEFAULT_CLIENT_FAILOVER_PROXY_PROVIDER);
} catch (Exception e) {
throw new YarnRuntimeException("Invalid default failover provider class" +
YarnConfiguration.DEFAULT_CLIENT_FAILOVER_PROXY_PROVIDER, e);
}
RMFailoverProxyProvider<T> provider = ReflectionUtils.newInstance(
conf.getClass(YarnConfiguration.CLIENT_FAILOVER_PROXY_PROVIDER,
defaultProviderClass, RMFailoverProxyProvider.class), conf);
provider.init(conf, (RMProxy<T>) this, protocol);
return provider;
}
可以看出provider是通过反射产生的实例,由yarn的配置参数YarnConfiguration.CLIENT_FAILOVER_PROXY_PROVIDER:yarn.client.failover-proxy-provider决定,默认值YarnConfiguration.DEFAULT_CLIENT_FAILOVER_PROXY_PROVIDER是org.apache.hadoop.yarn.client.ConfiguredRMFailoverProxyProvider。
第三个参数retryPolicy= createRetryPolicy(conf),是根据配置文件中定义的参数计算重试机制类,这里不进一步分析了。
回到create函数,进入RetryProxy类:
public static <T> Object create(Class<T> iface,
FailoverProxyProvider<T> proxyProvider, RetryPolicy retryPolicy) {
return Proxy.newProxyInstance(
proxyProvider.getInterface().getClassLoader(),
new Class<?>[] { iface },
new RetryInvocationHandler<T>(proxyProvider, retryPolicy)
);
}
最后调用的就是Proxy.newProxyInstance函数。
第一个参数是ClassLoader对象,proxyProvider.getInterface().getClassLoader()中的proxyProvider就是ConfiguredRMFailoverProxyProvider类
其初始化的地方就是createRMFailoverProxyProvider函数中的provider.init(conf, (RMProxy<T>) this, protocol);
proxyProvider.getInterface()返回的是ConfiguredRMFailoverProxyProvider对象初始化的时候传入的protocol,此处就是ApplicationClientProtocol.class
最后proxyProvider.getInterface().getClassLoader()=ApplicationClientProtocol.getClassLoader()
第二个参数就iface是ApplicationClientProtocol接口。
第三个参数是根据ConfiguredRMFailoverProxyProvider类和retryPolicy类生产的RetryInvocationHandler,其invoke函数为:
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
RetryPolicy policy = methodNameToPolicyMap.get(method.getName());
if (policy == null) {
policy = defaultPolicy;
}
// The number of times this method invocation has been failed over.
int invocationFailoverCount = 0;
final boolean isRpc = isRpcInvocation(currentProxy.proxy);
final int callId = isRpc? Client.nextCallId(): RpcConstants.INVALID_CALL_ID;
int retries = 0;
while (true) {
// The number of times this invocation handler has ever been failed over,
// before this method invocation attempt. Used to prevent concurrent
// failed method invocations from triggering multiple failover attempts.
long invocationAttemptFailoverCount;
synchronized (proxyProvider) {
invocationAttemptFailoverCount = proxyProviderFailoverCount;
}
if (isRpc) {
Client.setCallIdAndRetryCount(callId, retries);
}
try {
Object ret = invokeMethod(method, args); //调用Invoke的地方
hasMadeASuccessfulCall = true;
return ret;
} catch (Exception e) {
boolean isIdempotentOrAtMostOnce = proxyProvider.getInterface()
.getMethod(method.getName(), method.getParameterTypes())
.isAnnotationPresent(Idempotent.class);
if (!isIdempotentOrAtMostOnce) {
isIdempotentOrAtMostOnce = proxyProvider.getInterface()
.getMethod(method.getName(), method.getParameterTypes())
.isAnnotationPresent(AtMostOnce.class);
}
RetryAction action = policy.shouldRetry(e, retries++,
invocationFailoverCount, isIdempotentOrAtMostOnce);
if (action.action == RetryAction.RetryDecision.FAIL) {
if (action.reason != null) {
LOG.warn("Exception while invoking " + currentProxy.proxy.getClass()
+ "." + method.getName() + " over " + currentProxy.proxyInfo
+ ". Not retrying because " + action.reason, e);
}
throw e;
} else { // retry or failover
// avoid logging the failover if this is the first call on this
// proxy object, and we successfully achieve the failover without
// any flip-flopping
boolean worthLogging =
!(invocationFailoverCount == 0 && !hasMadeASuccessfulCall);
worthLogging |= LOG.isDebugEnabled();
if (action.action == RetryAction.RetryDecision.FAILOVER_AND_RETRY &&
worthLogging) {
String msg = "Exception while invoking " + method.getName()
+ " of class " + currentProxy.proxy.getClass().getSimpleName()
+ " over " + currentProxy.proxyInfo;
if (invocationFailoverCount > 0) {
msg += " after " + invocationFailoverCount + " fail over attempts";
}
msg += ". Trying to fail over " + formatSleepMessage(action.delayMillis);
LOG.info(msg, e);
} else {
if(LOG.isDebugEnabled()) {
LOG.debug("Exception while invoking " + method.getName()
+ " of class " + currentProxy.proxy.getClass().getSimpleName()
+ " over " + currentProxy.proxyInfo + ". Retrying "
+ formatSleepMessage(action.delayMillis), e);
}
}
if (action.delayMillis > 0) {
Thread.sleep(action.delayMillis);
}
if (action.action == RetryAction.RetryDecision.FAILOVER_AND_RETRY) {
// Make sure that concurrent failed method invocations only cause a
// single actual fail over.
synchronized (proxyProvider) {
if (invocationAttemptFailoverCount == proxyProviderFailoverCount) {
proxyProvider.performFailover(currentProxy.proxy);
proxyProviderFailoverCount++;
} else {
LOG.warn("A failover has occurred since the start of this method"
+ " invocation attempt.");
}
currentProxy = proxyProvider.getProxy();
}
invocationFailoverCount++;
}
}
}
}
}
调用的invoke方法:
protected Object invokeMethod(Method method, Object[] args) throws Throwable {
try {
if (!method.isAccessible()) {
method.setAccessible(true);
}
return method.invoke(currentProxy.proxy, args);
} catch (InvocationTargetException e) {
throw e.getCause();
}
}
总结:
java的动态代理原来很简单,但是hadoop中的实际应用略为复杂,主要是封装的层次比较多。