zoukankan      html  css  js  c++  java
  • 对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

     

  • 相关阅读:
    k8s蓝绿
    nginx总结
    promethues监控 之 TCP连接数
    制作私有ssl证书
    redis命令
    zabbix主机自动发现
    Kubernetes各组件服务重启
    linxu下常用命令
    encodeURIComponent
    查询条件
  • 原文地址:https://www.cnblogs.com/zhangjianbin/p/9245023.html
Copyright © 2011-2022 走看看