如何將傳統SSM項目註冊至Eureka

如何將傳統SSM項目註冊至Eureka

1、總體思路

  • 一、查看springboot版本的 eureka client ,斷點查看相關方法(實際上就是看看客戶端的源碼)。
  • 二、將相關jar包引入項目,並調用eureka client相關方法。

2、集成步驟(先直接上代碼)

一、引入依賴

<!-- 服務註冊-客戶端 -->
<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>

二、調用自定義的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);
    }
}

三、對外提供健康監控接口

/**
 * 對外提供監控接口
 * @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;
    }
}

3、解析思路(建議看看)

能夠發現,額外引入了 jersey ,由於eureka的客戶端就是經過jersey進行http請求的(後面會解釋這裏是怎麼知道的)。java

待解決問題

首先,咱們須要解決的問題以下:node

  • 註冊的Api
    • 註冊接口地址
    • 註冊接口參數
  • 如何調用
  • 須要支持 security.basic 認證訪問
  • 須要支持健康檢查,不能註冊後就下線了

下面咱們挨個解決問題git

註冊的API

官方文檔 RestApi(https://github.com/Netflix/eureka/wiki/Eureka-REST-operations)github

經過閱讀官方文檔,能夠看到註冊接口:spring

Operation HTTP action Descrition
註冊 POST /eureka/v2/apps/appID 輸入:JSON/XML,返回:204表示成功

注:這裏確實是返回204表示成功。json

官方列舉了註冊的參數,可是,只給出了xml格式的案例,對於咱們經常使用的方式並不友好。接下來咱們在嘗試經過eureka-client來獲取【參數對象】。api

獲取請求參數

經過 eureka-client 的debug日誌,能夠發現,註冊的方法在 com.netflix.discovery.DiscoveryClient 中。咱們繼續搜索 register 方法,在 825 行能夠看到以下代碼:springboot

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 中的參數內容,以下:app

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;
}

4、運行

執行 EurekaUtils.main() 方法

204
InboundJaxrsResponse{context=ClientResponse{method=POST, uri=http://eureka-node1:11001/eureka/apps/MDM-MC, status=204, reason=No Content}}

註冊成功。

5、常見問題

一、unauthorized

須要security.basic訪問

HttpAuthenticationFeature feature = HttpAuthenticationFeature.basic("admin", "123456");

二、404

註冊地址不對,這裏的url使用單一地址,且不要加上 帳號:密碼@

String url = "http://eureka-node1:11001/eureka/";

三、400

注意版本問題 或 是不是未認證。

6、博客

https://my.oschina.net/gmarshal/blog/2999556

歡迎關注個人我的微信訂閱號:(聽說這個頭像程序猿專用)

輸入圖片說明

相關文章
相關標籤/搜索