该协议主要是为了支持老项目可以消费springcloud提供的接口,并可以利用dubbo的服务发现,构建出一个springboot rest集群, dubbo与springboot结合时,不需要dubbo再次导出rest服务。而是由springboot提供rest服务dubbo端只负责注册,构建服务目录。
Git: spring-boot-starter-dubbo Example: dubbo-demo
###依赖jar包
<!--Feign 支持-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
###核心代码
public class FeignProtocol extends AbstractProxyProtocol {
private static ObjectFactory<HttpMessageConverters> objectFactory = new ObjectFactory<HttpMessageConverters>() {
[@Override](https://my.oschina.net/u/1162528)
public HttpMessageConverters getObject() throws BeansException {
return getApplicationContext().getBean(HttpMessageConverters.class);
}
};
protected <T> Runnable doExport(T impl, final Class<T> type, URL url) throws RpcException {
//不做任何操作,由springboot对外提供该服务,dubbo只负责注册服务
return new Runnable() {
[@Override](https://my.oschina.net/u/1162528)
public void run() {
RequestMappingHandlerMapping requestMappingHandlerMapping = getApplicationContext().getBean(RequestMappingHandlerMapping.class);
Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods();
for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethods.entrySet()) {
Object bean = entry.getValue().getBean();
if (type.isAssignableFrom(AopUtils.getTargetClass(bean))) {
requestMappingHandlerMapping.unregisterMapping(entry.getKey());
}
}
}
};
}
protected <T> T doRefer(Class<T> type, URL url) throws RpcException {
int timeout = url.getParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
int connections = url.getParameter(Constants.CONNECTIONS_KEY, 20);
int retries = url.getParameter(Constants.RETRIES_KEY, 0);
String schema = "http://";
if (url.getPort() == 443 || url.getPort() == 8433) {
schema = "https://";
}
String api = schema + url.getHost() + ":" + url.getPort();
FeignClient feignClient = type.getAnnotation(FeignClient.class);
if (feignClient != null) {
//如果feign注解携带url,将以url为准
if (!StringUtils.isBlank(feignClient.url())) {
api = getEnvironment().resolvePlaceholders(feignClient.url());
}
if (!StringUtils.isBlank(feignClient.path())) {
api = api + ("/" + feignClient.path()).replaceAll("[/]+", "/");
}
}
return target(api, type, connections, timeout, retries);
}
public int getDefaultPort() {
//该端口与springboot保持一致
String port = getEnvironment().resolvePlaceholders("${server.port}");
return !StringUtils.isBlank(port) ? Integer.parseInt(port) : 8080;
}
public static <T> T target(String url, Class<T> type) {
return target(url, type, 20, 3000, 0);
}
public static <T> T target(String url, Class<T> type, int connections, int timeout, int retries) {
SSLContext sslContext = SSLContexts.createSystemDefault();
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.INSTANCE)
.register("https", new SSLConnectionSocketFactory(sslContext))
.build();
PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(timeout)
.setSocketTimeout(timeout)
.build();
CloseableHttpClient httpClient = HttpClientBuilder.create()
.setConnectionManager(manager)
.setDefaultRequestConfig(requestConfig)
.setMaxConnPerRoute(connections)
.setMaxConnTotal(connections)
.setRetryHandler(new DefaultHttpRequestRetryHandler(0, true))
.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy())
.build();
return HystrixFeign.builder()
.requestInterceptors(getApplicationContext().getBeansOfType(RequestInterceptor.class).values())
.contract(new SpringMvcContract())
.encoder(new SpringEncoder(objectFactory))
.decoder(new SpringDecoder(objectFactory))
.client(new ApacheHttpClient(httpClient))
.errorDecoder(new ErrorDecoder.Default())
.retryer(new Retryer.Default(100, 500, retries))
.target(type, url);
}
@SuppressWarnings("unchecked")
private static WebApplicationContext getApplicationContext() {
Field contextsFiled = ReflectionUtils.findField(SpringExtensionFactory.class, "contexts");
contextsFiled.setAccessible(true);
for (ApplicationContext applicationContext : (Set<ApplicationContext>) ReflectionUtils.getField(contextsFiled, null)) {
if (applicationContext instanceof WebApplicationContext) {
return (WebApplicationContext) applicationContext;
}
}
throw new RpcException("not find webApplicationContext!");
}
private static Environment getEnvironment() {
return getApplicationContext().getEnvironment();
}
}
###exmaple
@Value("${server.port}")
private int port;
//服务端,多协议发布服务
[@Bean](https://my.oschina.net/bean)
public ServiceBean<UserService> userServiceServiceBean(@Autowired UserService userService) {
ServiceBean<UserService> serviceBean = new ServiceBean<UserService>();
serviceBean.setInterface(UserService.class);
serviceBean.setRef(userService);
serviceBean.setProtocols(Arrays.asList(new ProtocolConfig("dubbo"), new ProtocolConfig("feign", port)));
return serviceBean;
}
[@Bean](https://my.oschina.net/bean)
//消费端,此种方式可以避免使用@Reference注解,保持与spring注解一致
public ReferenceBean<UserService> userService() {
ReferenceBean<UserService> bean = new ReferenceBean<UserService>();
bean.setInterface(UserService.class);
return bean;
}
###接口
@FeignClient(path = "/user")
public interface UserService {
@RequestMapping(value = "{id}", method = RequestMethod.GET)
User findOne(@PathVariable(value = "id") Integer id);
@RequestMapping(value = "{id}", method = RequestMethod.DELETE)
void delete(@PathVariable(value = "id") Integer id);
@RequestMapping(value = "/", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
void save(@RequestBody User user);
@RequestMapping(value = "/", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE)
void update(@RequestBody User user);
@RequestMapping(value = "/findAll", method = RequestMethod.GET)
List<User> findAll();
}