SpringBoot自动装配

SpringBoot自动装配

  • 1. 前言
  • 2. SpringBoot如何实现自动化配置的?
    • 2.1. 简单介绍
    • 2.2. 深入核心
    • 2.3. 截图实例
  • 3. 自定义Starter
    • 3.1. 创建Test工程并导入依赖
    • 3.2. 创建TestConfiguration
    • 3.3. 创建META-INF/spring.factories
    • 3.4. 执行install,加载进仓库
    • 3.5. 创建另一个工程TestStarter,并导入依赖
    • 3.6. 编写主体类
    • 3.7. 测试
  • 4.自定义Loadbalancer负载路由规则
    • 4.1. 前言
      • 4.1.1. 背景
      • 4.1.2. 解决方案
    • 4.2. 深入Loadbalancer负载路由规则
    • 4.3. 自定义loadbalancer负载均衡策略
  • 5. 一些相关注解

1. 前言

SpringBoot自动化配置通常可以在SpringBoot项目中帮我们节省大量配置类的时间,方便了我们快速开发项目。这里我以SpringBoot:2.4.3为例子进行自动化配置解读。

2. SpringBoot如何实现自动化配置的?

2.1. 简单介绍

大概可以把@SpringBootApplication看作是:@Configuration@EnableAutoConfiguration@ComponentScan注解的集合。根据SpringBoot官网,这三个注解的作用分别是:

  • @SpringBootConfiguration:允许在上下文中注册额外的 bean 或导入其他配置类
  • @EnableAutoConfiguration:启用SpringBoot的自动配置机制
  • @ComponentScan:扫描被@Component (@Service,@Controller)注解的bean,注解默认会扫描启动类所在的包下所有的类 ,可以自定义不扫描某些bean。如下图所示,容器中将排除TypeExcludeFilterAutoConfigurationExcludeFilter
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
		// @ComponentScan主要负责对`SpringBoot`**主程序类**下所在包下的`bean`实例进行扫描

2.2. 深入核心

SpringBoot自动化配置中,最为核心的则为@EnableAutoConfiguration,如下

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage 
@Import(AutoConfigurationImportSelector.class) // 自动化配置主类
public @interface EnableAutoConfiguration {}

其中@AutoConfigurationPackage主要负责对SpringBoot主程序类下所在包下的bean实例(扫描后的)进行注册,注入IOC

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class) //对之前扫描的包中的实例进行bean注册,注入IOC中
public @interface AutoConfigurationPackage {}

@EnableAutoConfiguration通过注解@Import()实现对类的自动装配

  • AutoConfigurationImportSelector实现了DeferredImportSelector:延迟导入选择器(它继承于ImportSelector),实现了对类的延迟导入,也实现了@ConditionalOnXXX的应用
  • 它们重写了父类ImportSelectorselectImports方法,用于实现对依赖的自动导入功能
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {}

public interface DeferredImportSelector extends ImportSelector {}

// 真正自动化配置需要执行的方法
public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

然后selectImports调用getAutoConfigurationEntry()获取导入依赖的所有自动化配置并封装成实体返回

	protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
		//1、判断是否需要自动化配置,默认是true,可在application.yml中设置
		if (!isEnabled(annotationMetadata)) { 
			return EMPTY_ENTRY;
		}
    	//2、获取指定metadata主类下排除的auto-configuration的exclude和excludeName
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
    	//3、获取需要自动装配的所有配置类,读取META-INF/spring.factories
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		//4、去除重复配置
		configurations = removeDuplicates(configurations);
    	//5、根据注解@ConditionalOnXXX获取需要排除的一些自动化配置
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    	//6、根据条件排除一些自动化配置
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
    	//7、根据annotationMetadata获取到META-INF/spring-autoconfigure-metadata.properties
		// 这个文件主要类似于规范了自动化配置的一些@ConditionalOnXXX的应用,这里用于过滤这些配置
		configurations = getConfigurationClassFilter().filter(configurations);
        //8、添加listener事件监听
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

