Feign接口基于Sentinel配置默認熔斷規(guī)則

概述:基于sentinel配置熔斷規(guī)則是針對每個接口的疯特,如果feign接口配置規(guī)則,使用手動方式在sentinel控制臺中配置氏堤,那是一件不現(xiàn)實的工作,所以針對feign接口需要配置默認的熔斷規(guī)則捌归,同時會將初始化的熔斷規(guī)則存儲到nacos中
涉及到的核心功能類:FeignSentinelSupportConfig.java,自定義注解類:EnableFeignDegrade.java岭粤,工具類ClassScanner.java,SpringBeanUtil.java
注:本文基于nacos作為注冊中心及配置中心惜索,sentinel熔斷服務(wù),feign遠程調(diào)用服務(wù)L杲健=碚住!
接下來上代碼

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遠程接口調(diào)用-->
        <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默認配置規(guī)則類

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;//環(huán)境變量對象

    @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("開始加載默認規(guī)則虎囚,mainClass:{}", mainClass);
        if (mainClass == null) {
            throw new RuntimeException("can not fount main class");
        }
        // 添加開關(guān) 如果不使用nacos配置中心臼寄,則直接return,不初始化默認的熔斷規(guī)則
        if (nacosConfigEnable != null && !nacosConfigEnable) {
            logger.info("nacos配置中心關(guān)閉溜宽,加載默認規(guī)則結(jié)束");
            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;
            }
            //初始化降級規(guī)則
            initDeGradeRule(feignClientPackages);
        }
        logger.info("默認降級規(guī)則處理完成");
    }

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

    /**
     * 創(chuàng)建熔斷規(guī)則列表數(shù)據(jù)
     * 如果全部feign接口設(shè)置默認策略太多,則可以通過自定義注解质帅,標(biāo)記哪些feign接口類需要設(shè)置
     *
     * @param cla feign接口類
     * @return
     */
    public List<DegradeRule> initRules(Class cla) {
        List<DegradeRule> degradeRuleList = new ArrayList<>();
        // 在該處獲取自定義注解适揉,來判斷是否添加默認熔斷規(guī)則  如果feign接口類上添加了@EnableFeignDegrade注解并且enable為false,則不為該類中接口添加默認熔斷規(guī)則
        EnableFeignDegrade feignDegrade = (EnableFeignDegrade) cla.getAnnotation(EnableFeignDegrade.class);
        if (null != feignDegrade && !feignDegrade.enable()) {
            logger.info("{}類關(guān)閉了初始化默認熔斷規(guī)則功能", cla.getName());
            return degradeRuleList;
        }

        FeignClient feignClient = (FeignClient) cla.getAnnotation(FeignClient.class);
        // 獲取RequestMapping注解是否配置基礎(chǔ)路徑
        String classRequestMappingUrl = "";
        RequestMapping classRequestMapping = (RequestMapping) cla.getAnnotation(RequestMapping.class);
        if (null != classRequestMapping) {
            classRequestMappingUrl = classRequestMapping.value()[0];
        }
        // 這里區(qū)分四種情況
        // 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}"
        // 標(biāo)識是否配置url屬性煤惩,拼接http://前綴時判斷使用
        Boolean httpFlag = true;
        String baseUrl = null;
        String serviceUrl = feignClient.url();
        String serviceName = feignClient.name();
        // 如果url屬性不為空并且${開頭 }結(jié)尾嫉嘀,說明是動態(tài)配置的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;

    }

    /**
     * 初始化熔斷規(guī)則
     *
     * @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沒有規(guī)則,那就直接利用本地規(guī)則
        if (remoteDegradeRuleList == null || remoteDegradeRuleList.isEmpty()) {
            this.pushRules(nacosConfigManager, localDegradeRuleList);
            return;
        }
        //本地規(guī)則 合并 遠程規(guī)則策略
        this.proess(localDegradeRuleList, remoteDegradeRuleList);
        //推送本地規(guī)則魄揉,到nacos
        this.pushRules(nacosConfigManager, localDegradeRuleList);
    }

    /***
     * 組裝resourceName
     * @param crmu requestMapping路徑
     * @param serviceName 服務(wù)名稱或url
     * @param method 方法
     * @param httpFlag http標(biāo)記
     * @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參數(shù)剪侮,如果未配置,則默認為GET請求
            RequestMethod mMethod = null;
            if (methodRequestMapping.method().length > 0) {
                mMethod = methodRequestMapping.method()[0];
            }
            // @RequestMapping 注解不指定method參數(shù)洛退,則默認為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屬性瓣俯,說明使用服務(wù)名進行服務(wù)之間調(diào)用,所以需要拼接http://  拼接結(jié)果:http://serviceName/demo/get  前面拼接的請求方式(POST:)是因為sentinel需要 不拼接熔斷規(guī)則不生效
            if (!httpFlag) {
                resourceName = "POST:" + HTTP_PROTOCOL_PREFIX + serviceName + crmu + (mpm.startsWith("/") ? mpm : "/" + mpm);
            } else { // 配置url屬性兵怯,說明使用指定url調(diào)用 則不需要單獨拼接http://  拼接結(jié)果為:http://127.0.0.1:8080/demo/get  前面拼接的請求方式(POST:)是因為sentinel需要 不拼接熔斷規(guī)則不生效
                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;
    }

    /***
     * 創(chuàng)建默認熔斷規(guī)則
     * @param resourceName
     * @return
     */
    private DegradeRule buildDegradeRule(String resourceName) {
        DegradeRule rule = new DegradeRule(resourceName)
                // 熔斷策略
                .setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType())
                // Max allowed response time  RT 慢調(diào)用標(biāo)準(zhǔn)值 接口響應(yīng)時長超過2秒則被判定為慢調(diào)用
                .setCount(2000)
                // Retry timeout (in second)   窗口期  熔斷時長
                .setTimeWindow(30)
                // Circuit breaker opens when slow request ratio > 80%  慢調(diào)用比例   判定是否熔斷的條件之一
                .setSlowRatioThreshold(0.8)
                // 單位時長最小請求數(shù)   判定是否熔斷的條件之一
                .setMinRequestAmount(30)
                // 統(tǒng)計時長也叫單位時長
                .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初始化熔斷規(guī)則功能 默認使用
     *
     * @return
     */
    boolean enable() default true;

}

