SpringMVC

一、SpringMVC简介

Spring实现web模块,简化web开发;Spring中的web模块就是SpringMVC

SpringMVC通过一套MVC注解,让 POJO 成为处理请求的控制器,而无须实现任何接口。

SpringMVC图解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aNFzyj2a-1630299060213)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20210816171258424.png)]

二、简单的MVC流程

导包

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z66FU1Ok-1630299060214)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20210817104944338.png)]

配置文件

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>

具体如何发起其他形式的请求?

  1. 创建一个post类型的表单
  2. 表单项中携带一个_method的参数
  3. 这个_method的值就是DELETE或PUT

高版本(8.0以上)的Tomcat对Rest支持有点问题:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m4VsWnnS-1630299060215)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20210817211506565.png)]

解决方法:在jsp页面的开头加上isErrorPage=true

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g3ChCZgW-1630299060216)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20210817211544214.png)]


五、请求处理

三个注解

@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“

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ziD2Q6KJ-1630299060218)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20210818121229061.png)]

==注意:==在有多个filter配置的情况下,将解决乱码的filtet写在最前面。

六、数据输出

RequestScope

SpringMVC除了在方法上传入原生的request和session还能怎么样把数据带给页面呢?

  1. 可以在方法处传入Map、Model、ModelMap。

    ​ 给这些参数里面保存的数据都会放在请求域中,可以在页面中获取。

    @RequestMapping(value = "/hello2")
      public String test1(Map<String,Object> map){
        map.put("msg","芜湖");
        return "success";
    }
    
  2. 返回值使用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";
    }

解析视图的大致流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-63BqpEuw-1630299060221)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20210819143308111.png)]

  • 请求处理方法执行完成后,最终返回一个 ModelAndView 对象。对于那些返回 String,View 或 ModeMap 等类型的处理方法,Spring MVC 也会在内部将它们装配成一个 ModelAndView 对象,它包含了逻辑名和模型对象的视图
  • Spring MVC 借助视图解析器(ViewResolver)得到最终的视图对象(View),最终的视图可以是 JSP ,也可能是 Excel、JFreeChart 等各种表现形式的视图

视图

  • 视图的作用是渲染模型数据,将模型里的数据以某种形式呈现给客户。
    为了实现视图模型和具体实现技术的解耦,Spring 在 org.springframework.web.servlet 包中定义了一个高度抽象的 View 接口
  • 视图对象由视图解析器负责实例化。由于视图是无状态的,所以他们不会有线程安全的问题

视图解析器

  • SpringMVC 为逻辑视图名的解析提供了不同的策略,可以在 Spring WEB 上下文中配置一种或多种解析策略,并指定他们之间的先后顺序。每一种映射策略对应一个具体的视图解析器实现类。
  • 视图解析器的作用比较单一:将逻辑视图解析为一个具体的视图对象。
  • 所有的视图解析器都必须实现 ViewResolver 接口

一句话:视图解析器只是为了得到视图对象;视图对象才能真正的转发(将模型数据全部放在请求域中)或者重定向到页面;视图对象才能真正的渲染页面。

常用的视图解析器实现类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xwXYHsJl-1630299060221)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20210819144525043.png)]

八、JSR 303

JSR303是java为Bean数据合法性校验提供的标准框架,它已经包含在javaEE6.0中。

JSR303通过在Bean属性上标注类似于@NotNUll、@Max等标准的注解指定校验规则,并通过标准的验证接口对Bean进行验证。

Hibernate Validator

Hibernate Validator是JSR303的一个参考实现,除支持所有标准的校验注解外,还支持以下扩展注解:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5TU3daNW-1630299060222)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20210826114247670.png)]

@NotNull 和 @NotEmpty 和@NotBlank 区别:

@NotEmpty 用在集合类上面
@NotBlank 用在String上面
@NotNull 用在基本类型上

九、SpringMVC运行流程

  1. 所有请求,前端控制器(dispatcherServlet)收到请求,调用doDispatch进行处理。
  2. 根据HandlerMapping中保存的请求映射信息找到处理当前请求的处理器执行链(包含拦截器)
  3. 根据当前处理器找到它的HandlerAdapter(适配器)
  4. 拦截器的preHandle先执行
  5. 适配器执行目标方法,并返回ModelAndView
    1. ModelAttribute注解标注的方法提前运行
    2. 执行目标方法的时候(确定目标方法用的参数)
      1. 看是否有Model、Map以及其它
      2. 如果是自定义类型
        1. 从隐含模型中看有没有,如果有就从隐含模型中拿
        2. 如果没有,再看是否SessionAttributes标注的属性,如果是从Session中拿,拿不到会抛异常
        3. 都不是,利用反射创建对象
  6. 拦截器的postHandle执行
  7. 处理结果(页面渲染流程)
    1. 如果有异常使用异常解析器处理异常;处理完后还会返回ModelAndView
    2. 调用render进行页面渲染

图解:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zXBOJuxS-1630299060223)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20210827114056513.png)]

十、SpringMVC与Spring整合

整合的目的是为了分工明确。

SpringMVC的配置文件就来配置和网站转发逻辑以及网站功能有关的(视图解析器、文件上传解析器、ajax…)

Spring的配置文件来配置和业务有关的(事务控制,数据源…)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值