Spring集成Thrift - remoting支持

本文借鉴spring对hessian的支持,实现spring对Thrift的支持。服务端主要使用了spring的HttpRequestHandler接口和RemoteExporter接口。HttpRequestHandler接口用于暴露http服务,这样就可以接受http的请求,这个如果使用servlet也是可以的。RemoteExporter这块其实主要就是使用了它getServiceInterface和getProxyForService两个方法,这个和thrift服务端代码结合暴露内部的服务。客户端主要使用spring的MethodInterceptor和UrlBasedRemoteAccessor以及代理来实现对服务端的远程调用。下面看一下实现。

在pom.xml添加依赖jar包:

<dependency>
			<groupId>org.apache.thrift</groupId>
			<artifactId>libthrift</artifactId>
			<version>0.9.2</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.5.8</version>
		</dependency>
		<dependency>
			<groupId>commons-pool</groupId>
			<artifactId>commons-pool</artifactId>
			<version>1.6</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>4.0.9.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-expression</artifactId>
			<version>4.0.9.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
			<version>4.0.9.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>4.0.9.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
			<version>4.0.9.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>commons-fileupload</groupId>
			<artifactId>commons-fileupload</artifactId>
			<version>1.3.1</version>
		</dependency>

服务端

1.暴露服务ThriftHttpServiceExporter.java
package cn.slimsmart.thrift.spring.http.remote;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.HttpRequestHandler;
import org.springframework.web.HttpRequestMethodNotSupportedException;

public class ThriftHttpServiceExporter extends ThriftExporter implements HttpRequestHandler {

	@Override
	public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		if (!"POST".equals(request.getMethod())) {
			throw new HttpRequestMethodNotSupportedException(request.getMethod(), new String[] { "POST" }, "ThriftServiceExporter only supports POST requests");
		}
		ServletInputStream in = request.getInputStream();
		ServletOutputStream out = response.getOutputStream();
		try {
			response.setContentType(CONTENT_TYPE_THRIFT);
			invoke(in, out);
		} catch (Exception e) {
			response.setContentType("text/plain; charset=UTF-8");
			response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
			e.printStackTrace(new PrintWriter(out, true));
			if (logger.isErrorEnabled()) {
				logger.error("Thrift server direct error", e);
			}
		}
	}
}
2.实现类的调用ThriftExporter.java
package cn.slimsmart.thrift.spring.http.remote;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;

import org.apache.thrift.TException;
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.protocol.TProtocolFactory;
import org.apache.thrift.transport.TIOStreamTransport;
import org.apache.thrift.transport.TTransport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.remoting.support.RemoteExporter;
import org.springframework.util.Assert;

public class ThriftExporter extends RemoteExporter implements InitializingBean{
	
	public static final String CONTENT_TYPE_THRIFT = "application/x-thrift";
	protected final Logger logger = LoggerFactory.getLogger(getClass());
	
	private TProcessor processor;
	private TProtocolFactory inProtocolFactory;
	private TProtocolFactory outProtocolFactory;
	private Collection<Map.Entry<String, String>> customHeaders;

	@Override
	public void afterPropertiesSet() throws Exception {
		Class<?> Processor = Class.forName(getServiceInterface().getName().replace("$Iface", "$Processor"));
		Constructor<?> con = Processor.getConstructor(getServiceInterface());
		TProcessor processor = (TProcessor) con.newInstance(getService());

		this.processor = processor;
		this.inProtocolFactory = new TCompactProtocol.Factory();
		this.outProtocolFactory = new TCompactProtocol.Factory();
		this.customHeaders = new ArrayList<Map.Entry<String, String>>();
    }
	
	public void invoke(InputStream inputStream, OutputStream outputStream){
		Assert.notNull(this.processor, "Thrift processor has not been initialized");
		Assert.notNull(this.inProtocolFactory, "Thrift inProtocolFactory has not been initialized");
		Assert.notNull(this.outProtocolFactory, "Thrift outProtocolFactory has not been initialized");
		doInvoke(inputStream, outputStream);
	}
	
