Feign接口基于Sentinel配置默认熔断规则

概述:基于sentinel配置熔断规则是针对每个接口的,如果feign接口配置规则,使用手动方式在sentinel控制台中配置,那是一件不现实的工作,所以针对feign接口需要配置默认的熔断规则,同时会将初始化的熔断规则存储到nacos中
涉及到的核心功能类:FeignSentinelSupportConfig.java,自定义注解类:EnableFeignDegrade.java,工具类ClassScanner.java,SpringBeanUtil.java
注:本文基于nacos作为注册中心及配置中心,sentinel熔断服务,feign远程调用服务!!!
接下来上代码

1.需要的pom依赖

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>
        <!--feign远程接口调用-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-loadbalancer</artifactId>
        </dependency>

        <dependency>
            <groupId>com.netflix.feign</groupId>
            <artifactId>feign-okhttp</artifactId>
            <version>8.18.0</version>
        </dependency>
        <dependency>
            <groupId>io.github.openfeign.form</groupId>
            <artifactId>feign-form</artifactId>
            <version>3.8.0</version>
        </dependency>
        <dependency>
            <groupId>io.github.openfeign.form</groupId>
            <artifactId>feign-form-spring</artifactId>
            <version>3.8.0</version>
        </dependency>

2.主要功能类

FeignSentinelSupportConfig.java默认配置规则类

import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStrategy;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.PropertyNamingStrategy;
import com.alibaba.fastjson.serializer.SerializeConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.nacos.api.config.ConfigType;
import com.alibaba.nacos.api.exception.NacosException;
import com.sinochem.it.feign.annotation.EnableFeignDegrade;
import com.sinochem.it.feign.utils.ClassScanner;
import com.sinochem.it.feign.utils.SpringBeanUtil;
import com.sinochem.it.sentinel.constant.CommonConstant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

@Component
public class FeignSentinelSupportConfig implements ApplicationRunner, EnvironmentAware {

    private static final Logger logger = LoggerFactory.getLogger(FeignSentinelSupportConfig.class);

    private final static String HTTP_PROTOCOL_PREFIX = "http://";
    private final static String ANNOTATION_VALUE_PREFIX = "${";
    private final static String ANNOTATION_VALUE_SUFFIX = "}";
    private Environment environment;//环境变量对象

    @Value("${spring.cloud.nacos.config.enabled:}")
    private Boolean nacosConfigEnable;

    @Value("${spring.application.name:}")
    private String appName;

