话不多说,上来就干
注册路由路径
soul中的divide插件,个人理解是分流插件,即作为流量打进来的负载均衡插件,在使用插件前,需要在controller层的路径上绑定接口的请求具体url,通过加入注解进行切面处理,而soul中通过监听器模式,ContextRegisterListener类监听应用上下文,实现接口ApplicationListener<ContextRefreshedEvent> ,这个监听器通过泛型,进行上下文感应监听,在项目启动时,上下文构建的过程中,会执行实现方法中的onApplicationEvent()
@Override
public void onApplicationEvent(final ContextRefreshedEvent contextRefreshedEvent) {
//通过注册自旋状态码 registered 获取注册锁,防止并发
if (!registered.compareAndSet(false, true)) {
return;
}
// 判断是否全部注册,全部注册就在admin上注册全部
if (soulSpringMvcConfig.isFull()) {
RegisterUtils.doRegister(buildJsonParams(), url, RpcTypeEnum.HTTP);
}
}
在admin上注册全部路径
private String buildJsonParams() {
String contextPath = soulSpringMvcConfig.getContextPath();
String appName = soulSpringMvcConfig.getAppName();
Integer port = soulSpringMvcConfig.getPort();
// 匹配任意路径
String path = contextPath + "/**";
String configHost = soulSpringMvcConfig.getHost();
String host = StringUtils.isBlank(configHost) ? IpUtils.getHost() : configHost;
SpringMvcRegisterDTO registerDTO = SpringMvcRegisterDTO.builder()
.context(contextPath)
.host(host)
.port(port)
.appName(appName)
.path(path)
.rpcType(RpcTypeEnum.HTTP.getName())
.enabled(true)
.ruleName(path)
.build();
return OkHttpTools.getInstance().getGson().toJson(registerDTO);
}
上面是任意路径注册,如果soulSpringMvcConfig.isFull()==false怎么办,既只有部分url想注册到网关上,在soul-client-springmvc包下,有一个类SpringMvcClientBeanPostProcessor,这个类实现了spring的BeanPostProcessor接口
// bean初始化前作切面操作
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
// bean初始化后作切面操作
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
soul的SpringMvcClientBeanPostProcessor重写postProcessAfterInitialization()方法
public Object postProcessAfterInitialization(@NonNull final Object bean, @NonNull final String beanName) throws BeansException {
if (soulSpringMvcConfig.isFull()) {
return bean;
}
// 通过 @RestController @Controller @RequestMapping @RequestMapping 获取controller层每个方法对应的请求路径
Controller controller = AnnotationUtils.findAnnotation(bean.getClass(), Controller.class);
RestController restController = AnnotationUtils.findAnnotation(bean.getClass(), RestController.class);
RequestMapping requestMapping = AnnotationUtils.findAnnotation(bean.getClass(), RequestMapping.class);
if (controller != null || restController != null || requestMapping != null) {
SoulSpringMvcClient clazzAnnotation = AnnotationUtils.findAnnotation(bean.getClass(), SoulSpringMvcClient.class);
String prePath = "";
if (Objects.nonNull(clazzAnnotation)) {
// 如果是controller类路径是全匹配,直接将该路径匹配到注册admin上
if (clazzAnnotation.path().indexOf("*") > 1) {
String finalPrePath = prePath;
// 执行注册
executorService.execute(() -> RegisterUtils.doRegister(buildJsonParams(clazzAnnotation, finalPrePath), url,
RpcTypeEnum.HTTP));
return bean;
}
prePath = clazzAnnotation.path();
}
final Method[] methods = ReflectionUtils.getUniqueDeclaredMethods(bean.getClass());
for (Method method : methods) {
SoulSpringMvcClient soulSpringMvcClient = AnnotationUtils.findAnnotation(method, SoulSpringMvcClient.class);
if (Objects.nonNull(soulSpringMvcClient)) {
String finalPrePath = prePath;
// 获取方法路径执行注册
executorService.execute(() -> RegisterUtils.doRegister(buildJsonParams(soulSpringMvcClient, finalPrePath), url,
RpcTypeEnum.HTTP));
}
}
}
return bean;
}
我们再来看一下这个bean是在哪里被注入容器的
package org.dromara.soul.springboot.starter.client.springmvc;
import org.dromara.soul.client.springmvc.config.SoulSpringMvcConfig;
import org.dromara.soul.client.springmvc.init.ContextRegisterListener;
import org.dromara.soul.client.springmvc.init.SpringMvcClientBeanPostProcessor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* The type Soul client http configuration.
*
* @author xiaoyu
*/
@Configuration
public class SoulSpringMvcClientConfiguration {
/**
* Spring http client bean post processor spring http client bean post processor.
*
* @param soulSpringMvcConfig the soul http config
* @return the spring http client bean post processor
*/
@Bean
public SpringMvcClientBeanPostProcessor springHttpClientBeanPostProcessor(final SoulSpringMvcConfig soulSpringMvcConfig) {
return new SpringMvcClientBeanPostProcessor(soulSpringMvcConfig);
}
/**
* Context register listener context register listener.
*
* @param soulSpringMvcConfig the soul spring mvc config
* @return the context register listener
*/
@Bean
public ContextRegisterListener contextRegisterListener(final SoulSpringMvcConfig soulSpringMvcConfig) {
return new ContextRegisterListener(soulSpringMvcConfig);
}
/**
* Soul http config soul http config.
*
* @return the soul http config
*/
@Bean
@ConfigurationProperties(prefix = "soul.http")
public SoulSpringMvcConfig soulHttpConfig() {
return new SoulSpringMvcConfig();
}
}
在soul-spring-bootstrap-starter-client-springmvc这个包下,被注入了容器,到这里,所有divide插件需要的基本路由信息都已经加载完成
divide插件
divide插件类实际执行的方法是在DividePlugin这个类中,这个类继承了AbstractSoulPlugin抽象类,实现模板设计模式,具体的插件类,重写doExecute()方法
@Override
protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
assert soulContext != null;
final DivideRuleHandle ruleHandle = GsonUtils.getInstance().fromJson(rule.getHandle(), DivideRuleHandle.class);
final List<DivideUpstream> upstreamList = UpstreamCacheManager.getInstance().findUpstreamListBySelectorId(selector.getId());
if (CollectionUtils.isEmpty(upstreamList)) {
log.error("divide upstream configuration error: {}", rule.toString());
Object error = SoulResultWrap.error(SoulResultEnum.CANNOT_FIND_URL.getCode(), SoulResultEnum.CANNOT_FIND_URL.getMsg(), null);
return WebFluxResultUtils.result(exchange, error);
}
final String ip = Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress();
// 加载插件选择器策略
DivideUpstream divideUpstream = LoadBalanceUtils.selector(upstreamList, ruleHandle.getLoadBalance(), ip);
if (Objects.isNull(divideUpstream)) {
log.error("divide has no upstream");
Object error = SoulResultWrap.error(SoulResultEnum.CANNOT_FIND_URL.getCode(), SoulResultEnum.CANNOT_FIND_URL.getMsg(), null);
return WebFluxResultUtils.result(exchange, error);
}
// set the http url
String domain = buildDomain(divideUpstream);
String realURL = buildRealURL(domain, soulContext, exchange);
exchange.getAttributes().put(Constants.HTTP_URL, realURL);
// set the http timeout
exchange.getAttributes().put(Constants.HTTP_TIME_OUT, ruleHandle.getTimeout());
exchange.getAttributes().put(Constants.HTTP_RETRY, ruleHandle.getRetry());
return chain.execute(exchange);
}
这其中加载策略方法,是通过spi机制动态加载
public static DivideUpstream selector(final List<DivideUpstream> upstreamList, final String algorithm, final String ip) {
// 通过算法标记值 algorithm ,拓展类加载器动态加载负载均衡策略
LoadBalance loadBalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getJoin(algorithm);
// 具体的负载均衡算法
// 实现方式三种
// HashLoadBalance 哈希值
// RandomLoadBalance 随机
// RoundRobinLoadBalance 轮询
return loadBalance.select(upstreamList, ip);
}