對Feign的請求url 重寫


需求:對當前請求的 url 重新構建

debug feign 的執行可知,重寫 LoadBalancerFeignClient 類中的 execute 方法即可控制當前請求的url

代碼分析

當引入 spring-cloud-sleuth-stream 時, seluth也重寫了feign 的 feign.Client 查看 TraceFeignClientAutoConfiguration 類,

        @Configuration
	@ConditionalOnProperty(name = "spring.sleuth.feign.processor.enabled", matchIfMissing = true)
	protected static class FeignBeanPostProcessorConfiguration {

		@Bean
		FeignContextBeanPostProcessor feignContextBeanPostProcessor(BeanFactory beanFactory) {
			return new FeignContextBeanPostProcessor(beanFactory);// 對 FeignContext 進行了重新包裝 
		}
	}
@Bean  // 對 LoadBalancerFeignClient bean 的包裝類
TraceFeignObjectWrapper traceFeignObjectWrapper(BeanFactory beanFactory) {
return new TraceFeignObjectWrapper(beanFactory);
}
@Bean // 對 feign.Client 的所有方法進行攔截,如果 執行的 bean 不是 TraceFeignClient ,則返 TraceFeignClient bean,執行該類中的方法,該類也實現了 feign的 Client 接口
TraceFeignAspect traceFeignAspect(BeanFactory beanFactory) {
return new TraceFeignAspect(beanFactory);
}

  

final class FeignContextBeanPostProcessor implements BeanPostProcessor {

	private final BeanFactory beanFactory;
	private TraceFeignObjectWrapper traceFeignObjectWrapper;