	protected void doInvoke(InputStream inputStream, OutputStream outputStream){
		try {
			TTransport transport = new TIOStreamTransport(inputStream, outputStream);
			TProtocol inProtocol = inProtocolFactory.getProtocol(transport);
			TProtocol outProtocol = outProtocolFactory.getProtocol(transport);
			processor.process(inProtocol, outProtocol);
			outputStream.flush();
		} catch (TException e) {
			logger.error("error:{}",e);
			throw new ThriftServletException("thrift processor error : {}", e);
		} catch (IOException e) {
			logger.error("error:{}",e);
			throw new ThriftServletException("io error : {}", e);
		}
	}
	
	public void addCustomHeader(final String key, final String value) {
		this.customHeaders.add(new Map.Entry<String, String>() {
			public String getKey() {
				return key;
			}
			public String getValue() {
				return value;
			}
			public String setValue(String value) {
				return null;
			}
		});
	}

	public void setCustomHeaders(Collection<Map.Entry<String, String>> headers) {
		this.customHeaders.clear();
		this.customHeaders.addAll(headers);
	}
}

客户端

1.接口代理实现,使用HttpClient实现远程调用
package cn.slimsmart.thrift.spring.http.remote;

import java.lang.reflect.Constructor;
import java.util.Hashtable;

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.RefAddr;
import javax.naming.Reference;
import javax.naming.spi.ObjectFactory;

import org.apache.http.client.HttpClient;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.THttpClient;

public class ThriftProxyFactory implements ObjectFactory{
	
	private HttpClient httpClient;
	
	private final ClassLoader _loader;
	
	 public ThriftProxyFactory()
	  {
	    this(Thread.currentThread().getContextClassLoader());
	  }
	
	 public ThriftProxyFactory(ClassLoader loader)
	  {
	    _loader = loader;
	  }
	
	public Object create(Class<?> serviceInterface, String serviceUrl) throws Exception{
		THttpClient thc = new THttpClient(serviceUrl, httpClient);
		TProtocol loPFactory = new TCompactProtocol(thc);
		Class<?> client = Class.forName(serviceInterface.getName().replace("$Iface", "$Client"));
		Constructor<?> con = client.getConstructor(TProtocol.class);
		return con.newInstance(loPFactory);
	}

	@Override
	public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
		Reference ref = (Reference) obj;
	    String api = null;
	    String url = null;
	    for (int i = 0; i < ref.size(); i++) {
	      RefAddr addr = ref.get(i);
	 
	      String type = addr.getType();
	      String value = (String) addr.getContent();
	 
	      if (type.equals("type"))
	        api = value;
	      else if (type.equals("url"))
	        url = value;
	    }
	 
	    if (url == null)
	      throw new NamingException("`url' must be configured for ThriftProxyFactory.");
	    // XXX: could use meta protocol to grab this
	    if (api == null)
	      throw new NamingException("`type' must be configured for ThriftProxyFactory.");
	    Class<?> apiClass = Class.forName(api, false, _loader);
		return create(apiClass, url);
	}

	public HttpClient getHttpClient() {
		return httpClient;
	}

	public void setHttpClient(HttpClient httpClient) {
		this.httpClient = httpClient;
	}
}
2.接口代理工厂
package cn.slimsmart.thrift.spring.http.remote;

import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.factory.FactoryBean;

public class ThriftHttpProxyFactoryBean extends ThriftClientInterceptor implements FactoryBean<Object>{
	
	private Object serviceProxy;
	
	@Override
	public void afterPropertiesSet() {
		super.afterPropertiesSet();
		this.serviceProxy = new ProxyFactory(getServiceInterface(), this).getProxy(getBeanClassLoader());
	}

	public Object getObject() {
		return this.serviceProxy;
	}