2.3. 截图实例

  1. spring.factories:这里主要是一些key:value的形式保存数据
    SpringBoot自动装配_第1张图片
  2. spring-autoconfigure-metadata.properties:规定了一些额外的ConditionalOnXXX的规则,不在注解类上直接@ConditionalOnXXX的规则
    SpringBoot自动装配_第2张图片

3. 自定义Starter

3.1. 创建Test工程并导入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>Test</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>2.4.3</version>
        </dependency>
    </dependencies>

</project>

3.2. 创建TestConfiguration

package org.example.test;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author Deng HongWei
 * @version 1.0
 * @date 2023/12/25
 */
@Configuration
public class TestConfiguration {

    @Bean
    @ConditionalOnClass(Integer.class)
    public Integer myInteger(){
        return 1;
    }
}

3.3. 创建META-INF/spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.example.test.TestConfiguration

3.4. 执行install,加载进仓库

SpringBoot自动装配_第3张图片

3.5. 创建另一个工程TestStarter,并导入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>TestStarter</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.4.3</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <version>2.4.3</version>
        </dependency>

        <dependency>
            <groupId>org.example</groupId>
            <artifactId>Test</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>

3.6. 编写主体类

@SpringBootApplication
public class MyTestApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyTestApplication.class, args);
    }
}

3.7. 测试

package org.example.test;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

/**
 * @author Deng HongWei
 * @version 1.0
 * @date 2023/12/25
 */
@SpringBootTest
public class MyTestDO {

    @Autowired
    private Integer myTest;

    @Test
    public void testMy(){
        System.out.println(myTest);
    }
}

4.自定义Loadbalancer负载路由规则

4.1. 前言

4.1.1. 背景

在开发微服务程序时,如果一个服务又多个人合作开发,比如GateWay网关转发服务等,即服务GateWayAB两人同时开发,如果不做特殊处理,若使用nacos,则nacos上就会生成A1A2两个服务实例。这里就拿GateWay网关服务举例:如果服务是使用loadbalancer依赖用于负载均衡的话,则会直接使用默认的随机算法,A的服务发的请求就可能随机会发给B,但是AB都只是局域网,没有暴露IP(这里需要暴露IP可以用内网穿透做),这样实际上AB互相发请求是互相接收不到的,这就会扰乱开发效率,极大影响开发进度

4.1.2. 解决方案

  1. 自定义负载均衡策略:使用什么负载均衡依赖就自定义负载均衡策略去覆盖已有的默认自动化负载均衡配置
  2. 在服务与发现组件中使用namespace:比如使用nacos:namespace进行环境隔离
  3. 使用metadataheader进行数据管理:比如使用nacos:metadata,前端将自定义的元数据塞入header中并定义路由规则,每个服务实例都对应着一个自定义的元数据metadata,根据header自然会找到对应想要转发的服务实例。
  4. 使用云服务器进行内网穿透:使用utools软件中的内网穿透插件工具,将自己本地端口映射到云服务器中,在网关中进行IP拦截
  5. 等等…

4.2. 深入Loadbalancer负载路由规则

