如何在springcloud中使用feign 和hystrix使用时设置方法上面超时时间

springcloud中在方法中设置超时时间

 

maven依赖


    org.springframework.cloud
    spring-cloud-starter-openfeign
    provided

 

主要包结构

如何在springcloud中使用feign 和hystrix使用时设置方法上面超时时间_第1张图片

annotation:存放相关注解

configuration:用于注入对象到spring容器中

custom:核心自定义对象

holder:缓存相关

相关代码

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD, ElementType.TYPE})
public @interface FeignRequestOption {
    int readTimeout() default 15000;

    int connectionTimeout() default 5000;
}
import cn.bucheng.cloud.feign.holder.RequestOptionHolder;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandKey;
import com.netflix.hystrix.HystrixCommandProperties;
import feign.Feign;
import feign.Target;
import feign.hystrix.SetterFactory;

import java.lang.reflect.Method;

/**
 * @author yinchong
 * @create 2020/5/11 21:10
 * @description
 */
public class CustomHystrixSetterFactory implements SetterFactory {
    @Override
    public HystrixCommand.Setter create(Target target, Method method) {
        String groupKey = target.name();
        String commandKey = Feign.configKey(target.type(), method);
        RequestOptionHolder.Timeout timeout = RequestOptionHolder.getInstance().getMethodTimeout(commandKey);
        return HystrixCommand.Setter
                .withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey))
                .andCommandKey(HystrixCommandKey.Factory.asKey(commandKey))
                .andCommandPropertiesDefaults(
                        HystrixCommandProperties.defaultSetter()
                        .withExecutionTimeoutInMilliseconds(timeout.getReadTimeout())
                );
    }
}

 

import cn.bucheng.cloud.feign.annotation.FeignRequestOption;
import cn.bucheng.cloud.feign.holder.RequestOptionHolder;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AbstractClassTestingTypeFilter;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;

/**
 * @author yinchong
 * @create 2020/5/11 20:31
 * @description
 */