	FeignContextBeanPostProcessor(BeanFactory beanFactory) {
		this.beanFactory = beanFactory;
	}

	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName)
			throws BeansException {
		if (bean instanceof FeignContext && !(bean instanceof TraceFeignContext)) { //如果bean 是 FeignContext,則返回 TraceFeignContext 的bean
			return new TraceFeignContext(getTraceFeignObjectWrapper(), (FeignContext) bean); 
		}
		return bean;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName)
			throws BeansException {
		return bean;
	}

	private TraceFeignObjectWrapper getTraceFeignObjectWrapper() {
		if (this.traceFeignObjectWrapper == null) {
              // 查看 TraceFeignObjectWrapper 類 this.traceFeignObjectWrapper = this.beanFactory.getBean(TraceFeignObjectWrapper.class); } return this.traceFeignObjectWrapper; } }

  

查看 TraceFeignObjectWrapper 類,代碼如下:

final class TraceFeignObjectWrapper {

	private final BeanFactory beanFactory;

	private CachingSpringLoadBalancerFactory cachingSpringLoadBalancerFactory;
	private SpringClientFactory springClientFactory;

	TraceFeignObjectWrapper(BeanFactory beanFactory) {
		this.beanFactory = beanFactory;
	}

	Object wrap(Object bean) {
		if (bean instanceof Client && !(bean instanceof TraceFeignClient)) {
			if (bean instanceof LoadBalancerFeignClient) { //如果 bean 是 LoadBalancerFeignClient 的 bean,則返回  TraceLoadBalancerFeignClient 的 bean
				LoadBalancerFeignClient client = ((LoadBalancerFeignClient) bean);
				return new TraceLoadBalancerFeignClient(
						client.getDelegate(), factory(),
						clientFactory(), this.beanFactory);
			}
			return new TraceFeignClient(this.beanFactory, (Client) bean);
		}
		return bean;
	}

	CachingSpringLoadBalancerFactory factory() {
		if (this.cachingSpringLoadBalancerFactory == null) {
			this.cachingSpringLoadBalancerFactory = this.beanFactory
					.getBean(CachingSpringLoadBalancerFactory.class);
		}
		return this.cachingSpringLoadBalancerFactory;
	}

	SpringClientFactory clientFactory() {
		if (this.springClientFactory == null) {
			this.springClientFactory = this.beanFactory
					.getBean(SpringClientFactory.class);
		}
		return this.springClientFactory;
	}
}

  

當請求執行時,查看 LoadBalancerFeignClient類可知如圖所示, delegate 顯示為TraceFeignClient 的bean ,說明feign 的 負載均衡客戶端現在用的是 TraceLoadBalancerFeignClient

 

查看 AbstractLoadBalancerAwareClient 類,獲取當前server 的ip,端口,請求url,如圖所示

 

 feign 的默認 Client 為 Default 類,根據請求服務的ip,端口和url ,發起請求,所以最終請求的執行,如下面所示

 

由上面的流程可知,引入sleuth 時,sleuth 中的feign相關類重寫了 feign 的負載均衡類,所以要關閉 這個功能,當 配置文件中有 spring.sleuth.feign.enabled=false 時,則

sleuth 重寫 feign 負載均衡器 ,則不生效。

 

當配置文件中有 ribbon.eureka.enabled=false 屬性時,才會使用 ribbon.listOfServers 中配置的信息,所有要 實現 EnvironmentPostProcessor 類,將屬性放入 

defaultProperties 配置文件級別中,方便當項目中有相同屬性時,覆蓋已添加的屬性信息

@Slf4j
public class MockEnvironmentPostProcessor implements EnvironmentPostProcessor {

    private static final String PROPERTY_SOURCE_NAME = "defaultProperties";


    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        //獲取mock屬性配置,並綁定到MockProperties 對象中
        MockProperties target = new MockProperties();
        RelaxedDataBinder binder = new RelaxedDataBinder(target,
                MockProperties.MOCK_PREFIX);
        binder.bind(new PropertySourcesPropertyValues(environment.getPropertySources()));

        boolean enabled = target.isEnabled();
        if (true == enabled) {
            System.out.println("mock server 構建環境屬性");
            Map<String, Object> map = new HashMap<>();

            //對單個服務mock的處理
            if (StringUtils.isNotBlank(target.getServices())) {
                String[] services = target.getServices().split(",");
                for (String service : services) {
                    map.put(service.toUpperCase() + ".ribbon.listOfServers", target.getIpAddress());
                    System.out.println(String.format("對[%s]服務配置 mock地址[%s]", service, target.getIpAddress()));
                }
            }

            // 自定義 每個服務的ip地址 服務直連情況
            if (!target.getServicesMap().isEmpty()) {
                Map<String, String> servicesMap = target.getServicesMap();
                for (String key : servicesMap.keySet()) {
                    String ip = servicesMap.get(key);
                    map.put(key.toUpperCase() + ".ribbon.listOfServers", ip);
                    System.out.println(String.format("對[%s]服務配置直連地址[%s]", key, ip));
                }
            }

            // 服務重試切換次數
            map.put("ribbon.MaxAutoRetriesNextServer", 0);
            // 服務重試次數
            map.put("ribbon.MaxAutoRetries", 0);
            // ribbon 連接時間
            map.put("ribbon.connectTimeoutMillis", 10000);
            // ribbon 讀取時間
            map.put("ribbon.readTimeoutMillis", 60000);
            // 對所有操作請求都不重試
            map.put("ribbon.OkToRetryOnAllOperations", false);

            //ribbon不使用eureka上的服務信息
            map.put("ribbon.eureka.enabled", false);
            // 關閉 sleuth 對 feign client 的包裝
            map.put("spring.sleuth.feign.enabled", false);
            // 設置全局的超時時間 hystrix 熔斷超時時間
            map.put("hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds", 60000);
            addOrReplace(environment.getPropertySources(), map);

        }

    }

    private void addOrReplace(MutablePropertySources propertySources,
                              Map<String, Object> map) {
        MapPropertySource target = null;
        if (propertySources.contains(PROPERTY_SOURCE_NAME)) {
            PropertySource<?> source = propertySources.get(PROPERTY_SOURCE_NAME);
            if (source instanceof MapPropertySource) {
                target = (MapPropertySource) source;
                for (String key : map.keySet()) {
                    if (!target.containsProperty(key)) {
                        target.getSource().put(key, map.get(key));
                    }
                }
            }
        }
        if (target == null) {
            target = new MapPropertySource(PROPERTY_SOURCE_NAME, map);
        }
        if (!propertySources.contains(PROPERTY_SOURCE_NAME)) {
            propertySources.addLast(target);
        }
    }
}

 

在 spring.factories 文件中配置 

# Environment Post Processor
org.springframework.boot.env.EnvironmentPostProcessor=\
MockEnvironmentPostProcessor

 

繼承 LoadBalancerFeignClient 類,重寫  execute 方法

@Slf4j
public class MockLoadBalancerFeignClient extends LoadBalancerFeignClient {

    private MockProperties mockProperties;

    private DiscoveryClient discoveryClient;

    private Client delegate;
    private CachingSpringLoadBalancerFactory lbClientFactory;
    private SpringClientFactory clientFactory;

    public MockLoadBalancerFeignClient(Client delegate,
                                       CachingSpringLoadBalancerFactory lbClientFactory,
                                       SpringClientFactory clientFactory, MockProperties mockProperties, DiscoveryClient discoveryClient) {
        super(delegate, lbClientFactory, clientFactory);
        this.delegate = delegate;
        this.lbClientFactory = lbClientFactory;
        this.clientFactory = clientFactory;

        this.mockProperties = mockProperties;
        this.discoveryClient = discoveryClient;
        log.info("mock feign 負載均衡器初始化");
    }

    /**
     * 1. 如果配置 mock全局屬性(默認false),則請求的所有服務都走 mock 服務器
     * 2. 請求的服務在mock服務列表中,則請求走mock服務器
     * 3. 請求的服務不在 mock 服務列表中,則先從直連配置獲取服務信息,沒有則從注冊心上獲取服務信息,請求轉發到注冊中心上的服務
     * 4. 請求的服務不在 mock 服務列表中,也沒有開啟全局 mock 功能,則請求服務走 直連配置和注冊中心相結合
     *
     * @param request
     * @param options
     * @return
     * @throws IOException
     */
    @Override
    public Response execute(Request request, Request.Options options) throws IOException {
        String url = request.url();
        URI uri = URI.create(url);
        String clientName = uri.getHost();
        String[] mockServer = mockProperties.getIpAddress().split(":");
        //請求的客戶名稱轉為小寫
        clientName = clientName.toUpperCase();
        String newUrl = null;

        // 從全局中獲取該服務名稱的配置信息
        IClientConfig clientConfig = this.clientFactory.getClientConfig(clientName);
        if (null != clientConfig && mockProperties.getGlobal()) {
            // 配置當前服務的ip地址信息
//            clientConfig.set(CommonClientConfigKey.ListOfServers, mockProperties.getIpAddress());
            // 獲取當前服務的負載均衡器,對當前服務的負載均衡器添加服務ip地址信息
            if (this.clientFactory.getLoadBalancer(clientName).getAllServers().isEmpty()) {
                this.clientFactory.getLoadBalancer(clientName).
                        addServers(Arrays.asList(new Server(mockServer[0], Integer.parseInt(mockServer[1]))));
            }

            // 重新構建請求 URL
            newUrl = this.getNewRequestUrl(url, clientName);
            log.info("請求的 {} 服務已開啟全局 mock 功能,服務地址:{}", clientName, this.clientFactory.getLoadBalancer(clientName).getAllServers());
        } else {
            //獲取 配置 mock 服務的列表
            String services = this.mockProperties.getServices();
            if (StringUtils.isNotBlank(services)) {
                //  配置 mock 服務的列表轉為小寫
                services = services.toUpperCase();
                // mock 服務列表
                List<String> service = Arrays.asList(services.split(","));
                // 當前服務是否在 mock 服務列表中
                if (service.contains(clientName)) {
                    log.info("請求的 {} 服務在 mock 服務列表中,服務地址:{}", clientName, clientConfig.get(CommonClientConfigKey.ListOfServers));
                    newUrl = this.getNewRequestUrl(url, clientName);
                } else {
                    // 服務直連情況 加 注冊中心 處理
                    newUrl = getServiceInfoFromDiscoveryClient(url, clientName, clientConfig);
                }
            } else {
                if (mockProperties.getServicesMap().isEmpty()) {
                    String msg = "沒有配置 mock 服務列表,也沒有開啟全局mock功能,也沒有配置服務直連,請檢查配置或關閉mock功能";
                    throw new HystrixRuntimeException(HystrixRuntimeException.FailureType.BAD_REQUEST_EXCEPTION, null, msg, null, null);
                }

                // 服務直連情況 加 注冊中心 處理
                newUrl = getServiceInfoFromDiscoveryClient(url, clientName, clientConfig);
            }
        }
        return this.getResponse(request, options, newUrl);
    }

    /**
     * 1.如果有服務直接配置,則直接返回url
     * 2.如果不是直連,則從注冊中上獲取服務信息,並返回url
     *
     * @param url
     * @param clientName
     * @param clientConfig
     * @return
     */
    private String getServiceInfoFromDiscoveryClient(String url, String clientName, IClientConfig clientConfig) {
        String newUrl;

        //服務直連處理
        if (mockProperties.getServicesMap().size() > 0) {
            // 處理一些自定義的服務和ip地址,服務的直連情況
            Set<String> customServiceInfo = mockProperties.getServicesMap().keySet();
            if (customServiceInfo.contains(clientName.toUpperCase())) {
                log.info("請求的 {} 服務在直連列表中,服務地址:{}", clientName, mockProperties.getServicesMap().get(clientName));
                newUrl = url;
                return newUrl;
            }
        }


        if (null == this.discoveryClient) {
            String format = String.format("%s 服務沒有配置在mock列表中,並且也沒有開啟注冊中心功能,請檢查配置", clientName);
            throw new HystrixRuntimeException(HystrixRuntimeException.FailureType.COMMAND_EXCEPTION, null, format, null, null);
        }

        // 獲取 服務名的 服務信息
        List<ServiceInstance> instances = this.discoveryClient.getInstances(clientName);
        String host;
        if (null == instances || instances.isEmpty()) {
            host = String.format("%s 服務沒有配置在mock列表中,也沒有注冊在住冊中心上,請檢查配置", clientName);
            throw new HystrixRuntimeException(HystrixRuntimeException.FailureType.COMMAND_EXCEPTION, null, host, null, null);
        }

        // 獲取 服務在 注冊中心的地址信息
        host = instances.get(0).getHost() + ":" + instances.get(0).getPort();
        log.info("請求的 {} 服務在注則中心上,服務地址:{}", clientName, host);
        newUrl = url;

        if (null != clientConfig) {
//            clientConfig.set(CommonClientConfigKey.ListOfServers, host);
            // 獲取當前服務的負載均衡器,對當前服務的負載均衡器添加服務ip地址信息
            if (this.clientFactory.getLoadBalancer(clientName).getAllServers().isEmpty()) {
                this.clientFactory.getLoadBalancer(clientName).
                        addServers(Arrays.asList(new Server(instances.get(0).getHost(), instances.get(0).getPort())));
            }

        }
        return newUrl;
    }

    /**
     * 請求響應
     *
     * @param request
     * @param options
     * @param newUrl
     * @return
     * @throws IOException
     */
    private Response getResponse(Request request, Request.Options options, String newUrl) throws IOException {
        //重新構建 request 對象
        Request newRequest = Request.create(request.method(),
                newUrl, request.headers(), request.body(),
                request.charset());

        return super.execute(newRequest, options);
    }

    /**
     * 修改請求 url
     *
     * @param url
     * @param clientName
     * @return
     */
    private String getNewRequestUrl(String url, String clientName) {
        StringBuilder sb = new StringBuilder();
        sb.append(clientName);

        String mockServerUrl = mockProperties.getMockServerUrl();
        if (mockServerUrl.endsWith("/")) {
            sb.append(mockServerUrl);
        } else {
            sb.append(mockServerUrl).append("/");
        }
        sb.append(clientName.toLowerCase());

        String newUrl = url.replaceFirst(clientName, sb.toString());

        log.info("mock 服務重新構建請求 URL 地址:{}", newUrl);
        return newUrl;
    }
}

  

配置自化配置類

@Slf4j
@ConditionalOnProperty(prefix = MockProperties.MOCK_PREFIX, name = "enabled", havingValue = "true")
@EnableConfigurationProperties(value = {MockProperties.class})
@Configuration
public class MockAutoConfiguration {


    @Bean
    public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
                              SpringClientFactory clientFactory, @Autowired(required = false) DiscoveryClient discoveryClient, MockProperties mockProperties) {

        return new MockLoadBalancerFeignClient(new Client.Default(null, null),
                cachingFactory, clientFactory, mockProperties, discoveryClient);
    }

    @Bean
    public MockFeignInterceptor mockFeignInterceptor() {
        return new MockFeignInterceptor();
    }
}

 

