Spring RMI 原始碼淺析-RmiProxyFactoryBean 呼叫服務
RmiProxyFactoryBean 定義
[java] view plaincopyprint?- publicclass RmiProxyFactoryBean extends RmiClientInterceptor implements FactoryBean, BeanClassLoaderAware {
- }
父類RmiClientInterceptor 定義
[java] view plaincopyprint?- publicclass RmiClientInterceptor extends RemoteInvocationBasedAccessor implements
- //spring容器 bean例項化階段 是否要 查詢遠端物件 預查詢
- privateboolean lookupStubOnStartup = true;
- //查詢過的 遠端物件是否進行快取
- privateboolean cacheStub = true;
- //如果連線失敗 是否重新整理遠端呼叫stub
- privateboolean refreshStubOnConnectFailure = false;
- //rmi客戶端 套接字工廠
- private RMIClientSocketFactory registryClientSocketFactory;
- //快取遠端呼叫物件
- private Remote cachedStub;
- //查詢遠端物件時用到的監控器
- privatefinal Object stubMonitor = new Object();
- //.....略
- }
一:查詢遠端服務物件
RmiProxyFactoryBean 是InitializingBean介面的實現 Spring容器在bean的例項化(getBean)階段 回撥afterPropertiesSet 來查詢遠端物件 然後 生成遠端代理物件
[java] view plaincopyprint?- publicvoid
- //父類RmiClientInterceptor檢查serviceUrl是否配置
- //父類RmiClientInterceptor 查詢遠端物件
- super.afterPropertiesSet();
- //遠端呼叫介面檢查
- if (getServiceInterface() == null) {
- thrownew IllegalArgumentException("Property 'serviceInterface' is required");
- }
- //建立代理物件
- //因為父類RmiClientInterceptor實現了 MethodInterceptor 介面 所以this
- this.serviceProxy = new ProxyFactory(getServiceInterface(), this).getProxy(getBeanClassLoader());
- }
父類RmiClientInterceptor的 afterPropertiesSet 方法 幹了兩件事
1.驗證是否配置了serviceUrl 如果沒有 丟擲異常
2.查詢遠端物件
[java] view plaincopyprint?- publicvoid afterPropertiesSet() {
- //檢查serviceUrl 屬性是否為空 如果為空直接丟擲異常
- super.afterPropertiesSet();
- //查詢遠端物件
- prepare();
- }
- publicvoid prepare() throws RemoteLookupFailureException {
- //預查詢遠端物件 預設為true
- if (this.lookupStubOnStartup) {
- //通過標準Api 查詢遠端物件
- Remote remoteObj = lookupStub();
- //是否對stub進行快取
- if (this.cacheStub) {
- this.cachedStub = remoteObj;
- }
- }
- }
通過java API查詢遠端物件
[java] view plaincopyprint?- protected Remote lookupStub() throws RemoteLookupFailureException {
- try {
- Remote stub = null;
- if (this.registryClientSocketFactory != null) {
- ...略 }
- else {
- //TODO 通過客戶端配置 serviceUrl查詢物件
- stub = Naming.lookup(getServiceUrl());
- }
- return stub;
- }
- catch (MalformedURLException ex) {
- thrownew RemoteLookupFailureException("Service URL [" + getServiceUrl() + "] is invalid", ex);
- }
- catch (NotBoundException ex) {
- thrownew RemoteLookupFailureException(
- "Could not find RMI service [" + getServiceUrl() + "] in RMI registry", ex);
- }
- catch (RemoteException ex) {
- thrownew RemoteLookupFailureException("Lookup of RMI stub failed", ex);
- }
- }
二:返回代理物件
RmiProxyFactoryBean是FactoryBean介面的實現 其返回的是getObject方法 返回的物件
[java] view plaincopyprint?- /**
- * 返回遠端代理物件
- * 建立代理物件 是在afterPropertiesSet 方法完成
- */
- public Object getObject() {
- returnthis.serviceProxy;
- }
三:呼叫方法
父類實現了MethodInterceptor介面 在客戶端呼叫方法時會被攔截
[java] view plaincopyprint?- public Object invoke(MethodInvocation invocation) throws Throwable {
- //獲取遠端物件 如果配置了快取cacheStub=true 從快取中獲取 快取中沒有 現在立刻查詢
- Remote stub = getStub();
- try {
- //TODO 客戶端呼叫遠端方法時 攔截處理
- return doInvoke(invocation, stub);
- }
- catch (RemoteConnectFailureException ex) {
- return handleRemoteConnectFailure(invocation, ex);
- }
- catch (RemoteException ex) {
- //如果是連線失敗異常
- if (isConnectFailure(ex)) {
- //處理連線失敗. 是否需要重新整理
- return handleRemoteConnectFailure(invocation, ex);
- }
- else {
- throw ex;
- }
- }
- }
方法呼叫,如果是標準的Rmi 通過反射呼叫,非標準的交給doInvoke方法處理
[java] view plaincopyprint?- protected Object doInvoke(MethodInvocation invocation, Remote stub) throws Throwable {
- //spring RmiInvocationHandler包裝的遠端物件 非實現Remote介面的
- if (stub instanceof RmiInvocationHandler) {
- try {
- //不是標準的Rmi
- return doInvoke(invocation, (RmiInvocationHandler) stub);
- }
- //....略
- }
- else {
- //標準的java Rmi
- try {
- //直接通過反射呼叫
- return RmiClientInterceptorUtils.invokeRemoteMethod(invocation, stub);
- }
- //....略
- }
- }
標準Rmi方法呼叫處理 RmiClientInterceptorUtils的 invokeRemoteMethod方法
[java] view plaincopyprint?- publicstatic Object invokeRemoteMethod(MethodInvocation invocation, Object stub)
- throws InvocationTargetException {
- Method method = invocation.getMethod();
- try {
- if (method.getDeclaringClass().isInstance(stub)) {
- // directly implemented
- return method.invoke(stub, invocation.getArguments());
- }
- else {
- // not directly implemented
- Method stubMethod = stub.getClass().getMethod(method.getName(), method.getParameterTypes());
- return stubMethod.invoke(stub, invocation.getArguments());
- }
- }
- catch (InvocationTargetException ex) {
- throw ex;
- }
- catch (NoSuchMethodException ex) {
- thrownew RemoteProxyFailureException("No matching RMI stub method found for: " + method, ex);
- }
- catch (Throwable ex) {
- thrownew RemoteProxyFailureException("Invocation of RMI stub method failed: " + method, ex);
- }
- }
非標準Rmi處理 方法名 引數封裝成InvocationHandler 通過中轉站方法呼叫目標方法
[java] view plaincopyprint?- protected Object doInvoke(MethodInvocation methodInvocation, RmiInvocationHandler invocationHandler)
- throws RemoteException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
- // 如果是toString方法
- if (AopUtils.isToStringMethod(methodInvocation.getMethod())) {
- return"RMI invoker proxy for service URL [" + getServiceUrl() + "]";
- }
- //invocationHandler spring包裝過的Rmi遠端物件 服務端在暴露服務時包裝
- //createRemoteInvocation方法 返回RemoteInvocation例項 封裝了方法呼叫相關資訊 例如:引數, 方法名
- //Rmi服務端接受到資訊後 會通過RemoteInvocation封裝的資訊 進行呼叫
- return invocationHandler.invoke(createRemoteInvocation(methodInvocation));
- }
RemoteInvocation定義
[java] view plaincopyprint?- publicclass RemoteInvocation implements Serializable {
- private String methodName;
- private Class[] parameterTypes;
- private Object[] arguments;
- private Map attributes;
- public RemoteInvocation(MethodInvocation methodInvocation) {
- this.methodName = methodInvocation.getMethod().getName();
- this.parameterTypes = methodInvocation.getMethod().getParameterTypes();
- this.arguments = methodInvocation.getArguments();
- }
- }
對於客戶端方法呼叫 有兩種形式
1、標準的Rmi 即實現了jdk Remote介面的 直接使用反射機制呼叫
2、非標準的Rmi spring暴露服務時包裝成自己的物件[RmiInvocationHandler] 當客戶段呼叫的時候 被攔截器攔截 封裝方法名 引數等資訊 最後呼叫RmiInvocationHandler的invoke方法 invoke方法類似中轉站(泛化呼叫) 只要非標準Rmi 方法呼叫都會經過它呼叫目標方法。