1. 程式人生 > >IntelliJ IDEA建立spring cloud(eureka)訂單商品專案

IntelliJ IDEA建立spring cloud(eureka)訂單商品專案

IntelliJ IDEA建立spring cloud(eureka)訂單商品專案

spring cloud是基於springboot的一種協調多個微服務的框架。spring cloud包含多個微服務叢集的解決方案,本文將使用eureka來實現一個簡單的訂單商品專案。https://download.csdn.net/download/wujingchangye/10869048

下面先簡單說明下單體架構的優缺點:

  • 優點:部署簡單,技術簡單
  • 缺點:系統啟動較慢,系統的隔離性較差,可伸縮性較差,系統的修改週期較長

為了克服其缺點需要引入微服務架構,微服務架構風格是一種將單一應用程式開發為一組小服務的方法,每一個服務執行在自己的程序中,服務間的通訊採用輕量級通訊機制,通常用的是HTTP資源API,這些服務圍繞的業務能力構建並且通過全自動部署機制獨立部署,這些服務公用一個最小型的集中式的管理,服務可用不同的語言開發,使用不同的資料儲存技術


在這裡插入圖片描述

對於商品的微服務而言,商品服務是服務的提供者,訂單服務是服務的消費者。
對於訂單微服務而言,訂單服務是服務的提供者,人是服務的消費者。

在這裡插入圖片描述
因此在商品和訂單兩個微服務之間,我們需要協調好商品微服務的釋出,以及訂單微服務對於商品微服務的獲取。簡單而言,由於訂單微服務不需要被人使用或者說呼叫,因此其在整個eureka註冊中心中不需要進行註冊,即無法被其它服務發現。反之,商品微服務是需要其它微服務呼叫的,因此需要在eureka註冊中心中進行註冊。

下面我們先建立兩個springboot專案,分別是商品和訂單微服務專案。在IntelliJ IDEA中新建一個project(maven),注意IntelliJ IDEA和eclipse的專案組織方式是不同的,其project是多個專案的集合,裡面的每個專案稱為module,而eclipse中每個project是單獨的專案

。如下圖:我們建立一個project(命名為microservice),然後在該project下面新建三個module,分別表示商品(microservice_item)、訂單(microservice_order)和eureka伺服器(microservice_eureka)。
在這裡插入圖片描述
首先我們開啟project的pom.xml檔案進行配置,project的pom.xml檔案是所有module的pom.xml檔案的parent,通過簡單的配置可以省略module中的重複配置。在project的pom.xml中加入下列標籤。

<properties>
	<java.version>1.8</java.version>
</properties>
<!-- 引入Spring boot(Spring cloud是基於Spring boot的) -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.14.RELEASE</version>
</parent>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Edgware.SR4</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

由於spring cloud的基於spring boot的,因此需要parent下spring boot的啟動類。而dependencyManagement是所有子pom依賴的父類,但子pom引入spring cloud時,會向上搜尋其版本資訊,從而規範整體專案的版本號。

至此,project的配置已經完成,開始編寫microservice_item商品微服務。同樣是pom檔案的配置,加入spring boot和spring cloud eureka的支援。

<dependencies>
    <!-- 加入Spring boot Web支援 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka-server</artifactId>
    </dependency>
</dependencies>

接下來編寫item的業務邏輯,下圖給出item module的結構。
在這裡插入圖片描述
分別是1個controller、1個model和1個service。商品的model程式碼如下:

package com.fty_czbk.pojo;
public class Item {
    private Long id;        //唯一標識
    private String title;   //商品標題
    private String pic;     //圖片的pic地址
    private String desc;    //描述資訊
    private Long price;     //價格
    public Item() {
    }
    public Item(Long id, String title, String pic, String desc, Long price) {
        this.id = id;
        this.title = title;
        this.pic = pic;
        this.desc = desc;
        this.price = price;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getPic() {
        return pic;
    }
    public void setPic(String pic) {
        this.pic = pic;
    }
    public String getDesc() {
        return desc;
    }
    public void setDesc(String desc) {
        this.desc = desc;
    }
    public Long getPrice() {
        return price;
    }
    public void setPrice(Long price) {
        this.price = price;
    }
    @Override
    public String toString() {
        return "Item{" +
                "id=" + id +
                ", title='" + title + '\'' +
                ", pic='" + pic + '\'' +
                ", desc='" + desc + '\'' +
                ", price=" + price +
                '}';
    }
}

就是簡單的幾個欄位和getter和setter方法。然後是service(具體的業務功能,我們用靜態的資料來表示):

package com.fty_czbk.service;
import com.fty_czbk.pojo.Item;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;

@Service
public class ItemServices {

    //呼叫DAO查詢資料,用靜態資料表示
    private static final Map<Long, Item> MAP = new HashMap<Long,Item>();	
    static {
        MAP.put(1L, new Item(1L, "商品標題1", "http://圖片1", "商品描述1", 1000L));
        MAP.put(2L, new Item(2L, "商品標題2", "http://圖片2", "商品描述2", 2000L));
        MAP.put(3L, new Item(3L, "商品標題3", "http://圖片3", "商品描述3", 3000L));
        MAP.put(4L, new Item(4L, "商品標題4", "http://圖片4", "商品描述4", 4000L));
        MAP.put(5L, new Item(5L, "商品標題5", "http://圖片5", "商品描述5", 5000L));
        MAP.put(6L, new Item(6L, "商品標題6", "http://圖片6", "商品描述6", 6000L));
        MAP.put(7L, new Item(7L, "商品標題7", "http://圖片7", "商品描述7", 7000L));
        MAP.put(8L, new Item(8L, "商品標題8", "http://圖片8", "商品描述8", 8000L));
        MAP.put(9L, new Item(9L, "商品標題9", "http://圖片9", "商品描述9", 9000L));
    }	
    /**
     * 根據商品的id進行查詢
     */
    public Item queryItemById(Long id) {
        return MAP.get(id);
    }
}

重點就是queryItemById方法,代表一個具體的商品查詢業務。然後就是Controller,負責響應url請求、呼叫業務並返回資料。

package com.fty_czbk.controller;
import com.fty_czbk.pojo.Item;
import com.fty_czbk.service.ItemServices;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;	
@RestController//@Controller + @ResponseBody
public class ItemController {
    @Autowired
    private ItemServices itemServices;

    //@RequestMapping(value = "/item/{id}", method = RequestMethod.GET)
    @GetMapping(value = "/item/{id}")
    public Item queryItemById(@PathVariable(name = "id") Long id) {
        return itemServices.queryItemById(id);
    }		
}

@RestController等於@Controller + @ResponseBody, @GetMapping(value = “/item/{id}”)等於@RequestMapping(value = “/item/{id}”, method = RequestMethod.GET)
不懂的可以百度一下。最後是啟動類

package com.fty_czbk;	
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;	
@EnableDiscoveryClient  //申明該應用是eureka客戶端
@SpringBootApplication
public class ItemApplication {
//商品微服務,通過url查詢商品資訊
public static void main(String[] args) {
    SpringApplication.run(ItemApplication.class, args);
}

}
@EnableDiscoveryClient 可以先不寫,其表示申明該微服務是eureka的客戶端,此處我們還未對該專案進行配置,因此先當作單個專案來執行。這樣整體的程式碼就編寫完畢了,在配置檔案中進行配置即可。在resources下新建application.properties檔案,在裡面加上:

server.port=8083

這樣商品微服務就配置好了,注意這裡還未對該服務進行eureka註冊,所以還是一個單一的微服務專案,在瀏覽器中進行訪問商品。這裡有一個問題,我們知道spring boot框架中Controller的@RestController會使資料以json格式進行返回,但是此處顯示的是xml的格式,這是因為我們在pom中引入了eureka的依賴,其會覆蓋spring boot的預設設定,使返回值變為xml格式。如何更改回來後面會詳細說明。
在這裡插入圖片描述
接下來是訂單的微服務。同樣:
在這裡插入圖片描述
pom檔案的配置幾乎是一模一樣的,就是多引入了一個模擬http請求的包

<dependencies>
    <!-- 加入Spring boot Web支援 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- 加入OK http 包 -->
    <dependency>
        <groupId>com.squareup.okhttp3</groupId>
        <artifactId>okhttp</artifactId>
        <version>3.9.0</version>
    </dependency>