在 spring.factories 中配置

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
MockAutoConfiguration

  

自定義相關屬性配置和提示,關於mock的屬性配置

@ConfigurationProperties(prefix = MockProperties.MOCK_PREFIX, ignoreUnknownFields = true)
public class MockProperties {
    public static final String MOCK_PREFIX = "mock.server";

    /**
     * 開啟mock ,true:開啟  false:關閉
     */
    @Getter
    @Setter
    private boolean enabled = false;

    /**
     * mock 服務的ip地址或域名 例:10.12.141.146:18081
     */
    @Getter
    @Setter
    private String ipAddress;

    /**
     * 如果每個服務的 mock server 的地址都一樣的使用該配置,多個服務以 ,號 隔開 例:order-service,user-service
     */
    @Getter
    @Setter
    private String services;

    /**
     * 如果每個服務的 mock server 地址不一樣,使用該配置,key:服務名  value: ip地址 ,例
     */
    @Getter
    @Setter
    private Map<String, String> servicesMap = new ConcurrentHashMap<>();

    /**
     * mock server 服務url
     */
    @Getter
    @Setter
    private String mockServerUrl;


    /**
     * 是否需要所有服務都用 mock
     */


    private boolean global = false;

    public boolean getGlobal() {
        return global;
    }

    public void setGlobal(boolean global) {
        this.global = global;
    }
}

  

 

Mock server 相關屬性

當開啟mock 功能時,所有的 feign 請求都會帶上請求頭: X-Mock-Application: true 

# 開啟 mock 功能
mock.server.enabled=true
# mock 服務器ip地址和端口
mock.server.ip-address=123.90.8.1:18820
# mock 服務器的 url
mock.server.mock-server-url=/admin/mock
# mock 服務的列表,這里填寫 feign 服務的服務名,多個以 ,號隔開,並大寫
mock.server.services=PLATFORM-SERVICE,HELLO-SERVICE-1

如果項目中需要所有服務都要使用 mock 功能,則添加下面的屬性

# 開啟全局 mock 功能,也就是項目中所有的 feign服務都需要 mock
mock.server.global=true

如果項目中需要服務直連,則添加下面的屬性

# user-service 服務的直連ip地址和端口
mock.server.services-map.user-service=192.168.111.10:9010
# cache-service 服務的直連ip地址和端口
mock.server.services-map.cache-service=10.10.90.23:9090

 
         

關閉全局 mock 功能
mock.server.global=false

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM