文件上传是一个项目里经常要用到的功能,Spring
MVC
通过配置一个MultipartResolver
来上传文件。
在Spring
的控制器中,通过MultipartFile file
来接收文件,通过MultipartFile[] files
接收多个文件上传。
commons-fileupload
commons-fileupload
1.3.1
commons-io
commons-io
2.3
在src/main/resources/views
下新建upload.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
upload page
在文件MyMvcConfig
的addViewControllers
方法里面增加下面的转向配置,代码如下:
registry.addViewController("/toUpload").setViewName("/upload");
添加完成之后的代码如下所示:
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/index").setViewName("/index");
registry.addViewController("/toUpload").setViewName("/upload");
}
在文件MyMvcConfig
中增加下面的代码,提供对上传文件的支持:
@Bean
public MultipartResolver multipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setMaxUploadSize(1000000);
return multipartResolver;
}
package org.light4j.springMvc4.web;
import java.io.File;
import java.io.IOException;
import org.apache.commons.io.FileUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
@Controller
public class UploadController {
@RequestMapping(value = "/upload", method = RequestMethod.POST)
public @ResponseBody
String upload(MultipartFile file) {// ①
try {
FileUtils.writeByteArrayToFile(new File("e:/upload/" + file.getOriginalFilename()),file.getBytes()); // ②
return "ok";
} catch (IOException e) {
e.printStackTrace();
return "wrong";
}
}
}
消息转换器HttpMessageConverter
是用来处理request
和response
里面的数据的。Spring
为我们内置了大量的HttpMessageConverter
,例如,MappingJackson2HttpMessageConverter
,StringHttpMessageConverter
等。下面演示自定义的HttpMessageConverter
,并注册这个HttpMessageConverter
到Spring
MVC
import java.io.IOException;
import java.nio.charset.Charset;
import org.light4j.springMvc4.domain.DemoObj;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.util.StreamUtils;
/**
*① 继承AbstractHttpMessageConverter接口来实现自定义的HttpMessageConverter。
*② 新建一个我们自定义的媒体类型application/x-fishhh。
*③ 重写readInternal方法,处理请求的数据。代码表明我们处理由”-“隔开的数据,并转成DemoObj的对象。
*④ 表明本HttpMessageConverter只处理DemoObj这个类。
*⑤ 重写writeInternal方法,处理如何输出数据到response。此例中,我们在原样输出前面加上"hello:"
*
* @author Administrator
*
*/
public class MyMessageConverter extends AbstractHttpMessageConverter {//①
public MyMessageConverter() {
super(new MediaType("application", "x-fishhh",Charset.forName("UTF-8")));//②
}
/**
* ③
*/
@Override
protected DemoObj readInternal(Class extends DemoObj> clazz,
HttpInputMessage inputMessage) throws IOException,
HttpMessageNotReadableException {
String temp = StreamUtils.copyToString(inputMessage.getBody(),
Charset.forName("UTF-8"));
String[] tempArr = temp.split("-");
return new DemoObj(new Long(tempArr[0]), tempArr[1]);
}
/**
* ④
*/
@Override
protected boolean supports(Class> clazz) {
return DemoObj.class.isAssignableFrom(clazz);
}
/**
* ⑤
*/
@Override
protected void writeInternal(DemoObj obj, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
String out = "hello:" + obj.getId() + "-"+ obj.getName();
outputMessage.getBody().write(out.getBytes());
}
}
① 继承
AbstractHttpMessageConverter
接口来实现自定义的HttpMessageConverter
。
② 新建一个我们自定义的媒体类型application/x-xxxxxxx
。
③ 重写readInternal
方法,处理请求的数据。代码表明我们处理由”-“隔开的数据,并转成DemoObj
的对象。
④ 表明本HttpMessageConverter
只处理DemoObj
这个类。
⑤ 重写writeInternal
方法,处理如何输出数据到response
。此例中,我们在原样输出前面加上"hello:"
。
在文件MyMvcConfig
的方法addViewControllers
中添加viewController
映射访问演示页面converter.jsp
,代码如下:
registry.addViewController("/converter").setViewName("/converter");
添加完成之后的代码如下所示:
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/index").setViewName("/index");
registry.addViewController("/toUpload").setViewName("/upload");
registry.addViewController("/converter").setViewName("/converter");
}
配置自定义的HttpMessageConverter
的Bean
,在Spring
MVC
里面注册HttpMessageConverter
有两个方法:
1. 在文件
MyMvcConfig
中配置configureMessageConverters
:重载会覆盖掉Spring
MVC
默认注册的多个HttpMessageConverter
。
2. 在文件MyMvcConfig
中配置extendMessageConverters
:仅添加一个自定义的HttpMessageConverter
,不覆盖默认注册的HttpMessageConverter
。
所以,在此例中我们重写extendMessageConverters
,在文件MyMvcConfig
中增加下面的代码:
@Override
public void extendMessageConverters(List> converters) {
converters.add(converter());
}
@Bean
public MyMessageConverter converter(){
return new MyMessageConverter();
}
package org.light4j.springMvc4.web;
import org.light4j.springMvc4.domain.DemoObj;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class ConverterController {
@RequestMapping(value = "/convert", produces = { "application/x-longjiazuo" }) //①
public @ResponseBody DemoObj convert(@RequestBody DemoObj demoObj) {
return demoObj;
}
}
① 指定返回的媒体类型为我们自定义的媒体类型
application/x-longjiazuo
在src/main/resources
下新建conventer.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
HttpMessageConverter Demo
① 注意这里的数据格式,后台处理按此格式处理,用”-“隔开。
②contentType
设置的媒体类型是我们自定义的application/x-xxxxx
。
服务器端推送技术在我们日常开发中较为常用,可能早期很多人的解决方案是使用Ajax
向服务器轮询消息,使浏览器尽可能第一时间获得服务端的消息,因为这种方式的轮询频率不好控制,所以大大增加了服务端的压力。
下面要介绍的服务端推送方案都是基于:当客户端向服务端发送请求,服务端会抓住这个请求不放,等有数据更新的时候才返回给客户端,当客户端接收到消息后,再向服务端发送请求,周而复始。这种方式的好处是减少了服务器的请求数量,大大减少了服务器的压力。
除了服务器端推送技术之外,还有一个另外的双向通信的技术——WebSocket
,后面会在介绍Spring
Boot
的时候进行演示。
下面将提供基于SSE
(Server Send Event
服务端发送事件)的服务器端推送和基于Servlet3.0
+的异步方法特性的演示,其中第一种方式需要新式浏览器的支持,第二种方式是跨浏览器的。
import java.util.Random;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
//① 注意,这里使用输出的媒体类型为text/event-stream,这是服务器端SSE的支持,本例演示每5秒向浏览器推送随机消息。
@Controller
public class SseController {
@RequestMapping(value = "/push", produces = "text/event-stream") // ①
public @ResponseBody String push() {
Random r = new Random();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "data:Testing 1,2,3" + r.nextInt() + "\n\n";
}
}
在src/main/resources
下面新建sse.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
SSE Demo
">
① EventSource
对象只有新式的浏览器才有(Chrome
,Firefox
等)。EventSource
的SSE
的客户端。
② 添加SSE
客户端监听,在此获得服务器端推送的消息。
在文件WebInitializer
的方法onStartup
末尾增加以下代码开启异步方法支持,代码如下:
servlet.setAsyncSupported(true);//①
添加完成之后的代码如下所示:
Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx)); //③
servlet.addMapping("/");
servlet.setLoadOnStartup(1);
servlet.setAsyncSupported(true);//①
① 此句开启异步方法支持。
package org.light4j.springMvc4.web;
import org.light4j.springMvc4.service.PushService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.DeferredResult;
@Controller
public class AysncController {
@Autowired
PushService pushService; //①
@RequestMapping("/defer")
@ResponseBody
public DeferredResult deferredCall() { //②
return pushService.getAsyncUpdate();
}
}
异步任务实现是通过控制器从另外一个线程返回一个
DeferredResult
,这里的DeferredResult
是从pushService
中获得的。
① 定时任务,定时更新DeferredResult
。
② 返回给客户端DeferredResult
。
package org.light4j.springMvc4.service;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.async.DeferredResult;
@Service
public class PushService {
private DeferredResult deferredResult; //①
public DeferredResult getAsyncUpdate() {
deferredResult = new DeferredResult();
return deferredResult;
}
@Scheduled(fixedDelay = 5000)
public void refresh() {
if (deferredResult != null) {
deferredResult.setResult(new Long(System.currentTimeMillis()).toString());
}
}
}
① 在
PushService
里产生DeferredResult
给控制器使用,通过@Scheduled
注解的方法定时更新DeferredResult
在src/main/resources
下新建async.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
servlet async support
此处的代码使用的是
JQuery
的Ajax
请求,所以没有浏览器兼容性问题。
① 页面打开就向后台发送请求。
② 在控制台输出服务端推送的数据。
③ 一次请求完成后再向后台推送数据。
在文件MyMvcConfig
上使用注解@EnableScheduling
开启计划任务的支持,代码如下:
@Configuration
@EnableWebMvc// ①
@EnableScheduling
@ComponentScan("org.light4j.springMvc4")
public class MyMvcConfig extends WebMvcConfigurerAdapter {
}
在文件MyMvcConfig
的方法addViewControllers
添加viewController
映射访问演示页面async.jsp
,代码如下:
registry.addViewController("/async").setViewName("/async");
为了测试Web
项目通常不需要启动项目,我们需要一些Servlet
相关的模拟对象,比如:MockMVC
,MockHttpServletRequest
,MockHttpServletResponse
,MockHttpSession
等。
在Spring
里,我们使用@WebAppConfiguration
指定加载的ApplicationContext
是一个WebAppConfiguration
。
在下面的示例里面借助JUnit
和Spring TestContext framework
,分别演示对普通页面转向形控制器和RestController
进行测试。
在src/main/java
下新增DemoService
类,代码如下所示:
package org.light4j.springMvc4.service;
import org.springframework.stereotype.Service;
@Service
public class DemoService {
public String saySomething(){
return "hello";
}
}
在src/test/java
下新建TestControllerIntegrationTests
类,代码如下:
package org.light4j.springMvc4.web;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.light4j.springMvc4.MyMvcConfig;
import org.light4j.springMvc4.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {MyMvcConfig.class})
@WebAppConfiguration("src/main/resources") //①
public class TestControllerIntegrationTests {
private MockMvc mockMvc; //②
@Autowired
private DemoService demoService;//③
@Autowired
WebApplicationContext wac; //④
@Autowired
MockHttpSession session; //⑤
@Autowired
MockHttpServletRequest request; //⑥
@Before //7
public void setup() {
mockMvc =
MockMvcBuilders.webAppContextSetup(this.wac).build(); //②
}
@Test
public void testNormalController() throws Exception{
mockMvc.perform(get("/normal")) //⑧
.andExpect(status().isOk())//⑨
.andExpect(view().name("page"))//⑩
.andExpect(forwardedUrl("/WEB-INF/classes/views/page.jsp"))//11
.andExpect(model().attribute("msg", demoService.saySomething()));//12
}
@Test
public void testRestController() throws Exception{
mockMvc.perform(get("/testRest")) //13
.andExpect(status().isOk())
.andExpect(content().contentType("text/plain;charset=UTF-8"))//14
.andExpect(content().string(demoService.saySomething()));//15
}
}
①
@WebAppConfiguration
注解在类上,用来声明加载的ApplicationContext
是一个WebApplicationContext
。它的属性指定的是Web
资源的位置,默认为src/main/webapp
,本例修改为src/main/resource
。
②MockMvc
模拟MVC
对象,通过MockMvcBuilders.webAppContextSetup(this.wac).build()
进行初始化。
③ 可以在测试用例中注入Spring
的Bean
。
④ 可注入WebApplicationContext
。
⑤ 可注入模拟的http session
,此处仅作演示,没有使用。
⑥ 可注入模拟的http request
,此处仅作演示,没有使用。
⑦@Before
在测试开始前进行的初始化工作。
⑧ 模拟向/normal
进行get
请求。
⑨ 预期控制返回状态为200
.
⑩ 预期view
的名称为page
。
11 预期页面转向的真正路径为/WEB-INF/classes/views/page.jsp
。
12 预期model
里面的值是demoService.saySomething()
返回值hello
。
13.模拟向/testRest
进行get
请求。
14 预期返回值的媒体类型是text/plain;charset=UTF-8
。
15 预期返回值的内容为demoService.saySomething()
返回值hello
。
在src/main/java
下新增NormalController
类,代码如下所示:
package org.light4j.springMvc4.web;
import org.light4j.springMvc4.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class NormalController {
@Autowired
DemoService demoService;
@RequestMapping("/normal")
public String testPage(Model model){
model.addAttribute("msg", demoService.saySomething());
return "page";
}
}
在src/main/resources/view
下新建page.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
Test page
Welcome to Spring MVC world
在src/main/java
下新增RestController
类,代码如下所示:
package org.light4j.springMvc4.web;
import org.light4j.springMvc4.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@RestController
public class MyRestController {
@Autowired
DemoService demoService;
@RequestMapping(value = "/testRest" ,produces="text/plain;charset=UTF-8")
public @ResponseBody String testRest(){
return demoService.saySomething();
}
}