SpringBoot自动装配_第4张图片对于Loadbalancer负载路由规则,主要由以上五个类组成

  • LoadBalancerClientFactory: 创建客户端、负载均衡器和客户端配置实例的工厂
  • LoadBalancerClientConfiguration: 负载均衡器配置类
  • ReactorServiceInstanceLoadBalancer:响应式负载路由接口
  • RandomLoadBalancer:随机路由的负载均衡器,实现ReactorServiceInstanceLoadBalancer接口
  • RoundRobinLoadBalancer:轮询路由负载均衡器,实现ReactorServiceInstanceLoadBalancer接口
    我们就是要实现ReactorServiceInstanceLoadBalancer接口,开发一个基于naocs元数据的轮询负载器。现在目标明确了,下面从代码入手看看我们应该如何实现。
  1. LoadBalancerClientFactory

    // LoadBalancerClientFactory默认注入LoadBalancerClientConfiguration作为负载均衡器的工厂类
    public LoadBalancerClientFactory() {
    	super(LoadBalancerClientConfiguration.class, NAMESPACE, PROPERTY_NAME);
    }
    
  2. LoadBalancerClientConfiguration

    // 可以看出spring cloud balancer默认的负载器是RoundRobinLoadBalancer
    // 并且因为方法中标有@ConditionalOnMissingBean注解,所以我们可以扩展一个自己的ReactorLoadBalancer
    // 注:这里注入的是 LazyProvider,这主要因为在注册这个Bean的时候依赖的其他 Bean可能还没有被加载,所以利用 LazyProvider机制,防止注入报错
    @Bean
    @ConditionalOnMissingBean
    public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(
    		Environment environment,
    		LoadBalancerClientFactory loadBalancerClientFactory) {
    	String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
    	return new RoundRobinLoadBalancer(loadBalancerClientFactory.getLazyProvider(name,
    			ServiceInstanceListSupplier.class), name);
    }
    
  3. RoundRobinLoadBalancer

    public Mono<Response<ServiceInstance>> choose(Request request) {
        // 注入的时候注入的是Lazy Provider,这里取出实际的类 ServiceInstanceListSupplier
    	ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
    			.getIfAvailable(NoopServiceInstanceListSupplier::new);
        // 获取实例列表 并从列表中选择一个实例
    	return supplier.get(request).next()
    			.map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));
    }
    private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier,
    		List<ServiceInstance> serviceInstances) {
    	Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances);
        // ServiceInstanceListSupplier实现了SelectedInstanceCallback的话,则执行下面的逻辑进行回调。SelectedInstanceCallback就是每次负载均衡器选择实例之后进行的回调方法
    	if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
    		((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
    	}
    	return serviceInstanceResponse;
    }
    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
    	if (instances.isEmpty()) {
    		if (log.isWarnEnabled()) {
    			log.warn("No servers available for service: " + serviceId);
    		}
    		return new EmptyResponse();
    	}
        // TODO 加入一些自己的路由规则来获取ServiceInstance
        
    	// Ignore the sign bit, this allows pos to loop sequentially from 0 to
    	// Integer.MAX_VALUE
        // 循环规则:原子自增取绝对值
    	int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;
    	// 取模运算
        ServiceInstance instance = instances.get(pos % instances.size());
    	return new DefaultResponse(instance);
    }
    

    看完上面三个类代码,已经不需要考虑RoundRobinLoadBalancer的实现了,我们只需要基于RoundRobinLoadBalancer代码魔改出一套自己的路由规则即可。由此我们需要编写配置类注入自己的ReactorLoadBalancer,但是就剩下一个问题:我们的配置类该如何注入?

  4. LoadBalancerAutoConfiguration

    @Configuration(proxyBeanMethods = false)
    @LoadBalancerClients
    @EnableConfigurationProperties(LoadBalancerProperties.class)
    @AutoConfigureBefore({ ReactorLoadBalancerClientAutoConfiguration.class,
    		LoadBalancerBeanPostProcessorAutoConfiguration.class,
    		ReactiveLoadBalancerAutoConfiguration.class })
    public class LoadBalancerAutoConfiguration {
    
    	private final ObjectProvider<List<LoadBalancerClientSpecification>> configurations;
    
    	public LoadBalancerAutoConfiguration(
    			ObjectProvider<List<LoadBalancerClientSpecification>> configurations) {
    		this.configurations = configurations;
    	}
    
    	@Bean
    	@ConditionalOnMissingBean
    	public LoadBalancerZoneConfig zoneConfig(Environment environment) {
    		return new LoadBalancerZoneConfig(
    				environment.getProperty("spring.cloud.loadbalancer.zone"));
    	}
    
    	@ConditionalOnMissingBean
    	@Bean
    	// 读取所有的LoadBalancerClientSpecification作为LoadBalancerClientFactory的配置,并初始化FactoryBean实例
    	public LoadBalancerClientFactory loadBalancerClientFactory() {
    		LoadBalancerClientFactory clientFactory = new LoadBalancerClientFactory();
    		clientFactory.setConfigurations(
    				this.configurations.getIfAvailable(Collections::emptyList));
    		return clientFactory;
    	}
    }
    

    这些LoadBalancerClientSpecification是如何创建的?我也没有好的办法,在这个jar包中全局搜索LoadBalancerClientSpecification类,最后让我定位到LoadBalancerClientConfigurationRegistrar,其实在@LoadBalancerClients中也有对此处进行信息进行描述

  5. LoadBalancerClients

    @Configuration(proxyBeanMethods = false)
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.TYPE })
    @Documented
    @Import(LoadBalancerClientConfigurationRegistrar.class)
    public @interface LoadBalancerClients {
    
    	LoadBalancerClient[] value() default {};
    
    	/**
    	 * {@link LoadBalancerClientConfigurationRegistrar} creates a
    	 * {@link LoadBalancerClientSpecification} with this as an argument. These in turn are
    	 * added as default contexts in {@link LoadBalancerClientFactory}. Configuration
    	 * defined in these classes are used as defaults if values aren't defined via
    	 * {@link LoadBalancerClient#configuration()}
    	 * @return classes for default configurations
    	 */
    	Class<?>[] defaultConfiguration() default {};
    
    }
    
  6. LoadBalancerClientConfigurationRegistrar

    public class LoadBalancerClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar {
    	
        // 代码省略
    
    	@Override
    	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    		// 这里获取LoadBalancerClients元数据
            Map<String, Object> attrs = metadata.getAnnotationAttributes(LoadBalancerClients.class.getName(), true);
    		if (attrs != null && attrs.containsKey("value")) {
    			AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value");
    			for (AnnotationAttributes client : clients) {
    				registerClientConfiguration(registry, getClientName(client), client.get("configuration"));
    			}
    		}
            // 负载器默认配置类
    		if (attrs != null && attrs.containsKey("defaultConfiguration")) {
    			String name;
    			if (metadata.hasEnclosingClass()) {
    				name = "default." + metadata.getEnclosingClassName();
    			}
    			else {
    				name = "default." + metadata.getClassName();
    			}
    			registerClientConfiguration(registry, name, attrs.get("defaultConfiguration"));
    		}
            // 获取LoadBalancerClient元数据
    		Map<String, Object> client = metadata.getAnnotationAttributes(LoadBalancerClient.class.getName(), true);
            // 这个方法代码省略的部分  其实就是获取服务名称且LoadBalancerClient注解不指定名称会抛出IllegalStateException
    		String name = getClientName(client);
    		if (name != null) {
    			registerClientConfiguration(registry, name, client.get("configuration"));
    		}
    	}
        
        private static void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
    			Object configuration) {
            // buider模式构建一个LoadBalancerClientSpecification对象
    		BeanDefinitionBuilder builder = BeanDefinitionBuilder
    				.genericBeanDefinition(LoadBalancerClientSpecification.class);
    		builder.addConstructorArgValue(name);
            // 指定负载均衡器的配置类
    		builder.addConstructorArgValue(configuration);
            // 注册对象
    		registry.registerBeanDefinition(name + ".LoadBalancerClientSpecification", builder.getBeanDefinition());
    	}
    }
    