    <dependency>            <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka-server</artifactId>
    </dependency>
</dependencies>

然後是訂單專案的model,具體而言,一個Order包含使用者資訊和多個OrderDetails,一個OrderDetails包含多個Item。Item類在上面已經給出,這裡就不重複了。

package com.fty_czbk.pojo;
import java.util.Date;
import java.util.List;
public class Order {
    private String orderId;                 //訂單的id
    private Long userId;                    //使用者id
    private Date createDate;                //建立時間
    private Date updateDate;                //修改時間
    private List<OrderDetail> orderDetails; //訂單詳情

    public Order() {
    }
    public Order(String orderId, Long userId, Date createDate, Date updateDate, List<OrderDetail> orderDetails) {
        this.orderId = orderId;
        this.userId = userId;
        this.createDate = createDate;
        this.updateDate = updateDate;
        this.orderDetails = orderDetails;
    }
    public String getOrderId() {
        return orderId;
    }
    public void setOrderId(String orderId) {
        this.orderId = orderId;
    }
    public Long getUserId() {
        return userId;
    }
    public void setUserId(Long userId) {
        this.userId = userId;
    }
    public Date getCreateDate() {
        return createDate;
    }
    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }
    public Date getUpdateDate() {
        return updateDate;
    }
    public void setUpdateDate(Date updateDate) {
        this.updateDate = updateDate;
    }
    public List<OrderDetail> getOrderDetails() {
        return orderDetails;
    }
    public void setOrderDetails(List<OrderDetail> orderDetails) {
        this.orderDetails = orderDetails;
    }

    @Override
    public String toString() {
        return "Order{" +
                "orderId='" + orderId + '\'' +
                ", userId=" + userId +
                ", createDate=" + createDate +
                ", updateDate=" + updateDate +
                ", orderDetails=" + orderDetails +
                '}';
    }
}

OrderDetail類的程式碼如下:

package com.fty_czbk.pojo;
public class OrderDetail {
    private String orderId;     //訂單id
    private Item item;          //商品
    public OrderDetail() {
    }
    public OrderDetail(String orderId, Item item) {
        this.orderId = orderId;
        this.item = item;
    }
    public String getOrderId() {
        return orderId;
    }
    public void setOrderId(String orderId) {
        this.orderId = orderId;
    }
    public Item getItem() {
        return item;
    }
    public void setItem(Item item) {
        this.item = item;
    }
    @Override
    public String toString() {
        return "OrderDetail{" +
                "orderId='" + orderId + '\'' +
                ", item=" + item +
                '}';
    }
}

有點複雜,可以直接copy,本質上就是一個訂單的model,直接換成簡單兩個欄位而是可以的。然後就是service的編寫,首先是itemService,我們要通過http請求去獲取商品微服務的資訊,因此需要藉助okHttp包:

package com.fty_czbk.service;

import com.fty_czbk.pojo.Item;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@Service
public class ItemService {

	@Autowired
	private RestTemplate restTemplate;//等於HttpClient可以模擬請求
	@Value("${item.url}")
 	private String itemUrl;
	public Item queryItemById(Long id) {
 	       Item item = restTemplate.getForObject(itemUrl + id, Item.class);
	 		return item;
	}
}

這裡通過配置檔案中設定的商品微服務的地址,我們可以請求獲取到商品的資訊,從而完善我們的訂單內容。這裡就很關鍵了!
先給出OrderService的程式碼:

