传统Java Web非Spring Boot项目从Spring Cloud Eureka中获取服务


更新日志:
2018/3/3 21:05:43 新建
2018/3/11 7:11:59 新增注册到Eureka、从Eureka注销、新增Feign,更新配置文件,更新代码

部门项目的技术框架从 ZooKeeper+Dubbo 转型为Spring Cloud 微服务,转型顺利、开发方便、使用良好,于是完全废弃了ZooKeeper+Dubbo,而Web端后台管理界面的项目由于种种原因不希望大规模重构为Spring Boot项目,继续保持原有的SSM框架,并使用http调用微服务接口。为避免将微服务地址写死,这就需要Web项目连接到Spring Cloud Eureka 上通过服务名获取微服务真实地址。

项目依赖

	<!-- eureka 服务发现 -->
	<dependency>
		<groupId>com.netflix.eureka</groupId>
		<artifactId>eureka-client</artifactId>
		<version>1.7.0</version>
	</dependency>
	<!-- Ribbon 负载均衡 -->
	<dependency>
		<groupId>com.netflix.ribbon</groupId>
		<artifactId>ribbon-core</artifactId>
		<version>${netflix.ribbon.version}</version>
	</dependency>
    <dependency>
        <groupId>com.netflix.ribbon</groupId>
        <artifactId>ribbon-loadbalancer</artifactId>
        <version>${netflix.ribbon.version}</version>
        <exclusions>  
        	<exclusion>  
        		<groupId>io.reactivex</groupId>
				<artifactId>rxjava</artifactId>  
    		</exclusion>
    	</exclusions>    
    </dependency>
    <dependency>
        <groupId>com.netflix.ribbon</groupId>
        <artifactId>ribbon-eureka</artifactId>
        <version>${netflix.ribbon.version}</version>
    </dependency>
	<!-- Feign 包装http请求 -->
	<dependency>
		<groupId>io.github.openfeign</groupId>
		<artifactId>feign-hystrix</artifactId>
		<version>${netflix.feign.version}</version>
	</dependency>
	<dependency>
		<groupId>io.github.openfeign</groupId>
		<artifactId>feign-ribbon</artifactId>
		<version>${netflix.feign.version}</version>
	</dependency>
	<!-- <dependency>
		<groupId>io.github.openfeign</groupId>
		<artifactId>feign-gson</artifactId>
		<version>${netflix.feign.version}</version>
	</dependency> -->
	<dependency>
		<groupId>io.github.openfeign</groupId>
		<artifactId>feign-slf4j</artifactId>
		<version>${netflix.feign.version}</version>
	</dependency>
	<dependency>
		<groupId>io.github.openfeign</groupId>
		<artifactId>feign-jackson</artifactId>
		<version>${netflix.feign.version}</version>
	</dependency>
	
	<dependency>
		<groupId>io.reactivex</groupId>
		<artifactId>rxjava</artifactId>
		<version>1.1.1</version>
	</dependency>

这里使用netflix项目下的eureka、ribbon、Feign、Hystrix。
ribbon-开头的项目都是同一个版本号,所以就抽取出${netflix.ribbon.version}统一管理。feign-开头的项目也都是同一个的版本号,抽取${netflix.feign.version}统一管理。

需要注意:

  1. Maven依赖jar包冲突问题:rxjava项目在ribbon-loadbalancerfeign-hystrix依赖的hystrix-core中都有使用。当前最新版2.2.4的ribbon-loadbalancer使用rxjava:1.0.9。而feign-hystrix依赖的hystrix-core使用rxjava:1.1.1。因为依赖冲突,ribbon-loadbalancer中的rxjava:1.0.9代替掉了hystrix-core中的rxjava:1.1.1。这样当程序运行时会疯狂报找不到类Error,找不到rx/Single,这个类在2.0.9中并没有,2.1.1中有hystrix-core用到了,但是由于依赖冲突使用2.0.9的rxjava没有该类,所以报错。
    解决办法:ribbon-loadbalancer使用 exclusion 排除依赖 rxjava 即可。
  2. feign-core在中央仓库有两个groupId: com.netflix.feignio.github.openfeign 。groupIdcom.netflix.feign在2016年7月提交到8.18.0后就没有再提交,而groupIdio.github.openfeign已经在2018年三月份提交到9.6.0。

