第4章 Spring MVC基础

来源:互联网 发布:手机mac地址伪装 编辑:程序博客网 时间:2024/06/01 17:38

第4章 Spring MVC基础
4.1 Spring MVC概述
MVC:Model + View + Controller (数据模型 + 试图 + 控制器)。
三层架构:Presentation tier + Application tier + Data tier(展现层 + 应用层 + 数据访问层)。
MVC是三层架构的展现层,M实际上是数据模型,是包含数据的对象,用来和V之间的数据交互,V是视图页面,C当然就是控制器。
三层架构是整个应用的架构,是由Spring框架负责管理的。项目结构中Service层对应应用层,DAO层对应数据访问层。

4.2 Spring MVC项目快速搭建

4.2.1 点睛
Spring MVC提供了一个DispatcherServlet来开发Web应用。在Servlet2.5及以下的时候只要在web.xml下配置元素即可。在Servlet3.0,实现WebApplicationInitializer接口便可实现等同于web.xml的配置。

4.2.2 示例

package com.wisely.highlight_springmvc4;import java.util.List;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.scheduling.annotation.EnableScheduling;import org.springframework.web.multipart.MultipartResolver;import org.springframework.web.multipart.commons.CommonsMultipartResolver;import org.springframework.web.servlet.config.annotation.EnableWebMvc;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;import org.springframework.web.servlet.view.InternalResourceViewResolver;import org.springframework.web.servlet.view.JstlView;import com.wisely.highlight_springmvc4.interceptor.DemoInterceptor;import com.wisely.highlight_springmvc4.messageconverter.MyMessageConverter;@Configuration@EnableWebMvc// 1注解会开启一些默认配置@ComponentScan("com.wisely.highlight_springmvc4")public class MyMvcConfig extends WebMvcConfigurerAdapter {// 2运行时代码会将我们的页面自动编译到/WEB-INF/classes/views下    @Bean    public InternalResourceViewResolver viewResolver() {        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();        viewResolver.setPrefix("/WEB-INF/classes/views/");        viewResolver.setSuffix(".jsp");        viewResolver.setViewClass(JstlView.class);        return viewResolver;    }}
package com.wisely.highlight_springmvc4;import javax.servlet.ServletContext;import javax.servlet.ServletException;import javax.servlet.ServletRegistration.Dynamic;import org.springframework.web.WebApplicationInitializer;import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;import org.springframework.web.servlet.DispatcherServlet;public class WebInitializer implements WebApplicationInitializer {    //1WebApplicationInitializer是Spring提供用来配置Servlet3.0 + 配置的接口,从而实现了替代Web.xml的位置。    //实现此接口将会自动被SpringServletContainerInitializer(用来启动Servlet3.0容器)获取到。    @Override    public void onStartup(ServletContext servletContext)            throws ServletException {        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();        ctx.register(MyMvcConfig.class);        ctx.setServletContext(servletContext); //2新建WebApplicationContext,注册配置类,并将其和当前servletContext关联        Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx)); //3注册Spring MVC的DispatcherServlet        servlet.addMapping("/");        servlet.setLoadOnStartup(1);        servlet.setAsyncSupported(true);//1    }}

4.3 Spring MVC的常用注解

4.3.1 点睛
(1)@Controller
(2)@RequestMapping
(3)@ResponseBody
(4)@RequestBody
(5)@PathVariable 用来接受路径参数
(6)@RestController是一个组合注解,组合了@Controller和@ResponseBody。

4.3.2 示例

package com.wisely.highlight_springmvc4.web.ch4_3;import javax.servlet.http.HttpServletRequest;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import com.wisely.highlight_springmvc4.domain.DemoObj;@Controller // 1@RequestMapping("/anno") //2public class DemoAnnoController {    @RequestMapping(produces = "text/plain;charset=UTF-8")      // 3produces可定制返回的response的媒体类型和字符集    public @ResponseBody String index(HttpServletRequest request) { // 4        return "url:" + request.getRequestURL() + " can access";    }    @RequestMapping(value = "/pathvar/{str}", produces = "text/plain;charset=UTF-8")// 5    public @ResponseBody String demoPathVar(@PathVariable String str, //3            HttpServletRequest request) {        return "url:" + request.getRequestURL() + " can access,str: " + str;    }    @RequestMapping(value = "/requestParam", produces = "text/plain;charset=UTF-8") //6    public @ResponseBody String passRequestParam(Long id,            HttpServletRequest request) {        return "url:" + request.getRequestURL() + " can access,id: " + id;    }    @RequestMapping(value = "/obj", produces = "application/json;charset=UTF-8")//7    @ResponseBody //8    public String passObj(DemoObj obj, HttpServletRequest request) {         return "url:" + request.getRequestURL()                     + " can access, obj id: " + obj.getId()+" obj name:" + obj.getName();    }    @RequestMapping(value = { "/name1", "/name2" }, produces = "text/plain;charset=UTF-8")    //9演示映射不同的路径到相同的方法    public @ResponseBody String remove(HttpServletRequest request) {        return "url:" + request.getRequestURL() + " can access";    }}

4.4 Spring MVC基本配置
Spring MVC的定制配置需要我们的配置类继承一个WebMvcConfigurerAdapter类,并在此类使用@EnableWebMvc注解,来开启对Spring MVC的配置支持,这样我们就可以重写这个类的方法,完成我们的常用配置。

4.4.1 静态资源映射
1.点睛
程序的静态文件(js、css、图片)等需要直接访问,这时我们可以在配置里重写addResourceHandlers方法来实现。
2.示例

@Override    public void addResourceHandlers(ResourceHandlerRegistry registry) {        registry.addResourceHandler("/assets/**").addResourceLocations(                "classpath:/assets/");        // 3 addResourceLocations指的是文件放置的目录,addResourceHandler指的是对外暴露的访问路径    }

4.4.2 拦截器配置

1点睛
拦截器(Interceptor)实现对每一个请求处理前后进行相关的业务处理,类似于Servlet的Filter。
可让普通的Bean实现HandlerInterceptor接口或者继承HandlerInterceptorAdapter类来实现自定义拦截器。
通过重写WebMvcConfigurerAdapter的addInterceptors方法来注册自定义的拦截器

2.示例

package com.wisely.highlight_springmvc4.interceptor;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.springframework.web.servlet.ModelAndView;import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;public class DemoInterceptor extends HandlerInterceptorAdapter {    //1继承HandlerInterceptorAdapter类来实现自定义拦截器    @Override    public boolean preHandle(HttpServletRequest request, //2            HttpServletResponse response, Object handler) throws Exception {        long startTime = System.currentTimeMillis();        request.setAttribute("startTime", startTime);        return true;    }    @Override    public void postHandle(HttpServletRequest request, //3            HttpServletResponse response, Object handler,            ModelAndView modelAndView) throws Exception {        long startTime = (Long) request.getAttribute("startTime");        request.removeAttribute("startTime");        long endTime = System.currentTimeMillis();        System.out.println("本次请求处理时间为:" + new Long(endTime - startTime)+"ms");        request.setAttribute("handlingTime", endTime - startTime);    }}
@Override    public void addInterceptors(InterceptorRegistry registry) {        // 2重写addInterceptors方法,注册拦截器        registry.addInterceptor(demoInterceptor());    }

4.4.3 @ControllerAdvice
1.点睛
通过@ControllerAdvice,可以将对于控制器的全局配置放置在同一个位置,注解了@Controller的类的方法可使用@ExceptionHandler、@InitBinder、@ModelAttribute注解到方法上,这对所有注解了@RequestMapping的控制器内的方法有效。
@ExceptionHandler:用于全局处理控制器里的异常。
@InitBinder:用来设置WebDataBinder,WebDataBinder用来自动绑定到前台请求参数到Model中。
@ModelAttribute:@ModuelAttribute本来的作用是绑定键值对到Model里,此处是让全局的@RequestMapping都能获得在此设置的键值对。

2示例

package com.wisely.highlight_springmvc4.advice;import org.springframework.ui.Model;import org.springframework.web.bind.WebDataBinder;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.InitBinder;import org.springframework.web.bind.annotation.ModelAttribute;import org.springframework.web.context.request.WebRequest;import org.springframework.web.servlet.ModelAndView;@ControllerAdvice //1public class ExceptionHandlerAdvice {     @ExceptionHandler(value = Exception.class)    //2定义全局处理,通过@ExceptionHandler的value属性可过滤拦截的条件    public ModelAndView exception(Exception exception, WebRequest request) {        ModelAndView modelAndView = new ModelAndView("error");// error页面        modelAndView.addObject("errorMessage", exception.getMessage());        return modelAndView;    }    @ModelAttribute    //3 将键值对添加到全局,所有注解的@RequestMapping的方法可获得此键值对    public void addAttributes(Model model) {        model.addAttribute("msg", "额外信息"); //3    }    @InitBinder //4    public void initBinder(WebDataBinder webDataBinder) {        webDataBinder.setDisallowedFields("id"); //5    }}
package com.wisely.highlight_springmvc4.web.ch4_4;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.ModelAttribute;import org.springframework.web.bind.annotation.RequestMapping;import com.wisely.highlight_springmvc4.domain.DemoObj;@Controllerpublic class AdviceController {    @RequestMapping("/advice")    public String getSomething(@ModelAttribute("msg") String msg,DemoObj obj){//1        throw new IllegalArgumentException("非常抱歉,参数有误/"+"来自@ModelAttribute:"+ msg);    }}

4.4.4 其他配置

1 快捷的ViewController

@RequestMapping("/index")//2public  String hello(){    return "index";}

可以通过在配置中重写addViewControllers来简化配置:

@Overridepublic void addViewControllers(ViewControllerRegistry registry) {    registry.addViewController("/index").setViewName("/index");}

2.路径匹配参数配置
在Spring MVC中,路径参数如果带“.”的话,“.”后面的置将被忽略。
通过重写configurePathMatch方法可不忽略“.”后面的参数。

 @Override     public void configurePathMatch(PathMatchConfigurer configurer) {     configurer.setUseSuffixPatternMatch(false);     }

3.更多配置
更多配置请查看WebMvcConfigurerAdapter类的API。

4.5 Spring MVC的高级配置
4.5.1 文件上传配置

1.点睛
Spring MVC通过配置一个MultipartResolver来上传文件。
在Spring的控制器中,通过MultipartFile file来接收文件,通过MultipartFile[] files接收多个文件上传。

2.示例

@Beanpublic MultipartResolver multipartResolver() {    CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();    multipartResolver.setMaxUploadSize(1000000);    return multipartResolver;}
package com.wisely.highlight_springmvc4.web.ch4_5;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;@Controllerpublic class UploadController {    @RequestMapping(value = "/upload",method = RequestMethod.POST)    public @ResponseBody String upload(MultipartFile file) {        //1使用MultipartFile file接收上传的文件            try {                FileUtils.writeByteArrayToFile(new File("e:/upload/"+file.getOriginalFilename()),                        file.getBytes());                //2 使用FileUtils.writeByteArrayToFile快速写文件到磁盘                return "ok";            } catch (IOException e) {                e.printStackTrace();                return "wrong";            }    }}

4.5.2 自定义HttpMessageConverter

1.点睛
HttpMessageConverter是用来处理request和response里的数据的。

2示例

package com.wisely.highlight_springmvc4.messageconverter;import java.io.IOException;import java.nio.charset.Charset;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;import com.wisely.highlight_springmvc4.domain.DemoObj;public class MyMessageConverter extends AbstractHttpMessageConverter<DemoObj> {    //1继承AbstractHttpMessageConverter接口来实现自定义的HttpMessageConverter    public MyMessageConverter() {        super(new MediaType("application", "x-wisely",Charset.forName("UTF-8")));        //2新建一个我们自定义的媒体类型application/x-wisely    }    /**     * 3重写readIntenal方法,处理请求的数据     */    @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]);    }    /**     * 4表明本HttpMessageConverter只处理DemoObj这个类     */    @Override    protected boolean supports(Class<?> clazz) {        return DemoObj.class.isAssignableFrom(clazz);    }    /**     * 5重写writeInternal,处理如何输出数据到response。     */    @Override    protected void writeInternal(DemoObj obj, HttpOutputMessage outputMessage)            throws IOException, HttpMessageNotWritableException {        String out = "hello:" + obj.getId() + "-"                + obj.getName();        outputMessage.getBody().write(out.getBytes());    }}
@Override//extendMessageConverters:仅添加一个自定义的HttpMessageConverter,不覆盖默认注册的HttpMessageConverter。//configureMessageConverters:重载会覆盖掉Spring MVC默认注册的多个HttpMessageConverter。   public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {       converters.add(converter());   }@Bean public MyMessageConverter converter(){    return new MyMessageConverter();}
package com.wisely.highlight_springmvc4.web.ch4_5;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;import com.wisely.highlight_springmvc4.domain.DemoObj;@Controllerpublic class ConverterController {    @RequestMapping(value = "/convert", produces = { "application/x-wisely" }) //1    public @ResponseBody DemoObj convert(@RequestBody DemoObj demoObj) {        return demoObj;    }}
function req(){    $.ajax({        url: "convert",        data: "1-wangyunfei", //1        type:"POST",        contentType:"application/x-wisely", //2        success: function(data){            $("#resp").html(data);        }    });}

4.5.3 服务端推送技术
服务端推送的方案都是基于:当客户端向服务端发送请求,服务端会抓住这个请求不放,等有数据更新的时候才返回给客户端,当客户端接收到消息后,再向服务端发送请求,周而复始。
除了服务端推送技术以外,还有一个另外的双向通信的技术——WebSocket,后面介绍。
基于SSE(Server Send Event 服务端发送事件)的服务器端推送(需要新式浏览器的支持)和基于Servlet3.0+的异步方法特性(跨浏览器的)。
1.SSE

package com.wisely.highlight_springmvc4.web.ch4_5;import java.util.Random;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;@Controllerpublic class SseController {    @RequestMapping(value="/push",produces="text/event-stream")     //1 使用输出的媒体类型为text/event-stream,这是服务器端SSE的支持,    //本例演示每5秒钟向浏览器推送随机消息    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";    }}
<%@ 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) {      //1EventSource对象只有新式的浏览器才有,EventSource是SSE的客户端       var source = new EventSource('push');        s='';       source.addEventListener('message', function(e) {           //2添加SSE客户端监听,在此获得服务器端推送的消息。           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>

2.Servlet 3.0 + 异步方法处理

  Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx)); //3注册Spring MVC的DispatcherServlet  servlet.addMapping("/");  servlet.setLoadOnStartup(1);  servlet.setAsyncSupported(true);//1开启异步方法处理
package com.wisely.highlight_springmvc4.web.ch4_5;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;import com.wisely.highlight_springmvc4.service.PushService;@Controller//异步任务的实现是通过控制器从另外一个线程返回一个Deferred,这里的DeferredResult是从pushService中获得的public class AysncController {    @Autowired    PushService pushService;     //1定时任务,定时更新DeferredResult    @RequestMapping("/defer")    @ResponseBody    public DeferredResult<String> deferredCall() { //2返回给客户端DeferredResult        return pushService.getAsyncUpdate();    }}
package com.wisely.highlight_springmvc4.service;import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Service;import org.springframework.web.context.request.async.DeferredResult;@Servicepublic class PushService {    private DeferredResult<String> deferredResult; //1    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());        }    }}
<script type="text/javascript">    deferred();//1    function deferred(){        $.get('defer',function(data){            console.log(data); //2            deferred(); //3        });    }</script>

4.6 Spring MVC的测试

4.6.1 点睛
为了测试Web项通常不需要启动该项目,我们需要一些Servlet相关的模拟对象。
在Spring里,我们使用@WebAppConfiguration指定加载的ApplicationContext是一个WebApplicationContext。
测试驱动开发(TDD),我们按照需求先写一个自己预期结果的测试用例,这个测试用例刚开始肯定是失败的测试,随着不断的编码和重构,最终让测试用例通过测试,这样才能保证软件的质量和可控性。

4.6.2 示例

package com.wisely.highlight_springmvc4.web.ch4_6;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.ui.Model;import org.springframework.web.bind.annotation.RequestMapping;import com.wisely.highlight_springmvc4.service.DemoService;@Controllerpublic class NormalController {    @Autowired    DemoService demoService;    @RequestMapping("/normal")    public  String testPage(Model model){        model.addAttribute("msg", demoService.saySomething());        return "page";    }}
package com.wisely.highlight_springmvc4.web.ch4_6;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import com.wisely.highlight_springmvc4.service.DemoService;@RestControllerpublic class MyRestController {    @Autowired    DemoService demoService;    @RequestMapping(value = "/testRest" ,produces="text/plain;charset=UTF-8")    public @ResponseBody String testRest(){        return demoService.saySomething();    }}
package com.wisely.highlight_springmvc4.web.ch4_6;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.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;import com.wisely.highlight_springmvc4.MyMvcConfig;import com.wisely.highlight_springmvc4.service.DemoService;@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = {MyMvcConfig.class})@WebAppConfiguration("src/main/resources") //1 @WebAppConfiguration注解在类上,用来声明加载的ApplicationContext是一个WebApplicationContext。//它的属性指定的是Web资源的位置。public class TestControllerIntegrationTests {    private MockMvc mockMvc; //2模拟MVC对象    @Autowired    private DemoService demoService;//3    @Autowired     WebApplicationContext wac; //4 可注入WebApplicationContext    @Autowired     MockHttpSession session; //5 可注入模拟的http session    @Autowired     MockHttpServletRequest request; //6 可注入模拟的http request    @Before //7在测试开始前进行的初始化工作    public void setup() {        mockMvc =                MockMvcBuilders.webAppContextSetup(this.wac).build(); //2初始化        }    @Test    public void testNormalController() throws Exception{        mockMvc.perform(get("/normal")) //8 模拟向/normal进行get请求                .andExpect(status().isOk())//9 预期控制返回状态为200                .andExpect(view().name("page"))//10 预期view的名称为page                .andExpect(forwardedUrl("/WEB-INF/classes/views/page.jsp"))                //11预期页面转向的真正路径为/WEB-INF/classes/veiws/page.jsp                .andExpect(model().attribute("msg", demoService.saySomething()));                //12 预期model里的值是demoService.saySomething()返回值hello。    }    @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 预期返回值的内容为demoService.saySomething()返回值hello    }}
0 0
原创粉丝点击