一般來講一個dubbo
服務都是對內給內部調用的,但也有可能一個服務就是須要提供給外部使用,而且還不能有使用語言的侷限性。javascript
比較標準的作法是對外的服務咱們統一提供一個openAPI
,這樣的調用方須要按照標準提供相應的appID
以及密鑰來進行驗籤才能使用。這樣當然是比較規範和安全,但複雜度也不亞於開發一個單獨的系統了。java
這裏所講到的沒有那麼複雜,就只是把一個不須要各類權限檢驗的dubbo
服務對外提供爲HTTP
服務。git
調用示例:
github
如下是本文所涉及到的一些知識點:spring
其實思路很簡單,就是利用
SpringMVC
提供一個HTTP
接口。
在該接口中經過入參進行反射找到具體的dubbo
服務實現進行調用。json
首先須要定義一個HttpProviderConf
類用於保存聲明須要對外提供服務的包名,畢竟咱們反射時須要用到一個類的全限定名:api
public class HttpProviderConf {
/** * 提供http訪問的包 */
private List<String> usePackage ;
//省略getter setter方法
}複製代碼
就只有一個usePackage
成員變量,用於存放須要包名。
至於用List
的緣由是容許有多個。緩存
public class HttpRequest {
private String param ;//入參
private String service ;//請求service
private String method ;//請求方法
//省略getter setter方法
}複製代碼
其中param
是用於存放真正調用dubbo
服務時的入參,傳入json
在調用的時候解析成具體的參數對象。安全
service
存放dubbo
服務聲明的interface API
的包名。app
method
則是真正調用的方法名稱。
public class HttpResponse implements Serializable{
private static final long serialVersionUID = -552828440320737814L;
private boolean success;//成功標誌
private String code;//信息碼
private String description;//描述
//省略getter setter方法
}複製代碼
這裏只是封裝了經常使用的HTTP
服務的響應數據。
最重要的則是controller裏的實現代碼了。
先貼代碼:
@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<String, Class<?>> cacheMap = new HashMap<String, Class<?>>();
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;
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}複製代碼
先一步一步的看:
首先是定義了一個DubboController
,並使用了SpringMVC
的註解對外暴露HTTP
服務。
實現了org.springframework.context.ApplicationContextAware
類,
實現了setApplicationContext()
方法用於初始化Spring
上下文對象,在以後能夠獲取到容器裏的相應對象。
核心的invoke()
方法。
http://127.0.0.1:8080/SSM-SERVICE/dubboAPI/com.crossoverJie.api.UserInfoApi/getUserInfo
。com.crossoverJie.api.UserInfoApi
、getUserInfo
賦值到httpRequest
入參中。判斷傳入的包是不是對外提供的。以下配置:
<!--dubbo服務暴露爲http服務-->
<bean class="com.crossoverJie.dubbo.http.conf.HttpProviderConf">
<property name="usePackage">
<list>
<!--須要暴露服務的接口包名,可多個-->
<value>com.crossoverJie.api</value>
</list>
</property>
</bean>
<!--掃描暴露包-->
<context:component-scan base-package="com.crossoverJie.dubbo.http"/>複製代碼
其中的com.crossoverJie.api
就是本身須要暴露的包名,能夠多個。
接着在緩存map
中取出反射獲取到的接口類類型,若是獲取不到則經過反射獲取,並將值設置到緩存map
中,這樣不用每次都反射獲取,能夠節省系統開銷(反射很耗系統資源
)。
getUserInfo
方法。dubbo
調用的時候只能傳遞一個BO
類型,具體的參數列表能夠寫到BO
中。由於若是有多個在進行json
解析的時候是沒法賦值到兩個參數對象中去的。一般來講這樣提供的HTTP
接口再實際中用的很少,可是很方便調試。
好比寫了一個dubbo
的查詢接口,在測試環境或者是預發佈環境中就能夠直接經過HTTP
請求的方式進行簡單的測試,或者就是查詢數據。比在Java
中寫單測來測試或查詢快的不少。
git clone https://github.com/crossoverJie/SSM-DUBBO-HTTP.git複製代碼
cd SSM-DUBBO-HTTP複製代碼
mvn clean複製代碼
mvn install複製代碼
<dependency>
<groupId>com.crossoverJie</groupId>
<artifactId>SSM-HTTP-PROVIDER</artifactId>
<version>1.0.0</version>
</dependency>複製代碼
<!--dubbo服務暴露爲http服務-->
<bean class="com.crossoverJie.dubbo.http.conf.HttpProviderConf">
<property name="usePackage">
<list>
<!--須要暴露服務的接口包名,可多個-->
<value>com.crossoverJie.api</value>
</list>
</property>
</bean>
<!--掃描暴露包-->
<context:component-scan base-package="com.crossoverJie.dubbo.http"/>複製代碼
我的博客地址:crossoverjie.top。
GitHub地址:github.com/crossoverJi…。