经过我们不懈的调试,这块加载类加载流程也正如我们想的那样,我们可以在启动类上定义@LoadBalancerClients或者@LoadBalancerClient注解,指定负载均衡器的配置类,进而自动装配我的负载器完成需求。

4.3. 自定义loadbalancer负载均衡策略

  1. LoadBalancerTypeEnum:定义负载均衡方式的枚举

    public enum LoadBalancerTypeEnum {
     
        /**
         * 开发环境,获取自己的服务
         */
        DEV,
     
        /**
         * 网关,根据请求地址获取对应的服务
         */
        GATEWAY,
     
        /**
         * 轮循
         */
        ROUND_ROBIN,
     
        /**
         * 随机
         */
        RANDOM;
     
    }
    
  2. LoadBalanceProperties:添加配置类

    import lombok.Data;
    import org.springframework.boot.context.properties.ConfigurationProperties;
     
    /**
     * 负载均衡配置项
     */
    @Data
    @ConfigurationProperties(prefix = "spring.cloud.loadbalancer")
    public class LoadBalanceProperties {
     
        private LoadBalancerTypeEnum type = LoadBalancerTypeEnum.ROUND_ROBIN;
     
    }
    
  3. CustomSpringCloudLoadBalancer

    import cn.hutool.core.convert.Convert;
    import cn.hutool.core.lang.TypeReference;
    import com.ruoyi.common.core.utils.StringUtils;
    import com.ruoyi.common.core.utils.ip.IpUtils;
    import com.ruoyi.common.loadbalance.config.LoadBalancerTypeEnum;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.ObjectProvider;
    import org.springframework.cloud.client.ServiceInstance;
    import org.springframework.cloud.client.loadbalancer.DefaultRequest;
    import org.springframework.cloud.client.loadbalancer.DefaultResponse;
    import org.springframework.cloud.client.loadbalancer.EmptyResponse;
    import org.springframework.cloud.client.loadbalancer.Request;
    import org.springframework.cloud.client.loadbalancer.RequestData;
    import org.springframework.cloud.client.loadbalancer.RequestDataContext;
    import org.springframework.cloud.client.loadbalancer.Response;
    import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
    import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
    import org.springframework.cloud.loadbalancer.core.SelectedInstanceCallback;
    import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
    import org.springframework.http.HttpHeaders;
    import reactor.core.publisher.Mono;
     
    import java.util.List;
    import java.util.Objects;
    import java.util.Random;
    import java.util.concurrent.ThreadLocalRandom;
    import java.util.concurrent.atomic.AtomicInteger;
     
    /**
     * 自定义 SpringCloud 负载均衡算法
     * 负载均衡算法的默认实现是 {@link org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer}
     *
     */
    @Slf4j
    public class CustomSpringCloudLoadBalancer implements ReactorServiceInstanceLoadBalancer {
     
        private final String serviceId;
        private final AtomicInteger position;
        private final LoadBalancerTypeEnum type;
        private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
     
        public CustomSpringCloudLoadBalancer(String serviceId,
                                             LoadBalancerTypeEnum type,
                                             ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider) {
            this(serviceId, new Random().nextInt(1000), type, serviceInstanceListSupplierProvider);
        }
     
        public CustomSpringCloudLoadBalancer(String serviceId,
                                             int seedPosition,
                                             LoadBalancerTypeEnum type,
                                             ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider) {
            this.serviceId = serviceId;
            this.position = new AtomicInteger(seedPosition);
            this.type = type;
            this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
        }
     
        @Override
        public Mono<Response<ServiceInstance>> choose(Request request) {
            ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
            return supplier.get(request).next().map(serviceInstances -> processInstanceResponse(request, supplier, serviceInstances));
        }
     
        private Response<ServiceInstance> processInstanceResponse(Request request,
                                                                  ServiceInstanceListSupplier supplier,
                                                                  List<ServiceInstance> serviceInstances) {
            Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(request, serviceInstances);
            if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
                ((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
            }
            return serviceInstanceResponse;
        }
     
        private Response<ServiceInstance> getInstanceResponse(Request request, List<ServiceInstance> instances) {
            if (instances.isEmpty()) {
                if (log.isWarnEnabled()) {
                    log.warn("No servers available for service: " + serviceId);
                }
                return new EmptyResponse();
            }
     
            if (Objects.equals(type, LoadBalancerTypeEnum.ROUND_ROBIN)){
                return this.getRoundRobinInstance(instances);
            }else if (Objects.equals(type, LoadBalancerTypeEnum.RANDOM)){
                return this.getRandomInstance(instances);
            }else if (Objects.equals(type, LoadBalancerTypeEnum.DEV)){
                return this.getDevelopmentInstance(instances);
            }else if (Objects.equals(type, LoadBalancerTypeEnum.GATEWAY)){
                return this.getGatewayDevelopmentInstance(request, instances);
            }
            return this.getRoundRobinInstance(instances);
        }
     
        /**
         * 获取网关本机实例
         *
         * @param instances 实例
         * @return {@link Response }<{@link ServiceInstance }>
         * @author : lwq
         * @date : 2022-12-15 14:22:13
         */
        private Response<ServiceInstance> getGatewayDevelopmentInstance(Request request, List<ServiceInstance> instances) {
     
            //把request转为默认的DefaultRequest,从request中拿到请求的ip信息,再选择ip一样的微服务
            DefaultRequest<RequestDataContext> defaultRequest = Convert.convert(new TypeReference<DefaultRequest<RequestDataContext>>() {}, request);
            RequestDataContext context = defaultRequest.getContext();
            RequestData clientRequest = context.getClientRequest();
            HttpHeaders headers = clientRequest.getHeaders();
            String requestIp = IpUtils.getIpAddressFromHttpHeaders(headers);
            log.debug("客户端请求gateway的ip:{}", requestIp);
     
            //先取得和本地ip一样的服务,如果没有则按默认来取
            for (ServiceInstance instance : instances) {
                String currentServiceId = instance.getServiceId();
                String host = instance.getHost();
                log.debug("注册服务:{},ip:{}", currentServiceId, host);
                if (StringUtils.isNotEmpty(host) && StringUtils.equals(requestIp, host)) {
                    return new DefaultResponse(instance);
                }
            }
            return getRoundRobinInstance(instances);
        }
     
     
        /**
         * 获取本机实例
         *
         * @param instances 实例
         * @return {@link Response }<{@link ServiceInstance }>
         * @author : lwq
         * @date : 2022-12-15 14:22:13
         */
        private Response<ServiceInstance> getDevelopmentInstance(List<ServiceInstance> instances) {
            //获取本机ip
            String hostIp = IpUtils.getHostIp();
            log.debug("本机Ip:{}", hostIp);
     
            //先取得和本地ip一样的服务,如果没有则按默认来取
            for (ServiceInstance instance : instances) {
                String currentServiceId = instance.getServiceId();
                String host = instance.getHost();
                log.debug("注册服务:{},ip:{}", currentServiceId, host);
                if (StringUtils.isNotEmpty(host) && StringUtils.equals(hostIp, host)) {
                    return new DefaultResponse(instance);
                }
            }
            return getRoundRobinInstance(instances);
        }
     
        /**
         * 使用随机算法
         * 参考{link {@link org.springframework.cloud.loadbalancer.core.RandomLoadBalancer}}
         *
         * @param instances 实例
         * @return {@link Response }<{@link ServiceInstance }>
         * @author : lwq
         * @date : 2022-12-15 13:32:11
         */
        private Response<ServiceInstance> getRandomInstance(List<ServiceInstance> instances) {
            int index = ThreadLocalRandom.current().nextInt(instances.size());
            ServiceInstance instance = instances.get(index);
            return new DefaultResponse(instance);
        }
     
        /**
         * 使用RoundRobin机制获取节点
         *
         * @param instances 实例
         * @return {@link Response }<{@link ServiceInstance }>
         * @author : lwq
         * @date : 2022-12-15 13:28:31
         */
        private Response<ServiceInstance> getRoundRobinInstance(List<ServiceInstance> instances) {
            // 每一次计数器都自动+1,实现轮询的效果
            int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;
            ServiceInstance instance = instances.get(pos % instances.size());
            return new DefaultResponse(instance);
        }
    }
    
  4. IpUtils

    import java.net.InetAddress;
    import java.net.URL;
    import java.net.UnknownHostException;
    import java.util.List;
    import java.util.Objects;
    import javax.servlet.http.HttpServletRequest;
     
    import cn.hutool.core.collection.CollectionUtil;
    import cn.hutool.core.util.URLUtil;
    import org.springframework.http.HttpHeaders;
     
    /**
     * 获取IP方法
     */
    public class IpUtils{
     
        /**
         * 获取IP地址
         *
         * @return 本地IP地址
         */
        public static String getHostIp(){
            try{
                return InetAddress.getLocalHost().getHostAddress();
            }catch (UnknownHostException e){
            }
            return "127.0.0.1";
        }
     
        /**
         * 获取客户端IP
         *
         * @param httpHeaders 请求头
         * @return IP地址
         */
        public static String getIpAddressFromHttpHeaders(HttpHeaders httpHeaders){
            if (httpHeaders == null){
                return "unknown";
            }
            //前端请求自定义请求头,转发到哪个服务
            List<String> ipList = httpHeaders.get("forward-to");
            String ip = CollectionUtil.get(ipList, 0);
            //请求自带的请求头
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
                ipList = httpHeaders.get("x-forwarded-for");
                ip = CollectionUtil.get(ipList, 0);
            }
            //从referer获取请求的ip地址
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
                //从referer中获取请求的ip地址
                List<String> refererList = httpHeaders.get("referer");
                String referer = CollectionUtil.get(refererList, 0);
                URL url = URLUtil.url(referer);
                if (Objects.nonNull(url)){
                    ip = url.getHost();
                }else {
                    ip = "unknown";
                }
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
                ipList = httpHeaders.get("Proxy-Client-IP");
                ip = CollectionUtil.get(ipList, 0);
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
                ipList = httpHeaders.get("X-Forwarded-For");
                ip = CollectionUtil.get(ipList, 0);
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
                ipList = httpHeaders.get("WL-Proxy-Client-IP");
                ip = CollectionUtil.get(ipList, 0);
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
                ipList = httpHeaders.get("X-Real-IP");
                ip = CollectionUtil.get(ipList, 0);
            }
            return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip);
        }
    }
    

    vuerequest.js中可以这么写:config.headers['forward-to'] = '192.168.XX.XX'

  5. LoadBalancerClientConfiguration

    import com.ruoyi.common.loadbalance.core.CustomSpringCloudLoadBalancer;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.cloud.client.ServiceInstance;
    import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
    import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
    import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.env.Environment;
     
    /**
     * 自定义负载均衡客户端配置
     *
     */
    @SuppressWarnings("all")
    @Configuration(proxyBeanMethods = false)
    @EnableConfigurationProperties(LoadBalanceProperties.class)
    public class CustomLoadBalanceClientConfiguration {
     
        @Bean
        @ConditionalOnBean(LoadBalancerClientFactory.class)
        public ReactorLoadBalancer<ServiceInstance> customLoadBalancer(LoadBalanceProperties loadBalanceProperties,
                                                                       Environment environment,
                                                                       LoadBalancerClientFactory loadBalancerClientFactory) {
            String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
            return new CustomSpringCloudLoadBalancer(name, loadBalanceProperties.getType(),
                    loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class));
        }
    }
    
  6. CustomLoadBalanceAutoConfiguration

    import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
     
    /**
     * 自定义负载均衡自动配置
     *
     */
    @LoadBalancerClients(defaultConfiguration = CustomLoadBalanceClientConfiguration.class)
    public class CustomLoadBalanceAutoConfiguration {
     
    }
    
  7. 使用

    spring.cloud.loadbalancer.type=dev

