Http Invoke Spring

42435 ワード

Background


プロジェクトチームから最新のリクエストを受け取り、サーバとサーバの間でサービスを交換するように要求されました.
国内の大手企業SIプロジェクトで行ったことのある人は、開発環境が外部インターネットを許さないことを知っているはずだ.
そのため、Webスクロールは使用できません.これは私たちのシステムの機能です.(通常、Webスクロール機能は個別のサーバとして行われ、外部インターネットが利用可能であるが、今回はAシステム内の特定の業務に関連付けなければならないため、A(システム)−B(Webスクロール+Webサービス)間のサービス要求が可能である.

Requirements


以下に、受け取った要件を示します.
  • Aサーバ:ソリューション(イントラネットのみ);Bサーバ:外部ネットワークアクセス可能サーバ
  • Bサーバ機能:Webスクロール、インタフェース接続(SOAPとWSDL)
  • AおよびBサーバにはそれぞれDBが存在する.A-DBサーバとB-DBサーバにアクセスできます.ただし、BはB-DBサーバにしかアクセスできません.
  • Webスクロール機能とインタフェース機能は、AサーバにもBサーバにも存在する.AサーバがWebスクロールまたはインタフェース機能を呼び出す場合、Bサーバが外部ネットワークにアクセスできるため、実際の動作はBサーバ上で行わなければならない.Aサーバは(Bサーバ上で)結果値を返し、B−DBにアクセスしていつでもデータを取得できるようにしなければならない.
  • Aサーバの要求を必要とせず、Bサーバ内で独自のスケジューラを介して「Webスクロール」または「インタフェース」機能
  • を使用することができる.

    Arrangement


    上記の要件をまとめると、次の機能とガイドラインが提供されます.
  • A:ソリューション内の複数のデータ・ソース.
  • A(サーバ)からB(サーバ)へのサービス要求および認証.(Rest API)
  • ネットワークの問題で、処理に失敗したことを確認します.
  • Bサーバで正常に動作していても、ネットワークの問題が発生し、Aサーバが結果値を返すのに失敗します.
  • Task


    1.複数のデータソース


    MyBasisを使用しています.WebCrawlerとInterface Data Sourceを追加するには、追加のSql Sessionビンを定義します.これで問題がないように見えます.ただし、Webスクロール機能とインタフェース機能はモジュール化されているため、新しい定義のSql Sessionが簡単に追加/変更できるかどうかを確認する必要があります.そうしないと、モジュールの内部を変更して新しいバージョンに渡す必要がある場合があります.

    2.サーバサービスへの要求


    既存のサーバがサーバにサービスを要求するケースはすでに存在しており、これは他のシステムとの通信であるため、「Spring RestTemplateの使用」のガイドラインのみが提供されています.
    なぜRest APIを閉じたのですか?
  • 複数のMessageConverter
    現在は2つのモジュールのみが対象ですが、ソリューションには数十のモジュールがあり、各モジュールには多くの(複雑な)データ型オブジェクトがあります.
    後で他のモジュールに同じ要件がある場合は、追加の変更を必要とせずに、同じガイドを使用して迅速に転送することを考慮する必要があります.
  • ただし、同じアプリケーションが異なるWAS上にあるため、MessageConverterは不要であり、Javaオブジェクト(Serialize/deserialize)は使用可能であるため、検索すると次のキーワードが見つかります.
    RMI, Http Invoke, Hessian, Burlap
    内容から見ると、その中のいくつかの章/欠点が見えます.大体以下に示します.
  • RMI
  • を超えるファイアウォールの環境は限られている(任意のポートを使用)
  • 内部ネットワークは影響を受けないが、インターネット上では問題(トンネル)
  • RMIはJavaベースなので、両方のクライアントサービスは
  • を記述しています.
  • Hessian, Burlap
  • Httpで簡単なリモートサービスサポートを提供します.Webサービスのシンプル化
  • Httpに基づいて、ファイアウォールの問題はありません.
  • の複雑なデータモデルでは、シリアル化モデルが足りない可能性があります.
  • Hessian
  • RMIと似ています.バイナリ情報を利用して通信する.
  • Burlap
  • XMLベースのアップグレード技術.
  • Http Invoke
  • RMIとHessian/Burlap(ファイアウォールの問題なし)の利点を有するスプリングHTTP Invoke.
  • ファイアウォール+独立オブジェクトシリアル化機構
  • を通過する.
    結論:Http Invokeを用いた.

    3.ネットワークの問題による故障処理を保証する


    Real Code


    Bサーバ(サービスプロバイダ)


    [web.xml]
    要求テンプレートを次のように分離して使用します.
    コンマ(,)をinit-paramパラメータの値として登録することで、1つのテンプレートで複数のインタフェースを定義して使用できます.
    次のモードを登録して使用します.
    {物理インタフェースプールパッケージ名}:{サービス空ID}
    <!-- HTTPInvoker -->
    <servlet>
        <servlet-name>DefaultInvokeServlet</servlet-name>
        <servlet-class>jade.web.DefaultInvokeServlet</servlet-class>
        <init-param>
    		<param-name>parameters</param-name>
    		<param-value>
    				jade.web.ExecuteService:executeService, 
    				jade.web.ExecutionService:executionService
    		</param-value>
    	</init-param>
    	<load-on-startup>2</load-on-startup>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>DefaultInvokeServlet</servlet-name>
        <url-pattern>/http/execute/*</url-pattern>
    </servlet-mapping>
    [DefaultInvokeServlet.java]
    HttpRequestHandleServiceletクラスを継承し、1つの既存のサーバで1つのインタフェースのみを使用する複数のメソッドを変更して、1つのサーバで複数のインタフェースを処理します.
    package jade.web;
    
    import java.io.IOException;
    import java.util.HashMap;
    import java.util.Map;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.beans.factory.NoSuchBeanDefinitionException;
    import org.springframework.context.i18n.LocaleContextHolder;
    import org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter;
    import org.springframework.util.StringUtils;
    import org.springframework.web.HttpRequestHandler;
    import org.springframework.web.HttpRequestMethodNotSupportedException;
    import org.springframework.web.context.WebApplicationContext;
    import org.springframework.web.context.support.HttpRequestHandlerServlet;
    import org.springframework.web.context.support.WebApplicationContextUtils;
    
    public class DefaultInvokeServlet extends HttpRequestHandlerServlet {
    
    	private Map<String, HttpRequestHandler> target = new HashMap();
    	
    	private final String URL_PREFIX = "/http/execute/";
    	
    	@Override
    	public void init() throws ServletException {
    		this.httpInvokerBinder();
    	}
    
    	@Override
    	protected void service(HttpServletRequest request, HttpServletResponse response)
    			throws ServletException, IOException {
    
    		LocaleContextHolder.setLocale(request.getLocale()); 
    		try {
    			getHttpInvoker(request).handleRequest(request, response);
    		}
    		catch (HttpRequestMethodNotSupportedException ex) {
    			String[] supportedMethods = ex.getSupportedMethods();
    			if (supportedMethods != null) {
    				response.setHeader("Allow", StringUtils.arrayToDelimitedString(supportedMethods, ", "));
    			}
    			response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, ex.getMessage());
    		}
    		finally {
    			LocaleContextHolder.resetLocaleContext();
    		}
    	}
    	
    	private HttpRequestHandler getHttpInvoker(HttpServletRequest request) {
    		// URL_PREFIX
    		String requestedUri = request.getRequestURI();
    		String interfaceName = requestedUri.replace(URL_PREFIX, "");
    		if(this.target.containsKey(interfaceName)) {
    			return this.target.get(interfaceName);
    		}else {
    			return null;
    		}
    	}
    	
    	private void generateHttpInvoker(String interfaceName, String beanName) {
    		HttpInvokerServiceExporter httpInvoker = new HttpInvokerServiceExporter();
    		WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
    		httpInvoker.setService(wac.getBean(beanName));
    		try {
    			httpInvoker.setServiceInterface(Class.forName(interfaceName));
    		} catch (ClassNotFoundException e) {
    			e.printStackTrace();
    		}
    		httpInvoker.afterPropertiesSet();
    		this.target.put(interfaceName, httpInvoker);
    	}
    	
    	private void httpInvokerBinder() {
    		String candidateChars = this.getInitParameter("parameters");
    		String[] candidates = candidateChars.split(",");
    		for(String candidate : candidates) {
    			candidate = candidate.trim();
    			generateHttpInvoker(candidate.split(":")[0], candidate.split(":")[1]);
    		}
    	}
    }

    Aサーバ(クライアント)


    [application-context.xml]
    以下に示すように、既存のSpringコンテキストでは空に登録されています.
    <bean id="httpInvokerAdvisor" class="jade.web.HttpInvokerAdvisor">
      	<property name="serviceUrl" value="http://192.168.4.52:9090/http/execute/"/>
      	<property name="serviceInterface" value="jade.web.ExecuteService"/>
    </bean>
    [HttpInvokerAdvisor.java]
    AOPを利用して、いつでもどこでも気軽に利用できます.
    考慮すべきは,後で変更可能な点を分離するだけである.
    package jade.web;
    
    import java.util.Arrays;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean;
    
    import lombok.Setter;
    import jade.core.entity.ExecutionDetail;
    import jade.core.entity.ExecutionLevel;
    import smartsuite.ifproxy.core.entity.normalize.NormalizedElement;
    
    @Aspect
    public class HttpInvokerAdvisor {
    	
    	HttpInvokerProxyFactoryBean httpInvokerProxyFactoryBean;
    	
    	@Setter
    	private String serviceUrl;
    	
    	@Setter
    	private String serviceInterface;
    
    	@Around("execution(* jade.web.ExecuteService.executeByNormalizedData(..))")
    	public Object invoke1(ProceedingJoinPoint joinPoint) {
    		httpInvokerProxyFactoryBean = new HttpInvokerProxyFactoryBean();
    		try {
    			httpInvokerProxyFactoryBean.setServiceUrl(serviceUrl);
    			httpInvokerProxyFactoryBean.setServiceInterface(Class.forName(serviceInterface));
    			httpInvokerProxyFactoryBean.afterPropertiesSet();
    			
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    		
    		Object[] args = joinPoint.getArgs();
    		ExecuteService httpClient = (ExecuteService) httpInvokerProxyFactoryBean.getObject();
    		NormalizedElement el = (NormalizedElement)args[1];
    		ExecutionDetail detail = httpClient.executeByNormalizedData((String)args[0], el, (ExecutionLevel)args[2]);
    		return detail;
    	}
    	
    	@Around("execution(* jade.spring.service.ExecuteServiceSpring.executeByNormalizedData(..))")
    	public Object invoke2(ProceedingJoinPoint joinPoint) {
    		try {
    			httpInvokerProxyFactoryBean.setServiceUrl(serviceUrl);
    			httpInvokerProxyFactoryBean.setServiceInterface(Class.forName(serviceInterface));
    			httpInvokerProxyFactoryBean.afterPropertiesSet();
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    		
    		System.out.println("logAround() is running!");
    		System.out.println("hijacked method : " + joinPoint.getSignature().getName());
    		System.out.println("hijacked arguments : " + Arrays.toString(joinPoint.getArgs()));
    		
    		ExecuteService httpClient = (ExecuteService) httpInvokerProxyFactoryBean;
    		String returnText = httpClient.execute(null, joinPoint.getArgs(), null);
    		
    		return returnText;
    	}
    	
    }