DefaultRuleNacosManager.java操作nacos持久化類 優(yōu)化之后已經(jīng)合并到FeignSentinelSupportConfig類中彩匕,如果分開兩個類,啟動服務(wù)注入時會出現(xiàn)加載問題

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("系統(tǒng)異常", e);
            return null;
        }
    }

    public static <T> T getBean(Class<T> clazz) {
        try {
            return getContext().getBean(clazz);
        } catch (Exception e) {
            log.error("系統(tǒng)異常", e);
            return null;
        }
    }

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

}

3.主要測試類

測試類demo01 feign接口被調(diào)用方
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 返回值 成功C角M找恰! 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 返回值 成功M噤觥P靼帧! id:" + id;
    }

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

application.yml文件

server:
  port: 36001

# 應(yīng)用名稱
spring:
  application:
    name: demo01
  cloud:
    # nacos注冊中心配置
    nacos:
      server-addr: 127.0.0.1:39999
      discovery:
        group: SINO_MSA_GROUP
        namespace: Dev
    # sentinel控制臺配置宙攻,實現(xiàn)流控奠货,降級等
    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接口調(diào)用方
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服務(wù)熔斷降級至此!U秤拧仇味!";
            }

            @Override
            public String demo01Get1(String id) {
                return "GetDemo服務(wù)熔斷降級至此I胪纭!丹墨!";
            }

            @Override
            public String demo01Post() {
                return "PostDemo服務(wù)熔斷降級至此@缺椤!贩挣!";
            }

            @Override
            public String demo01Request() {
                return "RequestDemo服務(wù)熔斷降級至此:砬啊!王财!";
            }
        };
    }
}

application.yml文件

server:
  port: 36002

# 應(yīng)用名稱
spring:
  application:
    name: demo02
  cloud:
    # nacos注冊中心配置
    nacos:
      server-addr: 127.0.0.1:39999
      discovery:
        group: SINO_MSA_GROUP
        namespace: Dev
    # sentinel控制臺配置卵迂,實現(xiàn)流控,降級等
    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>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末绒净,一起剝皮案震驚了整個濱河市见咒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌挂疆,老刑警劉巖改览,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異缤言,居然都是意外死亡宝当,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門胆萧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來庆揩,“玉大人,你說我怎么就攤上這事跌穗《┥危” “怎么了?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵蚌吸,是天一觀的道長腾仅。 經(jīng)常有香客問我,道長套利,這世上最難降的妖魔是什么推励? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮肉迫,結(jié)果婚禮上验辞,老公的妹妹穿的比我還像新娘。我一直安慰自己喊衫,他們只是感情好跌造,可當(dāng)我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般壳贪。 火紅的嫁衣襯著肌膚如雪陵珍。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天违施,我揣著相機與錄音互纯,去河邊找鬼。 笑死磕蒲,一個胖子當(dāng)著我的面吹牛留潦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播辣往,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼兔院,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了站削?” 一聲冷哼從身側(cè)響起坊萝,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎许起,沒想到半個月后屹堰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡街氢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了睦袖。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片珊肃。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖馅笙,靈堂內(nèi)的尸體忽然破棺而出伦乔,到底是詐尸還是另有隱情,我是刑警寧澤董习,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布烈和,位于F島的核電站,受9級特大地震影響皿淋,放射性物質(zhì)發(fā)生泄漏招刹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一窝趣、第九天 我趴在偏房一處隱蔽的房頂上張望疯暑。 院中可真熱鬧,春花似錦哑舒、人聲如沸妇拯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽越锈。三九已至仗嗦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間甘凭,已是汗流浹背稀拐。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留对蒲,地道東北人钩蚊。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像蹈矮,于是被迫代替她去往敵國和親砰逻。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容