Skip to content

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