    private Class<?> deduceMainApplicationClass() {
        try {
            StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
            for (StackTraceElement stackTraceElement : stackTrace) {
                if ("main".equals(stackTraceElement.getMethodName())) {
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        } catch (ClassNotFoundException ex) {

        }
        return null;
    }

    @Override
    public void run(ApplicationArguments args) {
        Class<?> mainClass = deduceMainApplicationClass();
        logger.info("开始加载默认规则,mainClass:{}", mainClass);
        if (mainClass == null) {
            throw new RuntimeException("can not fount main class");
        }
        // 添加开关 如果不使用nacos配置中心,则直接return,不初始化默认的熔断规则
        if (nacosConfigEnable != null && !nacosConfigEnable) {
            logger.info("nacos配置中心关闭,加载默认规则结束");
            return;
        }
        EnableFeignClients enableFeignClientsAnnotation = mainClass.getAnnotation(EnableFeignClients.class);
        if (enableFeignClientsAnnotation != null) {
            String[] feignClientPackages;
            String[] feignClientDeclaredPackages = enableFeignClientsAnnotation.basePackages();
            //声明了feignClient的包名
            if (feignClientDeclaredPackages.length == 0) {
                feignClientPackages = new String[]{mainClass.getPackage().getName()};
            } else {
                feignClientPackages = feignClientDeclaredPackages;
            }
            //初始化降级规则
            initDeGradeRule(feignClientPackages);
        }
        logger.info("默认降级规则处理完成");
    }

    private Set<Class> getFeignClientClass(String[] packageNames) {
        ClassScanner classScanner = new ClassScanner();
        Set<Class> feignClientClass = classScanner.scan(packageNames, FeignClient.class);
        return feignClientClass;
    }

    /**
     * 创建熔断规则列表数据
     * 如果全部feign接口设置默认策略太多,则可以通过自定义注解,标记哪些feign接口类需要设置
     *
     * @param cla feign接口类
     * @return
     */
    public List<DegradeRule> initRules(Class cla) {
        List<DegradeRule> degradeRuleList = new ArrayList<>();
        // 在该处获取自定义注解,来判断是否添加默认熔断规则  如果feign接口类上添加了@EnableFeignDegrade注解并且enable为false,则不为该类中接口添加默认熔断规则
        EnableFeignDegrade feignDegrade = (EnableFeignDegrade) cla.getAnnotation(EnableFeignDegrade.class);
        if (null != feignDegrade && !feignDegrade.enable()) {
            logger.info("{}类关闭了初始化默认熔断规则功能", cla.getName());
            return degradeRuleList;
        }

        FeignClient feignClient = (FeignClient) cla.getAnnotation(FeignClient.class);
        // 获取RequestMapping注解是否配置基础路径
        String classRequestMappingUrl = "";
        RequestMapping classRequestMapping = (RequestMapping) cla.getAnnotation(RequestMapping.class);
        if (null != classRequestMapping) {
            classRequestMappingUrl = classRequestMapping.value()[0];
        }
        // 这里区分四种情况
        // 1.@FeignClient(name = "demo01", url = "http://127.0.0.1:36001"
        // 2.@FeignClient(name = "demo01", url = "${base.url}"
        // 3.@FeignClient(name = "demo01"
        // 4.@FeignClient(name = "${demo01.serviceName}"
        // 标识是否配置url属性,拼接http://前缀时判断使用
        Boolean httpFlag = true;
        String baseUrl = null;
        String serviceUrl = feignClient.url();
        String serviceName = feignClient.name();
        // 如果url属性不为空并且${开头 }结尾,说明是动态配置的url
        if (StringUtil.isNotBlank(serviceUrl) && serviceUrl.startsWith(ANNOTATION_VALUE_PREFIX) && serviceUrl.endsWith(ANNOTATION_VALUE_SUFFIX)) {
            baseUrl = environment.resolvePlaceholders(serviceUrl);
        } else if (StringUtil.isNotBlank(serviceUrl)) {
            // 如果http路径最后一位是/ 则去掉斜杠
            baseUrl = !serviceUrl.endsWith("/") ? serviceUrl : serviceUrl.substring(0, serviceUrl.length() - 1);
        } else if (StringUtil.isBlank(serviceUrl) && serviceName.startsWith(ANNOTATION_VALUE_PREFIX) && serviceName.endsWith(ANNOTATION_VALUE_SUFFIX)) {
            baseUrl = environment.resolvePlaceholders(serviceName);
            httpFlag = false;
        } else {
            baseUrl = serviceName;
            httpFlag = false;
        }

        Method[] methods = cla.getDeclaredMethods();
        for (Method method : methods) {
            degradeRuleList.add(buildDegradeRule(getResourceName(classRequestMappingUrl, baseUrl, method, httpFlag)));
        }
        DegradeRuleManager.loadRules(degradeRuleList);
        return degradeRuleList;

    }

    /**
     * 初始化熔断规则
     *
     * @param feignClientPackages
     */
    private void initDeGradeRule(String[] feignClientPackages) {
        List<DegradeRule> localDegradeRuleList = new ArrayList<>();
        Set<Class> feignClientClass = getFeignClientClass(feignClientPackages);
        for (Class clientClass : feignClientClass) {
            List<DegradeRule> rules = initRules(clientClass);
            localDegradeRuleList.addAll(rules);
        }

        NacosConfigManager nacosConfigManager = SpringBeanUtil.getBean("nacosConfigManager", NacosConfigManager.class);
        List<DegradeRule> remoteDegradeRuleList = this.fetchRemoteRules(nacosConfigManager);
        //远程nacos没有规则,那就直接利用本地规则
        if (remoteDegradeRuleList == null || remoteDegradeRuleList.isEmpty()) {
            this.pushRules(nacosConfigManager, localDegradeRuleList);
            return;
        }
        //本地规则 合并 远程规则策略
        this.proess(localDegradeRuleList, remoteDegradeRuleList);
        //推送本地规则,到nacos
        this.pushRules(nacosConfigManager, localDegradeRuleList);
    }

    /***
     * 组装resourceName
     * @param crmu requestMapping路径
     * @param serviceName 服务名称或url
     * @param method 方法
     * @param httpFlag http标记
     * @return
     */
    private String getResourceName(String crmu, String serviceName, Method method, Boolean httpFlag) {
//        crmu = crmu.startsWith("/") ? crmu : "/" + crmu;
        String resourceName = "";
        RequestMapping methodRequestMapping = method.getAnnotation(RequestMapping.class);
        if (null != methodRequestMapping) {
            String mrm = methodRequestMapping.value()[0];
            // 获取@RequestMapping中的method参数,如果未配置,则默认为GET请求
            RequestMethod mMethod = null;
            if (methodRequestMapping.method().length > 0) {
                mMethod = methodRequestMapping.method()[0];
            }
            // @RequestMapping 注解不指定method参数,则默认为GET请求
            if (!httpFlag) {
                if (mMethod != null) {
                    resourceName = mMethod + ":" + HTTP_PROTOCOL_PREFIX + serviceName + crmu + (mrm.startsWith("/") ? mrm : "/" + mrm);
                } else {
                    resourceName = "GET:" + HTTP_PROTOCOL_PREFIX + serviceName + crmu + (mrm.startsWith("/") ? mrm : "/" + mrm);
                }
            } else {
                if (mMethod != null) {
                    resourceName = mMethod + ":" + serviceName + crmu + (mrm.startsWith("/") ? mrm : "/" + mrm);
                } else {
                    resourceName = "GET:" + serviceName + crmu + (mrm.startsWith("/") ? mrm : "/" + mrm);
                }
            }
        }
        PostMapping methodPostMapping = method.getAnnotation(PostMapping.class);
        if (null != methodPostMapping) {
            String mpm = methodPostMapping.value()[0];
            // 未配置url属性,说明使用服务名进行服务之间调用,所以需要拼接http://  拼接结果:http://serviceName/demo/get  前面拼接的请求方式(POST:)是因为sentinel需要 不拼接熔断规则不生效
            if (!httpFlag) {
                resourceName = "POST:" + HTTP_PROTOCOL_PREFIX + serviceName + crmu + (mpm.startsWith("/") ? mpm : "/" + mpm);
            } else { // 配置url属性,说明使用指定url调用 则不需要单独拼接http://  拼接结果为:http://127.0.0.1:8080/demo/get  前面拼接的请求方式(POST:)是因为sentinel需要 不拼接熔断规则不生效
                resourceName = "POST:" + serviceName + crmu + (mpm.startsWith("/") ? mpm : "/" + mpm);
            }
        }
        GetMapping methodGetMapping = method.getAnnotation(GetMapping.class);
        if (null != methodGetMapping) {
            String mgm = methodGetMapping.value()[0];
            if (!httpFlag) {
                resourceName = "GET:" + HTTP_PROTOCOL_PREFIX + serviceName + crmu + (mgm.startsWith("/") ? mgm : "/" + mgm);
            } else {
                resourceName = "GET:" + serviceName + crmu + (mgm.startsWith("/") ? mgm : "/" + mgm);
            }
        }
        return resourceName;
    }

    /***
     * 创建默认熔断规则
     * @param resourceName
     * @return
     */
    private DegradeRule buildDegradeRule(String resourceName) {
        DegradeRule rule = new DegradeRule(resourceName)
                // 熔断策略
                .setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType())
                // Max allowed response time  RT 慢调用标准值 接口响应时长超过2秒则被判定为慢调用
                .setCount(2000)
                // Retry timeout (in second)   窗口期  熔断时长
                .setTimeWindow(30)
                // Circuit breaker opens when slow request ratio > 80%  慢调用比例   判定是否熔断的条件之一
                .setSlowRatioThreshold(0.8)
                // 单位时长最小请求数   判定是否熔断的条件之一
                .setMinRequestAmount(30)
                // 统计时长也叫单位时长
                .setStatIntervalMs(60000);
        return rule;
    }


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

    /************************************ nacos config ********************************************************/

    public String getConfig(NacosConfigManager nacosConfigManager, String dataId, String group) {
        try {
            return nacosConfigManager.getConfigService().getConfig(dataId, group, 10000l);
        } catch (NacosException e) {
            logger.error("NacosService publish e:{}", e);
        }
        return null;
    }


    private Boolean publish(NacosConfigManager nacosConfigManager, String dataId, String group, String content, String type) {
        try {
            return nacosConfigManager.getConfigService().publishConfig(dataId, group, content, type);
        } catch (NacosException e) {
            logger.error("NacosService publish e:{}", e);
        }
        return false;
    }

    public void pushRules(NacosConfigManager nacosConfigManager, List<DegradeRule> localDegradeRuleList) {

//        String appName = (String) getAttrBootstrapYml("spring.application.name");
//        if (StringUtil.isBlank(appName)) {
//            appName = (String) getAttrBootstrapEnvYml("spring.application.name");
//        }
        String dataId = appName + CommonConstant.DEGRADE_DATA_ID_POSTFIX;
        SerializeConfig serializeConfig = new SerializeConfig();
        serializeConfig.propertyNamingStrategy = PropertyNamingStrategy.CamelCase;
        String contentStr = JSON.toJSONString(localDegradeRuleList, serializeConfig, SerializerFeature.PrettyFormat);
        publish(nacosConfigManager, dataId, CommonConstant.GROUP_ID, contentStr, ConfigType.JSON.getType());
    }


    public void proess(List<DegradeRule> localDegradeRuleList, List<DegradeRule> remoteDegradeRuleList) {
        for (DegradeRule rule : remoteDegradeRuleList) {
            if (localDegradeRuleList.contains(rule)) {
                DegradeRule ldr = localDegradeRuleList.get(localDegradeRuleList.indexOf(rule));
                if (ldr.equals(rule)) {
                    continue;
                }
                localDegradeRuleList.remove(ldr);
                localDegradeRuleList.add(rule);
            } else {
                localDegradeRuleList.add(rule);
            }
        }
    }


    public List<DegradeRule> fetchRemoteRules(NacosConfigManager nacosConfigManager) {
//        String appName = (String) getAttrBootstrapYml("spring.application.name");
//        if (StringUtil.isBlank(appName)) {
//            appName = (String) getAttrBootstrapEnvYml("spring.application.name");
//        }
        String dataId = appName + CommonConstant.DEGRADE_DATA_ID_POSTFIX;
        return JSONObject.parseArray(this.getConfig(nacosConfigManager, dataId, CommonConstant.GROUP_ID), DegradeRule.class);
    }
}

EnableFeignDegrade.java自定义注解类

import java.lang.annotation.*;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnableFeignDegrade {


    /**
     * 是否启用feign初始化熔断规则功能 默认使用
     *
     * @return
     */
    boolean enable() default true;

}

DefaultRuleNacosManager.java操作nacos持久化类 优化之后已经合并到FeignSentinelSupportConfig类中,如果分开两个类,启动服务注入时会出现加载问题

import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.PropertyNamingStrategy;
import com.alibaba.fastjson.serializer.SerializeConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.nacos.api.config.ConfigType;
import com.alibaba.nacos.api.exception.NacosException;
import com.sinochem.it.sentinel.constant.CommonConstant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class DefaultRuleNacosManager {

    private static final Logger logger = LoggerFactory.getLogger(DefaultRuleNacosManager.class);

    @Autowired
    private NacosConfigManager nacosConfigManager;

    @Value("${spring.application.name}")
    private String appName;


    public String getConfig(String dataId, String group) {
        try {
            return nacosConfigManager.getConfigService().getConfig(dataId, group, 10000l);
        } catch (NacosException e) {
            logger.error("NacosService publish e:{}", e);
        }
        return null;
    }


    private Boolean publish(String dataId, String group, String content, String type) {
        try {
            return nacosConfigManager.getConfigService().publishConfig(dataId, group, content, type);
        } catch (NacosException e) {
            logger.error("NacosService publish e:{}", e);
        }
        return false;
    }

    public void pushRules(List<DegradeRule> localDegradeRuleList) {
        String dataId = appName + CommonConstant.DEGRADE_DATA_ID_POSTFIX;

        SerializeConfig serializeConfig = new SerializeConfig();
        serializeConfig.propertyNamingStrategy = PropertyNamingStrategy.CamelCase;
        String contentStr = JSON.toJSONString(localDegradeRuleList, serializeConfig, SerializerFeature.PrettyFormat);
        publish(dataId, CommonConstant.GROUP_ID, contentStr, ConfigType.JSON.getType());
    }


    public void proess(List<DegradeRule> localDegradeRuleList, List<DegradeRule> remoteDegradeRuleList) {
        for (DegradeRule rule : remoteDegradeRuleList) {
            if (localDegradeRuleList.contains(rule)) {
                DegradeRule ldr = localDegradeRuleList.get(localDegradeRuleList.indexOf(rule));
                if (ldr.equals(rule)) {
                    continue;
                }
                localDegradeRuleList.remove(ldr);
                localDegradeRuleList.add(rule);
            } else {
                localDegradeRuleList.add(rule);
            }
        }
    }


    public List<DegradeRule> fetchRemoteRules() {
        String dataId = appName + CommonConstant.DEGRADE_DATA_ID_POSTFIX;
        return JSONObject.parseArray(this.getConfig(dataId, CommonConstant.GROUP_ID), DegradeRule.class);
    }
}

ClassScanner.java工具类

import com.sinochem.it.feign.config.FeignSentinelSupportConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.ClassUtils;
import org.springframework.util.SystemPropertyUtils;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

public class ClassScanner implements ResourceLoaderAware {