配置文件

Ribbon配置

# ribbon.properties
# xxx-service对应的微服务名
xxx-service.ribbon.DeploymentContextBasedVipAddresses=xxx-service
# 固定写法,xxx-service使用的ribbon负载均衡器
xxx-service.ribbon.NIWSServerListClassName=com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList
# 每分钟更新xxx-service对应服务的可用地址列表
xxx-service.ribbon.ServerListRefreshInterval=60000

Eureka配置

Eureka默认在classpath中寻找eureka-client.properties配置文件

# 控制是否注册自身到eureka中,本项目虽然不对外提供服务,但需要Eureka监控,在Eureka列表上显示
eureka.registration.enabled=true
# eureka相关配置
# 默认为true,以实现更好的基于区域的负载平衡。
eureka.preferSameZone=true
# 是否要使用基于DNS的查找来确定其他eureka服务器
eureka.shouldUseDns=false
# 由于shouldUseDns为false,因此我们使用以下属性来明确指定到eureka服务器的路由(eureka Server地址)
eureka.serviceUrl.default=http://username:password@localhost:8761/eureka/
eureka.decoderName=JacksonJson
# 客户识别此服务的虚拟主机名,这里指的是eureka服务本身
eureka.vipAddress=XXXplatform
#服务指定应用名,这里指的是eureka服务本身
eureka.name=XXXlatform
#服务将被识别并将提供请求的端口
eureka.port=8080

初始化Ribbon、注册Eureka

之前初始化Ribbon、Eureka、注册到Eureka和获取地址的方法写在静态代码块和静态方法中,这样在项目停止时无法从Eureka中取消注册,这会使Eureka进入安全模式,死掉的项目一直显示在Eureka列表中。

继承ServletContextListener,重写contextInitialized、contextDestroyed,在应用上下文启动时初始化Ribbon、Eureka、注册到Eureka,应用上下文销毁时注销Eureka。

import java.io.IOException;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.netflix.appinfo.ApplicationInfoManager;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.config.ConfigurationManager;
import com.netflix.discovery.DefaultEurekaClientConfig;
import com.netflix.discovery.DiscoveryManager;

/**
 * @ClassName: EurekaInitAndRegisterListener
 * @Description:  服务器启动初始化Ribbon和注册到Eureka Server
 * @author SuXun
 * @date 2018年3月7日 上午9:21:12
 */
@SuppressWarnings("deprecation")
public class EurekaInitAndRegisterListener implements ServletContextListener {
	private static final Logger LOGGER = LoggerFactory.getLogger(EurekaInitAndRegisterListener.class);
	/**
	 * 默认的ribbon配置文件名, 该文件需要放在classpath目录下
	 */
	public static final String RIBBON_CONFIG_FILE_NAME = "ribbon.properties";

	@Override
	public void contextInitialized(ServletContextEvent sce) {
		LOGGER.info("开始初始化ribbon");
		try {
			// 加载ribbon配置文件
			ConfigurationManager.loadPropertiesFromResources(RIBBON_CONFIG_FILE_NAME);
		} catch (IOException e) {
			e.printStackTrace();
			LOGGER.error("ribbon初始化失败");
			throw new IllegalStateException("ribbon初始化失败");
		}
		LOGGER.info("ribbon初始化完成");
		// 初始化Eureka Client
		LOGGER.info("Eureka初始化完成,正在注册Eureka Server");
		DiscoveryManager.getInstance().initComponent(new MyInstanceConfig(), new DefaultEurekaClientConfig());
		ApplicationInfoManager.getInstance().setInstanceStatus(InstanceInfo.InstanceStatus.UP);
	}

	@Override
	public void contextDestroyed(ServletContextEvent sce) {
		DiscoveryManager.getInstance().shutdownComponent();
	}
}

这里有个自定义的类MyInstanceConfig,这个类作用是将注册到Eureka的hostName从主机名换成IP地址加端口号的形式。