	public Class<?> getObjectType() {
		return getServiceInterface();
	}

	public boolean isSingleton() {
		return true;
	}
}
3.通过MethodInterceptor实现代理调用
package cn.slimsmart.thrift.spring.http.remote;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.http.client.HttpClient;
import org.springframework.remoting.RemoteLookupFailureException;
import org.springframework.remoting.support.UrlBasedRemoteAccessor;
import org.springframework.util.Assert;

public class ThriftClientInterceptor extends UrlBasedRemoteAccessor implements MethodInterceptor {

	private ThriftProxyFactory proxyFactory = new ThriftProxyFactory();
	private Object thriftProxy;

	private HttpClient httpClient;
	private static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 100;
	private static final int DEFAULT_MAX_CONNECTIONS_PER_ROUTE = 5;
	private static final int DEFAULT_READ_TIMEOUT_MILLISECONDS = (60 * 1000);

	public void setProxyFactory(ThriftProxyFactory proxyFactory) {
		this.proxyFactory = proxyFactory == null ? new ThriftProxyFactory() : proxyFactory;
	}

	protected Object createThriftProxy(ThriftProxyFactory proxyFactory) throws Exception {
		Assert.notNull(getServiceInterface(), "'serviceInterface' is required");
		return proxyFactory.create(getServiceInterface(), getServiceUrl());
	}

	public void prepare() throws RemoteLookupFailureException {
		try {
			proxyFactory.setHttpClient(httpClient);
			this.thriftProxy = createThriftProxy(this.proxyFactory);
		} catch (Exception ex) {
			throw new RemoteLookupFailureException("Service URL [" + getServiceUrl() + "] is invalid", ex);
		}
	}

	@Override
	public void afterPropertiesSet() {
		super.afterPropertiesSet();
		if (getServiceInterface() == null) {
			throw new IllegalArgumentException("property serviceInterface is required.");
		}
		org.apache.http.conn.scheme.SchemeRegistry schemeRegistry = new org.apache.http.conn.scheme.SchemeRegistry();
		schemeRegistry.register(new org.apache.http.conn.scheme.Scheme("http", 80, org.apache.http.conn.scheme.PlainSocketFactory.getSocketFactory()));
		schemeRegistry.register(new org.apache.http.conn.scheme.Scheme("https", 443, org.apache.http.conn.ssl.SSLSocketFactory.getSocketFactory()));

		org.apache.http.impl.conn.PoolingClientConnectionManager connectionManager = new org.apache.http.impl.conn.PoolingClientConnectionManager(
				schemeRegistry);
		connectionManager.setMaxTotal(DEFAULT_MAX_TOTAL_CONNECTIONS);
		connectionManager.setDefaultMaxPerRoute(DEFAULT_MAX_CONNECTIONS_PER_ROUTE);

		this.httpClient = new org.apache.http.impl.client.DefaultHttpClient(connectionManager);
		setReadTimeout(DEFAULT_READ_TIMEOUT_MILLISECONDS);
		prepare();
	}

	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		Method method = invocation.getMethod();
		Object[] args = invocation.getArguments();
		String name = method.getName();
		if (args.length == 0) {
			if ("toString".equals(name)) {
				return "Thrift proxy for service URL [" + getServiceUrl() + "]";
			} else if ("hashCode".equals(name)) {
				return getServiceUrl().hashCode();
			}
		} else if (args.length == 1 && "equals".equals(name)) {
			return getServiceUrl().equals(args[0]);
		}
		if (this.thriftProxy == null) {
			throw new IllegalStateException("ThriftClientInterceptor is not properly initialized - " + "invoke 'prepare' before attempting any operations");
		}
		ClassLoader originalClassLoader = overrideThreadContextClassLoader();
		try {
			return method.invoke(thriftProxy, args);
		} catch (InvocationTargetException e) {
			logger.error("error:{}", e);
			throw new ThriftServletException("invoke error : {}", e);
		} catch (Throwable ex) {
			logger.error("error:{}", ex);
			throw new ThriftServletException("error : {}", ex);
		} finally {
			resetThreadContextClassLoader(originalClassLoader);
		}
	}

	public void setHttpClient(HttpClient httpClient) {
		this.httpClient = httpClient;
	}

	public HttpClient getHttpClient() {
		return this.httpClient;
	}

	public void setConnectTimeout(int timeout) {
		Assert.isTrue(timeout >= 0, "Timeout must be a non-negative value");
		getHttpClient().getParams().setIntParameter(org.apache.http.params.CoreConnectionPNames.CONNECTION_TIMEOUT, timeout);
	}

	public void setReadTimeout(int timeout) {
		Assert.isTrue(timeout >= 0, "Timeout must be a non-negative value");
		getHttpClient().getParams().setIntParameter(org.apache.http.params.CoreConnectionPNames.SO_TIMEOUT, timeout);
	}

}

