如何将传统SSM项目注册至Eureka

依然范特西╮ 提交于 2019-11-29 23:43:55

如何将传统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参数对象

参数对象 我们已经获取到了,通过调用 InstanceInfo 提供的构造函数即可。

看看EurekaClient是如何实现Api调用的

接下来我们查看具体的注册方法

DiscoveryClient 中的 eurekaTransport.registrationClient.register() 方法, 这里的 registrationClient 是 interface EurekaHttpClient 接口。

我们通过快捷键可以知道实现 EurekaHttpClient 的实现类有 AbstractJerseyEurekaHttpClientEurekaHttpClientDecorator

在两个实现类的 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

欢迎关注我的个人微信订阅号:(据说这个头像程序猿专用)

输入图片说明

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!