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 测试
1.9 使用多线程提高REST服务性能
- 使用Runnable异步处理Rest服务
- 使用DeferredResult异步处理Rest服务
- 异步处理设置
1.9.1 同步处理
我们传统请求是同步请求:Http请求发出之后在主线程中处理,然后Http响应。
但是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秒不到就能返回,副线程1秒后才返回。
1.9.4 使用DeferredResult异步处理Rest服务
1.9.4 原理
既然已经可以用Runnable异步处理Rest服务了,那么为啥还要用DeferredResult异步处理Rest服务呢?Runnable处理的场景如下:Runnable这种形式
- 副线程必须有主线程吊起
- 代码中副线程代码是写在主线程里面的
企业级开发中遇到以下业务场景时候就会出问题:
- 接收下单请求的应用1和真正处理下单请求的应用2并不是1台服务器,在应用1的JVM的线程1收到客户请求后,会把请求转发到消息队列,另一个服务器去监听这个消息队列,当消息队列里面有下单的请求后,由应用2真正处理下单的逻辑。下单完成之后,应用2会把真正的处理结果返回到消息队列里面。同时在应用1里面的线程2来监听这个消息队列。发现有订单处理结果之后会返回http响应,线程1,线程2是互相隔离的,上面的Callable是满足不了的。
- 我们使用对象模拟消息队列,placeOrder表示下单请求,completeOrder标识订单完成。当placeOrder有值时候标识收到了下单的消息, completeOrder有值时候表示下单完成消息。应用2的处理,我们让其休眠1秒替代。
- DeferredResult是在线程1和线程2之间传递信息;副线程处理和消息队列的处理,我们为了防止其干扰主线程的启动,我们创建一个新的线程主动去处理。
- 以上有3个线程:主线程接受下单请求、有一个线程真正处理下单(应用2)、有一个线程处理结果返回给前台。3个线程是互相隔离的,彼此不知道,互相通信是通过消息队列进行交互的。
1.9.5 代码实现
- 虚拟的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;
}
}
后端打印日志:
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
从里面我们可以看见configurer可以注册上面说的两种异步对应的拦截器进行拦截。