本篇文章介绍soul对spring cloud的插件支持,以及spring cloud服务接入soul网关。对应官方文档可以参考:dromara.org/zh-cn/docs/…
dubbo服务接入springcloud网关步骤
1、启动sole-admin。登陆localhost:9095, 进入System Manage -> Plugin, 把插件springcloud开启,其他插件有开启的全部关闭。
2、启动eureka服务
运行soul-examples-eureka下的EurekaServerApplication, 运行成功后,访问localhost:8761, 能正常访问代表eureka服务启动成功。
2、启动soul-bootstrap
- pom.xml引入对应jar包,添加对dubbo插件的支持
<!--soul springCloud plugin start-->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>soul-spring-boot-starter-plugin-springcloud</artifactId>
<version>${last.version}</version>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>soul-spring-boot-starter-plugin-httpclient</artifactId>
<version>${last.version}</version>
</dependency>
<!--soul springCloud plugin end-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
- 启动SoulBootstrapApplication,执行成功后发现服务注册到eureka上
3、启动sole-examples-springcloud
- Soul-examples-springcloud 的application.yml 添加如下配置
soul:
springcloud:
admin-url: http://localhost:9095
context-path: /springcloud
full: true # full为true,执行ContextRegisterListener进行元数据、选择器及其配置规则的配置,full为false,执行SpringCloudClientBeanPostProcessor进行元数据、选择器及其配置规则的配置
-
运行SoulTestSpringCloudApplication. 发现sole-admin后台 System-Manage -> Metadata 里面注册上的springcloud的相关数据, PluginList -> springCloud 也有了对应的加了注解@SoulSpringCloudClient的的SelectorList 和 RuleList.
-
查看eureka注册中心,发现服务注册上去
4、我们通过sole-bootstrap访问springcloud请求
访问localhost:9195/springcloud/order/path/111/dabing, 返回成功
{
"id": "111",
"name": "hello world spring cloud restful: dabing"
}
代表sole-bootstrap服务和springcloud服务都注册到了eureka 上了,可以正常用网关进行访问。
源码解读
通过前面几篇文章的学习,我们发现服务启动时会往soul-admin里面注册元数据和对应插件的选择器、配置规则,springcloud同样如此,并且有两种实现方式,实现了ApplicationListener的onApplicationEvent方法的ContextRegisterListener 和 实现了BeanPostProcessor 的postProcessAfterInitialization方法的SpringCloudClientBeanPostProcessor。两种实现方式由soul.springcloud.full控制选择哪种方式注册元数据。full为true,执行ContextRegisterListener的onApplicationEvent 方法进行元数据、选择器及其配置规则的注册,full为false,执行SpringCloudClientBeanPostProcessor的postProcessAfterInitialization 方法进行元数据、选择器及其配置规则的注册。
@Slf4j
public class ContextRegisterListener implements ApplicationListener<ContextRefreshedEvent> {
private final AtomicBoolean registered = new AtomicBoolean(false);
private final String url;
private final SoulSpringCloudConfig config;
private final Environment env;
/**
* Instantiates a new Context register listener.
*
* @param config the soul spring cloud config
* @param env the env
*/
public ContextRegisterListener(final SoulSpringCloudConfig config, final Environment env) {
ValidateUtils.validate(config, env);
this.config = config;
this.env = env;
this.url = config.getAdminUrl() + "/soul-client/springcloud-register";
}
@Override
public void onApplicationEvent(final ContextRefreshedEvent contextRefreshedEvent) {
if (!registered.compareAndSet(false, true)) {
return;
}
if (config.isFull()) {
RegisterUtils.doRegister(buildJsonParams(), url, RpcTypeEnum.SPRING_CLOUD);
}
}
private String buildJsonParams() {
String contextPath = config.getContextPath();
String appName = env.getProperty("spring.application.name");
String path = contextPath + "/**";
SpringCloudRegisterDTO registerDTO = SpringCloudRegisterDTO.builder()
.context(contextPath)
.appName(appName)
.path(path)
.rpcType(RpcTypeEnum.SPRING_CLOUD.getName())
.enabled(true)
.ruleName(path)
.build();
return OkHttpTools.getInstance().getGson().toJson(registerDTO);
}
}
@Slf4j
public class SpringCloudClientBeanPostProcessor implements BeanPostProcessor {
private final ThreadPoolExecutor executorService;
private final String url;
private final SoulSpringCloudConfig config;
private final Environment env;
/**
* Instantiates a new Soul client bean post processor.
*
* @param config the soul spring cloud config
* @param env the env
*/
public SpringCloudClientBeanPostProcessor(final SoulSpringCloudConfig config, final Environment env) {
ValidateUtils.validate(config, env);
this.config = config;
this.env = env;
this.url = config.getAdminUrl() + "/soul-client/springcloud-register";
executorService = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
}
@Override
public Object postProcessAfterInitialization(@NonNull final Object bean, @NonNull final String beanName) throws BeansException {
if (config.isFull()) {
return bean;
}
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) {
String prePath = "";
SoulSpringCloudClient clazzAnnotation = AnnotationUtils.findAnnotation(bean.getClass(), SoulSpringCloudClient.class);
if (Objects.nonNull(clazzAnnotation)) {
if (clazzAnnotation.path().indexOf("*") > 1) {
String finalPrePath = prePath;
executorService.execute(() -> RegisterUtils.doRegister(buildJsonParams(clazzAnnotation, finalPrePath), url,
RpcTypeEnum.SPRING_CLOUD));
return bean;
}
prePath = clazzAnnotation.path();
}
final Method[] methods = ReflectionUtils.getUniqueDeclaredMethods(bean.getClass());
for (Method method : methods) {
SoulSpringCloudClient soulSpringCloudClient = AnnotationUtils.findAnnotation(method, SoulSpringCloudClient.class);
if (Objects.nonNull(soulSpringCloudClient)) {
String finalPrePath = prePath;
executorService.execute(() -> RegisterUtils.doRegister(buildJsonParams(soulSpringCloudClient, finalPrePath), url,
RpcTypeEnum.SPRING_CLOUD));
}
}
}
return bean;
}
private String buildJsonParams(final SoulSpringCloudClient soulSpringCloudClient, final String prePath) {
String contextPath = config.getContextPath();
String appName = env.getProperty("spring.application.name");
String path = contextPath + prePath + soulSpringCloudClient.path();
String desc = soulSpringCloudClient.desc();
String configRuleName = soulSpringCloudClient.ruleName();
String ruleName = ("".equals(configRuleName)) ? path : configRuleName;
SpringCloudRegisterDTO registerDTO = SpringCloudRegisterDTO.builder()
.context(contextPath)
.appName(appName)
.path(path)
.pathDesc(desc)
.rpcType(soulSpringCloudClient.rpcType())
.enabled(soulSpringCloudClient.enabled())
.ruleName(ruleName)
.build();
return OkHttpTools.getInstance().getGson().toJson(registerDTO);
}
}
小插曲:
第一次启动bootstrap时忘记往pom.xml里面添加eureka的jar包,导致启动后服务调用失败。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>