    private static final Logger log = LoggerFactory.getLogger(FeignSentinelSupportConfig.class);

    private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
    private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver);

    private final List<TypeFilter> includeFilters = new LinkedList<>();
    private final List<TypeFilter> excludeFilters = new LinkedList<>();


    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
        this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
    }

    public void addIncludeFilter(TypeFilter includeFilter) {
        this.includeFilters.add(includeFilter);
    }

    public void addExcludeFilter(TypeFilter excludeFilter) {
        this.excludeFilters.add(0, excludeFilter);
    }

    public void resetFilters(boolean useDefaultFilters) {
        this.includeFilters.clear();
        this.excludeFilters.clear();
    }

    /**
     * 扫描指定路径指定注解的类
     * @param basePackage
     * @param annotations
     * @return
     */
    public static Set<Class> scan(String basePackage, Class<? extends Annotation>... annotations) {
        ClassScanner classScanner = new ClassScanner();

        for (Class<? extends Annotation> annotation : annotations) {
            classScanner.addIncludeFilter(new AnnotationTypeFilter(annotation));
        }
        return classScanner.doScan(basePackage);
    }

    /**
     * 扫描多个路径下包含指定注解的类
     * @param basePackages
     * @param annotations
     * @return
     */
    public static Set<Class> scan(String[] basePackages, Class<? extends Annotation>... annotations) {
        ClassScanner classScanner = new ClassScanner();
        for (Class<? extends Annotation> annotation : annotations) {
            classScanner.addIncludeFilter(new AnnotationTypeFilter(annotation));
        }

        Set<Class> classes = new HashSet<>();
        for (String basePackage : basePackages) {
            classes.addAll(classScanner.doScan(basePackage));
        }

        return classes;
    }

    /**
     * 扫描指定路径下的类信息
     * @param basePackage
     * @return
     */
    public Set<Class> doScan(String basePackage) {
        Set<Class> classes = new HashSet<>();

        // 扫描路径
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
                + ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage))
                + "/**/*.class";
        try {

            // 获取指定扫描路径的资源
            Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);

            for (Resource resource : resources) {
                if (resource.isReadable()) {

                    MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);

                    if ((this.includeFilters.size() == 0 && this.excludeFilters.size() == 0) || matches(metadataReader)) {

                        try {
                            // 返回符合条件的资源
                            classes.add(Class.forName(metadataReader.getClassMetadata().getClassName()));
                        } catch (ClassNotFoundException e) {
                            log.error("class forName 异常:", e);
                        }

                    }
                }
            }
        } catch (IOException e) {

            log.error("扫描加载资源io异常:", e);
            throw new BeanDefinitionStoreException("I/O failure during classpath scanning", e);
        }

        return classes;
    }


    /**
     * 资源是否匹配
     * @param metadataReader
     * @return
     * @throws IOException
     */
    private boolean matches(MetadataReader metadataReader) throws IOException {

        for (TypeFilter excludeFilter : this.excludeFilters) {
            if (excludeFilter.match(metadataReader, this.metadataReaderFactory)) {
                return false;
            }
        }

        for (TypeFilter includeFilter : this.includeFilters) {
            if (includeFilter.match(metadataReader, this.metadataReaderFactory)) {
                return true;
            }
        }
        return false;
    }
}