import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.util.Enumeration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.netflix.appinfo.MyDataCenterInstanceConfig;

/**
 * @ClassName: MyInstanceConfig
 * @Description:  
 * @author SuXun
 * @date 2018年3月7日 上午9:33:31
 */
public class MyInstanceConfig extends MyDataCenterInstanceConfig {
	private static final Logger LOG = LoggerFactory.getLogger(MyInstanceConfig.class); 
	@Override
	public String getHostName(boolean refresh) {

		try {
			return findFirstNonLoopbackAddress().getHostAddress();
		} catch (Exception e) {
			return super.getHostName(refresh);
		}
	}
	
	public InetAddress findFirstNonLoopbackAddress() {
		InetAddress result = null;
		try {
			int lowest = Integer.MAX_VALUE;
			for (Enumeration<NetworkInterface> nics = NetworkInterface
					.getNetworkInterfaces(); nics.hasMoreElements();) {
				NetworkInterface ifc = nics.nextElement();
				if (ifc.isUp()) {
					LOG.trace("Testing interface: " + ifc.getDisplayName());
					if (ifc.getIndex() < lowest || result == null) {
						lowest = ifc.getIndex();
					}
					else if (result != null) {
						continue;
					}

					// @formatter:off
						for (Enumeration<InetAddress> addrs = ifc
								.getInetAddresses(); addrs.hasMoreElements();) {
							InetAddress address = addrs.nextElement();
							if (address instanceof Inet4Address
									&& !address.isLoopbackAddress()) {
								LOG.trace("Found non-loopback interface: "
										+ ifc.getDisplayName());
								result = address;
							}
						}
					// @formatter:on
				}
			}
		}
		catch (IOException ex) {
			LOG.error("Cannot get first non-loopback address", ex);
		}

		if (result != null) {
			return result;
		}

		try {
			return InetAddress.getLocalHost();
		}
		catch (UnknownHostException e) {
			LOG.warn("Unable to retrieve localhost");
		}

		return null;
	}
}

虽然DiscoveryManager.getInstance().initComponent()方法已经被标记为@Deprecated了,但是ribbon的DiscoveryEnabledNIWSServerList组件代码中依然是通过DiscoveryManager来获取EurekaClient对象的。

获取服务地址

mport java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.netflix.client.ClientFactory;
import com.netflix.loadbalancer.DynamicServerListLoadBalancer;
import com.netflix.loadbalancer.RoundRobinRule;
import com.netflix.loadbalancer.Server;

/**
 * @ClassName: AlanServiceAddressSelector
 * @Description:  获取到目标服务注册在Eureka地址
 * @author SuXun
 * @date 2018年3月2日 下午5:23:24
 */
public class AlanServiceAddressSelector {
	
	private static final Logger log = LoggerFactory.getLogger(AlanServiceAddressSelector.class);
	private static RoundRobinRule chooseRule = new RoundRobinRule();

	/**
	 * 根据轮询策略选择一个地址
	 * @param clientName ribbon.properties配置文件中配置项的前缀名, 如myclient
	 * @return null表示该服务当前没有可用地址
	 */
	public static AlanServiceAddress selectOne(String clientName) {
		// ClientFactory.getNamedLoadBalancer会缓存结果, 所以不用担心它每次都会向eureka发起查询
		@SuppressWarnings("rawtypes")
		DynamicServerListLoadBalancer lb = (DynamicServerListLoadBalancer) ClientFactory
				.getNamedLoadBalancer(clientName);
		Server selected = chooseRule.choose(lb, null);
		if (null == selected) {
			log.warn("服务{}没有可用地址", clientName);
			return null;
		}
		log.debug("服务{}选择结果:{}", clientName, selected);
		return new AlanServiceAddress(selected.getPort(), selected.getHost());
	}

