功能需求:刚开始功能是让SpringBootAdmin将两个无关的naocs中的服务全部展示出来,发现SpringBootAdmin只会选择一个nacos中的服务展示,所以没走通。沟通后改为在服务端配置要监控的服务的IP端口,将这些服务手动注册进去。
实现方案:在看SpringBootAdmin client端注册流程的时候,发现是通过RegistrationApplicationListener去注册实例。所以我们可以仿造这个类去注册我们的实例。
1.在服务端配置文件中配置IP:
2.在服务端编写配置文件对应的Properties类
package com.itnode.springbootadmin.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.List;
@ConfigurationProperties(prefix = "server")
@Configuration
public class ServerListProperties {
private List<String> serverAddr;
public List<String> getServerAddr() {
return serverAddr;
}
public void setServerAddr(List<String> serverAddr) {
this.serverAddr = serverAddr;
}
}
3. 在服务端编写手动注册IP的类RegistrationApplicationListener(可仿造源码的RegistrationApplicationListener类进行修改),以下为我的注册流程,可根据自己需求自行更改。
/*
* Copyright 2014-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.itnode.springbootadmin.listener;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.itnode.springbootadmin.config.ServerListProperties;
import de.codecentric.boot.admin.client.config.InstanceProperties;
import de.codecentric.boot.admin.client.registration.Application;
import de.codecentric.boot.admin.client.registration.ApplicationFactory;
import de.codecentric.boot.admin.client.registration.ApplicationRegistrator;
import de.codecentric.boot.admin.server.domain.values.InstanceId;
import de.codecentric.boot.admin.server.domain.values.Registration;
import de.codecentric.boot.admin.server.services.InstanceRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.Ordered;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.annotation.Order;
import org.springframework.http.*;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.web.client.RestTemplate;
import reactor.core.publisher.Mono;
import java.time.Duration;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.LongAdder;
/**
* Listener responsible for starting and stopping the registration task when the application is
* ready.
*
* @author Johannes Edmeier
*/
public class RegistrationApplicationListener implements InitializingBean, DisposableBean {
private static final Logger LOGGER = LoggerFactory.getLogger(RegistrationApplicationListener.class);
private final ApplicationRegistrator registrator;
private final ThreadPoolTaskScheduler taskScheduler;
private boolean autoDeregister = false;
private boolean autoRegister = true;
private Duration registerPeriod = Duration.ofSeconds(10);//任务间隔
private volatile ScheduledFuture<?> scheduledTask;
private final ServerListProperties serverList;//配置的ip列表
private final ApplicationFactory applicationFactory;
private final ConcurrentHashMap<String, LongAdder> attempts = new ConcurrentHashMap<>();
private final AtomicReference<String> registeredId = new AtomicReference<>();
private final RestTemplate template;
private final String ADMIN_URL = "http://localhost:9090/instances";
private final String URL_PREFIX = "http://";
private final InstanceRegistry registry;
@Autowired
private InstanceProperties instanceProperties;
public RegistrationApplicationListener(ApplicationRegistrator registrator,
ApplicationFactory applicationFactory,
ServerListProperties serverList, RestTemplate template,
InstanceRegistry registry) {
this(registrator, registrationTaskScheduler(), applicationFactory, serverList, template, registry);
}
private static ThreadPoolTaskScheduler registrationTaskScheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(10);
taskScheduler.setRemoveOnCancelPolicy(true);
taskScheduler.setThreadNamePrefix("registrationTasks");
return taskScheduler;
}
RegistrationApplicationListener(ApplicationRegistrator registrator, ThreadPoolTaskScheduler taskScheduler,
ApplicationFactory applicationFactory, ServerListProperties serverList,
RestTemplate template, InstanceRegistry registry) {
this.registrator = registrator;
this.taskScheduler = taskScheduler;
this.applicationFactory = applicationFactory;
this.serverList = serverList;
this.template = template;
this.registry = registry;
}
@EventListener
@Order(Ordered.LOWEST_PRECEDENCE)
public void onApplicationReady(ApplicationReadyEvent event) {
if (autoRegister) {
startRegisterTask();
}
}
public void startRegisterTask() {
if (scheduledTask != null && !scheduledTask.isDone()) {
return;
}
for (String url : serverList.getServerAddr()) {
scheduledTask = taskScheduler.scheduleAtFixedRate(() -> register(url), registerPeriod);
}
LOGGER.debug("Scheduled registration task for every {}ms", registerPeriod);
}
public boolean register(String url) {
boolean isRegistrationSuccessful = false;
String applicationName = getName(url);
if (applicationName != null) {
instanceProperties.setName(applicationName);
}else {
return false;
}
instanceProperties.setManagementUrl(URL_PREFIX + url + "/actuator");
instanceProperties.setHealthUrl(URL_PREFIX + url + "/actuator/health");
instanceProperties.setServiceUrl(URL_PREFIX + url + "/");
//创建应用
Application self = createApplication();
//attempt记录注册尝试次数
LongAdder attempt = this.attempts.computeIfAbsent(ADMIN_URL, k -> new LongAdder());
boolean successful = register(self, ADMIN_URL, attempt.intValue() == 0);
if (!successful) {
attempt.increment();
} else {
attempt.reset();
isRegistrationSuccessful = true;
}
return isRegistrationSuccessful;
}
//远程调用actuator接口获取应用名
private String getName(String url) {
if (url.startsWith("172")) {
ResponseEntity<String> nacosforEntity = template.getForEntity(URL_PREFIX + url + "/actuator/nacos-discovery", String.class);
if (nacosforEntity.getStatusCodeValue() == 200) {
JSONObject jsonObject = JSON.parseObject(nacosforEntity.getBody());
JSONObject nacosDiscoveryProperties = (JSONObject) jsonObject.get("NacosDiscoveryProperties");
return (String) nacosDiscoveryProperties.get("service");
}
} else {
ResponseEntity<String> forEntity = template.getForEntity(URL_PREFIX + url + "/actuator/health", String.class);
if (forEntity.getStatusCodeValue() == 200) {
JSONObject jsonObject = JSON.parseObject(forEntity.getBody());
JSONObject components = (JSONObject) jsonObject.get("components");
JSONObject discoveryComposite = (JSONObject) components.get("discoveryComposite");
JSONObject discoveryCompositeComponents = (JSONObject) discoveryComposite.get("components");
JSONObject discoveryClient = (JSONObject) discoveryCompositeComponents.get("discoveryClient");
JSONObject details = (JSONObject) discoveryClient.get("details");
JSONArray services = (JSONArray) details.get("services");
return (String) services.get(0);
}
}
return null;
}
protected Application createApplication() {
return applicationFactory.createApplication();
}
protected boolean register(Application self, String adminUrl, boolean firstAttempt) {
try {
//构造一个Registration对象
Registration registration = Registration.builder().name(self.getName()).source("http-api")
.healthUrl(self.getHealthUrl()).managementUrl(self.getManagementUrl())
.metadata(self.getMetadata())
.serviceUrl(self.getServiceUrl()).build();
//调用InstanceRegistry的register方法注册进去
Mono<InstanceId> register = registry.register(registration);
InstanceId instanceId = register.block();
// ResponseEntity<Map<String, Object>> response = this.template.exchange(adminUrl, HttpMethod.POST, new HttpEntity(self, HTTP_HEADERS), RESPONSE_TYPE, new Object[0]);
// if (response.getStatusCode().is2xxSuccessful()) {
if(instanceId!=null){
if (this.registeredId.compareAndSet( null, instanceId.toString())) {
LOGGER.info("Application registered itself as {}", instanceId.getValue());
} else {
LOGGER.debug("Application refreshed itself as {}", instanceId.getValue());
}
return true;
}
if (firstAttempt) {
LOGGER.warn("Application failed to registered itself as {}. Response: {}. Further attempts are logged on DEBUG level", self, register.toString());
} else {
LOGGER.debug("Application failed to registered itself as {}. Response: {}", self, register.toString());
}
} catch (Exception var5) {
if (firstAttempt) {
LOGGER.warn("Failed to register application as {} at spring-boot-admin ({}): {}. Further attempts are logged on DEBUG level", new Object[]{self, adminUrl, var5.getMessage()});
} else {
LOGGER.debug("Failed to register application as {} at spring-boot-admin ({}): {}", new Object[]{self, adminUrl, var5.getMessage()});
}
}
return false;
}
@Override
public void afterPropertiesSet() {
taskScheduler.afterPropertiesSet();
}
@Override
public void destroy() {
taskScheduler.destroy();
}
}
4. 在服务端将RegistrationApplicationListener注入到Spring中
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
@Bean("myRegistrationApplicationListener")
public RegistrationApplicationListener registrationListener(ApplicationFactory applicationFactory,
ServerListProperties serverList, RestTemplate template,
ApplicationRegistrator registrator,
InstanceRegistry registry) {
RegistrationApplicationListener listener = new RegistrationApplicationListener(registrator,applicationFactory,serverList,template,registry);
return listener;
}
5. pom需要引入SpringBootAdmin中client和server的依赖
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
<version>${spring-boot-admin.version}</version>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>${spring-boot-admin.version}</version>
</dependency>
本人还在上学小菜鸡一枚,如有错误地方或任何建议都可提出,看见后会予以纠正。感谢!