SpringBeanUtil.java通过spring获取类工具类

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class SpringBeanUtil implements ApplicationContextAware {
    private static Logger log = LoggerFactory.getLogger(SpringBeanUtil.class);
    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (context == null) {
            context = applicationContext;
        }
    }

    public static ApplicationContext getContext() {
        return context;
    }

    public static Object getBean(String name) {
        try {
            return getContext().getBean(name);
        } catch (Exception e) {
            log.error("系统异常", e);
            return null;
        }
    }

    public static <T> T getBean(Class<T> clazz) {
        try {
            return getContext().getBean(clazz);
        } catch (Exception e) {
            log.error("系统异常", e);
            return null;
        }
    }

    public static <T> T getBean(String name, Class<T> clazz) {
        try {
            return getContext().getBean(name, clazz);
        } catch (Exception e) {
            log.error("系统异常", e);
            return null;
        }
    }

}

3.主要测试类

测试类demo01 feign接口被调用方
import org.springframework.web.bind.annotation.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping(value = "/demo01")
public class TestController {

    @GetMapping("/get/{id}")
    public String get(@PathVariable String id) {
        int i = ThreadLocalRandom.current().nextInt(40, 60);
        System.out.println("睡眠随机值:" + i);
        sleep(i);
        return "Hello Demo01 test get 返回值 成功!!! id:" + id;
    }