package com.fty_czbk.service;	
import com.fty_czbk.pojo.Item;
import com.fty_czbk.pojo.Order;
import com.fty_czbk.pojo.OrderDetail;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;	
import java.util.*;	
@Service
public class OrderService {
    //呼叫DAO查詢資料,用靜態資料表示
    private static final Map<String, Order> MAP = new HashMap<String, Order>();
    static {	
        //建立一個Order
        Order order = new Order();
        order.setOrderId("59193738268961441");
        order.setCreateDate(new Date());
        order.setUpdateDate(order.getCreateDate());
        order.setUserId(1L);	
        //訂單詳情設定
        List<OrderDetail> orderDetails = new ArrayList<OrderDetail>();	
        //在訂單中新增商品(商品資訊由呼叫商品微服務獲取)
        Item item = new Item();
        item.setId(1L);
        orderDetails.add(new OrderDetail(order.getOrderId(), item));	
        //在訂單中新增商品(商品資訊由呼叫商品微服務獲取)
        item = new Item();
        item.setId(2L);
        orderDetails.add(new OrderDetail(order.getOrderId(), item));

        order.setOrderDetails(orderDetails);	
        //放入訂單詳情
        MAP.put(order.getOrderId(), order);
    }

    @Autowired
    private ItemService itemService;
    /**
     * 根據訂單Id查詢訂單
     */
    public Order queryOrderById(String orderId) {	
        Order order = MAP.get(orderId);
        //商品資訊由呼叫商品微服務獲取
        //遍歷OrderDetail,獲取商品id,呼叫商品微服務
        List<OrderDetail> orderDetails = order.getOrderDetails();
        for (OrderDetail orderDetail: orderDetails) {
            Long id = orderDetail.getItem().getId();
            Item item = itemService.queryItemById(id);
            orderDetail.setItem(item);
        }

        return order;
    }
}

訂單中的其它資訊由靜態資料提供,而商品資訊通過呼叫ItemService進行獲取。**到這裡,我們可以發現,如果沒用用spring cloud對多個微服務進行管理,我們只能通過在配置檔案中設定微服務地址的方式獲取我們需要的商品資訊,這是非常不科學的,一旦商品微服務的地址改變,我們就需要重新配置application.properties檔案。**而eureka後續能為我們解決該問題。現在先來看OrderController程式碼:

package com.fty_czbk.Controller;	
import com.fty_czbk.pojo.Order;
import com.fty_czbk.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;	
@RestController
public class OrderController {

    @Autowired
    private OrderService orderService;

    @GetMapping(value = "/order/{orderId}")
    public Order queryOrderById(@PathVariable(name = "orderId") String orderId) {
        return orderService.queryOrderById(orderId);
    }
}

這裡也是非常簡單的請求呼叫的方式。看啟動類:

package com.fty_czbk;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Bean;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

@EnableDiscoveryClient   //同樣申明是eureka的客戶端,雖然是消費者
@SpringBootApplication
public class OrderApplication {

    @Bean
    public RestTemplate restTemplate() {//配置RestTemplate
        //使用OKHttpClient進行請求
        return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
    }

    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}

這裡注意,使用RestTemplate是需要進行配置的,我們傳入OkHttp3ClientHttpRequestFactory例項,從而使其內部呼叫OkHttp的請求程式碼。最後就是配置檔案

server.port=8082
#並沒有本質上解決url動態修改的問題 如果有多個商品微服務,則很難呼叫
#需要通過服務發現和註冊機制進行處理
item.url=http://localhost:8083/item/

通過瀏覽器進行訪問:
在這裡插入圖片描述
到此,進行總結,我們建立了兩個微服務,一個商品,一個訂單,訂單需要請求商品服務從而獲取商品資訊,而原始的作法是將商品的url寫死在配置檔案中,從而進行請求獲取,而隨著微服務數量的增加,這種方式顯然是不可取的,因此需要一個伺服器對多個服務進行管理,即eureka。
在這裡插入圖片描述
我們新建一個maven專案,然後加入以下內容,其中,排除eureka中的xml包就可以恢復返回json格式的要求。

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka-server</artifactId>
        <!-- eureka原本的設定會覆蓋json的返回,需要排除以下依賴 -->
        <exclusions>
            <exclusion>
                <groupId>com.fasterxml.jackson.dataformat</groupId>
                <artifactId>jackson-dataformat-xml</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

