org.springframework.cloud
spring-cloud-starter-openfeign
provided
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为隔离采用线程模式,方法上面设置超时执行时间才有效,信号量只能作用到类上面超时时间设置