SSM集成Eureka、Feign调用可以实现连接到Eureka服务、通过Feign调用微服务中注册的各个服务。
该方式以自定义注解标注在调用接口上、通过扫描特定包下的类生成接口代理对象,实现请求调用
以原生的feign为基础、参考openfeign、构建可解析json(fastjson)和form表单、可扩展请求拦截器、可配置重试策略、可以项目名调用(项目对应名称需在配置文件中配置)、可解析Spring注解、带有熔断、负载均衡功能的feign组件。
引入pom依赖
spring版本要高于4.3.6.RELEASE 低版本缺少部分注解 该版本支持jdk7
xml
<!-- eureka 服务发现 -->
<dependency>
<groupId>com.netflix.eureka</groupId>
<artifactId>eureka-client</artifactId>
<version>1.5.0</version>
</dependency>
<!-- Ribbon 负载均衡 -->
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon-core</artifactId>
<version>2.1.0</version>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon-loadbalancer</artifactId>
<version>2.1.0</version>
<exclusions>
<exclusion>
<groupId>io.reactivex</groupId>
<artifactId>rxjava</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon-eureka</artifactId>
<version>2.1.0</version>
</dependency>
<!-- Feign 包装http请求 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-hystrix</artifactId>
<version>9.7.0</version>
<!-- <version>10.1.0</version>-->
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-ribbon</artifactId>
<version>9.7.0</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-slf4j</artifactId>
<version>9.7.0</version>
</dependency>
<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form-spring</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>io.reactivex</groupId>
<artifactId>rxjava</artifactId>
<version>1.1.1</version>
</dependency>自定义注解
java
/**
* 自定义注解,来区分Feign接口并且指定调用的服务Url
* @Auther: wangxp
* @Date: 2020/4/28
* @Description: com.commnetsoft.common.feign
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface FeignClient {
/**
* 调用的服务地址 需与ribbon配置文件中对应所需地址的前缀名保持一致
* @return
*/
String name();
/**
* 用于指定调用的具体地址
* @return
*/
String url() default "";
/**
* 用于指定调用的地址前缀需要加"/",
* 如果是动态改变值 ,使用"{"+name+"}"包裹,并在servletContext中存入name对应的值
* servletContext可通过MicroUtils获取
* @return
*/
String path() default "";
/**
* 容错处理类,处理类型应实现对应的接口。默认为不处理
* @return
*/
Class<?> fallback() default void.class;
/**
* 单个feign自定义配置类,通过实现重写FeignConfiguration方法返回指定配置,返回null则使用默认配置
* @return:
* @date: 2020/7/27 16:26
*/
Class<? extends FeignConfiguration> configuration() default FeignConfiguration.class;
}连接Eureka、并生成feign代理对象
java
/**
* 生成Feign代理并注册到Spring实现类
* @Auther: wangxp
* @Date: 2020/4/28
* @Description: com.commnetsoft.common.feign
*/
@Component
public class FeignClientRegister implements BeanFactoryPostProcessor {
// 配置文件的路径信息
private static final String RIBBON_CONFIG_FILE_NAME = "application.properties";
//mvc注解解析
private SpringMvcContract springMvcContract = new SpringMvcContract();
//拦截器,添加鉴权的请求头信息
private FeignRequestInterceptor feignRequestInterceptor = new FeignRequestInterceptor();
//feign的fastjson解析工具
private FeignEncoder feignEncoder = new FeignEncoder();
private FastJsonDecoder fastJsonDecoder = new FastJsonDecoder();
// feign的form解析
// private FormEncoder formEncoder = new FormEncoder();
//日志级别
private Logger.Level level = Logger.Level.FULL;
//指定连接超时时长及响应超时时长。
private Request.Options options = new Request.Options(10*1000, 10*1000);
//指定重试策略
// private Retryer retryer = new Retryer.Default(5000, 5000, 1);
private Retryer retryer = Retryer.NEVER_RETRY;
// feign通过client名称解析指定地址并可解析自定义前缀。
private FeignRibbonClient feignRibbonClient = new FeignRibbonClient();
// 请求头
private String HTTP_HEAD = "http://";
//配置文件对象
private Properties properties = MicroUtils.getProperties();
// 配置文件中feign路径对应的名称
private final String FEIGN_PATH = "feign.client.scan.path";
// 多个feign包路径之间的分隔符
private final String FEIGN_REGEX = ";";
// 获取所有类类型字符
private final String CLASS_RESOURCE = "/**/*.class";
// 默认的回退类型
private final String DEFAULT_FALLBACK = "void";
// Resource查找器,查找类路径下中的资源
private final PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
private final SimpleMetadataReaderFactory register = new SimpleMetadataReaderFactory();
private final StandardEnvironment environment = new StandardEnvironment();
private static RoundRobinRule chooseRule = new RoundRobinRule();
private final static org.slf4j.Logger logger = LoggerFactory.getLogger(FeignClientRegister.class);
public FeignClientRegister(){
initRibbonAndEureka();
}
/**
* 初始化ribbon和Eureka
* @Params:[]
* @Return: void
**/
private void initRibbonAndEureka(){
logger.info("开始初始化ribbon");
try {
// 加载ribbon和eureka配置文件
ConfigurationManager.loadPropertiesFromResources(RIBBON_CONFIG_FILE_NAME);
} catch (IOException e) {
e.printStackTrace();
logger.error("ribbon初始化失败");
throw new IllegalStateException("ribbon初始化失败");
}
logger.info("ribbon初始化完成");
// 初始化Eureka Client
DiscoveryManager.getInstance().initComponent(new MyDataCenterInstanceConfig(), new DefaultEurekaClientConfig());
logger.info("eureka初始化完成");
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
//获取feign的配置文件
//获取要扫描的特定包路径
String scanPath = properties.getProperty(FEIGN_PATH);
if (scanPath == null || scanPath.isEmpty()) {
throw new RuntimeException("feign.client.scan.path配置未找到,请在resources/feign/application.properties中添加该配置.");
}
String[] split = scanPath.split(FEIGN_REGEX);
//获取服务地址信息,设置负载均衡策略
//设置zuul为默认、包含其他另添加
@SuppressWarnings("rawtypes")
DynamicServerListLoadBalancer lb = (DynamicServerListLoadBalancer) ClientFactory
.getNamedLoadBalancer("zuul");
chooseRule.choose(lb, null);
for (String string : split) {
Resource[] resources = getClazzFromAnnotation(string);
if (resources != null && resources.length != 0) {
for (int i = 0; i < resources.length; i++) {
Resource resource = resources[i];
MetadataReader metadataReader = null;
try {
//读取资源 根据class资源创建MetadataReader
metadataReader = register.getMetadataReader(resource);
} catch (IOException e) {
logger.error("读取不到该资源的信息"+resource.getFilename());
continue;
}
//读取资源的注解配置
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
//判断是否包含注解
if (!annotationMetadata.hasAnnotation(FeignClient.class.getName())){
continue;
}
//类信息
ClassMetadata classMetadata = metadataReader.getClassMetadata();
try {
//加载类获取注解实例
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Class<Object> api = (Class<Object>) Class.forName(classMetadata.getClassName());
FeignClient annotation = api.getAnnotation(FeignClient.class);
// 获取注解信息
String name = annotation.name();
String url = annotation.url();
String path = annotation.path();
Class<?> fallback = annotation.fallback();
Class<? extends FeignConfiguration> configuration = annotation.configuration();
Object newInstance = null;
if (!(fallback.getSimpleName().equals(DEFAULT_FALLBACK))) {
newInstance = fallback.newInstance();
}
// 当不为zuul时将不为zuul的加载
if (!name.equals("zuul")){
//获取服务地址信息,设置负载均衡策略
lb = (DynamicServerListLoadBalancer) ClientFactory
.getNamedLoadBalancer(name);
chooseRule.choose(lb, null);
}
//构建HystrixFeign并放入spring容器
Object hystrixFeign;
feign.hystrix.HystrixFeign.Builder hystrixFeignBuilder = getHystrixFeignBuilder(api,configuration);
if (StringUtils.isNotBlank(url)){
hystrixFeign = hystrixFeignBuilder.target(api, HTTP_HEAD+url+path,newInstance);
}else {
hystrixFeign = hystrixFeignBuilder.client(feignRibbonClient) //加入负载均衡
.target(api, HTTP_HEAD+name+path,newInstance);
}
beanFactory.registerSingleton(api.getName(), hystrixFeign);
} catch (ClassNotFoundException e) {
logger.error(e.toString());
// e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
logger.error(e.toString());
// e.printStackTrace();
} catch (IllegalAccessException e) {
logger.error(e.toString());
// TODO Auto-generated catch block
// e.printStackTrace();
}
}
}
}
}
/**
* 对HystrixFeignBuilder进行配置
* @param:
* @return:
* @auther:
* @date: 2020/5/15 15:24
*/
private feign.hystrix.HystrixFeign.Builder getHystrixFeignBuilder(final Class<?> feignApi,Class<? extends FeignConfiguration> configuration) throws IllegalAccessException, InstantiationException {
if (configuration.getSimpleName().equals("FeignConfiguration")){
return buildDefault(feignApi);
}else {
return buildCustom(feignApi,configuration);
}
}
/**
* 构建默认配置
* @Params:[feignApi]
* @Return: feign.hystrix.HystrixFeign.Builder
**/
private feign.hystrix.HystrixFeign.Builder buildDefault(final Class<?> feignApi){
return HystrixFeign.builder()
.contract(springMvcContract) //mvc注解解析
.requestInterceptor(feignRequestInterceptor) //拦截器,添加鉴权的请求头信息
.encoder(feignEncoder)
.decoder(fastJsonDecoder) //feign的fastjson解析工具
.logger(new Slf4jLogger(feignApi)).logLevel(level) //日志级别
.options(options) //指定连接超时时长及响应超时时长。
.retryer(retryer) //指定重试策略
.setterFactory(new SetterFactory() {
@Override
public HystrixCommand.Setter create(Target<?> target, Method method) {
//添加Hstrix请求响应超时时间
return HystrixCommand.Setter
.withGroupKey(HystrixCommandGroupKey.Factory.asKey(feignApi.getSimpleName()))
.andCommandPropertiesDefaults(
HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(10 * 1000)
//修改隔离策略 不使用异步调用
.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)
);
}
});
}
/**
* 构建自定义的feign配置
* @Params:[feignApi, configuration]
* @Return: feign.hystrix.HystrixFeign.Builder
**/
private feign.hystrix.HystrixFeign.Builder buildCustom(final Class<?> feignApi,Class<? extends FeignConfiguration> configuration) throws IllegalAccessException, InstantiationException{
FeignConfiguration feignConfiguration = configuration.newInstance();
Retryer retryerConfig = feignConfiguration.getRetryer();
Request.Options optionsConfig = feignConfiguration.getOptions();
Logger logger = feignConfiguration.getLogger();
Decoder decoder = feignConfiguration.getDecoder();
Contract contract = feignConfiguration.getContract();
Encoder encoder = feignConfiguration.getEncoder();
RequestInterceptor requestInterceptor = feignConfiguration.getRequestInterceptor();
return HystrixFeign.builder()
.contract((contract != null)? contract : springMvcContract) //mvc注解解析
.requestInterceptor((requestInterceptor != null)? requestInterceptor : feignRequestInterceptor) //拦截器,添加鉴权的请求头信息
.encoder((encoder != null) ? encoder : feignEncoder)
.decoder((decoder != null) ? decoder : fastJsonDecoder) //feign的fastjson解析工具
.logger((logger != null) ? logger : new Slf4jLogger(feignApi)).logLevel(level) //日志级别
.options((optionsConfig != null) ? optionsConfig : options) //指定连接超时时长及响应超时时长。
.retryer((retryerConfig != null) ? retryerConfig : retryer) //指定重试策略
.setterFactory(new SetterFactory() {
@Override
public HystrixCommand.Setter create(Target<?> target, Method method) {
//添加Hstrix请求响应超时时间
return HystrixCommand.Setter
.withGroupKey(HystrixCommandGroupKey.Factory.asKey(feignApi.getSimpleName()))
.andCommandPropertiesDefaults(
HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(10 * 1000)
.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)
);
}
});
}
/**
* 通过传入路径信息获取该路径下的所有资源
* @param: packagePath 传入的路径
* @return:
* @auther:
* @date: 2020/5/15 14:45
*/
public Resource[] getClazzFromAnnotation(String packagePath) {
String pathPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ ClassUtils.convertClassNameToResourcePath(environment.resolveRequiredPlaceholders(packagePath))
+ CLASS_RESOURCE;
try {
//加载路径
return resolver.getResources(pathPackage);
} catch (IOException e) {
//异常处理
logger.error("未找到" + packagePath + "的路径信息");
return null;
}
}
}单个feign的配置类。如单独配置需在注解中标名
java
/**
* feign组件的自定义配置类,方法返回null则使用改返回类型的默认配置
* @ClassName: FeignConfiguration
* @Description: TODO
* @Auther:
* @Date: 2020/7/27
* @Description: com.commnetsoft.microcore.config
*/
public class FeignConfiguration {
/**
* 注解解析
* @return:
* @date: 2020/7/27 16:07
*/
public Contract getContract(){ return null;}
/**
* 拦截器,可添加鉴权的请求头信息
* @return:
* @date: 2020/7/27 16:07
*/
public RequestInterceptor getRequestInterceptor(){return null;}
/**
* 请求和相应参数的解析器
* @return:
* @date: 2020/7/27 16:07
*/
public Encoder getEncoder(){return null;}
public Decoder getDecoder(){return null;}
/**
* 日志级别
* @return:
* @date: 2020/7/27 16:08
*/
public Logger getLogger(){return null;}
/**
* 配置连接超时时长及响应超时时长。
* @return:
* @date: 2020/7/27 16:08
*/
public Request.Options getOptions(){return null;}
/**
* 配置重试策略
* @return:
* @date: 2020/7/27 16:08
*/
public Retryer getRetryer(){return null;}
}feign配置
获取数据解码器
使用fastjson 将请求的返回数据转换为对象。还可对返回结果校验,不成功则抛出异常
java
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import feign.FeignException;
import feign.Response;
import feign.Util;
import feign.codec.DecodeException;
import feign.codec.Decoder;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.lang.reflect.Type;
import static feign.Util.ensureClosed;
/**
* 自定义fastjson的解码器,对获取到的json格式数据进行转换
*/
public class FastJsonDecoder implements Decoder {
// static {
// JSON.DEFFAULT_DATE_FORMAT="yyyy-MM-dd HH:mm:ss";
// }
@Override
public Object decode(Response response, Type type) throws IOException, DecodeException, FeignException {
if (response.status() == 404) {
return Util.emptyValueOf(type);
}
if (response.body() == null) {
return null;
}
Reader reader = response.body().asReader();
if (!reader.markSupported()) {
reader =new BufferedReader(reader);
}
String jsonString = "";
try {
// Read the first byte to see if we have any data
reader.mark(1);
if (reader.read() == -1) {
return null; // Eagerly returning null avoids "No content to map due to end-of-input"
}
reader.reset();
BufferedReader bufferedReader = (BufferedReader) reader;
StringBuffer buffer = new StringBuffer();
String line = "";
while ((line = bufferedReader.readLine()) != null){
buffer.append(line);
}
jsonString = buffer.toString();
JSONObject jsonObject = JSON.parseObject(jsonString);
Object code = jsonObject.get("code");
if (code == null || (!code.equals("success"))){
Object desc = jsonObject.get("desc");
throw new RuntimeException(jsonObject.get("message")+";"+((desc == null)?"":desc));
}
return jsonObject.toJavaObject(type);
} catch (JSONException e) {
if (e.getCause() != null && e.getCause() instanceof IOException) {
throw IOException.class.cast(e.getCause());
}
throw e;
} catch (RuntimeException e) {
throw e;
}finally {
ensureClosed(reader);
}
}
}编码器 json、form
编码器。对请求发送的数据进行转换。可转换为json(fastjson)和form表单提交的文件类型对象。
java
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import feign.FeignException;
import feign.Response;
import feign.Util;
import feign.codec.DecodeException;
import feign.codec.Decoder;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.lang.reflect.Type;
import static feign.Util.ensureClosed;
/**
* 自定义fastjson的解码器,对获取到的json格式数据进行转换
*/
public class FastJsonDecoder implements Decoder {
// static {
// JSON.DEFFAULT_DATE_FORMAT="yyyy-MM-dd HH:mm:ss";
// }
@Override
public Object decode(Response response, Type type) throws IOException, DecodeException, FeignException {
if (response.status() == 404) {
return Util.emptyValueOf(type);
}
if (response.body() == null) {
return null;
}
Reader reader = response.body().asReader();
if (!reader.markSupported()) {
reader =new BufferedReader(reader);
}
String jsonString = "";
try {
// Read the first byte to see if we have any data
reader.mark(1);
if (reader.read() == -1) {
return null; // Eagerly returning null avoids "No content to map due to end-of-input"
}
reader.reset();
BufferedReader bufferedReader = (BufferedReader) reader;
StringBuffer buffer = new StringBuffer();
String line = "";
while ((line = bufferedReader.readLine()) != null){
buffer.append(line);
}
jsonString = buffer.toString();
JSONObject jsonObject = JSON.parseObject(jsonString);
Object code = jsonObject.get("code");
if (code == null || (!code.equals("success"))){
Object desc = jsonObject.get("desc");
throw new RuntimeException(jsonObject.get("message")+";"+((desc == null)?"":desc));
}
return jsonObject.toJavaObject(type);
} catch (JSONException e) {
if (e.getCause() != null && e.getCause() instanceof IOException) {
throw IOException.class.cast(e.getCause());
}
throw e;
} catch (RuntimeException e) {
throw e;
}finally {
ensureClosed(reader);
}
}
}请求拦截器
可在请求中添加特定请求头信息(用于鉴权)
java
import com.commnetsoft.MicroservicePropertyEnum;
import com.commnetsoft.pub.util.common.MD5;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.slf4j.LoggerFactory;
/**
* @ClassName: FeignRequestInterceptor
* @Description: TODO
* @Auther:
* @Date: 2020/6/11
* @Description: com.commnetsoft.microcore.config
*/
public class FeignRequestInterceptor implements RequestInterceptor {
private final static org.slf4j.Logger logger = LoggerFactory.getLogger(FeignRequestInterceptor.class);
@Override
public void apply(RequestTemplate template) {
String HEARD = MicroservicePropertyEnum.MICR_CONFIG_FEIGN_KEY.getValue();
String authValue = MicroservicePropertyEnum.MICR_CONFIG_FEIGN_AUTH.getValue();
String heardValue;
logger.debug("feign鉴权信息:HEARD:{},AUTHVALUE:{}",HEARD, authValue);
heardValue = MD5.MD5Encode(authValue);
template.header(HEARD,heardValue);
}
}调用客户端处理
请求调用前的处理。可对自定义注解中的特定格式的请求前缀进行替换
{***}
java
/**
* feign调用前判断是否为动态前缀,若为动态前缀,则通过动态前缀的值获取session中存储的值并替换。
* @ClassName: FeignRibbonClient
* @Description: TODO
* @Auther:
* @Date: 2020/7/31
* @Description: com.commnetsoft.microcore.config.feignconfig
*/
import com.commnetsoft.microcore.util.MicroUtils;
import feign.Client;
import feign.Request;
import feign.Response;
import feign.ribbon.RibbonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSocketFactory;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class FeignRibbonClient extends RibbonClient {
public FeignRibbonClient(){
super((Client)(new Default((SSLSocketFactory)null, (HostnameVerifier)null)));
}
private final static Logger logger = LoggerFactory.getLogger(FeignRibbonClient.class);
@Override
public Response execute(Request request, Request.Options options) throws IOException {
String url = request.url();
if (url.contains("{")){
// 正则获取对应符合规则字符串,通过该字符串获取servletContext中存储的对应的值,替换
String regex = "\\{[0-9a-zA-Z_$]*\\}";
Pattern patten = Pattern.compile(regex);//编译正则表达式
Matcher matcher = patten.matcher(url);// 指定要匹配的字符串
matcher.find();
String group = matcher.group();
HttpServletRequest request1 = MicroUtils.getRequest();
HttpSession session = request1.getSession();
String attribute = (String)session.getAttribute(group.substring(1, group.length() - 1));
if (attribute == null){
logger.error("url地址中获取的"+group+"未在session中未设置值");
}
String permit = url.replace(group, attribute);
request = Request.create(request.method(),permit,request.headers(),request.body(),request.charset());
}
return super.execute(request, options);
}
}feign注解解析器
可实现对springmvc注解的解析。同时继承默认的default注解。默认注解仍可使用
java
import com.commnetsoft.microcore.config.springfeign.AnnotatedParameterProcessor;
import com.commnetsoft.microcore.config.springfeign.PathVariableParameterProcessor;
import com.commnetsoft.microcore.config.springfeign.RequestHeaderParameterProcessor;
import com.commnetsoft.microcore.config.springfeign.RequestParamParameterProcessor;
import feign.*;
import feign.Param.Expander;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.*;
/**
* 解析springmvc注解的contract 使feign接口在远程调用的时候可以使用mvc注解。微服务管理可以直接继承serviceapi中的调用接口使用
* 类继承feign默认的Default注解 两种都可实现
* @ClassName: SpringMvcContract
* @Description: TODO
* @Auther:
* @Date: 2020/6/17
* @Description: com.commnetsoft.microcore.config.springfeign
*/
public class SpringMvcContract extends Contract.Default implements ResourceLoaderAware {
private static final String ACCEPT = "Accept";
private static final String CONTENT_TYPE = "Content-Type";
private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer(); //参数名解析
private final Map<Class<? extends Annotation>, AnnotatedParameterProcessor> annotatedArgumentProcessors; //key为注解类型,value为处理对象
private final Map<String, Method> processedMethods; //加工;审核;处理(数据) 方法
private final ConversionService conversionService; //类型转换
private final Expander expander; //自定义扩展
private ResourceLoader resourceLoader; //资源访问加载 路径
public SpringMvcContract() {
this(Collections.<AnnotatedParameterProcessor>emptyList());
}
public SpringMvcContract(List<AnnotatedParameterProcessor> annotatedParameterProcessors) {
this(annotatedParameterProcessors, new DefaultConversionService());
}
public SpringMvcContract(List<AnnotatedParameterProcessor> annotatedParameterProcessors, ConversionService conversionService) {
this.processedMethods = new HashMap();
this.resourceLoader = new DefaultResourceLoader();
Assert.notNull(annotatedParameterProcessors, "Parameter processors can not be null.");
Assert.notNull(conversionService, "ConversionService can not be null.");
Object processors;
if (!annotatedParameterProcessors.isEmpty()) {
processors = new ArrayList(annotatedParameterProcessors);
} else {
processors = this.getDefaultAnnotatedArgumentsProcessors();
}
this.annotatedArgumentProcessors = this.toAnnotatedArgumentProcessorMap((List)processors);
this.conversionService = conversionService;
this.expander = new SpringMvcContract.ConvertingExpander(conversionService);
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
/**
* 重写的解析入口方法,调用默认的解析方式 后判断是否包含requestMapping注解
* @return:
* @date: 2020/6/17 15:56
*/
@Override
public MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
this.processedMethods.put(Feign.configKey(targetType, method), method);
MethodMetadata md = super.parseAndValidateMetadata(targetType, method);
RequestMapping classAnnotation = (RequestMapping)AnnotatedElementUtils.findMergedAnnotation(targetType, RequestMapping.class);
if (classAnnotation != null) {
if (!md.template().headers().containsKey(ACCEPT)) {
this.parseProduces(md, method, classAnnotation);
}
if (!md.template().headers().containsKey(CONTENT_TYPE)) {
this.parseConsumes(md, method, classAnnotation);
}
this.parseHeaders(md, method, classAnnotation);
}
return md;
}
/**
* 重写解析类上的注解,先解析Requestmapping,然后调用默认的注解解析
* RequestMapping只解析没有父类的接口类,原生的heard继承
* @return:
* @date: 2020/6/17 16:01
*/
@Override
protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
if (clz.getInterfaces().length == 0) {
RequestMapping classAnnotation = (RequestMapping)AnnotatedElementUtils.findMergedAnnotation(clz, RequestMapping.class);
if (classAnnotation != null && classAnnotation.value().length > 0) {
String pathValue = Util.emptyToNull(classAnnotation.value()[0]);
pathValue = this.resolve(pathValue);
if (!pathValue.startsWith("/")) {
pathValue = "/" + pathValue;
}
data.template().insert(0, pathValue);
}
}
super.processAnnotationOnClass(data,clz);
}
/**
* 重写解析方法上的注解先解析是否存在requMapping注解及其子注解,不存在则调用默认方式解析
* @return:
* @date: 2020/6/17 16:09
*/
@Override
protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) {
if (RequestMapping.class.isInstance(methodAnnotation) || methodAnnotation.annotationType().isAnnotationPresent(RequestMapping.class)) {
RequestMapping methodMapping = (RequestMapping)AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
RequestMethod[] methods = methodMapping.method();
if (methods.length == 0) {
methods = new RequestMethod[]{RequestMethod.GET};
}
this.checkOne(method, methods, "method");
data.template().method(methods[0].name());
this.checkAtMostOne(method, methodMapping.value(), "value");
if (methodMapping.value().length > 0) {
String pathValue = Util.emptyToNull(methodMapping.value()[0]);
if (pathValue != null) {
pathValue = this.resolve(pathValue);
if (!pathValue.startsWith("/") && !data.template().toString().endsWith("/")) {
pathValue = "/" + pathValue;
}
data.template().append(pathValue);
}
}
this.parseProduces(data, method, methodMapping);
this.parseConsumes(data, method, methodMapping);
this.parseHeaders(data, method, methodMapping);
data.indexToExpander(new LinkedHashMap());
}else{
super.processAnnotationOnMethod(data, methodAnnotation, method);
}
}
private String resolve(String value) {
return StringUtils.hasText(value) && this.resourceLoader instanceof ConfigurableApplicationContext ? ((ConfigurableApplicationContext)this.resourceLoader).getEnvironment().resolvePlaceholders(value) : value;
}
private void checkAtMostOne(Method method, Object[] values, String fieldName) {
Util.checkState(values != null && (values.length == 0 || values.length == 1), "Method %s can only contain at most 1 %s field. Found: %s", new Object[]{method.getName(), fieldName, values == null ? null : Arrays.asList(values)});
}
private void checkOne(Method method, Object[] values, String fieldName) {
Util.checkState(values != null && values.length == 1, "Method %s can only contain 1 %s field. Found: %s", new Object[]{method.getName(), fieldName, values == null ? null : Arrays.asList(values)});
}
/**
* 对参数上的注解进行解析,先解析包含在map中的注解,不存在则调用默认方式解析
* @return:
* @date: 2020/6/17 16:26
*/
@Override
protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) {
boolean isHttpAnnotation = false;
AnnotatedParameterProcessor.AnnotatedParameterContext context = (AnnotatedParameterProcessor.AnnotatedParameterContext) new SimpleAnnotatedParameterContext(data, paramIndex);
Method method = (Method)this.processedMethods.get(data.configKey());
Annotation[] var7 = annotations;
int var8 = annotations.length;
boolean temp = false;
for(int var9 = 0; var9 < var8; ++var9) {
Annotation parameterAnnotation = var7[var9];
AnnotatedParameterProcessor processor = (AnnotatedParameterProcessor)this.annotatedArgumentProcessors.get(parameterAnnotation.annotationType());
if (parameterAnnotation.annotationType() == RequestBody.class){
data.template().header(CONTENT_TYPE, "application/json");
}
if (processor != null) {
temp = true;
Annotation processParameterAnnotation = this.synthesizeWithMethodParameterNameAsFallbackValue(parameterAnnotation, method, paramIndex);
isHttpAnnotation |= processor.processArgument(context, processParameterAnnotation, method);
}else{
Class<? extends Annotation> annotationType = parameterAnnotation.annotationType();
if (annotationType == Param.class) {
Param paramAnnotation = (Param)parameterAnnotation;
String name = paramAnnotation.value();
Util.checkState(Util.emptyToNull(name) != null, "Param annotation was empty on param %s.", new Object[]{paramIndex});
this.nameParam(data, name, paramIndex);
Class<? extends Expander> expander = paramAnnotation.expander();
if (expander != Param.ToStringExpander.class) {
data.indexToExpanderClass().put(paramIndex, expander);
}
data.indexToEncoded().put(paramIndex, paramAnnotation.encoded());
isHttpAnnotation = true;
String varName = '{' + name + '}';
if (!data.template().url().contains(varName) && !searchMapValuesContainsSubstring(data.template().queries(), varName) && !searchMapValuesContainsSubstring(data.template().headers(), varName)) {
data.formParams().add(name);
}
} else if (annotationType == QueryMap.class) {
Util.checkState(data.queryMapIndex() == null, "QueryMap annotation was present on multiple parameters.", new Object[0]);
data.queryMapIndex(paramIndex);
data.queryMapEncoded(((QueryMap)QueryMap.class.cast(parameterAnnotation)).encoded());
isHttpAnnotation = true;
} else if (annotationType == HeaderMap.class) {
Util.checkState(data.headerMapIndex() == null, "HeaderMap annotation was present on multiple parameters.", new Object[0]);
data.headerMapIndex(paramIndex);
isHttpAnnotation = true;
}
}
}
if (temp && isHttpAnnotation && data.indexToExpander().get(paramIndex) == null && this.conversionService.canConvert(method.getParameterTypes()[paramIndex], String.class)) {
data.indexToExpander().put(paramIndex, this.expander);
}
return isHttpAnnotation;
}
/**
* 将requestMapping注解包含的返回类型存入
* @return:
* @date: 2020/6/17 16:32
*/
private void parseProduces(MethodMetadata md, Method method, RequestMapping annotation) {
String[] serverProduces = annotation.produces();
String clientAccepts = serverProduces.length == 0 ? null : Util.emptyToNull(serverProduces[0]);
if (clientAccepts != null) {
md.template().header(ACCEPT, new String[]{clientAccepts});
}
}
/**
* 将requestMapping注解包含的提交类型存入
* @return:
* @date: 2020/6/17 16:32
*/
private void parseConsumes(MethodMetadata md, Method method, RequestMapping annotation) {
String[] serverConsumes = annotation.consumes();
String clientProduces = serverConsumes.length == 0 ? null : Util.emptyToNull(serverConsumes[0]);
if (clientProduces != null) {
md.template().header(CONTENT_TYPE, new String[]{clientProduces});
}
}
/**
* 将requestMapping注解包含的请求头信息存入
* @return:
* @date: 2020/6/17 16:33
*/
private void parseHeaders(MethodMetadata md, Method method, RequestMapping annotation) {
if (annotation.headers() != null && annotation.headers().length > 0) {
String[] var4 = annotation.headers();
int var5 = var4.length;
for(int var6 = 0; var6 < var5; ++var6) {
String header = var4[var6];
int index = header.indexOf(61);
if (!header.contains("!=") && index >= 0) {
md.template().header(this.resolve(header.substring(0, index)), new String[]{this.resolve(header.substring(index + 1).trim())});
}
}
}
}
/**
* 将list集合中的AnnotatedParameterProcessor处理类的注解类型和对象放入map中返回
* @return:
* @date: 2020/6/17 15:51
*/
private Map<Class<? extends Annotation>, AnnotatedParameterProcessor> toAnnotatedArgumentProcessorMap(List<AnnotatedParameterProcessor> processors) {
Map<Class<? extends Annotation>, AnnotatedParameterProcessor> result = new HashMap();
Iterator var3 = processors.iterator();
while(var3.hasNext()) {
AnnotatedParameterProcessor processor = (AnnotatedParameterProcessor)var3.next();
result.put(processor.getAnnotationType(), processor);
}
return result;
}
/**
* 将处理注解类放入list集合中
* @return:
* @date: 2020/6/17 15:39
*/
private List<AnnotatedParameterProcessor> getDefaultAnnotatedArgumentsProcessors() {
List<AnnotatedParameterProcessor> annotatedArgumentResolvers = new ArrayList();
annotatedArgumentResolvers.add(new PathVariableParameterProcessor());
annotatedArgumentResolvers.add(new RequestParamParameterProcessor());
annotatedArgumentResolvers.add(new RequestHeaderParameterProcessor());
return annotatedArgumentResolvers;
}
/**
* 绑定默认值
* @return:
* @date: 2020/6/17 16:36
*/
private Annotation synthesizeWithMethodParameterNameAsFallbackValue(Annotation parameterAnnotation, Method method, int parameterIndex) {
Map<String, Object> annotationAttributes = AnnotationUtils.getAnnotationAttributes(parameterAnnotation);
Object defaultValue = AnnotationUtils.getDefaultValue(parameterAnnotation);
if (defaultValue instanceof String && defaultValue.equals(annotationAttributes.get("value"))) {
Type[] parameterTypes = method.getGenericParameterTypes();
String[] parameterNames = PARAMETER_NAME_DISCOVERER.getParameterNames(method);
if (this.shouldAddParameterName(parameterIndex, parameterTypes, parameterNames)) {
annotationAttributes.put("value", parameterNames[parameterIndex]);
}
}
return AnnotationUtils.synthesizeAnnotation(annotationAttributes, parameterAnnotation.annotationType(), (AnnotatedElement)null);
}
private boolean shouldAddParameterName(int parameterIndex, Type[] parameterTypes, String[] parameterNames) {
return parameterNames != null && parameterNames.length > parameterIndex && parameterTypes != null && parameterTypes.length > parameterIndex;
}
public static class ConvertingExpander implements Expander {
private final ConversionService conversionService;
public ConvertingExpander(ConversionService conversionService) {
this.conversionService = conversionService;
}
@Override
public String expand(Object value) {
return (String)this.conversionService.convert(value, String.class);
}
}
private class SimpleAnnotatedParameterContext implements AnnotatedParameterProcessor.AnnotatedParameterContext {
private final MethodMetadata methodMetadata;
private final int parameterIndex;
public SimpleAnnotatedParameterContext(MethodMetadata methodMetadata, int parameterIndex) {
this.methodMetadata = methodMetadata;
this.parameterIndex = parameterIndex;
}
@Override
public MethodMetadata getMethodMetadata() {
return this.methodMetadata;
}
@Override
public int getParameterIndex() {
return this.parameterIndex;
}
@Override
public void setParameterName(String name) {
SpringMvcContract.this.nameParam(this.methodMetadata, name, this.parameterIndex);
}
@Override
public Collection<String> setTemplateParameter(String name, Collection<String> rest) {
return SpringMvcContract.this.addTemplatedParam(rest, name);
}
}
private static <K, V> boolean searchMapValuesContainsSubstring(Map<K, Collection<String>> map, String search) {
Collection<Collection<String>> values = map.values();
if (values == null) {
return false;
} else {
Iterator var3 = values.iterator();
while(var3.hasNext()) {
Collection<String> entry = (Collection)var3.next();
Iterator var5 = entry.iterator();
while(var5.hasNext()) {
String value = (String)var5.next();
if (value.contains(search)) {
return true;
}
}
}
return false;
}
}
}mvc注解解析处理类
java
import feign.MethodMetadata;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collection;
/**
* 处理参数注解
* @ClassName: AnnotatedParameterProcessor
* @Description: TODO
* @Auther:
* @Date: 2020/6/17
* @Description: com.commnetsoft.microcore.config.springfeign
*/
public interface AnnotatedParameterProcessor {
Class<? extends Annotation> getAnnotationType();
boolean processArgument(AnnotatedParameterProcessor.AnnotatedParameterContext context, Annotation annotation, Method method);
public interface AnnotatedParameterContext {
MethodMetadata getMethodMetadata();
int getParameterIndex();
void setParameterName(String name);
Collection<String> setTemplateParameter(String name, Collection<String> rest);
}
}java
/**
* @ClassName: PathVariableParameterProcessor
* @Description: TODO
* @Auther:
* @Date: 2020/6/17
* @Description: com.commnetsoft.microcore.config.springfeign
*/
import feign.MethodMetadata;
import feign.Util;
import org.springframework.web.bind.annotation.PathVariable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
public class PathVariableParameterProcessor implements AnnotatedParameterProcessor {
private static final Class<PathVariable> ANNOTATION = PathVariable.class;
public PathVariableParameterProcessor() {
}
@Override
public Class<? extends Annotation> getAnnotationType() {
return ANNOTATION;
}
@Override
public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) {
String name = ((PathVariable)ANNOTATION.cast(annotation)).value();
Util.checkState(Util.emptyToNull(name) != null, "PathVariable annotation was empty on param %s.", new Object[]{context.getParameterIndex()});
context.setParameterName(name);
MethodMetadata data = context.getMethodMetadata();
String varName = '{' + name + '}';
if (!data.template().url().contains(varName) && !this.searchMapValues(data.template().queries(), varName) && !this.searchMapValues(data.template().headers(), varName)) {
data.formParams().add(name);
}
return true;
}
private <K, V> boolean searchMapValues(Map<K, Collection<V>> map, V search) {
Collection<Collection<V>> values = map.values();
if (values == null) {
return false;
} else {
Iterator var4 = values.iterator();
Collection entry;
do {
if (!var4.hasNext()) {
return false;
}
entry = (Collection)var4.next();
} while(!entry.contains(search));
return true;
}
}
}java
/**
* @ClassName: RequestHeaderParameterProcessor
* @Description: TODO
* @Auther:
* @Date: 2020/6/17
* @Description: com.commnetsoft.microcore.config.springfeign
*/
import feign.MethodMetadata;
import feign.Util;
import org.springframework.web.bind.annotation.RequestHeader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;
public class RequestHeaderParameterProcessor implements AnnotatedParameterProcessor {
private static final Class<RequestHeader> ANNOTATION = RequestHeader.class;
public RequestHeaderParameterProcessor() {
}
@Override
public Class<? extends Annotation> getAnnotationType() {
return ANNOTATION;
}
@Override
public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) {
int parameterIndex = context.getParameterIndex();
Class<?> parameterType = method.getParameterTypes()[parameterIndex];
MethodMetadata data = context.getMethodMetadata();
if (Map.class.isAssignableFrom(parameterType)) {
Util.checkState(data.headerMapIndex() == null, "Header map can only be present once.", new Object[0]);
data.headerMapIndex(parameterIndex);
return true;
} else {
String name = ((RequestHeader)ANNOTATION.cast(annotation)).value();
Util.checkState(Util.emptyToNull(name) != null, "RequestHeader.value() was empty on parameter %s", new Object[]{parameterIndex});
context.setParameterName(name);
Collection<String> header = context.setTemplateParameter(name, (Collection)data.template().headers().get(name));
data.template().header(name, header);
return true;
}
}
}java
/**
* @ClassName: RequestParamParameterProcessor
* @Description: TODO
* @Auther:
* @Date: 2020/6/17
* @Description: com.commnetsoft.microcore.config.springfeign
*/
import feign.MethodMetadata;
import feign.Util;
import org.springframework.web.bind.annotation.RequestParam;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;
public class RequestParamParameterProcessor implements AnnotatedParameterProcessor {
private static final Class<RequestParam> ANNOTATION = RequestParam.class;
public RequestParamParameterProcessor() {
}
@Override
public Class<? extends Annotation> getAnnotationType() {
return ANNOTATION;
}
@Override
public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) {
int parameterIndex = context.getParameterIndex();
Class<?> parameterType = method.getParameterTypes()[parameterIndex];
MethodMetadata data = context.getMethodMetadata();
if (Map.class.isAssignableFrom(parameterType)) {
Util.checkState(data.queryMapIndex() == null, "Query map can only be present once.", new Object[0]);
data.queryMapIndex(parameterIndex);
return true;
} else {
RequestParam requestParam = (RequestParam)ANNOTATION.cast(annotation);
String name = requestParam.value();
Util.checkState(Util.emptyToNull(name) != null, "RequestParam.value() was empty on parameter %s", new Object[]{parameterIndex});
context.setParameterName(name);
Collection<String> query = context.setTemplateParameter(name, (Collection)data.template().queries().get(name));
data.template().query(name, query);
return true;
}
}
}容器关闭时关闭eureka
java
DiscoveryManager.getInstance().shutdownComponent();工具类
java
import com.commnetsoft.core.web.Pageable;
import com.commnetsoft.core.web.Result;
import feign.FeignException;
import feign.RetryableException;
import org.springframework.util.ResourceUtils;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;
import java.util.Properties;
/**
* @Auther: wangxp
* @Date: 2020/5/5
* @Description: com.commnetsoft.common.util
*/
public class MicroUtils {
// 配置文件路径
private static final String FEIGN_CONFIG_PATH = "classpath:application.properties";
private final static Properties properties = new Properties();
static {
try {
File file = ResourceUtils.getFile(FEIGN_CONFIG_PATH);
properties.load(new FileInputStream(file));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static Properties getProperties(){
return properties;
}
public static Result disposeException(Exception e){
Throwable cause = e.getCause();
if (cause != null){
if (cause.getClass() == FeignException.class){
int status = ((FeignException) cause).status();
return new Result("失败","调用接口错误,状态码为"+status);
}else if(cause.getClass() == RetryableException.class){
return new Result("失败","调用接口错误,状态信息为"+cause.getMessage());
}
else{
String message = cause.getMessage();
return new Result("失败",message);
}
}else{
return new Result("失败",e.getMessage());
}
}
public static List getPageList(List list, Pageable pageable){
int size = list.size();
//集合为空直接返回
if (size == 0){
return list;
}
Integer rows = pageable.getRows();
Integer page = pageable.getPage();
// size大于需要数量时直接返回
if (size >= rows*page ){
if (page ==1){
return list.subList(0,rows);
}else {
return list.subList((page-1)*rows,rows*page);
}
}else{
if(page == 1){
return list.subList(0,size);
}else{
int i = size/rows;
int j = size%rows;
if (j != 0){
if (i ==1){
return list.subList(rows,size);
}else{
return list.subList(i*rows,size);
}
}else{
if (i ==1){
return list.subList(0,size);
}else{
return list.subList((i-1)*rows,size);
}
}
}
}
}
/**
* 获取request对象
* @return:
* @date: 2020/8/12 17:03
*/
public static HttpServletRequest getRequest(){
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
}
/**
* 获取servletcontext对象
* @return:
* @date: 2020/8/12 17:02
*/
public static ServletContext getServletContext(){
WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
return webApplicationContext.getServletContext();
}
/**
* 开启父子线程属性共享
* @return:
* @date: 2020/8/12 17:03
*/
public static void setRequestAttributes(){
RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(),true);
}
}配置文件
properties
#扫描feign组件所在的包的配置
#多个路径之间使用 ; 号隔开
feign.client.scan.path=com.commnetsoft.admin.api
#feign请求携带的鉴权参数
X-Commnet-Manage=commnetsoft.com
#eureka相关配置
# 控制是否注册自身到eureka中
<NolebasePageProperties />
eureka.registration.enabled=false
# 默认为true,以实现更好的基于区域的负载平衡。
eureka.preferSameZone=true
# 是否要使用基于DNS的查找来确定其他eureka服务器
eureka.shouldUseDns=false
# 由于shouldUseDns为false,因此我们使用以下属性来明确指定到eureka服务器的路由(eureka Server地址)
eureka.serviceUrl.default=http://192.168.1.226:9000/eureka/
eureka.decoderName=JacksonJson
# 客户识别此服务的虚拟主机名,这里指的是eureka服务本身
#eureka.vipAddress=XXXplatform
#服务指定应用名,这里指的是eureka服务本身
#eureka.name=XXXlatform
#服务将被识别并将提供请求的端口
#eureka.port=8080
#ribbon配置
# xxx-service对应的微服务名,添加服务需新增改配置
#permit.ribbon.DeploymentContextBasedVipAddresses=PERMIT
#data.ribbon.DeploymentContextBasedVipAddresses=DATA
#config.ribbon.DeploymentContextBasedVipAddresses=CONFIG
zuul.ribbon.DeploymentContextBasedVipAddresses=ZUUL
#user.ribbon.DeploymentContextBasedVipAddresses=USER
#base.ribbon.DeploymentContextBasedVipAddresses=BASE
# 固定写法,xxx-service使用的ribbon负载均衡器
ribbon.NIWSServerListClassName=com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList
# 每分钟更新xxx-service对应服务的可用地址列表
ribbon.ServerListRefreshInterval=60000