	/**
	 * 选出该服务所有可用地址
	 * @param clientName
	 * @return
	 */
	public static List<AlanServiceAddress> selectAvailableServers(String clientName) {
		@SuppressWarnings("rawtypes")
		DynamicServerListLoadBalancer lb = (DynamicServerListLoadBalancer) ClientFactory
				.getNamedLoadBalancer(clientName);
		List<Server> serverList = lb.getServerList(true);
		// List<Server> serverList = lb.getReachableServers();
		if (serverList.isEmpty()) {
			log.warn("服务{}没有可用地址", clientName);
			return Collections.emptyList();
		}
		log.debug("服务{}所有选择结果:{}", clientName, serverList);
		List<AlanServiceAddress> address = new ArrayList<AlanServiceAddress>();
		for (Server server : serverList) {
		address.add(new AlanServiceAddress(server.getPort(), server.getHost()));
		}
		return address;
	}
}

地址实体类:

/**
 * @ClassName: AlanServiceAddress
 * @Description:  地址实体类
 * @author SuXun
 * @date 2018年3月2日 下午2:14:17
 */
public class AlanServiceAddress {
	private int port;
	private String host;

	public AlanServiceAddress() {
	}

	public AlanServiceAddress(int port, String host) {
		this.port = port;
		this.host = host;
	}

	public int getPort() {
		return port;
	}

	public void setPort(int port) {
		this.port = port;
	}

	public String getHost() {
		return host;
	}

	public void setHost(String host) {
		this.host = host;
	}

	/**
	 * 将服务地址转换为 http://主机名:端口/ 的格式
	 * @return
	 */
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder(15 + host.length());
		sb.append("http://").append(host).append(":").append(port).append("/");

		return sb.toString();
	}
}

使用方法

// 选择出myclient对应服务全部可用地址
List<AlanServiceAddress> list = AlanServiceAddressSelector.selectAvailableServers("myclient");
System.out.println(list);

// 选择出myclient对应服务的一个可用地址(轮询), 返回null表示服务当前没有可用地址
AlanServiceAddress addr = AlanServiceAddressSelector.selectOne("myclient");
System.out.println(addr);

构建Feign客户端

根据服务名在Eureka获取地址,构建Feign,如果缓存有则返回缓存里的Feign,避免重复构建Feign。

构建Feign方法:

import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.LoggerFactory;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommand.Setter;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandProperties;
import com.xxx.platform.common.netflex.eureka.AlanServiceAddress;
import com.xxx.platform.common.netflex.eureka.AlanServiceAddressSelector;
import feign.Feign;
import feign.Logger;
import feign.Request.Options;
import feign.Retryer;
import feign.Target;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.hystrix.HystrixFeign;
import feign.hystrix.SetterFactory;
import feign.jackson.JacksonDecoder;
import feign.jackson.JacksonEncoder;
import feign.slf4j.Slf4jLogger;

/**
 * @ClassName: BaseFeignBuilder
 * @Description: 用于构建Feign
 * @author SuXun
 * @date 2018年3月5日 下午3:50:18
 */
public class BaseFeignBuilder {

	private static final org.slf4j.Logger log = LoggerFactory.getLogger(BaseFeignBuilder.class);
	private static ConcurrentHashMap<String, Object> cacheFeignMap = new ConcurrentHashMap<String, Object>();
	private static ConcurrentHashMap<String, String> cacheAddressMap = new ConcurrentHashMap<String, String>();

	/**
	 * 构建HystrixFeign,具有Hystrix提供的熔断和回退功能,JacksonEncoder、JacksonDecoder、Slf4jLogger、Logger.Level.FULL
	 * @param apiType 使用feign访问的接口类,如MedBodyClient.class
	 * @param clientName 配置文件中的ribbon client名字
	 * @param fallback 回退类
	 * @param url 添加网址
	 * @return
	 */
	public static <T> T buildHystrixFeign(Class<T> apiType, T fallback, String url) {
		// 之前用GsonEncoder()和GsonDecoder()对Date类型支持不好,改成JacksonEncoder和JacksonDecoder,日期转换正常
		return buildHystrixFeign(apiType, fallback, url, new JacksonEncoder(), new JacksonDecoder(),
				new Slf4jLogger(BaseFeignBuilder.class), Logger.Level.FULL);
	}

