通常来说一个dubbo
服务都是对内的,也就是给内部调用的,但也有可能一个服务就是需要提供给外部使用,并且还不能有使用语言的局限性。
比较标准的做法是对外的服务我们统一提供一个openAPI
,这样的调用方需要按照标准提供相应的appID
以及密钥来进行验签才能使用。这样固然是比较规范和安全,但复杂度也不亚于开发一个单独的系统了。
这里所讲到的没有那么复杂,就只是把一个不需要各种权限检验的dubbo
服务对外提供为HTTP
服务。
实现思路:
1、定义HttpProviderConf配置类
主要用于保存声明需要对外提供服务的包名
package com.bsk.dubbo.http.conf;
import java.util.List;
public class HttpProviderConf {
/**
* 提供http访问的包
*/
private List usePackage ;
public List getUsePackage() {
return usePackage;
}
public void setUsePackage(List usePackage) {
this.usePackage = usePackage;
}
}
2、封装请求响应类
package com.bsk.dubbo.http.req;
/**
* 请求
*/
public class HttpRequest {
private String param ;//入参
private String service ;//请求service
private String method ;//请求方法
public String getParam() {
return param;
}
public void setParam(String param) {
this.param = param;
}
public String getService() {
return service;
}
public void setService(String service) {
this.service = service;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
}
其中param
是用于存放真正调用dubbo
服务时的入参,传入json
在调用的时候解析成具体的参数对象。
service
存放dubbo
服务声明的interface API
的包名。
method
则是真正调用的方法名称。
package com.bsk.dubbo.http.rsp;
import java.io.Serializable;
/**
* 响应
*/
public class HttpResponse implements Serializable{
private static final long serialVersionUID = -552828440320737814L;
private boolean success;//成功标志
private String code;//信息码
private String description;//描述
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
3、编写暴露服务Controller接口
利用SpringMVC
提供一个HTTP
接口。
在该接口中通过入参进行反射找到具体的dubbo
服务实现进行调用。
package com.bsk.dubbo.http.controller;
import com.alibaba.fastjson.JSON;
import com.bsk.dubbo.http.conf.HttpProviderConf;
import com.bsk.dubbo.http.req.HttpRequest;
import com.bsk.dubbo.http.rsp.HttpResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.*;
import org.springframework.stereotype.Controller;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
@Controller
@RequestMapping("/dubboAPI")
public class DubboController implements ApplicationContextAware {
private final static Logger logger = LoggerFactory.getLogger(DubboController.class);
@Autowired
private HttpProviderConf httpProviderConf;
/**
* 缓存 Map
*/
private final Map> cacheMap = new HashMap>();
protected ApplicationContext applicationContext;
@ResponseBody
@RequestMapping(value = "/{service}/{method}", method = RequestMethod.POST)
public String api(HttpRequest httpRequest, HttpServletRequest request,
@PathVariable String service,
@PathVariable String method) {
logger.debug("ip:{}-httpRequest:{}", getIP(request), JSON.toJSONString(httpRequest));
String invoke = invoke(httpRequest, service, method);
logger.debug("callback :" + invoke);
return invoke;
}
private String invoke(HttpRequest httpRequest, String service, String method) {
httpRequest.setService(service);
httpRequest.setMethod(method);
HttpResponse response = new HttpResponse();
logger.debug("input param:" + JSON.toJSONString(httpRequest));
if (!CollectionUtils.isEmpty(httpProviderConf.getUsePackage())) {
boolean isPac = false;
for (String pac : httpProviderConf.getUsePackage()) {
if (service.startsWith(pac)) {
isPac = true;
break;
}
}
if (!isPac) {
//调用的是未经配置的包
logger.error("service is not correct,service=" + service);
response.setCode("2");
response.setSuccess(false);
response.setDescription("service is not correct,service=" + service);
}
}
try {
Class> serviceCla = cacheMap.get(service);
if (serviceCla == null) {
serviceCla = Class.forName(service);
logger.debug("serviceCla:" + JSON.toJSONString(serviceCla));
//设置缓存
cacheMap.put(service, serviceCla);
}
Method[] methods = serviceCla.getMethods();
Method targetMethod = null;
for (Method m : methods) {
if (m.getName().equals(method)) {
targetMethod = m;
break;
}
}
if (method == null) {
logger.error("method is not correct,method=" + method);
response.setCode("2");
response.setSuccess(false);
response.setDescription("method is not correct,method=" + method);
}
Object bean = this.applicationContext.getBean(serviceCla);
Object result = null;
Class>[] parameterTypes = targetMethod.getParameterTypes();
if (parameterTypes.length == 0) {
//没有参数
result = targetMethod.invoke(bean);
} else if (parameterTypes.length == 1) {
Object json = JSON.parseObject(httpRequest.getParam(), parameterTypes[0]);
result = targetMethod.invoke(bean, json);
} else {
logger.error("Can only have one parameter");
response.setSuccess(false);
response.setCode("2");
response.setDescription("Can only have one parameter");
}
return JSON.toJSONString(result);
} catch (ClassNotFoundException e) {
logger.error("class not found", e);
response.setSuccess(false);
response.setCode("2");
response.setDescription("class not found");
} catch (InvocationTargetException e) {
logger.error("InvocationTargetException", e);
response.setSuccess(false);
response.setCode("2");
response.setDescription("InvocationTargetException");
} catch (IllegalAccessException e) {
logger.error("IllegalAccessException", e);
response.setSuccess(false);
response.setCode("2");
response.setDescription("IllegalAccessException");
}
return JSON.toJSONString(response);
}
/**
* 获取IP
*
* @param request
* @return
*/
private String getIP(HttpServletRequest request) {
if (request == null) {
return null;
}
String s = request.getHeader("X-Forwarded-For");
if (s == null || s.length() == 0 || "unknown".equalsIgnoreCase(s)) {
s = request.getHeader("Proxy-Client-IP");
}
if (s == null || s.length() == 0 || "unknown".equalsIgnoreCase(s)) {
s = request.getHeader("WL-Proxy-Client-IP");
}
if (s == null || s.length() == 0 || "unknown".equalsIgnoreCase(s)) {
s = request.getHeader("HTTP_CLIENT_IP");
}
if (s == null || s.length() == 0 || "unknown".equalsIgnoreCase(s)) {
s = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (s == null || s.length() == 0 || "unknown".equalsIgnoreCase(s)) {
s = request.getRemoteAddr();
}
if ("127.0.0.1".equals(s) || "0:0:0:0:0:0:0:1".equals(s)) {
try {
s = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException unknownhostexception) {
return "";
}
}
return s;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
将这些实现封装成一个功能模块
具体使用请参考博文:https://crossoverjie.top/2017/05/07/SSM13/
4、测试示例
使用Jmeter工具进行测试:
测试结果:
5、总结
通常来说这样提供的HTTP
接口再实际中用的不多,但是很方便调试。
比如写了一个dubbo
的查询接口,在测试环境或者是预发布环境中就可以直接通过HTTP
请求的方式进行简单的测试,或者就是查询数据。比在Java
中写单测来测试或查询快很多。