    @GetMapping("/get1")
    public String get1(@RequestParam String id) {
        int i = ThreadLocalRandom.current().nextInt(40, 60);
        System.out.println("睡眠随机值:" + i);
        sleep(i);
        return "Hello Demo01 test get1 返回值 成功!!! id:" + id;
    }

    private static void sleep(int timeMs) {
        try {
            TimeUnit.MILLISECONDS.sleep(timeMs);
        } catch (InterruptedException e) {
            // ignore
        }
    }
}

application.yml文件

server:
  port: 36001

# 应用名称
spring:
  application:
    name: demo01
  cloud:
    # nacos注册中心配置
    nacos:
      server-addr: 127.0.0.1:39999
      discovery:
        group: SINO_MSA_GROUP
        namespace: Dev
    # sentinel控制台配置,实现流控,降级等
    sentinel:
      transport:
        dashboard: 127.0.0.1:39000

bootstrap.yml文件

spring:
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:39999
        group: SINO_MSA_GROUP
        name: demo01
        file-extension: yaml
        namespace: Dev

pom.xml文件

        <!-- nacos注册中心 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>

测试类demo02 feign接口调用方
import com.sinochem.it.demo02.feign.TestFeign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping(value = "/demo02")
public class TestController {

    @Autowired
    private TestFeign testFeign;