实例测试

1.接口定义并实现
使用前面的HelloWorld接口定义(略)
2.服务端spring配置
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
				http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
				http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
				http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd"
	default-lazy-init="true">
	
	<description>thrift-servlet服务</description>
    <bean  id="helloWorldImpl" class="cn.slimsmart.thrift.spring.http.demo.HelloWorldImpl"/>
</beans>
applicationContext-server.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
				http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
				http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
				http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd"
	default-lazy-init="true">
	
	<description>thrift-servlet服务</description>
	
    <bean name="/helloworldService" class="cn.slimsmart.thrift.spring.http.remote.ThriftHttpServiceExporter">
        <property name="service" ref="helloWorldImpl" />
        <property name="serviceInterface" value="cn.slimsmart.thrift.spring.http.demo.HelloWorld$Iface"/>
    </bean>
	
</beans>
3.web.xml 配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	id="WebApp_ID" version="2.5">
	<display-name>ThriftTest</display-name>

	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>
			classpath:applicationContext.xml
		</param-value>
	</context-param>
	
	<servlet>
		<servlet-name>dispatcherServlet</servlet-name>
		<servlet-class>
			org.springframework.web.servlet.DispatcherServlet
		</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath*:/applicationContext-server.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
	
	<servlet-mapping>
		<servlet-name>dispatcherServlet</servlet-name>
		<url-pattern>/remote/*</url-pattern>
	</servlet-mapping>
	
	<listener>
		<listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
	</listener>
 
	<session-config>
		<session-timeout>30</session-timeout>
	</session-config>
	
	<welcome-file-list>
		<welcome-file>index.html</welcome-file>
	</welcome-file-list>
	
</web-app>
启动服务端tomcat
4.客户端spring配置
applicationContext-client.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
				http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
				http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
				http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd"
	default-lazy-init="true">
	<description>thrift配置文件 </description>
	<bean id="helloworldService"
		class="cn.slimsmart.thrift.spring.http.remote.ThriftHttpProxyFactoryBean">
		<property name="serviceUrl"
			value="http://127.0.0.1:8080/thrift-spring-http-demo/remote/helloworldService" />
		<property name="serviceInterface"
			value="cn.slimsmart.thrift.spring.http.demo.HelloWorld$Iface" />
	</bean>
</beans>
5.客户端调用
HelloWorldClientTest.java
package cn.slimsmart.thrift.spring.http.demo;

import org.apache.thrift.TException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class HelloWorldClientTest {
	@SuppressWarnings("resource")
	public static void main(String[] args) throws TException {
		ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext-client.xml");
		HelloWorld.Iface client = (HelloWorld.Iface)context.getBean(HelloWorld.Iface.class);
		System.out.println(client.sayHello("han meimei"));
	}
}
参考:http://www.open-open.com/lib/view/open1357804231418.html
代码:http://download.csdn.net/detail/tianwei7518/8467505
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值