使用Spring MVC开发RESTful API(续)

陌路散爱 提交于 2019-12-10 04:06:06

使用多线程提高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)));
    }
}

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