SpringMVC学习笔记
MVC设计模式
其实在之前JavaWeb阶段我们已经接触到了MVC模式。
MVC里面的M指的的Model(通常包含bean、dao(mapper)、service);V指的是View,视图层,视图层主要的技术(JSP、HTML、FreeMaker、Themeleaf);C指的是Controller,控制层。控制层不负责具体数据、逻辑的处理和运算,它只负责将Model层的结果返回给对应的视图层去展示。
在JavaWeb阶段, Controller层指的就是Servlet; View层指的就是JSP或者HTML; Model层指的就是bean、dao、service。
在J2EE阶段,Controller层指的就是SpringMVC、Structs1\2; View层不变还是主流的页面展示技术; Model层包括bean、mybatis、service。
SpringMVC的优势
- SpringMVC是一款很轻量级的框架,要使用它的组件我们往往只需要定义一些最简单的Java类,然后添加某些注解就可以了
- SpringMVC的参数注入只直接注入到方法中,可以很好的做到不同请求间数据的隔离,而Struts2是注入到类实例变量上,不同的请求可能会覆盖参数。
- SpringMVC可以很轻易的和Spring整合,而Struts需要做比较复杂的配置。
SpringMVC简单使用
-
添加依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.0.RELEASE</version> </dependency>
-
在web.xml中配置DispatcherServlet
<servlet> <servlet-name>aa</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 指定SpringMVC 配置文件位置,DispatcherServlet初始化时会初始化Spring上下文(WebApplicationContext) --> <!-- 默认配置文件寻找位置:/WEB-INF/{servlet-name}-servlet.xml,如果名字符合默认寻找规则,可以不指定配置文件路径 --> <!--<init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/aa-servlet.xml</param-value> </init-param>--> <!-- 配置容器启动时初始化DispatcherServlet --> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>aa</servlet-name> <!-- 映射路径配置成/,代表当前Servlet是一个默认Servlet,就是当其他Servlet都无法处理请求时,由默认Servlet出马 --> <url-pattern>/</url-pattern> <!-- <url-pattern>/*</url-pattern>--> </servlet-mapping>
-
配置SpringMVC dispatcher-servlet.xml
SpringMVC大部分组件都有默认配置,我们一般简单应用只需要指定视图解析器就行了
dispatcher-servlet.xml
<!-- 配置视图解析器,用于将Handler方法中返回的视图名解析成真正可展示的页面 --> <mvc:view-resolvers> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/" /> <property name="suffix" value=".jsp" /> </bean> </mvc:view-resolvers>
-
定义Controller
新建一个普通类,然后添加
@Controller
注解,就可以了 -
定义请求处理方法(Handler)
在Controller类中定义一个普通的方法,添加
@RequestMapping
注解就可以了
SpringMVC主要组件
RootApplicationContext和ServletApplicationContext
两个ApplicationContext的联系和区别
视图解析器
视图解析器的作用是将请求处理方法中的返回值解析成一个真正可以渲染的页面。
常用的视图解析器
-
InternalResourceViewResolver
内部资源解析器: 用于将返回值对应到项目路径下的某个可显示的页面。比如方法返回值是index字符串,那么
InternalResourceViewResolver
解析器会在index前加上指定的前缀,在index后加上指定的后缀来拼接成指向某个视图的路径。<!-- 配置视图解析器,用于将Handler方法中返回的视图名解析成真正可展示的页面 --> <mvc:view-resolvers> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/" /> <property name="suffix" value=".jsp" /> </bean> </mvc:view-resolvers>
参数传递
页面参数传递到Controller
@RequestParam
获取请求参数,包括通过GET请求中的查询参数、POST请求中的参数等
@RequestMapping("/hello")
public String toHelloPage(@RequestParam String message, Model model) {
model.addAttribute("msg", message);
return "hello_springmvc";
}
// 当方法参数名和请求参数不一样时,可以使用注解的参数对应起来
@RequestMapping("/hello")
public String toHelloPage(@RequestParam("message") String msg, Model model) {
model.addAttribute("msg", message);
return "hello_springmvc";
}
访问: http://localhost:8080/hello?message=zhangsan 时,message参数会自动注入到方法的message参数中。
@RequestHeader
获取请求头中的参数,如浏览器默认自带的userAgent就是请求头参数
@RequestMapping("/hello")
public String toHelloPage(@RequestHeader String userAgent, Model model) {
model.addAttribute("msg", userAgent);
return "hello_springmvc";
}
// 当方法参数名和请求头参数不一样时,可以使用注解的参数对应起来
@RequestMapping("/hello")
public String toHelloPage(@RequestHeader("userAgent") String ua, Model model) {
model.addAttribute("msg", ua);
return "hello_springmvc";
}
会自动将请求头中的userAgent参数注入到方法中
@PathVariable
获取请求地址中的参数,注入到方法参数中
@RequestMapping("/hello/{sname}")
public String toHelloPage(@PathVariable String sname, Model model) {
model.addAttribute("msg", sname);
return "hello_springmvc";
}
// 当方法参数名和请求地址中参数不一样时,可以使用注解的参数对应起来
@RequestMapping("/hello/{sname}")
public String toHelloPage(@PathVariable("sname") String sn, Model model) {
model.addAttribute("msg", sn);
return "hello_springmvc";
}
访问:http://localhost:8080/hello/zhangsan 会自动将zhangsan注入到方法的参数中
@RequestAttribute
jsp中
<%
request.setAttribute("sname", "虞姬req");
request.getRequestDispatcher("/param/req_scope").forward(request, response);
%>
Controller中
/**
* 请求作用域中的参数的传递
* @return
*/
@RequestMapping("/req_scope")
public String requestScopeParamShow(@RequestAttribute String sname, Model model) {
model.addAttribute("sname", sname);
model.addAttribute("type", "请求作用域参数");
return "param_show";
}
获取请求作用域中的参数
@SessionAttribute
获取Session作用域中的参数,用法和上面请求作用域中使用类似
@CookieValue
jsp中代码
cookie参数传递:<br />
<%
response.addCookie(new Cookie("sname", "yuji_cookie"));
%>
<a href="/param/cookie">发起请求</a>
Controller中
/**
* Cookie中的参数的传递
* @return
*/
@RequestMapping("/cookie")
public String cookieParamShow(@CookieValue String sname, Model model) {
model.addAttribute("sname", sname);
model.addAttribute("type", "cookie参数");
return "param_show";
}
获取Cookie中的指定数据
上面这几个注解都是用于在方法的参数上标注,用于获取不同种类的参数
Controller中的数据传递到页面
Model
handler方法定义:
@RequestMapping("/show_msg")
public String showMessage(Model model) {
model.addAttribute("msg", "我是通过model传过来的参数");
return "message_page";
}
message_page.jsp
<h1>
获取后台参数: ${msg}
</h1>
ModelAndView
/**
* ModelAndView其实是将数据Model和视图View做了一个封装,底层实现原理一样
*/
@RequestMapping("/show_msg")
public ModelAndView showMessage(ModelAndView mv) {
mv.addObject("msg", "我是通过model传过来的参数");
mv.setViewName("message_page");
return mv;
}
Model和ModelAndView传递的参数本质上也是用请求作用域来实现的,所以仅对请求转发有效。
视图控制器
用于通过配置的方式简化我们项目中不含业务逻辑的页面跳转,省去了我们写一个空方法的步骤。
视图控制器的作用和用法
试想一下,如果我们的工程中有这样的一个需求,比如当用户点了某个链接和按钮的时候,我们需要做一个页面跳转,这个跳转的目标可能是一个公开目录的页面也可能是私有目录(/WEB-INF/下)的页面、还可能是跳转到后台Controller的某个方法中,我们如何实现?
示例:比如我们想将登陆页设置为一个项目默认欢迎页面
方案一:在Controller中添加一个拦截/的handler方法
@RequestMapping("/")
public String toWelcomePage() {
return "login";
}
方法二:使用视图控制器
在dispatcher-servlet.xml(SpringMVC配置文件)配置中添加
<mvc:view-controller path="/" view-name="login" />
- path: 指定要匹配的访问路径
- view-name: 返回的逻辑视图名
- status-code: 设置响应状态码。(注:不能通过只设置状态码,不设置view-name实现只返回code,没有页面的处理)
view-name不仅可以是逻辑视图名,还可以添加forward、redirect前缀,通过转发或重定向的方式跳转到一个具体的路径,如:<mvc:view-controller path="/" view-anme=“forward:/WEB-INF/login.jsp” />
设置Web项目的默认欢迎页
-
在web.xml中使用标签指定
-
使用上面提到的视图控制器
<mvc:view-controller path="/" view-name="逻辑视图名" /> <!-- 当工程中存在Tomcat默认欢迎页同名文件时此种方式可能失效,比如index.html、index.jsp、index.htm -->
静态资源处理
<!-- 针对静态资源的处理 -->
<!-- http://localhost:8080/static/css/index.css -->
<mvc:resources mapping="/static/**" location="/static/" />
重定向和转发
在Servlet实现重定向和转发:
重定向:response.sendRedirect(“xxx.jsp”);
转发: request.getRequestDispatcher(“xxx.jsp”).forward(request, response);
在SpringMVC中
请求转发有三种方式
- 在Handler方法中直接返回逻辑视图名称
- 在Handler方法中返回"forward:路径"
- 在Handler方法中返回InternalResourceView对象
其中第二种和第三种作用一样, 都是将路径用请求转发的方式跳转,返回的路径时不经过视图解析器处理的;而第一种虽然也是通过请求转发方式跳转,但是返回值会作为逻辑视图名称,经过视图解析器处理后,才渲染。
重定向有两种方式
- 在Handler方法中返回"redirect:路径"
- 在Handler方法中返回RedirectView对象
这两种方法效果一样。 注意使用重定向的方式跳转页面,Model中的数据就无法带到页面了(因为Model底层也是用请求作用域来传递参数的)。
RESTFull服务接口开发
SpringMVC给我们提供了一系列的注解用于支持RESTFull风格的服务开发
常用RESTFull注解
-
@RestController
该注解本身是一个组合注解,由
@Controller
和@ResponseBody
两个注解组成,所以拥有这两个注解的作用。 在类上声明后,该类会变成一个Controller类,同时,方法的返回值会作为响应体经过消息转换器直接响应给客户端,而不会将其作为视图渲染。@RestController // 这个注解其实是组合了下面两个注解的作用 //@Controller //@ResponseBody public class UserRestController { }
-
@GetMapping
:作用: 申明当前handler方法只匹配GET请求
使用示例
@GetMapping("/{id}") public User getUserById(@PathVariable Integer id) { return userService.findById(id); }
-
@PostMapping
: 申明当前handler方法只匹配POST请求 -
@DeleteMapping
: 申明当前handler方法只匹配DELETE请求 -
@PutMapping
: 申明当前handler方法只匹配PUT请求
上面四个注解分别对应HTTP的一种请求方法,使用方法类似。
上面四个注解其实是一种快捷注解, 等效于使用下面代码:
@RequestMapping(value = "/user", method = RequestMethod.GET)
@RequestMapping(value = "/user", method = RequestMethod.POST)
@RequestMapping(value = "/user", method = RequestMethod.PUT)
@RequestMapping(value = "/user", method = RequestMethod.DELETE)
- @PathVariable: 获取请求地址中的变量,注入到方法参数中
返回JSON格式的数据 —— 消息转换器
消息转换器是什么
之前我们在写普通Controller时,handler方法的返回值要是就是一个String(逻辑视图名),要么就是一个ModelAndView、View对象。
而我们使用了RestController之后,方法的返回值已经不会被作为视图去渲染了,这时候我们的方法其实可以返回任意类型的数据。这些数据会直接通过响应体以流的方式返回给客户端。
我们知道Java中的对象是不能直接用流的方式读取的,需要序列化。比如我们的handler方法返回了一个User类型,SpringMVC就不知道如果将这个类型返回给客户端了。这时就需要我们通过配置消息转换器来完成这种类型对象的返回处理。 而在RESTFull服务中,对象绝大部分传递方式就是通过JSON格式。
如何配置一个转换JSON格式的消息转换器
-
使用SpringMVC默认的Jackson库
-
引入依赖
<!-- jackson依赖,用于将handler方法返回的对象直接转换成JSON数据 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.7</version> </dependency>
-
开启mvc注解支持
<mvc:annotation-driven />
-
定义Controller类
@RestController @RequestMapping("/user") public class UserRestController { @GetMapping("/{id}") public User getUserById(Integer id) { return userService.findById(id); } }
Jackson是SpringMVC默认的JSON格式消息转换器, 所以在不配置额外转json参数时,我们可以直接只引入jackson依赖,再开启mvc直接支持就可以了。而第三方的转换库如FastJSON就必须显示配置MessageConverter
-
-
使用阿里巴巴的FastJSON库
-
引入依赖
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.57</version> </dependency>
-
配置MessageConverter
<mvc:annotation-driven> <mvc:message-converters> <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"> <property name="defaultCharset" value="UTF-8" /> <property name="fastJsonConfig"> <!-- 设置转换JSON的参数 --> <bean class="com.alibaba.fastjson.support.config.FastJsonConfig"> <property name="dateFormat" value="yyyy-MM-dd HH:mm:ss" /> </bean> </property> <property name="supportedMediaTypes"> <!-- 指定转换完JSON后返回的响应头和编码,添加text/html是为了处理在IE下application/json会弹出下载框问题 --> <list> <!--<value>text/html;charset=UTF-8</value>--> <value>application/json;charset=UTF-8</value> </list> </property> </bean> </mvc:message-converters> </mvc:annotation-driven>
-
定义Controller类
@RestController @RequestMapping("/user") public class UserRestController { @GetMapping("/{id}") public User getUserById(Integer id) { return userService.findById(id); } }
-
通过网页模拟RESTFull请求
通过SpringMVC给我们提供的一个过滤器,我们可以用表单模拟各种RESTFull的请求,使用方法如下:
-
在web.xml中添加过滤器
<filter> <filter-name>httpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>httpMethodFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
-
在表单中添加name为_method的hidden域,并将表单的method设置为post
<!-- 使用表单模拟发起一个DELETE请求,删除user表中id为57的数据 --> <form action="/user/57" method="post"> <input type="hidden" name="_method" value="delete" /> <input type="number" name="id" /> <input type="text" name="username" /> <input type="submit" /> </form>
由于RESTFull请求通常发生在服务于服务之间的调用,所以有些请求用浏览器不太好模拟,建议使用专业的网络测试工具postman来进行测试。
RestTemplate工具类
SpringMVC帮我们封装的一个Rest请求工具,可以使用一个URI地址发起网络请求,并且将结果封装成一个指定的对象。
处理请求乱码
SpringMVC给我们提供了一个专门用来解决post请求乱码的过滤器,我们只需将其配置到web.xml中,就可以避免post请求乱码,免去了我们自己写过滤器的麻烦
在web.xml中
<!-- 解决POST请求乱码 -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
注意: 如果项目中配置了多个过滤器,建议将此过滤器放到所有过滤器的最上面。
在SpringMVC方法中支持的参数
SpringMVC的handler方法中支持诸如很多特殊类型的参数,常用的有
注入原生Servlet对象
- javax.servlet.ServletRequest
- javax.servlet.ServletResponse
- javax.serlvet.http.HttpSession
以流的方式读取请求体、以流的方式响应到客户端
java.io.InputStream、java.io.Readerjava.io.OutputStream、java.io.Writer
读取请求体和请求头数据
- HttpEntity
向页面传参
- java.util.Map,org.springframework.ui.Model、org.springframework.ui.ModelMap
文件上传
说明
在Java中,主流的文件上传方式有两种,分别是通用文件上传(commons-fileupload)和Servlet3.0方式的文件上传。SpringMVC分别对着两种方式都做了支持。
- CommonsMultipartResolver
- StandardServletMultipartResolver
除此之外SpringMVC对文件上传的接口做了统一的封装,使用
MultipartFile
接口代替了通用上传中的FileItem
和Servlet3.0中的Part
,使得开发人员在业务代码中可以使用统一的接口处理,而不用管底层用的是哪种文件上传实现方式。
基于Apache Fileupload通用文件上传包
-
添加依赖
<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.4</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency>
-
在SpringMVC配置文件中添加MultipartResolver文件上传解析器
<!-- 配置基于apache fileupload的通用文件上传器(注意:id属性不可省略) --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- 指定文件上传编码 --> <property name="defaultEncoding" value="UTF-8" /> </bean>
-
在Controller中编写文件上传逻辑代码
@RequestMapping("/upload") public String upload(@RequestParam("myFile") MultipartFile file, HttpServletRequest req, Model model) { try { // 使用SpringMVC给我们提供的工具类获取项目中upload文件夹在硬盘上的绝对路径 String uploadPath = WebUtils.getRealPath(req.getServletContext(), "/upload/"); // 将上传的文件写到上传目录 file.transferTo(new File(uploadPath+file.getOriginalFilename())); } catch (IOException e) { e.printStackTrace(); } }
基于Servlet3.0标准API文件上传
-
开启Servlet文件上传支持
web.xml
<servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 开启Servlet文件上传支持 --> <multipart-config /> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
-
在SpringMVC中配置MultipartResolver
<!-- 配置基于Servlet3.0文件上传器(注意:id属性不可省略) --> <bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver" />
-
在Controller中编写文件上传逻辑代码
实现方式同通用上传代码一样。
文件名上传乱码问题
-
使用通用上传方式的解决方法
在配置的CommonsMultipartResolver bean中注入属性
<!-- 配置基于apache fileupload的通用文件上传器(注意:id属性不可省略) --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- 指定文件上传编码 --> <property name="defaultEncoding" value="UTF-8" /> </bean>
-
使用Servlet3.0标准上传方式的解决方法
通过配置SpringMVC提供的编码过滤器解决
web.xml
<filter> <filter-name>characterFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
本质上还是设置请求的编码 request.setCharacterEncoding(“UTF-8”);
拦截器
SpringMVC的优点
Spring MVC
Spring框架提供了构建Web应用程序的全功能MVC模块,即Spring MVC
Spring MVC框架,提供了一个DispatcherServlet,作为前端控制器来分派请求,同时,提供了灵活的配置处理程序映射、视图解析、语言环境和主题解析,并支持文件上传
SpringMVC的优点
1.清晰的角色划分:控制器(controller)、验证器(validator)、命令对象(command obect)、表单对象(form object)、模型对象(model object)、Servlet分发器(DispatcherServlet)、处理器映射(handler mapping)、试图解析器(view resoler)等等。每一个角色都可以由一个专门的对象来实现。
**2.强大而直接的配置方式:**将框架类和应用程序类都能作为JavaBean配置,支持跨多个context的引用,例如,在web控制器中对业务对象和验证器validator)的引用。
**3.可适配、非侵入:**可以根据不同的应用场景,选择何事的控制器子类(simple型、command型、from型、wizard型、multi-action型或者自定义),而不是一个单一控制器(比如Action/ActionForm)继承。
**4.可重用的业务代码:**可以使用现有的业务对象作为命令或表单对象,而不需要去扩展某个特定框架的基类。
**5.可定制的绑定(binding)和验证(validation):**比如将类型不匹配作为应用级的验证错误,这可以保证错误的值。再比如本地化的日期和数字绑定等等。在其他某些框架中,你只能使用字符串表单对象,需要手动解析它并转换到业务对象。
**6.可定制的handler mapping和view resolution:**Spring提供从最简单的URL映射,到复杂的、专用的定制策略。与某些web MVC框架强制开发人员使用单一特定技术相比,Spring显得更加灵活。
**7.灵活的model转换:**在Springweb框架中,使用基于Map的键/值对来达到轻易的与各种视图技术集成。
**8.可定制的本地化和主题(theme)解析:**支持在JSP中可选择地使用Spring标签库、支持JSTL、支持Velocity(不需要额外的中间层)等等。
**9.简单而强大的JSP标签库(Spring Tag Library):**支持包括诸如数据绑定和主题(theme)之类的许多功能。他提供在标记方面的最大灵活性。
**10.JSP表单标签库:**在Spring2.0中引入的表单标签库,使用在JSP编写表单更加容易。
现。
**2.强大而直接的配置方式:**将框架类和应用程序类都能作为JavaBean配置,支持跨多个context的引用,例如,在web控制器中对业务对象和验证器validator)的引用。
**3.可适配、非侵入:**可以根据不同的应用场景,选择何事的控制器子类(simple型、command型、from型、wizard型、multi-action型或者自定义),而不是一个单一控制器(比如Action/ActionForm)继承。
**4.可重用的业务代码:**可以使用现有的业务对象作为命令或表单对象,而不需要去扩展某个特定框架的基类。
**5.可定制的绑定(binding)和验证(validation):**比如将类型不匹配作为应用级的验证错误,这可以保证错误的值。再比如本地化的日期和数字绑定等等。在其他某些框架中,你只能使用字符串表单对象,需要手动解析它并转换到业务对象。
**6.可定制的handler mapping和view resolution:**Spring提供从最简单的URL映射,到复杂的、专用的定制策略。与某些web MVC框架强制开发人员使用单一特定技术相比,Spring显得更加灵活。
**7.灵活的model转换:**在Springweb框架中,使用基于Map的键/值对来达到轻易的与各种视图技术集成。
**8.可定制的本地化和主题(theme)解析:**支持在JSP中可选择地使用Spring标签库、支持JSTL、支持Velocity(不需要额外的中间层)等等。
**9.简单而强大的JSP标签库(Spring Tag Library):**支持包括诸如数据绑定和主题(theme)之类的许多功能。他提供在标记方面的最大灵活性。
**10.JSP表单标签库:**在Spring2.0中引入的表单标签库,使用在JSP编写表单更加容易。
**11.Spring Bean的生命周期:**可以被限制在当前的HTTp Request或者HTTp Session。准确的说,这并非Spring MVC框架本身特性,而应归属于Spring MVC使用的WebApplicationContext容器。