1. 程式人生 > >使用Spring MVC開發RESTful API(續)

使用Spring MVC開發RESTful API(續)

描述 多線程 用戶 接收http esp 訂單號 開始 邏輯 配置

使用多線程提高REST服務性能

異步處理REST服務,提高服務器吞吐量

使用Runnable異步處理Rest服務

技術分享圖片

AsyncController.java

@RestController
@GetMapping("/async")
public class AsyncController {
    private Logger logger = LoggerFactory.getLogger(getClass());

    @RequestMapping("/order")
    public Callable<String> order() throws Exception {
        logger.info("主線程開始");
        Callable<String> result = new Callable<String>() {
            @Override
            public String call() throws Exception {
                logger.info("副線程開始");
                Thread.sleep(2000); // 模擬處理下單消耗的時間
                logger.info("副線程結束");
                return "success";
            }
        };
        logger.info("主線程結束");
        return result;
    }
}

使用DeferredResult異步處理Rest服務

技術分享圖片

應用1/線程1:接收下單請求,放到消息隊列

應用1/線程2:監聽器,監聽消息隊列是否有下單處理結果,返回HTTP響應

應用2:處理下單邏輯

AsyncController.java

@GetMapping("/order2")
public DeferredResult<String> order2() throws Exception {
    logger.info("主線程開始");
    // 主線程,相當於圖中應用1/線程1,接收HTTP請求
    // 收到下單請求,生成一個隨機訂單號,放到消息隊列裏
    String orderNumber = RandomStringUtils.randomNumeric(8);
    mockQueue.setPlaceOrder(orderNumber);

    // 用於接收處理結果
    DeferredResult<String> result = new DeferredResult<>();
    deferredResultHolder.getMap().put(orderNumber, result);
    logger.info("主線程結束");
    return result;
}

MockQueue.java,模擬隊列

@Component
public class MockQueue {
    private String placeOrder; // 下單消息
    private String completeOrder; // 訂單完成訂單完成

    private Logger logger = LoggerFactory.getLogger(getClass());

    public String getPlaceOrder() {
        return placeOrder;
    }

    public void setPlaceOrder(String placeOrder) {
        // 此線程是模擬應用2,處理下單邏輯
        new Thread(() -> {
            logger.info("接到下單請求:" + placeOrder);
            try {
                Thread.sleep(1000); // 模擬處理下單過程
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.completeOrder = placeOrder;
            logger.info("下單請求處理完畢:" + placeOrder);
        }).start();
    }

    public String getCompleteOrder() {
        return completeOrder;
    }

    public void setCompleteOrder(String completeOrder) {
        this.completeOrder = completeOrder;
    }
}

DeferredResultHolder.java ,用於在線程1與線程2之間傳遞傳遞DeferredResult對象

@Component
public class DeferredResultHolder {
    // 訂單號,訂單處理結果
    private Map<String, DeferredResult<String>> map = new HashMap<>();

    public Map<String, DeferredResult<String>> getMap() {
        return map;
    }

    public void setMap(Map<String, DeferredResult<String>> map) {
        this.map = map;
    }
}

QueueListener.java,監聽器

@Component
public class QueueListener implements ApplicationListener<ContextRefreshedEvent> {
    @Autowired
    private MockQueue mockQueue;

    @Autowired
    private DeferredResultHolder deferredResultHolder;

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        // 相當於圖中應用1/線程2,模擬監聽器
        new Thread(() -> {
            while (true) {
                if (StringUtils.isNotBlank(mockQueue.getCompleteOrder())) {
                    String orderNumber = mockQueue.getCompleteOrder();
                    logger.info("返回訂單處理結果:" + orderNumber);
                    deferredResultHolder.getMap().get(orderNumber)
                        .setResult("place order success");
                    mockQueue.setCompleteOrder(null);
                } else {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                    }
                }
            }
        }).start();
    }
}

啟動應用並訪問http://localhost:8080/async/order2

技術分享圖片

異步處理配置

用攔截器攔截異步處理的請求以有線程池的配置

// 用攔截器攔截異步處理的請求,有如下兩個方法註冊攔截器,分別對應異步處理的兩種方式
// 區別是有超時時間
// configurer.registerCallableInterceptors()
// configurer.registerDeferredResultInterceptors()

// Runnable使用的簡單的異步線程池來處理,線程不可重用

使用Swagger自動生成文檔

引入Swagger

引入相關依賴,immoc-security-demo/pom.xml

<!-- 引入swagger -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.7.0</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.7.0</version>
</dependency>

加註解,DemoApplication.java

@EnableSwagger2 //  啟用Swagger2

重啟應用,訪問鏈接http://localhost:8080/swagger-ui.html

技術分享圖片

詳細描述

方法的描述

@ApiOperation(value = "用戶查詢服務")

參數的描述

// 參數被封裝到對象裏
@ApiModelProperty("用戶名")
// 參數直接寫在方法裏
@ApiParam("用戶ID")

使用WireMock偽造REST服務

與前端開發並行工作,開發階段,前端包括app和頁面開發時都需要測試數據,這時WireMock就派上用場了。這與你再寫個web應用提供測試數據有什麽不同呢。因為WireMock不用重啟,定義url和返回數據都很方便。

下載並啟動

下載:http://wiremock.org/docs/running-standalone/

指定端口啟動:

java -jar wiremock-standalone-2.18.0.jar --port 9999
# --port 9999 指定端口,默認端口8080, --port 0 隨機端口

技術分享圖片

模擬請求和響應

引入依賴

<!--  引入WireMock-->
<dependency>
    <groupId>com.github.tomakehurst</groupId>
    <artifactId>wiremock</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
</dependency>

編寫代碼,MockServer.java

public class MockServer {
    public static void main(String[] args) throws IOException {
        configureFor("192.168.5.210", 9999);
        // configureFor(9999);
        removeAllMappings();

        mock("/order/1", "01.txt");
        mock("/order/2", "02.txt");
    }

    private static void mock(String url, String fileName) throws IOException {
        ClassPathResource resource = 
            new ClassPathResource("mock/response/" + fileName);
        String content = 
            StringUtils.join(FileUtils.readLines(resource.getFile(), "UTF-8"), "\n");
        stubFor(get(urlPathEqualTo(url))
                .willReturn(aResponse().withBody(content).withStatus(200)));
    }
}

技術分享圖片

使用Spring MVC開發RESTful API(續)