如何将传统SSM项目注册至Eureka
一、整体思路
- 1、查看springboot版本的 eureka client ,断点查看相关方法(实际上就是看看客户端的源码)。
- 2、将相关jar包引入项目,并调用eureka client相关方法。
二、集成步骤(先直接上代码)
1、引入依赖
<!-- 服务注册-客户端 -->
<dependency>
<groupId>com.netflix.eureka</groupId>
<artifactId>eureka-client</artifactId>
<version>1.4.11</version>
<exclusions>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</exclusion>
<exclusion>
<groupId>javax.ws.rs</groupId>
<artifactId>jsr311-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--客户端注册到eureka就是用的此工具包-->
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
<version>2.22.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>2.9.4</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.25.1</version>
</dependency>
2、调用自定义的Utils方法
import com.netflix.appinfo.DataCenterInfo;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.appinfo.LeaseInfo;
import com.netflix.appinfo.MyDataCenterInfo;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;
import javax.ws.rs.client.*;
import javax.ws.rs.core.Response;
import java.util.HashMap;
import java.util.Map;
import static com.netflix.appinfo.DataCenterInfo.Name.MyOwn;
/**
* eureka注册中心工具类
* @author GaoYuan
* @date 2019/1/6 下午3:46
*/
public class EurekaUtils {
private static String ipAddr = "192.168.0.231";
private static String instanceId = ipAddr + ":demo:8087";
private static String appName = "demo";
private static String appGroupName;
private static String sid = "na";
private static com.netflix.appinfo.InstanceInfo.PortWrapper port = new com.netflix.appinfo.InstanceInfo.PortWrapper(true, 8087);
private static com.netflix.appinfo.InstanceInfo.PortWrapper securePort = new com.netflix.appinfo.InstanceInfo.PortWrapper(false, 443);
private static String homePageUrl = "http://"+ipAddr+":"+port.getPort()+"/demo";
private static String statusPageUrl = "http://"+ipAddr+":"+port.getPort()+"/demo/info";
private static String healthCheckUrl = "http://"+ipAddr+":"+port.getPort()+"/demo/health";
private static String secureHealthCheckUrl = null;
private static String vipAddress = "demo";
private static String secureVipAddress = "demo";
private static String statusPageRelativeUrl = "/demo/info";
private static String statusPageExplicitUrl = null;
private static String healthCheckRelativeUrl = "/demo/health";
private static String healthCheckSecureExplicitUrl = null;
private static String vipAddressUnresolved = "demo";
private static String secureVipAddressUnresolved = "demo";
private static String healthCheckExplicitUrl = null;
private static int countryId = 1;
private static boolean isSecurePortEnabled = false;
private static boolean isUnsecurePortEnabled = true;
private static DataCenterInfo dataCenterInfo = new MyDataCenterInfo(MyOwn);
private static String hostName = ipAddr;
private static InstanceInfo.InstanceStatus status = InstanceInfo.InstanceStatus.UP;
private static InstanceInfo.InstanceStatus overriddenstatus = InstanceInfo.InstanceStatus.UP;
private static boolean isInstanceInfoDirty = true;
/** 续租信息
* renewalIntervalInSecs 租期更新时间间隔
* durationInSecs 租期到期时间,到期就下线 */
private static LeaseInfo leaseInfo = new LeaseInfo(2, 30, 0L, 0L, 0, 0, 0);
private static boolean isCoordinatingDiscoveryServer = false;
// private static Map<String, String> metadata = new ConcurrentHashMap<>();
@XStreamAlias("metadata")
private static volatile Map<String, String> metadata = new HashMap<String, String>();
private static long lastUpdatedTimestamp = System.currentTimeMillis();
private static long lastDirtyTimestamp = System.currentTimeMillis();
private static volatile InstanceInfo.ActionType actionType = null;
private static String asgName = null;
private static String version = "unknown";
public static void main(String[] args){
String url = "http://eureka-node1:11001/eureka/";
com.netflix.appinfo.InstanceInfo instanceInfo = new com.netflix.appinfo.InstanceInfo(
instanceId, appName, appGroupName, ipAddr, sid, port, securePort,
homePageUrl, statusPageUrl, healthCheckUrl, secureHealthCheckUrl,
vipAddress, secureVipAddress, countryId, dataCenterInfo, hostName,
status, overriddenstatus, leaseInfo, isCoordinatingDiscoveryServer,
(HashMap)metadata, lastUpdatedTimestamp, lastDirtyTimestamp, actionType, asgName);
InstanceInfo.Builder builder = new InstanceInfo.Builder(instanceInfo);
builder.setStatusPageUrl(statusPageRelativeUrl, statusPageExplicitUrl);
builder.setHealthCheckUrls(healthCheckRelativeUrl, healthCheckExplicitUrl, healthCheckSecureExplicitUrl);
builder.setVIPAddress(vipAddress);
builder.setSecureVIPAddress(secureVipAddress);
Client jerseyClient = ClientBuilder.newClient();
// 基本认证的Feature
HttpAuthenticationFeature feature = HttpAuthenticationFeature.basic("admin", "123456");
// 将Feature注册到Jersey Client中
WebTarget target = jerseyClient
.register(feature)
.target(url)
.path("/apps/"+ instanceInfo.getAppName());
//获取执行器
Invocation.Builder invocationBuilder = target.request(javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE);
//添加头信息
invocationBuilder.header("Accept-Encoding", "gzip");
invocationBuilder.header("Content-Type", "application/json");
//对当前URI执行GET方法请求;获得响应对象
Map<String,Object> map = new HashMap<>();
map.put("instance", builder.getRawInstance());
Response response = invocationBuilder.post(Entity.entity(map, javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE));
//从响应对象中获取需要的内容 - 返回状态204表示成功(你没看错,就是204)
System.out.println(response.getStatus());
System.out.println(response);
}
}
3、对外提供健康监控接口
/**
* 对外提供监控接口
* @author GaoYuan
* @date 2019/1/10 上午8:39
*/
@RestController
public class EurekaController {
@GetMapping("/health")
public Object health(){
Map<String, String> map = new HashMap<>(2);
map.put("description", "Spring Cloud Eureka Discovery Client");
map.put("status", "UP");
return map;
}
@GetMapping("/info")
public Object info(){
Map<String, String> map = new HashMap<>(2);
map.put("name", "管理平台");
return map;
}
}
三、解析思路(建议看看)
可以发现,额外引入了 jersey ,因为eureka的客户端就是通过jersey进行http请求的(后面会解释这里是怎么知道的)。
待解决问题
首先,我们需要解决的问题如下:
- 注册的Api
- 注册接口地址
- 注册接口参数
- 如何调用
- 需要支持 security.basic 认证访问
- 需要支持健康检查,不能注册后就下线了
下面我们挨个解决问题
注册的API
官方文档 RestApi(https://github.com/Netflix/eureka/wiki/Eureka-REST-operations)
通过阅读官方文档,可以看到注册接口:
Operation | HTTP action | Descrition |
---|---|---|
注册 | POST /eureka/v2/apps/appID | 输入:JSON/XML,返回:204表示成功 |
注:这里确实是返回204表示成功。
官方列举了注册的参数,但是,只给出了xml格式的案例,对于我们常用的方式并不友好。接下来我们在尝试通过eureka-client来获取【参数对象】。
获取请求参数
通过 eureka-client 的debug日志,可以发现,注册的方法在 com.netflix.discovery.DiscoveryClient
中。我们继续搜索 register 方法,在 825 行可以看到如下代码:
boolean register() throws Throwable {
logger.info(PREFIX + "{}: registering service...", appPathIdentifier);
EurekaHttpResponse<Void> httpResponse;
try {
httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
} catch (Exception e) {
logger.warn(PREFIX + "{} - registration failed {}", appPathIdentifier, e.getMessage(), e);
throw e;
}
if (logger.isInfoEnabled()) {
logger.info(PREFIX + "{} - registration status: {}", appPathIdentifier, httpResponse.getStatusCode());
}
return httpResponse.getStatusCode() == 204;
}
主要代码是
httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
其中 instanceInfo
就是我们所需要的【参数对象】,完整路径是 com.netflix.appinfo.InstanceInfo
,我们此处打上断点,可以看到 instanceInfo 中的参数内容,如下:
参数对象 我们已经获取到了,通过调用 InstanceInfo 提供的构造函数即可。
看看EurekaClient是如何实现Api调用的
接下来我们查看具体的注册方法
DiscoveryClient 中的 eurekaTransport.registrationClient.register()
方法,
这里的 registrationClient 是 interface EurekaHttpClient
接口。
我们通过快捷键可以知道实现 EurekaHttpClient 的实现类有
AbstractJerseyEurekaHttpClient
和 EurekaHttpClientDecorator
在两个实现类的 regist 中都打上断点,可以发现最终调用了 AbstractJerseyEurekaHttpClient
的 注册方法,具体实现方法如下:
@Override
public EurekaHttpResponse<Void> register(InstanceInfo info) {
String urlPath = "apps/" + info.getAppName();
ClientResponse response = null;
try {
Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();
addExtraHeaders(resourceBuilder);
response = resourceBuilder
.header("Accept-Encoding", "gzip")
.type(MediaType.APPLICATION_JSON_TYPE)
.accept(MediaType.APPLICATION_JSON)
.post(ClientResponse.class, info);
return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
} finally {
if (logger.isDebugEnabled()) {
logger.debug("Jersey HTTP POST {}/{} with instance {}; statusCode={}", serviceUrl, urlPath, info.getId(),
response == null ? "N/A" : response.getStatus());
}
if (response != null) {
response.close();
}
}
}
由此可发现,此方法使用 jerseyClient
进行接口请求,相关核心代码都出现了。
接下来,我们在传统的SSM项目中,构造 jersey 调用即可。下面就不啰嗦了
如何支持security.basic访问
// 基本认证的Feature
HttpAuthenticationFeature feature = HttpAuthenticationFeature.basic("admin", "123456");
// 将Feature注册到Jersey Client中
WebTarget target = jerseyClient
.register(feature)
.target(url)
.path("/apps/"+ instanceInfo.getAppName());
如何支持健康检查
其实就是对外提供/health接口就行了,注意,此接口不要有权限拦截。
@GetMapping("/health")
public Object health(){
Map<String, String> map = new HashMap<>(2);
map.put("description", "Spring Cloud Eureka Discovery Client");
map.put("status", "UP");
return map;
}
四、运行
执行 EurekaUtils.main() 方法
204
InboundJaxrsResponse{context=ClientResponse{method=POST, uri=http://eureka-node1:11001/eureka/apps/MDM-MC, status=204, reason=No Content}}
注册成功。
五、常见问题
1、unauthorized
需要security.basic访问
HttpAuthenticationFeature feature = HttpAuthenticationFeature.basic("admin", "123456");
2、404
注册地址不对,这里的url使用单一地址,且不要加上 账号:密码@
String url = "http://eureka-node1:11001/eureka/";
3、400
注意版本问题 或 是否是未认证。
六、博客
https://my.oschina.net/gmarshal/blog/2999556
欢迎关注我的个人微信订阅号:(据说这个头像程序猿专用)
来源:oschina
链接:https://my.oschina.net/u/3720017/blog/2999556