而該專案只需要一個啟動類即可

package com.fty_czbk;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

//申明Eureka服務端,即註冊中心
@EnableEurekaServer
@SpringBootApplication
public class EurekaServer {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServer.class, args);
    }
}

重點就在於@EnableEurekaServer這個註釋,其它的全部不需要動,然後就配置檔案:

server.port=6999
#需不需要把自己加入註冊中心
eureka.client.register-with-eureka=false
#是否需要獲取註冊資訊
eureka.client.fetch-registry=false
#註冊中心地址
eureka.instance.hostname=localhost
eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/

這裡就是eureka的配置,有幾個關鍵屬性:

  • eureka.client.register-with-eureka需不需要把當前專案(自身)加入註冊中心,即該微服務能不能被其它人發現。因為我們自身是伺服器,所以false。
  • eureka.client.fetch-registry=false當前專案是否需要發現其它專案,即是否要引用eureka中的其它服務。因為我們自身是伺服器,所以false。
  • 最後兩個屬性就是配置eureka伺服器的地址。

啟動eureka伺服器,在瀏覽器中輸入:
在這裡插入圖片描述
就可以看到eureka的預設管理服務介面,當前Instances currently registered with Eureka中是沒有任何一個註冊服務的,這就是我們配置檔案中設定的。
接下來對商品和訂單微服務的配置檔案進行配置,注意除了配置檔案以外,還需要在商品和訂單專案的啟動類中新增@EnableDiscoveryClient註釋,該點上面已經說明。

spring.application.name=microservice-item

eureka.client.register-with-eureka=true
eureka.client.fetch-registry=false

#與eureka伺服器地址相同
eureka.client.service-url.defaultZone=http://localhost:6999/eureka/

#將自己的ip地址註冊到eureka伺服器中
eureka.instance.prefer-ip-address=true

在商品專案配置檔案中新增以上內容,首先就是spring.application.name,這個就表示當前微服務在eureka中的註冊名,這個是非常重要的,後面對於該服務的發現就會根據該名字。然後就是兩個關鍵欄位eureka.client.register-with-eureka=true和 eureka.client.fetch-registry=false前者因為商品服務是需要被訂單服務發現的,所以設定為需要註冊到eureka中,後者因為商品服務不需要呼叫其它服務,因此設定為false。
同理,在訂單微服務的註冊資訊中輸入以下內容:

spring.application.name=microservice-order	
#應用資訊不需要註冊,因為是一個消費者,不是服務的提供者
eureka.client.register-with-eureka=false
#是否從註冊中心中獲取註冊資訊,需要獲取商品資訊,以此設定為true
eureka.client.fetch-registry=true	
#與eureka伺服器地址相同
eureka.client.service-url.defaultZone=http://localhost:6999/eureka/

到此為止,我們還需要對訂單的ItemService類進行修改:

package com.fty_czbk.service;

import com.fty_czbk.pojo.Item;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.List;
@Service
public class ItemService {

    @Autowired
    private DiscoveryClient discoveryClient;

    @Autowired
    private RestTemplate restTemplate;

    public Item queryItemById(Long id) {
        String serviceId = "microservice-item";
        List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
        if(instances == null || instances.isEmpty()) {
            return null;
        }
        ServiceInstance serviceInstance = instances.get(0);
        String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort();
        Item item = restTemplate.getForObject(url + "/item/" + id, Item.class);
        return item;
    }
}

可以看到,eureka中的DiscoveryClient 物件可以根據商品配置檔案中設定的註冊id,自動的獲取商品的地址和埠號,以此就能輕鬆的管理商品服務。開啟eureka瀏覽器,也可以看到註冊的資訊。
在這裡插入圖片描述