public class FeignConfigLoader implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
    private Environment environment;
    private ResourceLoader resourceLoader;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        Map attributes = metadata.getAnnotationAttributes(EnableFeignClients.class.getCanonicalName());
        if (attributes == null) {
            return;
        }

        ClassPathScanningCandidateComponentProvider scanner = getScanner();
        scanner.setResourceLoader(this.resourceLoader);

        Set basePackages;

        Map attrs = metadata
                .getAnnotationAttributes(EnableFeignClients.class.getName());
        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
                FeignClient.class);
        final Class[] clients = attrs == null ? null
                : (Class[]) attrs.get("clients");
        if (clients == null || clients.length == 0) {
            scanner.addIncludeFilter(annotationTypeFilter);
            basePackages = getBasePackages(metadata);
        } else {
            final Set clientClasses = new HashSet<>();
            basePackages = new HashSet<>();
            for (Class clazz : clients) {
                basePackages.add(ClassUtils.getPackageName(clazz));
                clientClasses.add(clazz.getCanonicalName());
            }
            AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
                @Override
                protected boolean match(ClassMetadata metadata) {
                    String cleaned = metadata.getClassName().replaceAll("\\$", ".");
                    return clientClasses.contains(cleaned);
                }
            };
            scanner.addIncludeFilter(
                    new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
        }

        for (String basePackage : basePackages) {
            Set candidateComponents = scanner
                    .findCandidateComponents(basePackage);
            for (BeanDefinition candidateComponent : candidateComponents) {
                if (candidateComponent instanceof AnnotatedBeanDefinition) {
                    AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                    AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                    Map metadataAttribute = annotationMetadata
                            .getAnnotationAttributes(
                                    FeignClient.class.getCanonicalName());
                    String name = getName(metadataAttribute);
                    String className = annotationMetadata.getClassName();
                    Class clazz = clazz(className);
                    Annotation annotation = clazz.getAnnotation(FeignRequestOption.class);
                    RequestOptionHolder.Timeout timeout = new RequestOptionHolder.Timeout();
                    if (annotation != null) {
                        FeignRequestOption requestOption = (FeignRequestOption) annotation;
                        timeout = timeout(requestOption);
                    }
                    RequestOptionHolder.Timeout maxTimeout = timeout;

                    Method[] methods = clazz.getMethods();
                    if (methods != null) {
                        for (Method method : methods) {
                            FeignRequestOption requestOption = method.getAnnotation(FeignRequestOption.class);
                            if (requestOption != null) {
                                timeout = timeout(requestOption);
                            }
                            if (maxTimeout.getConnectionTimeout() < timeout.getConnectionTimeout()) {
                                maxTimeout.setConnectionTimeout(timeout.getConnectionTimeout());
                            }
                            if (maxTimeout.getReadTimeout() < timeout.getReadTimeout()) {
                                maxTimeout.setReadTimeout(timeout.getReadTimeout());
                            }
                            RequestOptionHolder.getInstance().putMethodTimeout(clazz, method, timeout);
                        }
                    }


                    RequestOptionHolder.getInstance().putNameTimeout(name, maxTimeout);
                }
            }
        }

    }

    String getName(Map attributes) {
        String name = (String) attributes.get("serviceId");
        if (!StringUtils.hasText(name)) {
            name = (String) attributes.get("name");
        }
        if (!StringUtils.hasText(name)) {
            name = (String) attributes.get("value");
        }
        name = resolve(name);
        if (!StringUtils.hasText(name)) {
            return "";
        }

        String host = null;
        try {
            String url;
            if (!name.startsWith("http://") && !name.startsWith("https://")) {
                url = "http://" + name;
            } else {
                url = name;
            }
            host = new URI(url).getHost();

        } catch (URISyntaxException e) {
        }
        Assert.state(host != null, "Service id not legal hostname (" + name + ")");
        return name;
    }

    private String resolve(String value) {
        if (StringUtils.hasText(value)) {
            return this.environment.resolvePlaceholders(value);
        }
        return value;
    }


    private RequestOptionHolder.Timeout timeout(FeignRequestOption option) {
        return new RequestOptionHolder.Timeout(option.readTimeout(), option.connectionTimeout());
    }

    /**
     * 根据className获取实例clazz对象
     *
     * @param className
     * @return
     */
    private Class clazz(String className) {
        try {
            return Class.forName(className);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }


    protected ClassPathScanningCandidateComponentProvider getScanner() {
        return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
            @Override
            protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
                boolean isCandidate = false;
                if (beanDefinition.getMetadata().isIndependent()) {
                    if (!beanDefinition.getMetadata().isAnnotation()) {
                        isCandidate = true;
                    }
                }
                return isCandidate;
            }
        };
    }

    protected Set getBasePackages(AnnotationMetadata importingClassMetadata) {
        Map attributes = importingClassMetadata
                .getAnnotationAttributes(EnableFeignClients.class.getCanonicalName());

        Set basePackages = new HashSet<>();
        for (String pkg : (String[]) attributes.get("value")) {
            if (StringUtils.hasText(pkg)) {
                basePackages.add(pkg);
            }
        }
        for (String pkg : (String[]) attributes.get("basePackages")) {
            if (StringUtils.hasText(pkg)) {
                basePackages.add(pkg);
            }
        }
        for (Class clazz : (Class[]) attributes.get("basePackageClasses")) {
            basePackages.add(ClassUtils.getPackageName(clazz));
        }

        if (basePackages.isEmpty()) {
            basePackages.add(
                    ClassUtils.getPackageName(importingClassMetadata.getClassName()));
        }
        return basePackages;
    }

    private static class AllTypeFilter implements TypeFilter {

        private final List delegates;

        /**
         * Creates a new {@link AllTypeFilter} to match if all the given delegates match.
         *
         * @param delegates must not be {@literal null}.
         */
        public AllTypeFilter(List delegates) {
            Assert.notNull(delegates, "This argument is required, it must not be null");
            this.delegates = delegates;
        }

        @Override
        public boolean match(MetadataReader metadataReader,
                             MetadataReaderFactory metadataReaderFactory) throws IOException {

            for (TypeFilter filter : this.delegates) {
                if (!filter.match(metadataReader, metadataReaderFactory)) {
                    return false;
                }
            }

            return true;
        }
    }
}
import cn.bucheng.cloud.feign.holder.RequestOptionHolder;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.cloud.openfeign.FeignClientProperties;

import java.util.Map;

/**
 * @author yinchong
 * @create 2020/5/11 21:34
 * @description
 */
public class SemaphoreConfigApplyProcessor implements BeanPostProcessor, BeanFactoryAware {

    private volatile boolean apply = false;
    private DefaultListableBeanFactory beanFactory;

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (apply) {
            return bean;
        }
        Map nameTimeoutMap = RequestOptionHolder.getInstance().nameTimeoutMap();
        if (nameTimeoutMap != null) {
            for (Map.Entry entry : nameTimeoutMap.entrySet()) {
                String name = entry.getKey();
                RequestOptionHolder.Timeout timeout = entry.getValue();
                FeignClientProperties configuration = beanFactory.getBean(FeignClientProperties.class);
                if(configuration==null){
                    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientProperties.class);
                    beanFactory.registerBeanDefinition(FeignClientProperties.class.getCanonicalName(),builder.getBeanDefinition());
                }
                Map config = configuration.getConfig();
                FeignClientProperties.FeignClientConfiguration feignClientConfiguration = config.get(name);
                if (feignClientConfiguration == null) {
                    feignClientConfiguration = new FeignClientProperties.FeignClientConfiguration();
                    config.put(name, feignClientConfiguration);
                }
                feignClientConfiguration.setReadTimeout(timeout.getReadTimeout());
                feignClientConfiguration.setConnectTimeout(timeout.getConnectionTimeout());

            }
        }
        return bean;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = (DefaultListableBeanFactory) beanFactory;
    }
}
import cn.bucheng.cloud.feign.custom.CustomHystrixSetterFactory;
import cn.bucheng.cloud.feign.custom.SemaphoreConfigApplyProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author yinchong
 * @create 2020/5/11 20:32
 * @description
 */
@Configuration
public class FeignAutoConfiguration {

    @Bean
    public SemaphoreConfigApplyProcessor configApplyProcessor(){
        return new SemaphoreConfigApplyProcessor();
    }

    @Bean
    public CustomHystrixSetterFactory hystrixSetterFactory(){
        return new CustomHystrixSetterFactory();
    }
}
import cn.bucheng.cloud.feign.annotation.FeignRequestOption;
import feign.Feign;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * @author yinchong
 * @create 2020/5/11 20:33
 * @description
 */
public class RequestOptionHolder {

    private static RequestOptionHolder instance = new RequestOptionHolder();

    private Map methodTimeoutMap = new HashMap<>(30);
    private Map nameTimeoutMap = new HashMap<>(30);

    private RequestOptionHolder() {

    }

    public static RequestOptionHolder getInstance() {
        return instance;
    }

    public void putMethodTimeout(Class type, Method method, Timeout timeout) {
        String methodKey = Feign.configKey(type, method);
        methodTimeoutMap.put(methodKey, timeout);
    }

    public void putMethodTimeout(Class type, Method method, FeignRequestOption requestOption) {
        Timeout timeout = new Timeout(requestOption.readTimeout(), requestOption.connectionTimeout());
        putMethodTimeout(type, method, requestOption);
    }

    public Timeout getMethodTimeout(String methodKey) {
        return methodTimeoutMap.get(methodKey);
    }

    public Timeout getMethodTimeout(Class type, Method method) {
        String methodKey = Feign.configKey(type, method);
        return getMethodTimeout(methodKey);
    }

    public void putNameTimeout(String name, Timeout timeout) {
        nameTimeoutMap.put(name, timeout);
    }

    public Map nameTimeoutMap() {
        return nameTimeoutMap;
    }


    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Timeout {
        private int readTimeout = 15000;
        private int connectionTimeout = 5000;
    }
}

在spring.factories中添加对象

org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.bucheng.cloud.feign.configuration.FeignAutoConfiguration,\
  cn.bucheng.cloud.feign.custom.FeignConfigLoader

 

使用

@FeignClient(name = "cloud-provider")
@RequestMapping("user")
public interface UserService {
    @RequestMapping(value = "/save", method = RequestMethod.POST)
    String save(UserPO userPO);

    @RequestMapping(value = "/listAll", method = RequestMethod.POST)
    @FeignRequestOption(readTimeout = 4000)
    List listAll();

    @RequestMapping(value = "/isVIP", method = RequestMethod.POST)
    Boolean isVIP(@RequestParam("id") Long id);
}

其他和feign使用一样,只需在执行方法上面添加FeignRequestOption设置执行时间

 

注意

目前只支持hystrix为隔离采用线程模式,方法上面设置超时执行时间才有效,信号量只能作用到类上面超时时间设置

你可能感兴趣的:(springcloud)