SpringMVC
一、简介
SpringMVC就是使用Spring实现web模块
MVC:层层独立,进行解耦。
SpringMVC支持注解,支持REST风格的URL请求。组件式开发,灵活,可扩展。反正就是牛逼。
SpringMVC实现思想
二、SpringMVC HelloWorld
1、导包
2、配置spring mvc 的前端控制器
因为spring mvc的思想就是有一个前端控制器拦截所有请求,并智能派发请求。前端控制器其实是一个servlet,所以应该的web.xml中配置这个前端控制器。
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:SpringMVC.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
/
和/*
的区别:我们知道前端控制器会拦截所有请求,然后将请求分发给对应的处理器。所以的前端控制的的servelt-mapping中的url-pattern必须配置正确。那这里就必须明白/
和/*
的区别。
/*
代表拦截所有请求,包括jsp页面,如果使用/*
那我们就无法正常访问jsp页面了。/
代表也代表拦截所有请求,但是不包括jsp页面,能保证jsp访问正常。
3、配置Spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--扫描所有组件:使用注解更方便-->
<context:component-scan base-package="com.gzk"/>
</beans>
4、编写一个处理器
package com.gzk.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class MyFirstController {
@RequestMapping("/hello")
public String myFirstRequest(){
System.out.println("正在处理。。。。。。。。。");
return "/WEB-INF/pages/success.jsp";
}
}
5、spring配置一个视图解析器,能够帮我们拼接页面地址。
<!--spring配置一个视图解析器,能够帮我们拼接页面地址。-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="viewResolver">
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>
6、HelloWorld细节
1、细节一:运行流程
- 客户端发起hello请求到tomcat服务器。
- springmvc的前端控制器接收到所有请求。
- 前端控制器使用请求地址匹配@RequestMapping标注的方法。
- 前端控制器找到目标处理器(类)和目标处理方法(方法),spring使用反射执行目标处理方法。
- 方法执行完毕之后会有一个返回值,springmvc认为这个返回值就是要去的页面地址。
- 拿到方法返回值后,用试图解析器拼串得到要去的页面地址。
- 拿到页面地址后,前端控制器帮我们**转发**到页面。
2、细节二:RequestMapping注解
1、RequestMapping告知springmvc这个方法可以处理那个请求。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SDBFZopX-1600518575705)(C:\Users\21232\AppData\Roaming\Typora\typora-user-images\1597041840356.png)]
2、RequestMapping既可以标注方法上,还可以标注在类上
3、RequestMapping标注在类上的作用
可以为类中的所有处理器方法设置请求的基准路径。
@RequestMapping("/haha")
@Controller
public class HiContorller {
@RequestMapping("/hi")
public String hi(){
System.out.println("hi......");
return "hi";
}
@RequestMapping("/hi2")
public String hi2(){
System.out.println("hi......");
return "hi";
}
}
3、细节三:Spring配置文件的默认位置。
1、在web.xml中配置前端控制器时,我们会显示的指定spring的配置文件的位置。如果spring的配置文件的文件名和文件位置位于spring指定的默认位置时我们可以不指定。
2、如果我们没有指定Spring配置文件的位置,spring将会在/WEB-INF/前端控制器名-servlet.xml
查找spring的配置文件。
没有指定配置文件是报错:
4、细节四:url-pattern
在前端控制器中我们指定url-pattern配置的是’/’,那这里的’/‘到底拦截的是那些请求呢?
我们所写的代码最终部署到tomcat服务器上,通过tomcat服务器中的conf/web.xml文件,我们可以知道,’/'拦截的是所有静态资源。
资源分类为静态资源和非静态资源。其中Servlet和jsp(jsp本身就是Servlet)为非静态资源。其余为静态资源。
然而在我们的项目中也有一个web.xml文件,其实我们项目中的每个web.xml文件都继承与tomcat服务器中的大web.xml文件。如果当我们在项目中的小web.xml文件中配置了一个url-pattern为’/'的Servlet,那么我们的Servlet将会对于我们的这个项目来说,我们配置的这个Servlet(也就是前端控制器)将会覆盖tomcat服务器大web.xml中配置的DefaultServlet。也就达到了所有的静态资源都有前端控制器处理。
前端控制器的url-pattern指明了前端控制器将会拦截处理那些请求,其余的请求会交给服务器中其他的Servlet处理。例如jsp的请求将会交个tomcat服务中web.xml中配置的JspServlet处理。
/
和/*
的区别
-
/
:拦截的是静态资源(除jsp和Servlet) -
/*
:拦截的是所以请求包括jsp和Servlet。我们不能为前端控制器配置url-pattern为
/*
,因为假设我们去访问/index.jsp
是,如果该请求被前端控制器Servlet处理了,那前端控制器就会去匹配一个能处理/index.jsp
请求的处理器,如果没有匹配到,这将会出错。
5、细节五:RequestMapping的属性
1、method规定请求的方式
@RequestMapping("/haha")
@Controller
public class HiContorller {
@RequestMapping(value = "/handle02",method = RequestMethod.POST)
public String handle02(){
return "hi";
}
}
如果请求方式不支持将会报错。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dsH4ZLw2-1600518575722)(C:\Users\21232\AppData\Roaming\Typora\typora-user-images\1597050496297.png)]
http协议中支持的所有请求方式
GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS,TRACE;
默认所以的请求方式都接收。
2、params规定请求参数
params指定必须包含或必须不包含某个参数,如果不满足会报错
@RequestMapping("/haha")
@Controller
public class HiContorller {
@RequestMapping(value = "/handle02",params = {"username"})
public String handle02(){
return "hi";
}
}
如果不满足会报错。
param支持一些表达式,来指定某个参数的详细信息。
param:指定必须携带这个参数
!param:指定必须不携带这个参数
param=123:指定携带指定的参数,并且参数的值为指定的值
param!=123:指定携带的参数的值不能为指定参数,分为两种情况符合,一种为不携带该参数,此时该参数的值为null,另一种情况携带了该参数,且值不等于指定的值。
可以同时携带多个参数,且同时满足以上情况
3、header规定请求头
同param一致,能写简单的表达式
@RequestMapping(value = "/handle02",headers = {"User-Agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18363"})
public String handle02(){
return "hi";
}
4、consumes规定只接受内容类型是那种的请求,规定请求头中的Context-Type属性
5、produces告诉浏览器返回的内容类型是什么,给响应头中加上Context—Type属性。
6、细节六:RequestMapping使用ant风格的url
ant风格url就是url地址可以写模糊的通配符。
模糊的通配符有
- ?:替代任意一个字符
- *:替代任意多个字符或一层路径
- **:替代多层路径
1、精确匹配
@RequestMapping("/antTest01")
public String antTest01(){
return "success";
}
2、?
匹配任意一个字符
@RequestMapping("/antTest0?")
public String antTest01(){
return "success";
}
只匹配一个字符,不能是0个或多个。
一个请求若同时能精确匹配和模糊匹配,那精确匹配优先。
2、* 匹配任意多个字符,可以是0个,匹配一层路径。
//匹配任意多个字符,可以是0个
@RequestMapping("/antTest0*")
public String antTest02(){
System.out.println("antTest02");
return "success";
}
//匹配一层路径,只能是一层,不能是0层或多层
@RequestMapping("/a/*/antTest01")
public String antTest02(){
System.out.println("antTest02");
return "success";
}
3、**匹配多层路径,可以是0层,1层,多层
//匹配多层路径
@RequestMapping("/a/**/antTest01")
public String antTest02(){
System.out.println("antTest02");
return "success";
}
注意:永远都是精确匹配优先。精确度如下:
精确匹配 > ?匹配一个字符 > *匹配多个字符1层路径 > **匹配多层路径
7、细节七:@PathVariable获取路径上的占位符
spring3.0新增功能,为了支持REST风格的url
通过@PathVarianle可以将URL中占位符参数绑定到控制器处理方法中的参数中。
@RequestMapping("/antTest01/{user}")
public String test1(@PathVariable("user") String username){
System.out.println(username);
return "success";
}
获取占位符。
三、REST风格的URL地址
对资源的操作类型不使用请求地址区分,而是使用请求的方式区分。
请求方式:
- get:获取资源
- post:新建资源
- delete:删除资源
- put:更新资源
REST对资源的增删改查使用请求方式来区分。
0、问题:从前端页面上只能发起get和post请求方式,delete和put的请求方式无法使用。
spring解决了这个问题。
1、spring使用REST风格URL
我们知道从页面只能发起get方式和post方式的请求,那如何能发起delete和put方式的请求呢。?SpringMVC中有一个过滤器,可以把普通的请求转化为规定形式的请求。所以可以通过配置该过滤器就可以将普通的请求转换为delete和put请求。
1、在web.xml中配置spring提供的过滤器,该过滤器可以将普通请求转换为规定形式的请求。
<filter>
<filter-name>hiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>hiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
2、在页面发起请求
①发起get请求,和以前一样。
<a href="book/1">查询图书</a>
②发起post请求,和一起一样。
<form action="book" method="post">
<input type="submit" value="添加图书">
</form>
③发起delete或put请求
步骤:
- 创建一个post类型的表单
- 表单项中携带一个
_method
的参数 - 这个
_method
的参数的值为请求方式delete或put
<form action="book/1" method="post">
<input name="_method" value="delete"/>
<input type="submit" value="删除图书">
</form>
<form action="book/1" method="post">
<input name="_method" value="put"/>
<input type="submit" value="添加图书">
</form>
解决高版本Tomcat对REST风格的URL不支持的delete、put请求。
Tomcat8及以上的的tomcat版本对jsp要严格的要求,不接受delete和put方式的请求,所以会出现错。
错误的解决办法:
<%@ page contentType="text/html;charset=UTF-8"
language="java" isErrorPage="true" %>
四、请求处理
1、SpringMVC获取请求参数
1、介绍
在之前的web项目中我们通过Servlet的httpServletRequest对象获取请求中携带的参数。
String username = request.getParameter("参数名");
然而现在我们使用注解@Controller来声明一个处理器类,然而处理方法来处理请求,然而这个方法中并没有httpServletRequest对象,所以现在我们该如何获取请求中的参数呢?
2、获取请求参数的方式
①默认方式获取请求参数
直接给处理器参数上写一个和请求参数同名的变量,这个变量就来接收请求参数的值。若请求携带了该参数,则处理器方法参数的值为请求携带参数的值,若请求没有携带该参数,则方法处理器参数的值为null
。
请求地址携带名为username的参数
<a href="handle02?username=gzk">handle02</a>
处理器方法使用同名的参数username获取请求参数username的值。
@RequestMapping("/handle02")
public String handle02(String username){
System.out.println(username);
return "success";
}
注意:如果请求中没有携带username参数,则处理器方法参数username的值为username
②使用@RequestParam注解指明方法处理器的参数接收请求中的那个参数。
请求中携带名为user的参数
<a href="handle02?user=gzk">handle02</a>
处理器方法使用@RequestParam注解将请求参数中的user的值赋给处理器参数username
@RequestMapping("/handle02")
public String handle02(@RequestParam("user") String username){
System.out.println(username);
return "success";
}
注意:请求默认是必须携带user参数的,如果不带将会报错。可以通过设置@RequestParam的required属性来改变这一特点。
1、@RequestParam的属性设置
@RequestParam具有三个属性:
- value:指定要获取请求参数的key
- required:指定这个请求参数是否必须的,默认为true
- defaultValue:指定如果这个请求参数没有携带,则参数的默认值为指定值。
@RequestMapping("/handle02")
public String handle02(@RequestParam(value = "user",required = true,defaultValue = "张三") String username){
System.out.println(username);
return "success";
}
2、@PathVariable与@RequestParam的区别
-
@PathVariable获取请求路径中的占位符的值
-
@RequestParam获取请求参数
2、SpringMVC获取请求头中某个key的值。
1、介绍
在以前的web项目中我们使用request.getHeader(“key”)获取请求头中的某个值,那SpringMVC中该如何获取请求头信息呢?
2、获取请求头信息
@RequestMapping("/handle02")
public String handle02(@RequestHeader("User-Agent") String User_Agent){
System.out.println(User_Agent);
return "success";
}
注意:如果请求头中没有这个参数将会报错。@RequestHeader和@RequestParam一样,也有value、required、defaultValue属性。
3、SpringMVC获取Cookie信息
1、介绍
在以前的web项目中我们使用一下代码获取Cookie信息。
Cookie[] cookies = request.getCookies();
for(Cookie cookie : cookies){
if(cookie.getName().equals("JSESSIONID")){
String cookValue = cookie.getValue();
}
}
2、获取cookie的信息
@RequestMapping("/handle02")
public String handle02(@CookieValue("JSESSIONID") String JSESSIONID){
System.out.println(JSESSIONID);
return "success";
}
注意:如果请求头中没有这个参数将会报错。@CookieValue和@RequestHeader、@RequestParam一样,也有value、required、defaultValue属性。
4、SpringMVC自动封装POJO
1、案例
1、请求页面
<form action="book" method="post">
书名:<input type="text" name="bookName"/> <br/>
作者:<input type="text" name="author"/> <br/>
价格:<input type="text" name="price"/> <br/>
库存:<input type="text" name="stock"/> <br/>
销量:<input type="text" name="sales"/> <br/>
<input type="submit" value="提交"/>
</form>
2、处理器
@RequestMapping(value = "/book",method = RequestMethod.POST)
public String addBook(Book book){
System.out.println("添加图书"+book);
return "success";
}
3、Book对象
public class Book {
private String bookName;
private String author;
private Double price;
private Integer stock;
private Integer sales;
......
}
结论:
通过以上案例我们可以知道,SpringMVC可以自动将请求参数为我们封装为我们想要的pojo对象。
SpringMVC是如何为我们封装对象的呢?
SpringMVC会尝试通过当前pojo对象的每个属性名为参数名去获取参数值,如果请求参数中存在,则进行属性的装配,否则属性装配该类型的默认值。
2、级联封装POJO
1、请求页面
<form action="book" method="post">
书名:<input type="text" name="bookName"/> <br/>
作者:<input type="text" name="author"/> <br/>
价格:<input type="text" name="price"/> <br/>
库存:<input type="text" name="stock"/> <br/>
销量:<input type="text" name="sales"/> <br/>
<hr/>
省:<input type="text" name="address.province"/>
市:<input type="text" name="address.city"/>
街道:<input type="text" name="address.street"/>
<input type="submit" value="提交"/>
</form>
2、Book对象
public class Book {
private String bookName;
private String author;
private Double price;
private Integer stock;
private Integer sales;
private Address address;
}
3、Adress对象
public class Address {
private String province;
private String city;
private String street;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6Iedxhag-1600518575760)(C:\Users\21232\AppData\Roaming\Typora\typora-user-images\1597191892474.png)]
5、SpringMVC请求处理-传入原生的API
1、介绍
在以前的web项目中我们可以往request域、session域中添加放置属性,但在SpringMVC中我们如何获取ServletHttpRequest和HttpSession对象呢。SpringMVC可以直接在参数上直接传递原生的API。
@RequestMapping("/handle03")
public String handle03(HttpServletRequest request, HttpSession session){
request.setAttribute("username","张三");
session.setAttribute("password","123456");
System.out.println("hello");
return "success";
}
总结:只需在处理器方法参数上写上原生API对象,SpringMVC就可以自己传入该对象。
2、SpringMVC可以传入那些原生API对象
SpringMVC可以传入的原生API对象并不多,只用一下几个。
- HttpServletRequest:请求对象
- HttpServletResponse:响应对象
- HttpSession:回话对象
- java.security.Principal:HTTPS安全信息对象
- Locale:国家化相关的区域信息对象
- InputStream:ServletInputStream inputStream = request.getInputStream()请求字节流。
- OutputStream:ServletOutputStream outputStream = response.getOutputStream();响应字节流,文件下载时使用。
- Reader:BufferedReader reader = request.getReader()请求字符流。
- Writer:PrintWriter writer = response.getWriter();响应字符流。
6、CharacterFilter处理请求乱码
1、提交的数据可能有乱码有两种可能:
-
请求乱码
-
get请求乱码:
解决方案:修改服务器tomcat的cong/server.xml
-
post请求乱码:
解决方案:在第一次获取请求参数之前设置request.setCharacterEncoding(“UTF-8”);
可以写一个Filter过滤器来设置请求参数。Spring为我们提供该过滤器。
-
-
响应乱码
- 解决响应乱码:
response.setContentType("text/html;charset=UTF-8")
- 解决响应乱码:
2、使用Spring为我们提供的CharacterFilter过滤器处理编码问题。只需要在web.xml中配置该过滤器就可以了。
web.xml中配置CharacterFilter过滤器
<!--字符编码过滤器-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<!--字符编码过滤器 mapping-->
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
3、注意点:
我们知道请求编码的设置必须在第一次获取参数之前设置编码格式,响应编码的设置必须在响应之前设置。我们还知道我们使用REST风格的URL是配置了一个HiddenHttpMethodFilter的过滤器,在这个过滤器中我们获取一个名为_method
的参数。那么我们请求编码的设置的正确且能正常使用,则CharacterEncodingFilter
过滤器所对应的filter-mapping
必须放在HiddenHttpMethodFilter
过滤器所对应的filter-mapping
的前面。
总结:
鉴于CharacterEncodingFilter
的特点与重要性,我们建议在创建项目时就在web.xml中将CharacterEncodingFilter
过滤器设置完毕(放在web.xml前面)。
以上都是SpringMVC如何处理请求的问题
五、SpringMVC往Request域对象放数据
1、方式一:使用原生API
在处理器方法参数中使用原生API对象,将数据传入request或者session对象中,然后页面中获取数据。
@RequestMapping("/handle03")
public String handle03(HttpServletRequest request, HttpSession session){
request.setAttribute("username","张三");
session.setAttribute("password","123456");
System.out.println("hello");
return "success";
}
2、方式二:处理器方法传入Map、Model、ModelMap类型的参数
可以在处理器方法传入Map、Model、ModelMap类型的参数,给这三种类型参数里面保存的数据都会放在请求域中,也可以在页面获取。
①Map传递数据,将保存的数据放入Request域中
@RequestMapping("/handle01")
public String handle01(Map<String,Object> map){
map.put("username","李白");
System.out.println("handle01........");
return "success";
}
②Model传递数据,将保存的数据放入Request域中
@RequestMapping("/handle02")
public String handle02(Model model){
model.addAttribute("password","1234");
System.out.println("handle02........");
return "success";
}
③ModelMap传递数据,将保存的数据放入Request域中
@RequestMapping("handle03")
public String handle03(ModelMap modelMap){
modelMap.addAttribute("sex","男");
return "success";
}
总结:
- Map、Model、ModelMap都是将保存的数据放在Request域中。
- Map、Model、ModelMap其实都是BindingAwareModelMap在工作。
- BindingAwareModelMap=org.springframework.validation.support.BindingAwareModelMap
- BindingAwareModelMap保存的数据都会放在请求域中。
Map、Model、ModelMap、BindingAwareModelMap的关系
3、方式三:返回值类型为ModelAndView。
将处理器的返回值设置为ModelAndView类型。将数据保存到Request域中。
@RequestMapping("/handle04")
public ModelAndView handle04(){
ModelAndView modelAndView = new ModelAndView("success");
modelAndView.addObject("username","杜甫");
return modelAndView;
}
ModelAndView既包含视图信息也包含了模型数据,而且数据保存在请求域中。
六、SpringMVC往Session域对象中放数据
SpringMVC中使用@SessionAttributes
给Session域中保存数据的方式。
@SessionAttributes
只能标注在类上
说明:
@SessionAttributes(value = “username”)该注解的作用是使用BindingAwareModelMap、ModelAndView给request域中保存对象的同时会给session域保存一份。value属性指明那些数据会同时保存在session域对象中。
@SessionAttributes(value = "username")
@Controller
public class OutputController {
@RequestMapping("/handle01")
public String handle01(Map<String,Object> map){
map.put("username","李白");
System.out.println("handle01........"+map.getClass());
return "success";
}
@RequestMapping("/handle02")
public String handle02(Model model){
model.addAttribute("password","1234");
System.out.println("handle02........"+model.getClass());
return "success";
}
@RequestMapping("/handle03")
public String handle03(ModelMap modelMap){
System.out.println("handle02........"+modelMap.getClass());
modelMap.addAttribute("sex","男");
return "success";
}
@RequestMapping("/handle04")
public ModelAndView handle04(){
ModelAndView modelAndView = new ModelAndView("success");
modelAndView.addObject("username","杜甫");
return modelAndView;
}
}
@SessionAttributes(value = {"username","password"})
@SessionAttributes(value = {“username”,“password”},types = {String.class,Integer.class})
type属性指定某些类型的数据同时往session域对象中保存一份。
注意:@SessionAttributes尽量不要使用,因为可能引发异常。给session中放数据推荐使用原生API
七、@ModelAttribute注解
1、介绍
@ModelAttribute注解只能够标注在方法上和方法参数上。如果@ModelAttribute标注在方法,则指明该方法将会在目标处理器运行之前被执行。如果@ModelAttribute标注在方法参数上,则指明该参数SpringMVC不会新创建,而是从当前的BindingAwareModelMap获取该参数。
2、使用
@ModelAttribute
public void modelTest(Map<String,Object> map){
map.put("username","白居易");
System.out.println("ModelAttribute。。。。。。。。。");
}
@RequestMapping("/handle05")
public String handle05(@ModelAttribute("username") String username){
System.out.println(username);
return "success";
}
@ModelAttribute
public void modelTest(Map<String,Object> map){
map.put("username","白居易");
System.out.println("ModelAttribute。。。。。。。。。");
}
八、SpringMVC九大组件
- 文件上传组件
- private MultipartResolver multipartResolver;
- 区域信息组件和国际化有关
- private LocaleResolver localeResolver;
- 主题解析器,强大的主题切换
- private ThemeResolver themeResolver;
- Handler映射信息
- private List handlerMappings;
- Handler的设配信息
- private List handlerAdapters;
- 异常解析器,SpringMVC强大的异常解析功能
- private List handlerExceptionResolvers;
- 请求地址转换为视图解析器
- private RequestToViewNameTranslator viewNameTranslator;
- SpringMVC中允许重定向携带数据的功能
- private FlashMapManager flashMapManager;
- 视图解析器
- private List viewResolvers;
九大组件全部为接口。
九、视图解析器
1、forward前缀指定一个请求转发操作
之前我们直接返回一个字符串,该字符串会受到视图解析器的解析,进行字符串的拼接。如下
①视图解析器
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="viewResolver">
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>
②处理器
@RequestMapping(value = "/hello")
public String hello(){
return "success";
}
解释:
处理器方法返回的字符串success
会受到视图解析器的解析,进行字符串的拼接得到请求转发要去的地址。解析过程:视图解析器的prefix + 处理器方法返回值 + 视图解析器的suffix的。即/WEB-INF/pages/
+success
+.jsp
=/WEB-INF/pages/success.jsp
(页面请求转发地址)。
现在我们使用forward前缀指定一个请求转发操作:
处理器方法
@RequestMapping(value = "/handle01")
public String handle01(){
return "forward:/hello.jsp";
}
处理器方法
@RequestMapping(value = "/handle01")
public String handle01(){
System.out.println("handle01");
return "forward:/hello.jsp";
}
@RequestMapping(value = "/handle02")
public String handle02(){
System.out.println("handle02");
return "forward:/handle01";
}
2、redirect前缀指定一个重定向操作
@RequestMapping("/handle03")
public String handle03(){
System.out.println("handle03");
return "redirect:/hello.jsp";
}
注意:
原生的Servlet重定向的路径需要加上项目名才能成功,response.sendRedirect("/项目名/要去的页面路径")。
在SpringMVC中redirect前缀后面直接加要去的页面路径,不需要加项目名。
redirect重定向到处理器方法
@RequestMapping("/handle03")
public String handle03(){
System.out.println("handle03");
return "redirect:/hello.jsp";
}
@RequestMapping("/handle04")
public String handle04(){
System.out.println("handle04");
return "redirect:/handle03";
}
3、视图解析的原理
1、方法的返回值作为页面的参考地址,转发或重定向到目标页面。
2、视图解析器可能会进行页面地址的拼串。
视图解析器只是为了得到视图对象;视图对象才能真正的转发(将模型数据全部放在请求域中)或者重定向到页面。视图对象才能真正的渲染视图。
4、JSTLView支持便捷的国际化功能
JSTLView可以便捷的实现国际化功能,所以要想便捷的实现国际化功能可以在spring配置文件中配置jstlView的视图解析器。JstlView是InternalResourceView的子类,所以使用一下配置来实现JstlView。
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="viewResolver">
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
</bean>
SpringMVC实现国际化比较简单,步骤如下。
①配置JstlView视图
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="viewResolver">
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
</bean>
其实只要导入国际化的jar包,spring会自动使用JstlView视图。
jar包如下
- org.apache.taglibs:taglibs-standard-impl:1.2.1
- org.apache.taglibs:taglibs-standard-spec:1.2.1
②创建国际化资源文件 - i18n_en_US.properties
welcomeInfo=welcome to login
username=email
passwrod=Password
lgoinbtn=Login
- i18n_zh_CN.properties
welcomeInfo=欢迎登录
username=邮箱
passwrod=密码
lgoinbtn=登录
③在spring中配置资源文件管理器,然资源文件管理器管理国际化资源
<!--配置资源文件管理器-->
<bean class="org.springframework.context.support.ResourceBundleMessageSource" id="messageSource">
<property name="basename" value="i18n"/>
</bean>
④在jsp页面中使用fmt:message标签获取国际化资源
<div>
<h3><fmt:message key="welcomeInfo" /></h3>
<form action="login" method="post">
<fmt:message key="username"/> :<input name="username">
<hr/>
<fmt:message key="passwrod"/> :<input name="password">
<hr/>
<input type="submit" value='<fmt:message key="lgoinbtn"/>'>
</form>
</div>
注意:
要想使用springmvc的国际化功能,那么该请求就必须经过SpringMVC。而且该请求必须使用默认的视图,也就是不能使用forward来请求转发请求。
5、view-controller将请求直接映射为一个地址。
要想请求使用springmvc的强大功能,请求必须被SpringMVC处理到。但是有可能某些请求不能被SpringMVC捕捉到。这是该请求无法利用SpringMVC的强大功能。如下
jsp页面
<a href="login.jsp">Go Login</a>
该请求直接请求的是一个jsp页面。我们知道springmvc能够处理的请求是能够被SpringMVC的前端控制器捕获到的请求。然而我们配置的前端控制器无法捕获到jsp和servler的请求。所以上面login.jsp的请求前端控制器无法捕获到,也就是SpringMVC无法处理该请求,该请求直接由Tomcat服务器处理了。
此时我们可以通过写一个处理器方法使得该请求通过SpringMVC。
jsp页面
<a href="toLoginPage">去登录</a>
处理器方法
@RequestMapping("/toLoginPage")
public String toLoginPage(){
return "login";
}
通过写一个处理器方法可以使得请求通过SpringMVC,请求可以使用强大的SpringMVC的功能。但是我们发现该处理器方法没有做任何业务处理仅仅通过一个返回值进行了一个请求转发。为了解决这种情况,spring提供了view-controller配置标签,将一个请求直接映射为一个地址,不用通过处理器方法。
spring配置文件
<!--使用mvc名称空间
path:用来将那个请求直接映射为一个地址
view-name:该请求的目标视图
-->
<mvc:view-controller path="/toLoginPage" view-name="login"/>
注意,SpringMVC的开挂模式:
当我们配置view-controller之后,我们发现其他没有使用view-controller的请求都出现了404的错误。为了解决这个错误我们必须开启SpringMVC的开挂模式。配置如下
<!--开启mvc的注解驱动模式,开启mvc的的开挂模式-->
<mvc:annotation-driven></mvc:annotation-driven>
配置了该mvc的注解驱动模式,其他请求的404错误就消失了。
6、SpringMVC的开挂模式:注解驱动的开启
<!--开启mvc的注解驱动模式,开启mvc的的开挂模式-->
<mvc:annotation-driven></mvc:annotation-driven>
只有开启了mvc的注解驱动模式,springmvc的高级模式就开启了。
7、扩展:自定义视图和自定义视图解析器
视图解析器根据方法的返回值得到视图
多个视图解析器都会尝试能否得到视图
视图对象不同就可以具有不同功能
。。。。。。
十、使用SpringMVC做一个REST风格的CRUD
十一、SpringMVC的表单标签
通过SpringMVC的表单标签可以实现将模型数据中的属性和html表单元素相绑定,以实现表单数据更便捷编辑和表单值的回显。
使用SpringMVC的表单标签的步骤
-
导入SpringMVC的表单标签的标签库
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
-
使用标签库创建表单
<form:form action="" modelAttribute="employee">
<%--
path:就是原来thml页面中input的name项目
path的作用:
1、作为原生的input标签的name属性
2、自动回显隐含模型中某个对象对应的这个属性的值
--%>
lastName:<form:input path="lastName"/><br/>
email:<form:input path="email"/><br/>
gender:
<form:radiobutton path="gender" value="1"/>男<br/>
<form:radiobutton path="gender" value="0"/>女<br/>
department:
<%--
items:指定option要遍历的集合,会进行自动遍历
itemLabel:指定遍历出的那个属性是作为option标签体的值(html显示的值)
itemValue:指定遍历出的那个属性是作为option标签体的提交的值
--%>
<form:select path="department.id" items="${departments}" itemLabel="departmentName" itemValue="id">
</form:select>
<input type="submit" name="添加"/>
</form:form>
原生的表单
<form action="" method="post">
lastName:<input type="text" name="lastName"/> <br/>
email:<input type="email" name="email"/> <br/>
gender:
男:<input type="radio" name="gender" value="1">
女:<input type="radio" name="gender" value="0"><br/>
dept:
<select name="department.id">
<c:forEach items="${requestScope.departments}" var="department">
<option value="${department.id}">${department.departmentName}</option>
</c:forEach>
</select>
<input type="submit" name="提交"/>
</form>
原生表单与SpringMVC表单标签的表单对比
注意点:
SpringMVC会在隐含模型(请求域)获取指定数据来回显下面path指定的值。下面path会获取modelAttribute指定那个对象的属性,如果modelAttribute指定的对象中没有path指定的属性,SpringMVC报500的错误。如果没有指定modelAttribute属性,则modelAttribute的默认值为command。也就是SpringMVC会在隐含模型总获取command数据来回显path指定的内容。
十二、如何阻止SpringMVC的前端控制器Servlet装逼
当我们给前端控制器Servlet的url-pattern配置为/
的时候,前端控制器将会拦截除jsp(Servlet)之外的所有请求,并将拦截到的所有请求交由处理器处理,如果找不到合适的处理器,就报错。
那么问题来了。我们在前端引用的css、html、img、js、jquery对会被前端控制器拦截到,但是前端控制器是根本无法找到所对应的处理器的。这是SpringMVC就会报错。那如何让SpringMVC不装逼呢?也就是让SpringMVC知道如果前端控制器无法处理,就别装逼处理了,交个Tomcat处理就行了。
解决这个问题其实很简单,就是告知spring到当前端控制器无法处理某些请求时不要报错,而是交给默认的Servlet处理,也就是交给tomcat处理。
配置如下:
<mvc:default-servlet-handler/>
注意:
当我们配置了如上的配置之后,我们发现一些静态资源可以访问了但是jsp无法访问了,解决这个问题必须加上springmvc的开挂模式,开启SpringMVC注解驱动。
<mvc:annotation-driven />
十三、数据转换、数据格式化、数据校验
SpringMVC封装自定义类型对象的时候有以下问题?
- Javabean要和页面提交的数据进行–绑定?
- 页面提交的数据都是字符串?
数据绑定期间需要处理以下问题
- 数据类型的转换?由页面获取的字符串转换为Javabean属性对应类型。
- 数据格式化问题?比如日期格式的转换。
- 数据校验?我们提交的数据必须是合法的。可以进行前端校验。但前端校验可靠性不高,所以重要的数据必须进行后端的校验。
1、自定义类型转换器
Converter是ConverterService中的组件
1、必须将自定义的Converter放入ConverterService组件中。
2、将WebDataBinder中的ConverterService设置成我们这个加了自定义类型转换器中的
自定义类型转换器步骤
①实现Converter接口【 org.springframework.core.convert.converter.Converter】
package com.gzk.component;
import com.gzk.dao.DepartmentDao;
import com.gzk.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.converter.Converter;
/**
* 实现Converter接口创建自定义类型转换器
**/
public class MyStringToEmployeeConverter implements Converter<String, Employee> {
@Autowired
DepartmentDao departmentDao;
/**
* 编写类型转换的流程
* @param s
* @return
*/
@Override
public Employee convert(String s) {
Employee employee = new Employee();
if (s.contains("-")) {
String[] splist = s.split("-");
employee.setLastName(splist[0]);
employee.setEmail(splist[1]);
employee.setGender(Integer.parseInt(splist[2]));
employee.setDepartment(departmentDao.getDepartment(Integer.parseInt(splist[3])));
}
return employee;
}
}
②配置ConversionService并且告知SpringMVC不要使用默认的ConversionService,而是使用带有我们自定义Converter的ConversionService。
<bean class="org.springframework.context.support.ConversionServiceFactoryBean" id="conversionService">
<property name="converters">
<set>
<bean class="com.gzk.component.MyStringToEmployeeConverter"/>
</set>
</property>
</bean>
<!--告知spring使用我们自己配置的conversionService-->
<mvc:annotation-driven conversion-service="conversionService" />
2、数据格式化
使用@DateTimeFormat(pattern = “yyyy-MM-dd”)注解告知pojo的日期属性的格式
public class Employee {
private Integer id;
private String lastName;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birth;
private String email;
//1 male, 0 female
private Integer gender;
private Department department;
}
注意:
我们使用自定义类型转换器的时候使用的是ConversionServiceFactoryBean工厂类创建的ConversionService。该工厂创建的ConversionService是只有类型转换的功能,是没有数据格式化的功能的。所以我们不使用ConversionServiceFactoryBean工厂类创建ConversionService。我们使用FormattingConversionServiceFactoryBean来创建ConversionService,FormattingConversionServiceFactoryBean创建的ConversionService不仅具有类型转换的功能而且还具有数据格式化的功能。
<bean class="org.springframework.format.support.FormattingConversionServiceFactoryBean" id="conversionService">
<property name="converters">
<set>
<bean class="com.gzk.component.MyStringToEmployeeConverter"/>
</set>
</property>
</bean>
3、数据校验
1、简介
只进行前端校验是不安全的,在重要的数据上一定要进行后端的数据校验。
后端数据校验的方法。
方式①:和我们以前一样,我们可以在业务中取出每一个数据进行验证,如果校验失败,则返回到页面,提示用户数据不合法重新输入数据。麻烦,我们不推荐。
方式②:SpringMVC可以使用JSR303来做数据的校验。
2、JSR303
JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 6.0 中 。JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对 Bean 进行验证
JSR303是一个规范。
3、Hibernate Validator
Hibernate Validator 是 JSR 303 的一个参考实现,除支持所有标准的校验注解外,它还支持以下的扩展注解。
4、SpringMVC使用Hibernate Validator进行数据校验
步骤:
①导入Hibernate Validator的jar包
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.0.0.CR2</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator-annotation-processor</artifactId>
<version>5.0.0.CR2</version>
</dependency>
②给javabean的属性添加上校验注解。
public class Employee {
private Integer id;
@NotEmpty //lastName属性不为不为空
@Length(max = 12,min = 6) //lastName属性的长度在>=6并且<=12
private String lastName;
//@Future //日期必需是将来的日期
@DateTimeFormat(pattern = "yyyy-MM-dd")
@Past //日期必需是过去的日期
private Date birth;
@Email //email属性必须符合邮箱的格式
private String email;
//1 male, 0 female
private Integer gender;
}
③告诉SpringMVC在封装对象时这个Javabean需要数据校验
@Valid注解指明那个对象需要进行数据校验
@RequestMapping(value = "/addEmp",method = RequestMethod.POST)
public String addEmp(@Valid Employee employee){
System.out.println(employee);
employeeDao.save(employee);
return "redirect:/emps";
}
④获取校验结果:
给需要校验的Javabean后面紧跟一个BindingResult。这个BindingResult就是封装前一个bean的校验结果。BindingResult可以获取是否有检验错误。
@RequestMapping(value = "/addEmp",method = RequestMethod.POST)
public String addEmp(@Valid Employee employee, BindingResult bindingResult){
//查看校验结果中是否有错
boolean flag = bindingResult.hasErrors();
if(flag){//校验失败
System.out.println("数据校验失败");
return "add";
}else{//校验成功
System.out.println(employee);
employeeDao.save(employee);
return "redirect:/emps";
}
}
⑤使用form:errors取出错误信息。
pring MVC 除了会将表单/命令对象的校验结果保存到对应的 BindingResult 或 Errors 对象中外,还会将所有校验结果保存到 “隐含模型”。所以我们可以在页面上获取到校验结果。
<form:form action="addEmp" modelAttribute="employee" method="post">
<%--
path:就是原来thml页面中input的name项目
path的作用:
1、作为原生的input标签的name属性
2、自动回显隐含模型中某个对象对应的这个属性的值
--%>
lastName:<form:input path="lastName"/><form:errors path="lastName"/> <br/>
email:<form:input path="email"/><form:errors path="email"/><br/>
gender:
<form:radiobutton path="gender" value="1"/>男
<form:radiobutton path="gender" value="0"/>女<br/>
birth:<form:input path="birth"/><form:errors path="birth"/><br/>
department:
<%--
items:指定option要遍历的集合,会进行自动遍历
itemLabel:指定遍历出的那个属性是作为option标签体的值(html显示的值)
itemValue:指定遍历出的那个属性是作为option标签体的提交的值
--%>
<form:select path="department.id" items="${departments}" itemLabel="departmentName" itemValue="id">
</form:select><br/>
<input type="submit" name="添加"/>
</form:form>
在实际的场景中我们可能不会使用SpringMVC的form标签去开发前端页面,这种情况我们如何获取校验结果的信息呢?其实我们可以将校验结果封装带request请区域中,然后在前端的页面中获取。
将校验结果放入请求域中。
@RequestMapping(value = "/addEmp",method = RequestMethod.POST)
public String addEmp(@Valid Employee employee, BindingResult bindingResult,Model model){
//查看校验结果中是否有错
boolean flag = bindingResult.hasErrors();
if(flag){//校验失败
System.out.println("数据校验失败");
Map<String,Object> errorMap = new HashMap<>();
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
for (FieldError fieldError: fieldErrors) {
errorMap.put(fieldError.getField(),fieldError.getDefaultMessage());
}
model.addAttribute("errorInfo",errorMap);
return "add";
}else{//校验成功
System.out.println(employee);
employeeDao.save(employee);
return "redirect:/emps";
}
}
页面获取校验结果
${requestScope.errorInfo.lastName}<br/>
${requestScope.errorInfo.email}<br/>
${requestScope.errorInfo.birth}<br/>
5、【国际化】定制自己的错误消息
1、简介
每个属性在数据绑定和数据校验发生错误时,都会生成一个对应的 FieldError 对象。当一个属性校验失败后,校验框架会为该属性生成 4 个消息代码,这些代码以校验注解类名为前缀,结合 modleAttribute、属性名及属性类型名生成多个对应的消息代码:
例如 User 类中的 password 属性标准了一个 @Pattern 注解,当该属性值不满足 @Pattern 所定义的规则时, 就会产生以下 4 个错误代码:
Pattern.user.password
Pattern.password
Pattern.java.lang.String
Pattern
当使用 Spring MVC 标签显示错误消息时, Spring MVC 会查看 WEB 上下文是否装配了对应的国际化消息,如果没有,则显示默认的错误消息,否则使用国际化消息。
2、国际化】定制自己的错误消息步骤:
①创建国际化资源文件,且资源文件中的key必须为错误代码。
②让spring管理国际化资源文件
<bean class="org.springframework.context.support.ResourceBundleMessageSource" id="messageSource">
<property name="basename" value="errors"/>
</bean>
以上两步就可以在定制自己的错误信息了。
注意:
若数据类型转换或数据格式转换时发生错误,或该有的参数不存在,或调用处理方法时发生错误,都会在隐含模型中创建错误消息。其错误代码前缀说明如下:required:必要的参数不存在。如
- required:必要的参数不存在。如 @RequiredParam(“param1”) 标注了一个入参,但是该参数不存在
- typeMismatch:在数据绑定时,发生数据类型不匹配的问题
- methodInvocation:Spring MVC 在调用处理方法时发生了错误
也可以在国际化资源文件中配置上诉错误代码
3、自定义错误消息的细节
1、动态传入消息参数
上面的案例并没有使用动态传入消息参数。
动态传入消息参数,使用{}来动态获取消息参数
Length.java.lang.String=长度必须在{1}到{2}之间{0}
4、简单的定制自己的错误消息
以上的步骤制定自己的错误信息太复杂了,其实可以使用注解的属性message来指定错误信息。
@NotEmpty(message = "不能为空啊啊啊")
@Length(max = 19,min = 6,message = "长度在6到19之间啊啊啊啊")
private String lastName;
十四、<mvc:annotation-driven />的解析
在之前的使用过程中我们发现<mvc:annotation-driven />专治各种不服,能过处理多种复杂的情况。
只要请求不好使就召唤<mvc:annotation-driven />标签。
十五、<mvc:annotation-driven />与<mvc:default-servlet-handler />的强大作用。
<mvc:annotation-driven />与<mvc:default-servlet-handler />是SpringMVC中两个强大的标签。
下面讨论两者的作用。
情况①:两个标签都没有配置
如果这个两个标签都没有配置,动态资源(@RequestMapping映射的资源)能访问,静态资源(html、css、js、img…)不能访问。
情况②:只配置了<mvc:default-servlet-handler />
如果只配置了<mvc:default-servlet-handler />,静态资源可以访问,但是动态资源无法访问。
情况③:<mvc:annotation-driven />与<mvc:default-servlet-handler />两个都配置上
如果两者都配置上,静态资源和动态资源都可以访问。
情况④:只配置了<mvc:default-servlet-handler />
动态资源(@RequestMapping映射的资源)能访问,静态资源(html、css、js、img…)不能访问。
十六、SpringMVC支持ajax异步请求
SpringMVC快速完成ajax功能?
ajax请求的要求
-
ajax请求需要页面发起异步请求。
-
ajax请求需要服务器返回的数据是json数据就行了。
1、原生javaweb实现ajax请求步骤
- 导入Gson包
- 返回的数据用Gson转换为json数据
- 服务器将json数据返回页面
2、SpringMVC实现ajax请求步骤
-
导入jackson jar包
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.1.5</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.1.5</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.1.5</version> </dependency>
2. 写处理ajax的处理器方法
/**
* @ResponseBody指明该方法的返回的数据放在响应体中,返回值将不会接收视图解析器的解析
* 如果返回值是一个对象,jackson将会自动将对象转换为json格式
*/
@ResponseBody
@RequestMapping("/ajaxGetEmps")
public Collection<Employee> ajaxGetEmps(){
Collection<Employee> all = employeeDao.getAll();
return all;
}
3、jackson中的某些注解
-
@JsonIgnore:告知jackson不需要将某些属性转换为json格式
@JsonIgnore private Department department;
-
@JsonFormat(pattern = “yyyy-MM-dd”,timezone = “GMT+8”):规定某些属性转换为json的形式。
@JsonFormat(pattern = "yyyy-MM-dd",timezone = "GMT+8") private Date birth;
十七、requestBody注解获取请求体
requestBody可以获取一个请求的请求体。
只有post请求才有请求体,get请求的请求参数放在url路径中。post请求的请求参数放在请求体中。
@RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody String body){
System.out.println(body);
return "success";
}
RequestBody可以接收json数据
十八、HTTPEntity类型参数获取请求头和请求体
@RequestMapping("/test01")
public String test01(HttpEntity<String> headers){
System.out.println(headers);
return "success";
}
十九、ResponseEntity类型返回值设置响应体和响应头
@RequestMapping("/test02")
public ResponseEntity<String> test02() {
String body = "<h1>success......</h1>";
MultiValueMap<String, String> headers = new HttpHeaders();
headers.set("Content-Language", "en_US");
headers.set("setCookie", "username");
HttpStatus status = HttpStatus.OK;
ResponseEntity<String> responseEntity = new ResponseEntity<>(body, headers, status);
return responseEntity;
}
二十、使用ajax发送json数据给服务器
1、前端ajax发送json数据到服务器
<script type="text/javascript">
$("a:first").click(function () {
var emp = {lastName:"z张三",email:"1223@qq.com",gender:0};
alert(typeof(emps))
var empstr = JSON.stringify(emp);
alert(typeof(empstr))
$.ajax({
url:"testRequestBody",
type:"POST",
contentType:"application/json",
data:empstr,
success:function (data) {
console.log(data)
}
})
return false;
});
</script>
2、服务器使用@RequestBody接收json数据,封装为Javabean对象。
@RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody Employee employee){
System.out.println(employee);
return "success";
}
总结:
responseBody注解可以将Javabean返回值封装到请求体中,然后jackson将会把Javabean对象转换为json字符串。
RequestBody注解可以接收ajax请求发送的json数据,并将json数据封装到RequestBody修饰的javabean对象中。
二十一、SpringMVC文件下载
文件下载的关键是将响应头的"Content-Disposition"的属性设置为"attachment;filename=file_location"。然后使用文件流输出到客户端。
SpringMVC的文件下载
@RequestMapping("/download")
public ResponseEntity<byte[]> download(HttpServletRequest request) throws Exception {
ServletContext servletContext = request.getServletContext();
//文件的路径
String file_location = servletContext.getRealPath("/js/jquery-1.7.2.js");
FileInputStream fileInputStream = new FileInputStream(file_location);
byte[] data = new byte[fileInputStream.available()];
fileInputStream.read(data);
MultiValueMap<String, String> headers = new HttpHeaders();
headers.set("Content-Disposition","attachment;filename="+file_location);
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(data, headers, HttpStatus.OK);
return responseEntity;
}
SpringMVC的文件下载不是很优秀,所以一般直接将原生的API传入,然后使用原生的文件下载,原生的文件下载可以使用一些文件下载jar,比较好。
二十二、SpringMVC文件上传
1、SpringMVC文件上传的步骤
①导入文件上传的jar包
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.0.1</version>
</dependency>
②编写文件上传表单,设置 enctype属性为"multipart/form-data"
<form action="upload" method="post" enctype="multipart/form-data">
用户头像:<input type="file" name="userimg"/><br/>
用户名 :<input type="text" name="username" /><br/>
<input type="submit" />
</form>
③spring配置文件中配置文件上传解析器
文件上传解析器为SpringMVC九大组件之一
<bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver">
<property name="maxUploadSize" value="#{1024*1024*20}"></property>
<property name="defaultEncoding" value="utf-8"></property>
</bean>
该组件的id必须为multipartResolver。
④文件上传请求处理
只需在处理器方法上写一个MultiPartFile类型的参数,就可以将上传文件的流包装在指定的参数中。
@Controller
public class FileUploadController {
@RequestMapping("/upload")
public String upload(@RequestParam("username") String username , @RequestParam("userimg") MultipartFile file)throws IOException {
System.out.println(username);
System.out.println(file.getOriginalFilename());
file.transferTo(new File("E:\\"+file.getOriginalFilename()));
return "forward:/index.jsp";
}
}
2、多文件上传
1、编写上传文件表单
<form action="upload" method="post" enctype="multipart/form-data">
用户头像:<input type="file" multiple name="userimg"/><br/>
用户名 :<input type="text" name="username" /><br/>
<input type="submit" />
</form>
2、编写处理请求方法
@Controller
public class FileUploadController {
@RequestMapping("/upload")
public String upload(@RequestParam("username") String username, @RequestParam("userimg") MultipartFile[] files)throws IOException {
for (MultipartFile file : files){
System.out.println(username);
System.out.println(file.getOriginalFilename());
file.transferTo(new File("E:\\" + file.getOriginalFilename()));
}
return "forward:/index.jsp";
}
}
二十三、拦截器
1、介绍
SpringMVC提供拦截器机制,拦截器允许在运行目标方法之前进行一些拦截工作,或者目标方法之后进行一些处理。
拦截器是一个接口,要想实现拦截器必须实现HandlerInterceptor接口。Spring MVC也可以使用拦截器对请求进行拦截处理,用户可以自定义拦截器来实现特定的功能,自定义的拦截器必须实现HandlerInterceptor接口
☆preHandle():这个方法在业务处理器处理请求之前被调用,在该方法中对用户请求 request 进行处理。如果程序员决定该拦截器对请求进行拦截处理后还要调用其他的拦截器,或者是业务处理器去进行处理,则返回true;如果程序员决定不需要再调用其他的组件去处理请求,则返回false。
☆postHandle():这个方法在业务处理器处理完请求后,但是DispatcherServlet 向客户端返回响应前被调用,在该方法中对用户请求request进行处理。
☆afterCompletion():这个方法在 DispatcherServlet 完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作。
2、SpringMVC设置一个拦截器的步骤
①自定义一个拦截器,实现HandlerInterceptor接口
package com.gzk.controller;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyFirstInterceptor implements HandlerInterceptor {
/**
* 目标方法运行之前运行
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("HandlerInterceptor........preHandle......");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("HandlerInterceptor........postHandle......");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("HandlerInterceptor........afterCompletion......");
}
}
②在spring配置文件中配置拦截器
√下面这种配置默认拦截所有请求
<mvc:interceptors>
<!--默认拦截所有请求-->
<bean class="com.gzk.controller.MyFirstInterceptor"/>
</mvc:interceptors>
√配置更详细的拦截器
<mvc:interceptors>
<!--更详细的配置某个拦截器-->
<mvc:interceptor>
<!--设置拦截器的拦截路径-->
<mvc:mapping path="/test01"/>
<bean class="com.gzk.controller.MyFirstInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
3、拦截器的运行流程
①正常运行流程
HandlerInterceptor的preHandle方法运行→→目标方法执行→→HandlerInterceptor的postHandle方法运行→→页面显示→→HandlerInterceptor的afterCompletion方法运行。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W51KyqHd-1600518575902)(C:\Users\21232\AppData\Roaming\Typora\typora-user-images\1597739126324.png)]
②HandlerInterceptor的preHandle方法返回false。
HandlerInterceptor的preHandle方法没有放行目标方法,所以以后所有的流程都没有了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PUd6JpI5-1600518575940)(C:\Users\21232\AppData\Roaming\Typora\typora-user-images\1597739899660.png)]
③目标方法炸了,出现异常,postHandle方法不会执行
preHandle方法放行→→目标方法执行→→afterCompletion方法运行。
只要preHandle方发放行了,afterCompletion方法就会执行。
4、多拦截器运行流程,与filter类似
正常流程
已放行的拦截器的afterCompletion方法都会运行
二十四、国际化
1、简单国际化
❶简单国际化的实现步骤
①创建国际化资源文件
②让spring的ResourceBundleMessageSource管理国际化资源文件
③在前端页面获取国际化资源文件的值。
SpringMVC中区域信息是由SpringMVC九大组件的localeResolver区域信息解析器得到的。
是按照浏览器带来的语言信息决定的。Locale locale = request.getLocale();获取浏览器的区域信息
2、程序中获取国际化信息
@Autowired
private MessageSource messageSource;
@RequestMapping("/toLoginPage")
public String toLoginPage(Locale locale){
System.out.println(locale);
String welcomeInfo = messageSource.getMessage("welcomeInfo", null, locale);
System.out.println(welcomeInfo);
return "login";
}
3、点击链接切换国际化
我们知道SpringMVC的国际化管理器使用的默认的区域信息解析器其实的区域信息是直接从请求域中获取的。知道这点之后我们可以自定义一个区域信息解析器,然后使国际化按照我们的需求进行展示。
从请求域中获取区域解析器
实现一个自定义的区域解析器
package com.gzk.controller;
import org.springframework.web.servlet.LocaleResolver;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;
public class MyLocaleResolver implements LocaleResolver {
/**
* 自定义区域解析器的原理:
* 从请求域中获取请求参数locale,如果locale参数如果存在则按照locale参数的值创建locale对象,
* 并返回该loca对象
* 如果不存在locale请求参数,则从请求域中直接获取locale信息
* @param httpServletRequest
* @return
*/
@Override
public Locale resolveLocale(HttpServletRequest httpServletRequest) {
Locale locale = null;
String localeStr = httpServletRequest.getParameter("locale");
if ((!"".equals(localeStr)) && localeStr != null) {
locale = new Locale(localeStr.split("_")[0], localeStr.split("_")[1]);
} else {
locale = httpServletRequest.getLocale();
}
return locale;
}
@Override
public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
throw new UnsupportedOperationException("Cannot change HTTP accept header - use a different locale resolution strategy");
}
}
Spring配置自定义区域解析器
<!--配置区域信息解析器-->
<bean class="com.gzk.controller.MyLocaleResolver" id="localeResolver"/>
然后就可以了。
二十五、拦截器与Filter
什么时候使用拦截器什么时候使用过滤器
拦截器的功能更为强大。如果某些功能需要其他组件配合使用,则使用拦截器。简单功能使用过滤器。
二十六、异常处理
1、简介
SpringMVC通过九大组件之一的HandlerExceptionResolver处理程序的异常,包括Handler映射、数据绑定、目标方法执行时发生的异常。
SpringMVC默认使用的异常处理器为下面的三个
ExceptionHandlerExceptionResolver →→→→→→→ @ExceptionHandler
ResponseStatusExceptionResolver →→→→→→→ @ResponseStatus
DefaultHandlerExceptionResolver →→→→→→→SpringMVC自带异常
每个异常解析器一次处理异常,如果能处理,则将异常处理,如果不能,则将异常将有下一个异常解析器处理。如果异常解析器都不能处理,就直接将异常抛给tomcat服务器。
2、@ExceptionHandler注解处理本类异常
//使用@ExceptionHandler注解告知SpringMVC这个方法专门处理value属性所指明的异常,它的作用范围为本类
//给方法参数上随表添加一个EXception类型的异常,用来接收发生的异常
//SpringMVC只能识别Exception类型的参数,其他类型的参数都无法识别
@ExceptionHandler(value = {ArithmeticException.class,NullPointerException.class})
public ModelAndView hanleExceprion01(Exception exception){
ModelAndView modelAndView = new ModelAndView("myError");
modelAndView.addObject("exception", exception);
return modelAndView;
}
使用@ExceptionHandler注解告知SpringMVC这个方法专门处理value属性所指明的异常,它的作用范围为本类。给方法参数上随表添加一个EXception类型的异常,用来接收发生的异常。SpringMVC只能识别Exception类型的参数,其他类型的参数都无法识别
3、@ExceptionHandler注解处理全局异常
只需将@ExceptionHandler标注的异常处理方法集中到一个类,并且使用@ControllerAdvice告知SpringMVC该类可以处理异常。
4、注意
如果本类中也有@ExceptionHandler标注的异常处理方法,那优先使用本类的。
5、总结
6、 @ResponseStatus异常解析
7、默认异常处理器
8、SimpleMappingExceptionResolver
如果希望对所有异常进行统一处理,可以使用 SimpleMappingExceptionResolver,它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常
配置方法:
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver" id="exceptionResolver">
<!--将异常与视图一一对应-->
<property name="exceptionMappings">
<props>
<prop key="java.lang.NullPointerException">myError</prop>
<prop key="java.lang.ArithmeticException">myError</prop>
</props>
</property>
<!--配置将异常信息保存到请求域时的key的值-->
<property name="exceptionAttribute" value="excpetionInfo"/>
</bean>
异常解析器优先级
ExceptionHandlerExceptionResolver(本地大于全局) 大于 ResponseStatusExceptionResolver 大于 DefaultHandlerExceptionResolver 大于 SimpleMappingExceptionResolver
二十七、SpringMVC运行流程
1、所有的请求,前端控制器(DispatcherServlert)收到请求,调用doDispatch进行处理
2、根据@HandlerMapping注解中保存的请求映射信息,找到能处理当前请求的处理器执行链(包含拦截器)
3、根据当前处理器找到他的HandlerAdapter(适配器)
4、拦截器的preHandle先执行
5、适配器执行目标方法,并返回ModelAndView
6、拦截器的postHandle执行
7、处理结果,页面渲染
1、如果有异常使用异常解析器处理,并返回ModelAndView
2、如果没有异常,视图对象调用rander方法进行渲染。
3、拦截器的afterCompletion方法执行。
二十八、SpringMVC与spring整合
SpringMVC和Spring整合的目的:分工明确
SpringMVC的配置文件就来配置和网站转发逻辑以及网站功能有关的东西(视图解析器,文件上传解析器,支持ajax等)
Spring的配置文件来配置和业务有关的(事务控制,数据源、等等)
1、整合方式
方式一:合并配置文件,不推荐。
<import resource="SpringMVC.xml"/>
这种方式可以合并两个配置文件,spring和SpringMVC使用的是同一个容器。
方式二:SpringMVC和Spring分容器
我们在web.xml中配置了SpringMVC的配置文件,所以在tomcat启动时会为我们创建ioc容器。
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:SpringMVC.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
那么想要创建spring的容器,就必须指明spring的配置文件的位置。
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
由于创建两个IOC容器,所以配置的组件会被创建两次,这显然不符合我们的设想。我们想让SpringMVC的IOC容器只管理它的组件,spring也只管理业务逻辑的组件。所以在SpringMVC的配置文件和Sprin的配置文件中,我们必须明确指定他们包扫描组件的范围。
指定SpringMVC只扫描标有@Controller和@ControllerAdvice的组件
<context:component-scan base-package="com.gzk" use-default-filters="false">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:exclude-filter type="annotation"
expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
</context:component-scan>
指定Spring扫描其他剩余组件。
<context:component-scan base-package="com.gzk">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:include-filter type="annotation"
expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
</context:component-scan>
注意:
SpringMVC容器(子容器)和Spring容器(父容器)是子与父的关系。Spring默认的规定,子容器可以访问父容器中的组件,但是父容器不能访问子容器中的组件。
在 Spring MVC 配置文件中引用业务层的 Bean
多个 Spring IOC 容器之间可以设置为父子关系,以实现良好的解耦。
Spring MVC WEB 层容器可作为 “业务层” Spring 容器的子容器:即 WEB 层容器可以引用业务层容器的 Bean,而业务层容器却访问不到 WEB 层容器的 Bean