    @GetMapping(value = "/get")
    public String get() {
        long start = System.currentTimeMillis();
        String s = testFeign.demo01Get("123");
        System.out.println(System.currentTimeMillis() - start + "ms");
        return s;
    }

    @GetMapping(value = "/get1")
    public String get1() {
        long start = System.currentTimeMillis();
        String s = testFeign.demo01Get1("456");
        System.out.println(System.currentTimeMillis() - start + "ms");
        return s;
    }

    @PostMapping(value = "/post")
    public String post() {
        long start = System.currentTimeMillis();
        String s = testFeign.demo01Post();
        System.out.println(System.currentTimeMillis() - start + "ms");
        return s;
    }

    @RequestMapping(value = "/req", method = RequestMethod.POST)
    public String req() {
        long start = System.currentTimeMillis();
        String s = testFeign.demo01Request();
        System.out.println(System.currentTimeMillis() - start + "ms");
        return s;
    }
}
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;

//@FeignClient(name = "demo01", url = "http://127.0.0.1:36001",fallbackFactory = TestFeignFallbackFactory.class)
//@FeignClient(name = "demo01", url = "${base.url}",fallbackFactory = TestFeignFallbackFactory.class)
//@FeignClient(name = "demo01",fallbackFactory = TestFeignFallbackFactory.class)
@FeignClient(name = "${demo01.serviceName}",fallbackFactory = TestFeignFallbackFactory.class)
public interface TestFeign {

