SpringBoot自动化配置通常可以在SpringBoot项目中帮我们节省大量配置类的时间,方便了我们快速开发项目。这里我以SpringBoot:2.4.3为例子进行自动化配置解读。
大概可以把@SpringBootApplication
看作是:@Configuration
、@EnableAutoConfiguration
、@ComponentScan
注解的集合。根据SpringBoot
官网,这三个注解的作用分别是:
@SpringBootConfiguration
:允许在上下文中注册额外的 bean 或导入其他配置类@EnableAutoConfiguration
:启用SpringBoot
的自动配置机制@ComponentScan
:扫描被@Component (@Service,@Controller)
注解的bean
,注解默认会扫描启动类所在的包下所有的类 ,可以自定义不扫描某些bean
。如下图所示,容器中将排除TypeExcludeFilter
和AutoConfigurationExcludeFilter
。@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`实例进行扫描
在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
的应用ImportSelector
的selectImports
方法,用于实现对依赖的自动导入功能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);
}
spring.factories
:这里主要是一些key:value
的形式保存数据spring-autoconfigure-metadata.properties
:规定了一些额外的ConditionalOnXXX
的规则,不在注解类上直接@ConditionalOnXXX
的规则<?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>
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;
}
}
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.example.test.TestConfiguration
<?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>
@SpringBootApplication
public class MyTestApplication {
public static void main(String[] args) {
SpringApplication.run(MyTestApplication.class, args);
}
}
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);
}
}
在开发微服务程序时,如果一个服务又多个人合作开发,比如
GateWay
网关转发服务等,即服务GateWay
由A
、B
两人同时开发,如果不做特殊处理,若使用nacos
,则nacos
上就会生成A1
、A2
两个服务实例。这里就拿GateWay
网关服务举例:如果服务是使用loadbalancer
依赖用于负载均衡的话,则会直接使用默认的随机算法,A
的服务发的请求就可能随机会发给B
,但是A
、B
都只是局域网,没有暴露IP
(这里需要暴露IP
可以用内网穿透做),这样实际上A
与B
互相发请求是互相接收不到的,这就会扰乱开发效率,极大影响开发进度
namespace
:比如使用nacos:namespace
进行环境隔离metadata
、header
进行数据管理:比如使用nacos:metadata
,前端将自定义的元数据塞入header
中并定义路由规则,每个服务实例都对应着一个自定义的元数据metadata
,根据header
自然会找到对应想要转发的服务实例。utools
软件中的内网穿透插件工具,将自己本地端口映射到云服务器中,在网关中进行IP
拦截对于Loadbalancer负载路由规则,主要由以上五个类组成
LoadBalancerClientFactory
: 创建客户端、负载均衡器和客户端配置实例的工厂LoadBalancerClientConfiguration
: 负载均衡器配置类ReactorServiceInstanceLoadBalancer
:响应式负载路由接口RandomLoadBalancer
:随机路由的负载均衡器,实现ReactorServiceInstanceLoadBalancer
接口RoundRobinLoadBalancer
:轮询路由负载均衡器,实现ReactorServiceInstanceLoadBalancer
接口ReactorServiceInstanceLoadBalancer
接口,开发一个基于naocs
元数据的轮询负载器。现在目标明确了,下面从代码入手看看我们应该如何实现。LoadBalancerClientFactory
// LoadBalancerClientFactory默认注入LoadBalancerClientConfiguration作为负载均衡器的工厂类
public LoadBalancerClientFactory() {
super(LoadBalancerClientConfiguration.class, NAMESPACE, PROPERTY_NAME);
}
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);
}
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
,但是就剩下一个问题:我们的配置类该如何注入?
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
中也有对此处进行信息进行描述
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 {};
}
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
注解,指定负载均衡器的配置类,进而自动装配我的负载器完成需求。
LoadBalancerTypeEnum
:定义负载均衡方式的枚举
public enum LoadBalancerTypeEnum {
/**
* 开发环境,获取自己的服务
*/
DEV,
/**
* 网关,根据请求地址获取对应的服务
*/
GATEWAY,
/**
* 轮循
*/
ROUND_ROBIN,
/**
* 随机
*/
RANDOM;
}
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;
}
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);
}
}
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);
}
}
在vue
的request.js
中可以这么写:config.headers['forward-to'] = '192.168.XX.XX'
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));
}
}
CustomLoadBalanceAutoConfiguration
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
/**
* 自定义负载均衡自动配置
*
*/
@LoadBalancerClients(defaultConfiguration = CustomLoadBalanceClientConfiguration.class)
public class CustomLoadBalanceAutoConfiguration {
}
使用
spring.cloud.loadbalancer.type=dev
- 条件依赖的注解是可以直接在
SpringBoot
代码中直接使用的,而先后顺序的注解则只能在jar
包的源文件中生效- 下面就拿
@ConditionalOnMissingBean
进行举例说明,顺便浅谈下@Bean
工作状态
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!";
}
}
UserConfigure
@Configuration
public class UserConfigure {
@Bean
public UserService createBean1() {
return new UserServiceImpl1();
}
@Bean
// 如果在ExampleController中放开对应接口注释,这里就可以进行该注释,即可避免报错
// @ConditionalOnMissingBean
public UserService createBean2() {
return new UserServiceImpl2();
}
}
@ConditionalOnMissingBean
的报错信息@ConditionalOnMissingBean
的打印信息