SpringMvc4.x-4(高级配置)

18 篇文章 0 订阅
6 篇文章 0 订阅

文件上传配置

文件上传是一个项目里经常要用到的功能,Spring MVC通过配置一个MultipartResolver来上传文件。

Spring的控制器中,通过MultipartFile file来接收文件,通过MultipartFile[] files接收多个文件上传。

1. 添加文件上传依赖

<!-- file upload -->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.1</version>
</dependency>
<!-- 非必需,可简化IO操作 -->
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.3</version>
</dependency>

2. 增加上传页面upload.jsp。

src/main/resources/views下新建upload.jsp


<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>upload page</title>

</head>
<body>

<div class="upload">
    <form action="upload" enctype="multipart/form-data" method="post">
        <input type="file" name="file"/><br/>
        <input type="submit" value="上传">
    </form>
</div>


</body>
</html>

3. 添加转向到upload页面的ViewController

在文件MyMvcConfigaddViewControllers方法里面增加下面的转向配置,代码如下:

registry.addViewController("/toUpload").setViewName("/upload");

添加完成之后的代码如下所示:


@Override
public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/index").setViewName("/index");
    registry.addViewController("/toUpload").setViewName("/upload");
}

4. MultipartResolver配置

在文件MyMvcConfig中增加下面的代码,提供对上传文件的支持:

@Bean
public MultipartResolver multipartResolver() {
    CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
    multipartResolver.setMaxUploadSize(1000000);
    return multipartResolver;
}

5. 控制器

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

消息转换器HttpMessageConverter是用来处理requestresponse里面的数据的。Spring为我们内置了大量的HttpMessageConverter,例如,MappingJackson2HttpMessageConverter,StringHttpMessageConverter等。下面演示自定义的HttpMessageConverter,并注册这个HttpMessageConverterSpring MVC

1. 自定义HttpMessageConverter

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<DemoObj> {//①

    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:"

2. 配置

在文件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");
}

配置自定义的HttpMessageConverterBean,在Spring MVC里面注册HttpMessageConverter有两个方法:

1. 在文件MyMvcConfig中配置configureMessageConverters:重载会覆盖掉Spring MVC默认注册的多个HttpMessageConverter
2. 在文件MyMvcConfig中配置extendMessageConverters:仅添加一个自定义的HttpMessageConverter,不覆盖默认注册的HttpMessageConverter

所以,在此例中我们重写extendMessageConverters,在文件MyMvcConfig中增加下面的代码:

@Override
 public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(converter());
}

@Bean 
public MyMessageConverter converter(){
        return new MyMessageConverter();
}

3. 演示控制器

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

4. 演示页面

src/main/resources下新建conventer.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>HttpMessageConverter Demo</title>
</head>
<body>
    <div id="resp"></div><input type="button" onclick="req();" value="请求"/>
<script src="assets/js/jquery.js" type="text/javascript"></script>
<script>
    function req(){
        $.ajax({
            url: "convert",
            data: "1-longjiazuo", //①
            type:"POST",
            contentType:"application/x-longjiazuo", //②
            success: function(data){
                $("#resp").html(data);
            }
        });
    }

</script>
</body>
</html>

代码解释:

① 注意这里的数据格式,后台处理按此格式处理,用”-“隔开。
② contentType设置的媒体类型是我们自定义的application/x-xxxxx。 

服务器端推送技术之SSE

服务器端推送技术在我们日常开发中较为常用,可能早期很多人的解决方案是使用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"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>SSE Demo</title>

</head>
<body>
<div id="msgFrompPush"></div>
<script type="text/javascript" src="<c:url value="assets/js/jquery.js" />"></script>
<script type="text/javascript">

 if (!!window.EventSource) { //①
       var source = new EventSource('push'); 
       s='';
       source.addEventListener('message', function(e) {//②
           s+=e.data+"<br/>";
           $("#msgFrompPush").html(s);

       });

       source.addEventListener('open', function(e) {
            console.log("连接打开.");
       }, false);

       source.addEventListener('error', function(e) {
            if (e.readyState == EventSource.CLOSED) {
               console.log("连接关闭");
            } else {
                console.log(e.readyState);    
            }
       }, false);
    } else {
            console.log("你的浏览器不支持SSE");
    } 
</script>
</body>
</html>

① EventSource对象只有新式的浏览器才有(ChromeFirefox等)。EventSourceSSE的客户端。
② 添加SSE客户端监听,在此获得服务器端推送的消息。

服务器端推送技术之Servlet3.0+异步方法处理

1.开启异步方法支持

在文件WebInitializer的方法onStartup末尾增加以下代码开启异步方法支持,代码如下:

servlet.setAsyncSupported(true);//①

添加完成之后的代码如下所示:

 
Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx)); //③
     servlet.addMapping("/");
     servlet.setLoadOnStartup(1);
     servlet.setAsyncSupported(true);//①

代码解释:

① 此句开启异步方法支持。

2. 演示控制器

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<String> deferredCall() { //②
        return pushService.getAsyncUpdate();
    }
}

代码解释:

异步任务实现是通过控制器从另外一个线程返回一个DeferredResult,这里的DeferredResult是从pushService中获得的。
① 定时任务,定时更新DeferredResult
② 返回给客户端DeferredResult

3. 定时任务

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<String> deferredResult; //①

    public DeferredResult<String> getAsyncUpdate() {
        deferredResult = new DeferredResult<String>();
        return deferredResult;
    }

    @Scheduled(fixedDelay = 5000)
    public void refresh() {
        if (deferredResult != null) {
            deferredResult.setResult(new Long(System.currentTimeMillis()).toString());
        }
    }
}

代码解释:

① 在PushService里产生DeferredResult给控制器使用,通过@Scheduled注解的方法定时更新DeferredResult

4. 演示页面

src/main/resources下新建async.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>servlet async support</title>

</head>
<body>

<script type="text/javascript" src="assets/js/jquery.js"></script>
<script type="text/javascript">

    deferred();//①

    function deferred(){
        $.get('defer',function(data){
            console.log(data); //②
            deferred(); //③
        });
    }

</script>
</body>
</html>

代码解释:

此处的代码使用的是JQueryAjax请求,所以没有浏览器兼容性问题。
① 页面打开就向后台发送请求。
② 在控制台输出服务端推送的数据。
③ 一次请求完成后再向后台推送数据。

5. 配置

在文件MyMvcConfig上使用注解@EnableScheduling开启计划任务的支持,代码如下:

@Configuration
@EnableWebMvc// ①
@EnableScheduling
@ComponentScan("org.light4j.springMvc4")
public class MyMvcConfig extends WebMvcConfigurerAdapter {

}

在文件MyMvcConfig的方法addViewControllers添加viewController映射访问演示页面async.jsp,代码如下:

registry.addViewController("/async").setViewName("/async");

Spring MVC的测试

为了测试Web项目通常不需要启动项目,我们需要一些Servlet相关的模拟对象,比如:MockMVCMockHttpServletRequestMockHttpServletResponseMockHttpSession等。

Spring里,我们使用@WebAppConfiguration指定加载的ApplicationContext是一个WebAppConfiguration

在下面的示例里面借助JUnitSpring 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";
    }
}

3. 测试用例

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()进行初始化。
③ 可以在测试用例中注入SpringBean
④ 可注入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

4. 编写普通控制器

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";
    }
}

5. 编写普通控制器的演示页面

src/main/resources/view下新建page.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Test page</title>
</head>
<body>
    <pre>
        Welcome to Spring MVC world
    </pre>
</body>
</html>

6. 编写RestController控制器

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();
    }
}

 

 

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值