一、SpringMVC简介
Spring实现web模块,简化web开发;Spring中的web模块就是SpringMVC
SpringMVC通过一套MVC注解,让 POJO 成为处理请求的控制器,而无须实现任何接口。
SpringMVC图解
二、简单的MVC流程
导包
配置文件
web.xml:springmvc的前端控制器,指定springmvc配置文件的位置
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- SpringMVC思想是有一个前端控制器能拦截所有请求,并只能派发
这个前端控制器是一个Servlet,应该在web.xml中配置这个Servlet拦截所有请求
-->
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!-- contextConfigLocation:指定SpringMVC配置文件位置-->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!-- Servlet启动加载
Servlet原本是第一次访问创建对象
load-on-startup:服务器启动的时候创建对象:值越小优先级越高,越先创建对象
-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<!-- /*和/都是拦截所有请求
/*的范围更大,拦截到.jsp的请求
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
springmvc.xml:包扫描;视图解析器
<?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"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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
http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 扫描所有组件-->
<context:component-scan base-package="com.mvc.controller"></context:component-scan>
<mvc:annotation-driven />
<!-- 配置视图解析器,能拼接页面地址-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/pages/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
</beans>
==说明:==如果在web.xml中没有指明springmvc的配置文件位置,服务器也会去一个默认的地方找配置文件:
/WEB-INF/springDispatcherServlet-servlet.xml
我们可以在web应用的/WEB_INF下创建一个springDispatcherServlet-servlet.xml文件,里面写上mvc的配置。
其中,springDispatcherServlet是前端控制器名,可以自行修改。
运行流程
- 客户端发起hello请求
- 来到Tomcat服务器
- SpringMVC前端控制器收到所有请求
- 来看请求地址和@RequestMapping标注的哪个匹配,来找到到底使用哪个类的哪个方法
- 前端控制器找到了目标处理器类和方法,利用反射执行目标方法
- 方法执行完成后有一个返回值,SpringMVC认为这个返回值就是页面要去的地址
- 拿到方法的返回值后,用视图解析器进行拼串得到完整的地址
- 拿到完整地址,前端控制器帮我们转发到页面。
三、RequestMapping
@RequestMapping可以标注在类和方法上。
标注在类上面,可以为当前类所有方法的请求地址指定一个基准路径。
此时的hello请求的地址就变成了:工程路径/kass/hello
@RequestMapping("/kass")//为当前类所有方法的请求地址指定一个基准路径
@Controller
public class FirstController {
@RequestMapping("/hello")
public String myFirstRequest(){
System.out.println("hello请求来啦!");
return "success";
}
RequestMapping中的属性
method
http协议中的所有请求方式:
get、post、head、put、patch、delete、options
eg:
method = RequestMethod.GET表示只接受这种类型的请求,默认情况下是什么请求都可以
params
params可以规定请求参数
==eg1:==params = {“username”}表示发起的请求中,必须有名为username的请求参数.
==eg2:==params = {"!username"}表示发起的请求中,不能带有名为username的请求参数
==eg3:==params = {“username=123”}表示发起的请求中,必须有名为username的请求参数,且值为123
==eg4:==params = {“username=!123”}表示发起的请求中,参数username的值不等于123,所以可以没有username(null)
params = {"username!=123","password","!age"}
/*请求参数满足以下条件:
请求的username不能是123;
必须有password参数
不能有age参数
*/
headers
规定请求头,也和params一样能写简单的表达式
headers = {"USer-Agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0"}
可以设置只让火狐访问,而chrome等不能访问。
附上测试几个属性的代码:
@RequestMapping("/kass")//为当前类所有方法的请求地址指定一个基准路径
@Controller
public class FirstController {
/**
* @RequestMapping请求映射
* /代表从当前项目下开始,处理当前项目下的hello请求
*/
@RequestMapping("/hello")
public String myFirstRequest(){
System.out.println("hello请求来啦!");
//视图解析器会自动拼串
return "success";
}
//测试method与params
@RequestMapping(value = "/world",method = RequestMethod.GET,params = {"username!=123","password","!age"})
public String test(){
System.out.println("第二个请求方法");
return"success";
}
//测试headers
/**
* USer-Agent:浏览器信息
* 让火狐能访问,chrome不能访问
* @return
*/
@RequestMapping(value ="test1",headers = {"USer-Agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0"})
public String test1(){
return "success";
}
}
ant风格的url
简单来说就是模糊查询。
三个通配符的作用:
?:-----能替代任意单个字符
*可以代替任意多个字符,和一层路径
** 能够代替多层路径
/**
* ?匹配1个字符,不可以是0个或多个
* 模糊和精确多个匹配下,精确优先
* @return
*/
@RequestMapping("/test?")
public String test1(){
return "success";
}
/**
* *匹配0个或多个字符,也可以匹配一层路径
* @return
*/
@RequestMapping("/test*")
public String test2(){
return "success";
}
模糊匹配和精确匹配都存的情况下,优先执行精确的。
路径上的占位符
路径上可以有占位符:占位符的语法就是可以在任意路径的地方写一个{变量名}
路径上的占位符只能占一层路径
四、REST
系统希望以非常简介的URL地址来发请求,REST通过请求方式来区分对资源的操作
Http协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:
- GET用来获取资源
- POST用来创建资源
- PUT用来更新资源
- DELETE用来删除资源
问题是,如何从页面发起DELETE、PUT请求?
Spring提供了对Rest风格的支持,SpringMVC中有一个Filter,它可以把普通的请求转化为规定形式的请求
首先需要配置这个Filter:
<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>
具体如何发起其他形式的请求?
- 创建一个post类型的表单
- 表单项中携带一个_method的参数
- 这个_method的值就是DELETE或PUT
高版本(8.0以上)的Tomcat对Rest支持有点问题:
解决方法:在jsp页面的开头加上isErrorPage=true
五、请求处理
三个注解
@Controller
public class HelloController {
/**
* SpringMVC如何获取请求带来的各种信息
* @RequestParam:获取请求参数
* 默认方式获取请求参数:
* 直接给方法的参数名写成和请求参数名相同的变量,这个变量就来接收请求参数的值(参数可以没有)
* 也可以加注解,加注解后参数是默认必须带的
*
* @RequestHeader:获取请求头中某个key的值
*
* @CookieValue:获取某个Cookie
*
* value:指定要获取参数的key
* required:这个参数是否必须有
* defaultValue:默认值,没带默认是null
*
*
* @param username
* @return
*/
@RequestMapping("/hello")
public String test1(@RequestParam(value="username") String username,
@RequestHeader("User-Agent") String userAgent,
@CookieValue("JSESSIONID") String cookie){
System.out.println(username);
System.out.println("浏览器信息"+userAgent);
System.out.println(cookie);
return "success";
}
}
请求参数
如果请求参数是一个POJO:
SpringMVC会自动为这个POJO进行赋值
将POJO中的每一个属性,从request参数中尝试获取出来,然后封装到POJO中
还可以级联赋值
示例代码如下:
jsp:
<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/>
<br/>
<input type="text" name="address.province"><br/>
<input type="text" name="address.city"><br/>
<input type="submit">
</form>
pojo(Book):
public class Book {
private String bookName;
private String author;
private Double price;
private Integer stock;
private Integer sales;
private Address address;
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@Override
public String toString() {
return "Book{" +
"bookName='" + bookName + '\'' +
", author='" + author + '\'' +
", price=" + price +
", stock=" + stock +
", sales=" + sales +
", address=" + address +
'}';
}
public Book() {
}
public Book(String bookName, String author, Double price, Integer stock, Integer sales) {
this.bookName = bookName;
this.author = author;
this.price = price;
this.stock = stock;
this.sales = sales;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public Integer getStock() {
return stock;
}
public void setStock(Integer stock) {
this.stock = stock;
}
public Integer getSales() {
return sales;
}
public void setSales(Integer sales) {
this.sales = sales;
}
}
Address类:
public class Address {
private String province;
private String city;
public String getProvince() {
return province;
}
@Override
public String toString() {
return "Address{" +
"province='" + province + '\'' +
", city='" + city + '\'' +
'}';
}
public void setProvince(String province) {
this.province = province;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
}
控制器:
@Controller
public class Controller1 {
@RequestMapping("/book")
public String addBook(@RequestParam(value = "book") Book book){
System.out.println(book);
return "success";
}
}
传入原生API
SpringMVC可以直接在参数上写原生API,比如session,request等
/**
HttpServletRequest
HttpServletResponse
HttpSession
java.security.Principal
Locale:国际化有关的区域信息对象
InputStream:
ServletInputStream inputStream=request.getInputStream();
OutputStream:
ServletOutputStream outputStream=response.getOutputSream();
Reader:
BufferedReader reader=request.getReader();
Writer:
PrintWriter writer=response.getWriter();
*/
@RequestMapping("/hello1")
public String test(HttpServletRequest request, HttpSession session){
request.setAttribute("key1","request1");
session.setAttribute("key2","session1");
return "success";
}
乱码问题
/**
提交的数据可能出现乱码问题:
请求乱码:
GET请求:在server.xml中配置
POST请求:在web.xml中配置springMVC提供的filter
*/
<!-- 配置一个字符编码的Filter-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!--
encoding:指定解决post请求乱码
forceEncoding:解决响应乱码
-->
<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>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
/**
响应乱码:
response.setContextType("text/html;setChar=UTF-8");
*/
在server.xml的配置中,找到下面这行代码,添加框住的部分:URIEncoding=”UTF-8“
==注意:==在有多个filter配置的情况下,将解决乱码的filtet写在最前面。
六、数据输出
RequestScope
SpringMVC除了在方法上传入原生的request和session还能怎么样把数据带给页面呢?
-
可以在方法处传入Map、Model、ModelMap。
给这些参数里面保存的数据都会放在请求域中,可以在页面中获取。
@RequestMapping(value = "/hello2") public String test1(Map<String,Object> map){ map.put("msg","芜湖"); return "success"; }
-
返回值使用ModelAndView,保存的数据同样会储存在request域中
@RequestMapping(value = "/hello5") public ModelAndView test4(Model model){ //其中success我们就叫视图名;视图解析器会帮我们最终拼串得到页面真实地址 // ModelAndView view = new ModelAndView("success"); ModelAndView view = new ModelAndView(); view.setViewName("success"); model.addAttribute("msg","嘻嘻!!"); return view; }
SessionScope
使用@SessionAttributes(value=“key”)注解,这个注解只能加在类上面
value指定保存数据时要给session中存放的数据的key
value={”key“}:只要保存的是这种key的数据,都会在session域中保存一份
types={String.class}:只要保存的是这种类型的数据,给session域中也放一份。
但是,通常会使用原生的session的API来往Session域中保存数据,上面的注解方法有可能会出问题。
七、视图解析
forward
/**
* forward:转发到一个页面,不会由视图解析器拼串
* /hello.jsp:转发到当前项目下的hello.jsp
* @return
*/
@RequestMapping("/hello")
public String test1(){
return"forward:/hello.jsp";
}
@RequestMapping("/hello1")
public String test2(){
return "forward:/hello";
}
客户端发起hello1请求,会转发到hello请求,再转发到hello.jsp页面
redirect
//redirect:重定向
@RequestMapping("/handle1")
public String test3(){
System.out.println("handle1");
return "redirect:/hello";
}
@RequestMapping("/handle2")
public String test4(){
return "redirect:handle1";
}
解析视图的大致流程
- 请求处理方法执行完成后,最终返回一个 ModelAndView 对象。对于那些返回 String,View 或 ModeMap 等类型的处理方法,Spring MVC 也会在内部将它们装配成一个 ModelAndView 对象,它包含了逻辑名和模型对象的视图
- Spring MVC 借助视图解析器(ViewResolver)得到最终的视图对象(View),最终的视图可以是 JSP ,也可能是 Excel、JFreeChart 等各种表现形式的视图
视图
- 视图的作用是渲染模型数据,将模型里的数据以某种形式呈现给客户。
为了实现视图模型和具体实现技术的解耦,Spring 在 org.springframework.web.servlet 包中定义了一个高度抽象的 View 接口 - 视图对象由视图解析器负责实例化。由于视图是无状态的,所以他们不会有线程安全的问题
视图解析器
- SpringMVC 为逻辑视图名的解析提供了不同的策略,可以在 Spring WEB 上下文中配置一种或多种解析策略,并指定他们之间的先后顺序。每一种映射策略对应一个具体的视图解析器实现类。
- 视图解析器的作用比较单一:将逻辑视图解析为一个具体的视图对象。
- 所有的视图解析器都必须实现 ViewResolver 接口
一句话:视图解析器只是为了得到视图对象;视图对象才能真正的转发(将模型数据全部放在请求域中)或者重定向到页面;视图对象才能真正的渲染页面。
常用的视图解析器实现类
八、JSR 303
JSR303是java为Bean数据合法性校验提供的标准框架,它已经包含在javaEE6.0中。
JSR303通过在Bean属性上标注类似于@NotNUll、@Max等标准的注解指定校验规则,并通过标准的验证接口对Bean进行验证。
Hibernate Validator
Hibernate Validator是JSR303的一个参考实现,除支持所有标准的校验注解外,还支持以下扩展注解:
@NotNull 和 @NotEmpty 和@NotBlank 区别:
@NotEmpty 用在集合类上面
@NotBlank 用在String上面
@NotNull 用在基本类型上
九、SpringMVC运行流程
- 所有请求,前端控制器(dispatcherServlet)收到请求,调用doDispatch进行处理。
- 根据HandlerMapping中保存的请求映射信息找到处理当前请求的处理器执行链(包含拦截器)
- 根据当前处理器找到它的HandlerAdapter(适配器)
- 拦截器的preHandle先执行
- 适配器执行目标方法,并返回ModelAndView
- ModelAttribute注解标注的方法提前运行
- 执行目标方法的时候(确定目标方法用的参数)
- 看是否有Model、Map以及其它
- 如果是自定义类型
- 从隐含模型中看有没有,如果有就从隐含模型中拿
- 如果没有,再看是否SessionAttributes标注的属性,如果是从Session中拿,拿不到会抛异常
- 都不是,利用反射创建对象
- 拦截器的postHandle执行
- 处理结果(页面渲染流程)
- 如果有异常使用异常解析器处理异常;处理完后还会返回ModelAndView
- 调用render进行页面渲染
图解:
十、SpringMVC与Spring整合
整合的目的是为了分工明确。
SpringMVC的配置文件就来配置和网站转发逻辑以及网站功能有关的(视图解析器、文件上传解析器、ajax…)
Spring的配置文件来配置和业务有关的(事务控制,数据源…)