本文借鉴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