6.使用Spring MVC开发RESTful API(四)

1.8 常见文件上传

1.8.1 测试用例

 @Test
    public void whenUploadSuccess() throws Exception{
      String result = mockMvc.perform(MockMvcRequestBuilders.fileUpload("/file")//请求参数url
               //file-方法参数名  test.txt--file里面传递的文件名  multipart/form-data--参数类型   hello upload--参数内容
               .file(new MockMultipartFile("file","test.txt","multipart/form-data","hello upload".getBytes("UTF-8"))))
               .andExpect(MockMvcResultMatchers.status().isOk())
               .andReturn().getResponse().getContentAsString();

      System.out.println("whenUploadSuccess:"+result);
    }

1.8.2 后端

@RestController  
@RequestMapping("/file")  
public class FileController {  
    private String folder = "F:\\std\\spring-security\\spring-security-demo\\src\\main\\java\\com\\yxm\\security\\web\\controller";  
  
  @PostMapping  
  public FileInfo upload(MultipartFile file) throws Exception{  
       System.out.println(file.getName());  
  System.out.println(file.getOriginalFilename());  
  System.out.println(file.getSize());  
  
  File localFile = new File(folder, new Date().getTime() + ".txt");  
  file.transferTo(localFile);  
  
 return new FileInfo(localFile.getAbsolutePath());  
  }  
  
    @GetMapping("/{id}")  
    public void download(@PathVariable String id, HttpServletRequest request, HttpServletResponse response) throws Exception {  
         //jdk7提供的一种操作  
  try (InputStream inputStream = new FileInputStream(new File(folder, id + ".txt"));  
  OutputStream outputStream = response.getOutputStream();) {  
  
            response.setContentType("application/x-download");  
  response.addHeader("Content-Disposition", "attachment;filename=test.txt");  
  
  IOUtils.copy(inputStream, outputStream);  
  outputStream.flush();  
  }  
    }  
}

1.8.3 测试

运行测试用例(创建文件):
6.使用Spring MVC开发RESTful API(四)_第1张图片

6.使用Spring MVC开发RESTful API(四)_第2张图片

App端测试:
6.使用Spring MVC开发RESTful API(四)_第3张图片

1.9 使用多线程提高REST服务性能

  • 使用Runnable异步处理Rest服务
  • 使用DeferredResult异步处理Rest服务
  • 异步处理设置

1.9.1 同步处理

我们传统请求是同步请求:Http请求发出之后在主线程中处理,然后Http响应。
6.使用Spring MVC开发RESTful API(四)_第4张图片
但是tomcat这种中间件可以管理的线程数有数量限制的,当有线程数达到这个线程数时候,所有可用线程都是被占用了。新的请求进来就没法处理了。

1.9.2 异步处理

所谓异步处理是:当http请求进来之后,Tomcat主线程去调用副线程去执行业务逻辑,当副线程处理完结果之后,再由主线程将结果返回回去。副线程处理整个业务逻辑中,主线程可以空闲出来去处理其他请求。采用这种方式的话,服务器吞吐量会有效提升。

1.9.3 代码实现(使用Runnable异步处理Rest服务)

我们以线程休眠1秒当做下单调用其他服务。我们通过Callable实现副线程。

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

   @RequestMapping("/order")
   public Callable order() throws Exception{
       /**
        * 异步处理:我们用Callable
        */
       logger.info("主线程开始");
       Callable result = new Callable() {
           @Override
           public String call() throws Exception {
               logger.info("副线程开始");
               Thread.sleep(1000);
               logger.info("副线程返回");
               return "success";
           }
       };
       Thread.sleep(1000);
       logger.info("主线程返回");
       return result;
   }
}

前端测试(测试后1秒就返回了):
6.使用Spring MVC开发RESTful API(四)_第5张图片

测试后后台打印:
6.使用Spring MVC开发RESTful API(四)_第6张图片

我们可以知道:主线程在1秒不到就能返回,副线程1秒后才返回。

1.9.4 使用DeferredResult异步处理Rest服务

1.9.4 原理

既然已经可以用Runnable异步处理Rest服务了,那么为啥还要用DeferredResult异步处理Rest服务呢?Runnable处理的场景如下:Runnable这种形式

  1. 副线程必须有主线程吊起
  2. 代码中副线程代码是写在主线程里面的

6.使用Spring MVC开发RESTful API(四)_第7张图片

企业级开发中遇到以下业务场景时候就会出问题:

  1. 接收下单请求的应用1和真正处理下单请求的应用2并不是1台服务器,在应用1的JVM的线程1收到客户请求后,会把请求转发到消息队列,另一个服务器去监听这个消息队列,当消息队列里面有下单的请求后,由应用2真正处理下单的逻辑。下单完成之后,应用2会把真正的处理结果返回到消息队列里面。同时在应用1里面的线程2来监听这个消息队列。发现有订单处理结果之后会返回http响应,线程1,线程2是互相隔离的,上面的Callable是满足不了的。

6.使用Spring MVC开发RESTful API(四)_第8张图片

  1. 我们使用对象模拟消息队列,placeOrder表示下单请求,completeOrder标识订单完成。当placeOrder有值时候标识收到了下单的消息, completeOrder有值时候表示下单完成消息。应用2的处理,我们让其休眠1秒替代。
  2. DeferredResult是在线程1和线程2之间传递信息;副线程处理和消息队列的处理,我们为了防止其干扰主线程的启动,我们创建一个新的线程主动去处理。
  3. 以上有3个线程:主线程接受下单请求、有一个线程真正处理下单(应用2)、有一个线程处理结果返回给前台。3个线程是互相隔离的,彼此不知道,互相通信是通过消息队列进行交互的。

1.9.5 代码实现

  1. 虚拟的mq对象:MQ(并声明其为一个组件)
@Component//定义为Spring的组件
public class MQ {
    private String placeOrder;//下单请求
    private String completeOrder;//订单完成

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

    public String getPlaceOrder() {
       return placeOrder;
    }

    public void setPlaceOrder(String placeOrder) throws Exception{
        /**
         * 下单业务逻辑处理:下单之后我们用线程休眠1秒替代应用2出路,然后设置completeOrder为完成
         */
        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:

@Component
public class DeferredResultHolder {
    private Map> map =  new HashMap>();

    public Map> getMap() {
        return map;
    }

    public void setMap(Map> map) {
        this.map = map;
    }
}

监听器逻辑处理:

@Component//声明Application应用监听器;模拟线程2
public class MQListener implements ApplicationListener {
    @Autowired
    private MQ mqueue;

    @Autowired
    private DeferredResultHolder deferredResultHolder;

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

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        new Thread(()->{
            while (true){
                if(StringUtils.isNotBlank(mqueue.getCompleteOrder())){
                    //不为空,处理并返回
                    String orderNumber = mqueue.getCompleteOrder();
                    logger.info("订单号处理结果:"+orderNumber);
                    deferredResultHolder.getMap().get(orderNumber).setResult("order success!")
                    mqueue.setCompleteOrder(null);//已经处理了 就清空 防止死循环
                }else {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}

主线程处理:

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

    @Autowired
    private DeferredResultHolder deferredResultHolder;

    @Autowired
    private MQ mqueue;

   @RequestMapping("/order")
   public Callable order() throws Exception{
       /**
        * 异步处理:我们用Callable
        */
       logger.info("主线程开始");
       Callable result = new Callable() {
           @Override
           public String call() throws Exception {
               logger.info("副线程开始");
               Thread.sleep(1000);
               logger.info("副线程返回");
               return "success";
           }
       };
       logger.info("主线程返回");
       return result;
   }

    @RequestMapping("/orderDeferredResult")
    public DeferredResult orderDeferredResult() throws Exception{
        /**
         * 异步处理:我们用Callable
         */
        logger.info("主线程开始");
        String orderNumber = RandomStringUtils.randomNumeric(8);
        mqueue.setPlaceOrder(orderNumber);

        DeferredResult result = new DeferredResult<>();
        deferredResultHolder.getMap().put(orderNumber, result);
        logger.info("主线程返回");
        return result;
    }
}

测试:
web端访问:
6.使用Spring MVC开发RESTful API(四)_第9张图片

后端打印日志:

2020-02-22 08:12:48.055  INFO 12868 --- [nio-8088-exec-5] com.yxm.security.async.AsyncController   : 主线程开始
2020-02-22 08:12:48.058  INFO 12868 --- [nio-8088-exec-5] com.yxm.security.async.AsyncController   : 主线程返回
2020-02-22 08:12:48.064  INFO 12868 --- [      Thread-49] com.yxm.security.async.MQ                : 接到下单请求, 65643893
2020-02-22 08:12:49.068  INFO 12868 --- [      Thread-49] com.yxm.security.async.MQ                : 下单请求处理完毕,65643893
2020-02-22 08:12:50.034  INFO 12868 --- [      Thread-37] com.yxm.security.async.MQListener        : 订单号处理结果:65643893

从打印的日志看出:nio主线程里面3毫秒就返回了,然后其他线程各自处理了一段时间。

1.9.5 异步处理设置

我们之前在WebConfig中配置了拦截器,如果我们需要拦截异步请求的时候,我们需要重写方法:configureAsyncSupport
6.使用Spring MVC开发RESTful API(四)_第10张图片

从里面我们可以看见configurer可以注册上面说的两种异步对应的拦截器进行拦截。

你可能感兴趣的:(spring-security)