    @GetMapping(value = "/demo01/get/{id}")
    String demo01Get(@PathVariable String id);

    @GetMapping(value = "/demo01/get1")
    String demo01Get1(@RequestParam String id);

    @PostMapping(value = "/demo01/post")
    String demo01Post();

    @RequestMapping(value = "/demo01/request",method = RequestMethod.POST)
    String demo01Request();
}
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;

@Component
public class TestFeignFallbackFactory implements FallbackFactory<TestFeign> {

    @Override
    public TestFeign create(Throwable cause) {
        return new TestFeign() {
            @Override
            public String demo01Get(String id) {
                return "GetDemo服务熔断降级至此!!!";
            }

            @Override
            public String demo01Get1(String id) {
                return "GetDemo服务熔断降级至此!!!";
            }

            @Override
            public String demo01Post() {
                return "PostDemo服务熔断降级至此!!!";
            }

            @Override
            public String demo01Request() {
                return "RequestDemo服务熔断降级至此!!!";
            }
        };
    }
}

application.yml文件

server:
  port: 36002

# 应用名称
spring:
  application:
    name: demo02
  cloud:
    # nacos注册中心配置
    nacos:
      server-addr: 127.0.0.1:39999
      discovery:
        group: SINO_MSA_GROUP
        namespace: Dev
    # sentinel控制台配置,实现流控,降级等
    sentinel:
      transport:
        dashboard: 127.0.0.1:39000

feign:
  sentinel:
    enabled: true

base.url: http://127.0.0.1:36001
demo01.serviceName: demo01

bootstrap.yml文件

spring:
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:39999
        group: SINO_MSA_GROUP
        name: demo02
        file-extension: yaml
        namespace: Dev

pom.xml文件

        <!-- nacos注册中心 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.sinochem.it</groupId>
            <artifactId>sino-msa-feign</artifactId>
            <version>2.1.0-SNAPSHOT</version>
        </dependency>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容