5. 一些相关注解

SpringBoot自动装配_第5张图片

  • 条件依赖的注解是可以直接在SpringBoot代码中直接使用的,而先后顺序的注解则只能在jar包的源文件中生效
  • 下面就拿@ConditionalOnMissingBean进行举例说明,顺便浅谈下@Bean工作状态
  1. ExampleController
    @RestController
    public class ExampleController {
        private static final Logger log = LoggerFactory.getLogger(ExampleController.class);
        // 这里如果放开注释,如果注入两个相同返回值(父类接口相同、实现类不同)就会报错
    //    @Autowired
    //    private UserService userService;
    
        @RequestMapping("/")
        public String home() {
    //        userService.outPutMyData();
            log.info("Hello world!");
            return "Hello World!";
        }
    }
    
  2. UserConfigure
    @Configuration
    public class UserConfigure {
    
        @Bean
        public UserService createBean1() {
            return new UserServiceImpl1();
        }
    
        @Bean
        // 如果在ExampleController中放开对应接口注释,这里就可以进行该注释,即可避免报错
    //    @ConditionalOnMissingBean
        public UserService createBean2() {
            return new UserServiceImpl2();
        }
    }
    
  3. 没使用@ConditionalOnMissingBean的报错信息
    SpringBoot自动装配_第6张图片
  4. 使用了@ConditionalOnMissingBean的打印信息
    SpringBoot自动装配_第7张图片

你可能感兴趣的:(spring框架,spring,boot,java,spring)