	/**
	 * 构建HystrixFeign,具有Hystrix提供的熔断和回退功能
	 * @param apiType 使用feign访问的接口类,如MedBodyClient.class
	 * @param clientName clientName 配置文件中的ribbon client名字
	 * @param fallback 回退类
	 * @param url 添加网址
	 * @param encoder 编码器
	 * @param decoder 解码器
	 * @param logger 日志对象
	 * @param logLevel 日志级别
	 * @return
	 */
	public static <T> T buildHystrixFeign(Class<T> apiType, T fallback, String url, Encoder encoder, Decoder decoder,
			Logger logger, Logger.Level logLevel) {
		return HystrixFeign.builder().encoder(encoder).decoder(decoder).logger(logger).logLevel(logLevel)
				//options添加Feign请求响应超时时间
				.options(new Options(60 * 1000, 60 * 1000)).retryer(Retryer.NEVER_RETRY)
				.setterFactory(new SetterFactory() {
					@Override
					public Setter create(Target<?> target, Method method) {
						//添加Hstrix请求响应超时时间
						return HystrixCommand.Setter
								.withGroupKey(HystrixCommandGroupKey.Factory.asKey(apiType.getClass().getSimpleName()))
								.andCommandPropertiesDefaults(
										HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(60 * 1000) // 超时配置
						);
					}
				}).target(apiType, url, fallback);
	}

	/**
	 * 获取HystrixFeign。缓存有在缓存取,缓存没有重新构建Feign
	 * @param apiType 使用feign访问的接口类,如MedBodyClient.class
	 * @param clientName clientName 配置文件中的ribbon client名字
	 * @param separator 添加网址分割
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static <T> T getCacheFeign(Class<T> apiType, String clientName, T fallback, String separator) {
		
		String resultAddress = getResultAddress(clientName);

		String cacheKey = apiType.getName() + "-" + clientName + "-" + fallback.getClass().getName() + "-"
				+ resultAddress + separator;
		Object cacheFeign = cacheFeignMap.get(cacheKey);
		if (cacheFeign == null) {
			T buildFeign = buildHystrixFeign(apiType, fallback, resultAddress + separator);
			cacheFeignMap.put(cacheKey, buildFeign);
			return buildFeign;
		} else {
			return (T) cacheFeign;
		}
	}
	/**
	 * 获取服务地址,取不到最新地址在缓存取旧地址,有新地址则返回新地址并刷新缓存
	 * @param clientName
 	 * @return
 	 */
	public static String getResultAddress(String clientName) {
		String recentAddress = null;
		AlanServiceAddress alanServiceAddress = AlanServiceAddressSelector.selectOne(clientName);
		recentAddress = alanServiceAddress == null ? "" : alanServiceAddress.toString();
		String cacheAddress = cacheAddressMap.get(clientName);
		String resultAddress = "";

		if (StringUtils.isBlank(recentAddress)) {
			if (StringUtils.isBlank(cacheAddress)) {
				log.error("服务" + clientName + "无可用地址");
				throw new RuntimeException("服务" + clientName + "无可用地址");
			} else {
				resultAddress = cacheAddress;
			}
		} else {
			resultAddress = recentAddress;
			cacheAddressMap.put(clientName, recentAddress);
		}
		return resultAddress;
	}
}

通用Feign接口

利用Feign继承特性,特殊需求接口只要继承通用接口就可获得访问通用接口的能力.

import java.util.Map;
import com.xxx.commons.base.ResultJsonEntity;
import feign.Headers;
import feign.Param;
import feign.RequestLine;

/**
 * @ClassName: BaseFeignClient
 * @Description: Feign基类
 * @author SuXun
 * @date 2018年3月5日 下午1:25:59
 */
// @Herders里边的键值对冒号后面必须有个空格!
@Headers({ "Content-Type: application/json", "Accept: application/json" })
public interface BaseFeignClient {

	@RequestLine("POST /select")
	ResultJsonEntity select(Object obj);

	@RequestLine("GET /selectAll")
	ResultJsonEntity selectAll();

	// 因为Example无法被序列化成json,所以参数为Map
	@RequestLine("POST /selectByExample")
	ResultJsonEntity selectByExample(Map<String, Object> map);

	@RequestLine("GET /selectByPrimaryKey/{key}")
	ResultJsonEntity selectByPrimaryKey(@Param("key") String key);

	@RequestLine("POST /insertSelective")
	ResultJsonEntity insertSelective(Object obj);

	@RequestLine("POST /updateByPrimaryKeySelective")
	ResultJsonEntity updateByPrimaryKeySelective(Object obj);

	/**
	 * @param map key包含:int pageNum,int rowNum,T record和查询条件
	 * @return
	 */
	@RequestLine("POST /getPageExampleList")
	ResultJsonEntity getPageExampleList(Map<String, Object> map);

}

通用回退类

因为使用HystrixClient.build(),使得Feign拥有熔断器、回退的功能。这里根据通用接口实现的回退类。
这里的ResultJsonEntity、ResultEnum、ResultJsonUtil用于返回平台无关的json数据。

import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.xxx.commons.base.ResultJsonEntity;
import com.xxx.commons.enums.ResultEnum;
import com.xxx.commons.util.ResultJsonUtil;

/**
 * @ClassName: BaseFeignClientFallback
 * @Description:  Feign基类的回退类
 * @author SuXun
 * @date 2018年3月5日 下午5:04:13
 */
public class BaseFeignClientFallback implements BaseFeignClient {

	protected static final Logger LOG = LoggerFactory.getLogger(BaseFeignClientFallback.class);

	@Override
	public ResultJsonEntity select(Object obj) {
		LOG.error("{} select 出错 进入熔断 ", this.getClass().getName());
		return ResultJsonUtil.returnResult(ResultEnum.FAIL);
	}

	@Override
	public ResultJsonEntity selectAll() {
		LOG.error("{} selectAll 出错 进入熔断", this.getClass().getName());
		return ResultJsonUtil.returnResult(ResultEnum.FAIL);
	}

	@Override
	public ResultJsonEntity selectByExample(Map<String, Object> map) {
		LOG.error("{} selectByMap 出错 进入熔断 ", this.getClass().getName());
		return ResultJsonUtil.returnResult(ResultEnum.FAIL);
	}

	@Override
	public ResultJsonEntity selectByPrimaryKey(String key) {
		LOG.error("{} selectByPrimaryKey 出错 进入熔断 ", this.getClass().getName());
		return ResultJsonUtil.returnResult(ResultEnum.FAIL);
	}

	@Override
	public ResultJsonEntity insertSelective(Object obj) {
		LOG.error("{} insertSelective 出错 进入熔断 ", this.getClass().getName());
		return ResultJsonUtil.returnResult(ResultEnum.FAIL);
	}

	@Override
	public ResultJsonEntity updateByPrimaryKeySelective(Object obj) {
		LOG.error("{} updateByPrimaryKeySelective 出错 进入熔断 ", this.getClass().getName());
		return ResultJsonUtil.returnResult(ResultEnum.FAIL);
	}

	@Override
	public ResultJsonEntity getPageExampleList(Map<String, Object> map) {
		LOG.error("{} getPageExampleList 出错 进入熔断 ", this.getClass().getName());
		return ResultJsonUtil.returnResult(ResultEnum.FAIL);
	}
}

FeignClient使用方法

为了使Feign拥有负载均衡的能力,需要在 @ModelAttribute 注解的方法重复调用 getCacheFeigngetCacheFeign 方法可以获取最新的地址,根据地址构建Feign或者在缓存取出Feign。

private BaseFeignClient xxxClient = null;
@ModelAttribute
public xxxEntity get(@RequestParam(required = false) String id) {
	xxxClient = BaseFeignBuilder.getCacheFeign(BaseFeignClient.class,
			"xxx-service", new BaseFeignClientFallback(), "xxx");
}

遗憾

非常遗憾,这里是手动获取Eureka中的地址,看起来还不够优雅。
Feign可以结合Ribbon使用,通过传入服务名找地址,之前实现后发现需要两次访问才可以正确访问到服务,两次中必然有一次返回找不到地址,所以没有使用。我会继续研究,下次更新使Feign进行Http调用更优雅一些。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM