【Spring】SpringMVC

1. 什么是 MVC

  • MVC是模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件设计规范。

  • 是将业务逻辑、数据、显示分离的方法来组织代码。

  • MVC主要作用是降低了视图与业务逻辑间的双向偶合

  • MVC不是一种设计模式,MVC是一种架构模式。当然不同的MVC存在差异。

Model(模型):数据模型,提供要展示的数据,因此包含数据和行为,可以认为是领域模型或JavaBean组件(包含数据和行为),不过现在一般都分离开来:Value Object(数据Dao) 和 服务层(行为Service)。也就是模型提供了模型数据查询和模型数据的状态更新等功能,包括数据和业务。

View(视图):负责进行模型的展示,一般就是我们见到的用户界面,客户想看到的东西。

Controller(控制器):接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,由视图负责展示。也就是说控制器做了个调度员的工作。

最典型的MVC就是JSP + servlet + javabean的模式。

在这里插入图片描述

2. 第一个 Spring MVC 程序——配置版

2.1 新建一个 Maven 工程作为父工程,其 pom.xml 如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.ice</groupId>
    <artifactId>spring-mvc</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.2</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/javax.servlet/jstl -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
    </dependencies>
</project>

之后在该项目下新建 module 即可

2.2 新建 module

在这里插入图片描述

扫描二维码关注公众号,回复: 12481001 查看本文章

2.3 添加 web 支持

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2.3 配置 web.xml,注册 DispatcherServlet

<?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">

    <!--1.注册DispatcherServlet-->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--关联一个springmvc的配置文件:【servlet-name】-servlet.xml-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc-servlet.xml</param-value>
        </init-param>
        <!--启动级别-1-->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!--/ 匹配所有的请求;(不包括.jsp)-->
    <!--/* 匹配所有的请求;(包括.jsp)-->
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

2.4 编写 SpringMVC 的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

</beans>

2.5 添加处理映射器

<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>

2. 添加 处理器适配器

<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>

2.7 添加 视图解析器

<!--视图解析器:DispatcherServlet给他的ModelAndView-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="InternalResourceViewResolver">
   <!--前缀-->
   <property name="prefix" value="/WEB-INF/jsp/"/>
   <!--后缀-->
   <property name="suffix" value=".jsp"/>
</bean>

2.8 编写业务 Controller

package com.ice.controller;


import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class HelloController implements Controller {
    
    
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
        //ModelAndView 模型和视图
        ModelAndView mv = new ModelAndView();

        //封装对象,放在ModelAndView中。Model
        mv.addObject("msg", "HelloSpringMVC!");
        //封装要跳转的视图,放在ModelAndView中
        mv.setViewName("hello"); //: /WEB-INF/jsp/hello.jsp
        return mv;
    }
}

2.9 编写 hello.jsp

在 WEB-INF文件夹下新建jsp文件夹,在其中新建 hello.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
${
    
    msg}
</body>
</html>

2.10 注册bean

<!--Handler-->
<bean id="/hello" class="com.kuang.controller.HelloController"/>

2.11 配置 Tomcat 测试

在这里插入图片描述


可能遇到的问题:访问404
  1. 查看控制台输出,看一下是不是缺少了什么jar包。

  2. 如果jar包存在,显示无法输出,就在IDEA的项目发布中,添加lib依赖!

在这里插入图片描述

  1. 重启Tomcat 即可解决!

3. 第一个 Spring MVC 程序——注解版

3.1 新建 module

记住添加 web 支持,同时完善 maven 配置,解决资源过滤问题

<build>
   <resources>
       <resource>
           <directory>src/main/java</directory>
           <includes>
               <include>**/*.properties</include>
               <include>**/*.xml</include>
           </includes>
           <filtering>false</filtering>
       </resource>
       <resource>
           <directory>src/main/resources</directory>
           <includes>
               <include>**/*.properties</include>
               <include>**/*.xml</include>
           </includes>
           <filtering>false</filtering>
       </resource>
   </resources>
</build>

3.2 配置 web.xml

<?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">

   <!--1.注册servlet-->
   <servlet>
       <servlet-name>SpringMVC</servlet-name>
       <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
       <!--通过初始化参数指定SpringMVC配置文件的位置,进行关联-->
       <init-param>
           <param-name>contextConfigLocation</param-name>
           <param-value>classpath:springmvc-servlet.xml</param-value>
       </init-param>
       <!-- 启动顺序,数字越小,启动越早 -->
       <load-on-startup>1</load-on-startup>
   </servlet>

   <!--所有请求都会被springmvc拦截 -->
   <servlet-mapping>
       <servlet-name>SpringMVC</servlet-name>
       <url-pattern>/</url-pattern>
   </servlet-mapping>

</web-app>

/ 和 /* 的区别:

< url-pattern > / </ url-pattern > 不会匹配到 .jsp, 只针对我们编写的请求;即:.jsp 不会进入 spring 的 DispatcherServlet 类 。

< url-pattern > /* </ url-pattern > 会匹配 *.jsp,会出现返回 jsp 视图时再次进入 spring 的 DispatcherServlet 类,导致找不到对应的 controller 所以报 404 404 404 错。

3.3 添加 Spring MVC 配置文件

<?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 http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!-- 自动扫描包,让指定包下的注解生效,由IOC容器统一管理 -->
    <context:component-scan base-package="com.ice.controller"/>
    <!-- 让Spring MVC不处理静态资源 -->
    <mvc:default-servlet-handler />
    <!--
    支持mvc注解驱动
        在spring中一般采用@RequestMapping注解来完成映射关系
        要想使@RequestMapping注解生效
        必须向上下文中注册DefaultAnnotationHandlerMapping
        和一个AnnotationMethodHandlerAdapter实例
        这两个实例分别在类级别和方法级别处理。
        而annotation-driven配置帮助我们自动完成上述两个实例的注入。
     -->
    <mvc:annotation-driven />

    <!-- 视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          id="internalResourceViewResolver">
        <!-- 前缀 -->
        <property name="prefix" value="/WEB-INF/jsp/" />
        <!-- 后缀 -->
        <property name="suffix" value=".jsp" />
    </bean>

</beans>

3.4 创建 Controller

package com.ice.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/HelloController")
public class HelloController {
    
    

    //真实访问地址 : 项目名/HelloController/hello
    @RequestMapping("/hello")
    public String sayHello(Model model){
    
    
        //向模型中添加属性msg与值,可以在JSP页面中取出并渲染
        model.addAttribute("msg","hello,SpringMVC");
        //web-inf/jsp/hello.jsp
        return "hello";
    }
    
}
  • @Controller 是为了让Spring IOC容器初始化时自动扫描到
  • @RequestMapping 是为了映射请求路径,这里因为类与方法上都有映射所以访问时应该是 /HelloController/hello
  • 方法中声明 Model 类型的参数是为了把 Action 中的数据带到视图中
  • 方法返回的结果是视图的名称 hello,加上配置文件中的前后缀变成 WEB-INF/jsp/hello.jsp

3.5 创建视图层

WEB-INF/jsp 目录中创建 hello.jsp , 视图可以直接取出并展示从 Controller 带回的信息

可以通过 EL 表达式取出 Model中存放的值,或者对象

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>SpringMVC</title>
</head>
<body>
${
    
    msg}
</body>
</html>

3.6 配置 Tomcat 运行

在这里插入图片描述

4. Spring MVC

4.1 Spring MVC 的特点

  1. 轻量级,简单易学
  2. 高效 , 基于请求响应的 MVC 框架
  3. 与 Spring 兼容性好,无缝结合
  4. 约定优于配置
  5. 功能强大:RESTful、数据验证、格式化、本地化、主题等
  6. 简洁灵活

4.2 Spring MVC 基本原理

Spring 的 web 框架围绕 DispatcherServlet 设计。DispatcherServlet 的作用是将请求分发到不同的处理器。从Spring 2.5开始,使用Java 5或者以上版本的用户可以采用基于注解的 @Controller 声明方式。

Spring MVC 框架像许多其他 MVC 框架一样, 以请求为驱动 , 围绕一个中心 Servlet 分派请求及提供其他功能DispatcherServlet 实际上就是一个 Servlet (它继承自 HttpServlet 基类)

在这里插入图片描述

SpringMVC的原理如下图所示:

当发起请求时被前置的控制器拦截到请求,根据请求参数生成代理请求,找到请求对应的实际控制器,控制器处理请求,创建数据模型,访问数据库,将模型响应给中心控制器,控制器使用模型与视图渲染视图结果,将结果返回给中心控制器,再将结果返回给请求者。

在这里插入图片描述

4.3 Spring MVC 执行流程

4.3.1 工作原理

在这里插入图片描述

图为 Spring MVC 的一个较完整的流程图,红线部分表示要自己实现的,另外视图解析器也要自己配置。

4.3.2 主要组件

  • 前端控制器(DispatcherServlet)

    接收请求,响应结果,返回可以是 json,String 等数据类型,也可以是页面(Model)

  • 处理器映射器(HandlerMapping)

    Spring mvc 使用 HandlerMapping 来找到并保存 url 请求和处理函数间的 mapping 关系

    DefaultAnnotationHandlerMapping 为例来具体看HandlerMapping 的作用,DefaultAnnotationHandlerMapping 将扫描当前所有已经注册的 spring beans 中的@RequestMapping 注解以找出 url 和 handler method 处理函数的关系并予以关联。

  • 处理器(Handler)

    就是我们常说的 Controller 控制器啦,由程序员编写

  • 处理器适配器(HandlerAdapter)

    可以将处理器包装成适配器,这样一个适配器就可以支持多种类型的处理器,Spring MVC通过HandlerAdapter来实际调用处理函数

    AnnotationMethodHandlerAdapter 为例,DispatcherServlet 中根据 HandlerMapping 找到对应的 handler method 后,首先检查当前工程中注册的所有可用的handlerAdapter,根据 handlerAdapter 中的 supports() 方法找到可以使用的 handlerAdapter。通过调用 handlerAdapter 中的 handle() 方法来处理及准备 handler method 中的参数及 annotation (这就是 spring mvc 如何将 reqeust 中的参数变成 handle method 中的输入参数的地方),最终调用实际的 handle method。

    Spring 为什么要结合使用 HandlerMapping 以及 HandlerAdapter 来处理 Handler?

            符合面向对象中的单一职责原则,代码架构清晰,便于维护,最重要的是代码可复用性高。如 HandlerAdapter 可能会被用于处理多种 Handler。

  • 视图解析器(ViewResovler)

    进行视图解析,返回view对象(常见的有JSP,FreeMark等)

4.3.2 工作原理

  1. 客户端浏览器在发出一个请求之后,先解析主机名的 IP​​ 尝试连接,然后发送 HTTP 请求

  2. web 服务器接收到 HTTP 请求之后,会解析主机,解析上下文,解析资源名,然后根据 sever.xml 找到引擎下的对应主机,找到对应的上下文,找到对应的资源在哪里,再去读对应 webapps 目录下的应用,读取 WEB-INF 下的 web.xml 文件

  3. 这才正真开始!这时候的 request 其实还有点迷茫,因为还差那么一点,主机对应的具体应用找见了,但还没有找到具体的哪个页面。找到了组织,但还要具体找组织中哪个人去处理我问题。读着读着 web.xml,spring 突然跳出来,说:“交给我吧!”

    <!--Spring MVC 的大脑:DispatcherServlet-->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--配置初始化参数,其作用是指定 Spring MVC 配置文件的位置和名称-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc-servlet.xml</param-value>
        </init-param>
        <!--启动级别 1-->
        <load-on-startup>1</load-on-startup>
    </servlet>
    
    <!--/ 匹配所有的请求;(不包括.jsp)-->
    <!--/* 匹配所有的请求;(包括.jsp)-->
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    
    • <load-on-startup>是表示在容器在启动的时候就加载 Servlet 实例。也就是说请求还没来的时候,这个实例已经初始化了。实例缓冲池中有这个,当请求来的时候,直接交给该实例就可以。那么,其实它所做的工作无非就是把项目中的处理类都扫描之后,做一个映射,把具体的处理请求方法的 url 和方法名组成一个 Map。所以,Spring 的配置文件应该是在初始化的时候就被读取了

      这里请求和处理类组成 Map 可以是配置文件配置 Bean,然后处理类继承 Controller 接口重写抽象方法,也可以用 @RequestMapping("/HelloController") 注解形式,将请求和 handler 一一对应。

    • DispatcherServlet 是一个 Servlet,init() 实例化之后,一定会接收请求,它应该也有自己的 service 方法,所对应的业务逻辑类似于流程图,拿到请求中 url,然后 handleMapping 的 Map 集合中映射对应的处理方法,拿到结果之后,DispatcherServlet 就知道执行哪一个 controller。之后,就反射执行这个处理请求的方法,得到 ModelAndView。在之后,调用视图解析器,给 viewName 加个前缀、后缀,返回给DispatcherServlet。之后转发给对应的页面,Model 等价于Request,页面从 Model 中读取相应内容之后,返回客户端进行显示。

    • 像拦截器这种硬编码的内容在 DispatcherServletinit() 方法中就会被读取加载了(DispatcherServlet 有默认的初始化策略函数),然后请求过来时,也会先执行 onFresh() 方法,添加一些动态请求信息,方便后面工作流程中调用。

  4. DispatcherServlet 实例调用它的 doService() 方法,doService() 方法内部调用该实例的 doDispatch() 方法。在 doDispatch() 方法中:

    1. 调用 DispatcherServlet 实例的 getHandler() 方法,其内部调用 HandlerMappinggetHandler() 方法,返回 HandlerExecutionChain 实例

      // DispatcherServlet 实例的 getHandler() 方法
      protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
              
              
          if (this.handlerMappings != null) {
              
              
              for (HandlerMapping mapping : this.handlerMappings) {
              
              
                  HandlerExecutionChain handler = mapping.getHandler(request);  //  HandlerMapping 的 getHandler() 方法
                  if (handler != null) {
              
              
                      return handler;
                  }
              }
          }
          return null;
      }
      

      HandlerMappinggetHandler() 方法不止返回之前匹配的 handler,其内部会加载该请求需要经过的拦截器,一并封装成 HandlerExecutionChain 对象返回。

      protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
               
               
       HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
                                      (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
      
       for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
               
               
           if (interceptor instanceof MappedInterceptor) {
               
               
               MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
               if (mappedInterceptor.matches(request)) {
               
               
                   chain.addInterceptor(mappedInterceptor.getInterceptor());
               }
           }
           else {
               
               
               chain.addInterceptor(interceptor);
           }
       }
       return chain;
      }
      
    2. 调用 DispatcherServlet 实例的 getHandlerAdapter() 方法,遍历已有的 HandlerAdapter 实例,找到支持本次 handler 的 HandlerAdapter 实例并返回

      protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
              
              
          if (this.handlerAdapters != null) {
              
              
              for (HandlerAdapter adapter : this.handlerAdapters) {
              
              
                  if (adapter.supports(handler)) {
              
              
                      return adapter;
                  }
              }
          }
          throw new ServletException("No adapter for handler [" + handler +
                                     "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
      }
      
    3. 执行 HandlerExecutionChain 实例的 applyPreHandle() 方法,其内部调用拦截器的 preHandle() 方法

      boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
              
              
          for (int i = 0; i < this.interceptorList.size(); i++) {
              
              
              HandlerInterceptor interceptor = this.interceptorList.get(i);
              if (!interceptor.preHandle(request, response, this.handler)) {
              
              
                  triggerAfterCompletion(request, response, null);
                  return false;
              }
              this.interceptorIndex = i;
          }
          return true;
      }
      

      拦截器方法返回的是布尔值,True 表示可以继续执行, False 表示需要进行拦截,终止请求。

    4. 执行 HandlerAdapter 实例的 handle() 方法,其内部调用与之匹配的我们自己实现的 Controller 的方法,并返回一个 ModelAndView 对象

      Controller 中对应的方法是真正处理请求的地方。调用业务层方法,业务层会跟持久层交互,持久层和数据库交互。

    5. 执行 HandlerExecutionChain 实例的 applyPostHandle() 方法,其内部调用拦截器的 postHandle() 方法

      void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
          throws Exception {
              
              
      
          for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
              
              
              HandlerInterceptor interceptor = this.interceptorList.get(i);
              interceptor.postHandle(request, response, this.handler, mv);
          }
      }
      
    6. 执行 DispatcherServlet 实例的 processDispatchResult() 方法,进行视图处理:

      1. 调用 DispatcherServlet 实例的 render() 方法

        1. 调用 DispatcherServlet 实例的 resolveViewName() 方法,返回 View 的实例

          protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
                                         Locale locale, HttpServletRequest request) throws Exception {
                      
                      
          
              if (this.viewResolvers != null) {
                      
                      
                  for (ViewResolver viewResolver : this.viewResolvers) {
                      
                      
                      View view = viewResolver.resolveViewName(viewName, locale);
                      if (view != null) {
                      
                      
                          return view;
                      }
                  }
              }
              return null;
          }
          

          此处会调用视图解析器的实例,视图解析器使我们在配置文件中定义过的,功能其实就是拼接视图名,包装一个 View 对象返回

          <!--视图解析器:DispatcherServlet给他的ModelAndView-->
          <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
           <!--前缀-->
           <property name="prefix" value="/WEB-INF/jsp/"/>
           <!--后缀-->
           <property name="suffix" value=".jsp"/>
          </bean>
          
        2. 调用前面返回的 View 实例的 render() 方法,返回给客户端浏览器

      2. 调用 HandlerExecutionChain 实例的 triggerAfterCompletion() 方法,该方法在视图渲染后执行,这个方法的主要是用来处理控制器抛出的异常。

5. RESTful 风格

5.1 概念

RESTful 就是一个资源定位及资源操作的风格。不是标准也不是协议,只是一种风格。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

传统方式操作资源 :通过不同的参数来实现不同的效果!方法单一,post 和 get

  • http://127.0.0.1/item/queryItem.action?id=1 查询,GET

  • http://127.0.0.1/item/saveItem.action 新增,POST

  • http://127.0.0.1/item/updateItem.action 更新,POST

  • http://127.0.0.1/item/deleteItem.action?id=1 删除,GET或POST

使用 RESTful 操作资源 :可以通过不同的请求方式来实现不同的效果!如下:请求地址一样,但是功能可以不同!

  • http://127.0.0.1/item/1 查询,GET
  • http://127.0.0.1/item 新增,POST
  • http://127.0.0.1/item 更新,PUT
  • http://127.0.0.1/item/1 删除,DELETE

5.2 RESTful 架构风格的特点

5.2.1 资源

所谓"资源",就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实在。资源总要通过某种载体反应其内容,文本可以用 txt 格式表现,也可以用 HTML 格式、XML 格式表现,甚至可以采用二进制格式;图片可以用 JPG 格式表现,也可以用 PNG 格式表现;JSON 是现在最常用的资源表示格式。

资源是以 json(或其他 Representation )为载体的、面向用户的一组数据集,资源对信息的表达倾向于概念模型中的数据:

  • 资源总是以某种 Representation 为载体显示的,即序列化的信息
  • 常用的 Representation 是 json(推荐)或者 xml(不推荐)等
  • Represntation 是 REST 架构的表现层

5.2.2 统一接口

RESTful 架构风格规定,数据的元操作,即 CRUD(Create,Retrieve,Update 和 Delete,即数据的增删查改)操作,分别对应于 HTTP 方法:GET 用来获取资源,POST 用来新建资源(也可以用于更新资源),PUT 用来更新资源,DELETE 用来删除资源,这样就统一了数据操作的接口,仅通过 HTTP 方法,就可以完成对数据的所有增删查改工作。即:

  • GET(SELECT):从服务器取出资源(一项或多项)。
  • POST(CREATE):在服务器新建一个资源。
  • PUT(UPDATE):在服务器更新资源(客户端提供完整资源数据)。
  • PATCH(UPDATE):在服务器更新资源(客户端提供需要修改的资源数据)。
  • DELETE(DELETE):从服务器删除资源。

5.2.3 URI

可以用一个 URI(统一资源定位符)指向资源,即每个 URI 都对应一个特定的资源。要获取这个资源,访问它的 URI 就可以,因此 URI 就成了每一个资源的地址或识别符。

一般的,每个资源至少有一个 URI 与之对应,最典型的 URI 即 URL。

5.2.4 无状态

所谓无状态的,即所有的资源,都可以通过 URI 定位,而且这个定位与其他资源无关,也不会因为其他资源的变化而改变。有状态和无状态的区别,举个简单的例子说明一下。如查询员工的工资,如果查询工资是需要登录系统,进入查询工资的页面,执行相关操作后,获取工资的多少,则这种情况是有状态的,因为查询工资的每一步操作都依赖于前一步操作,只要前置操作不成功,后续操作就无法执行;如果输入一个 url 即可得到指定员工的工资,则这种情况是无状态的,因为获取工资不依赖于其他资源或状态,且这种情况下,员工工资是一个资源,由一个 url 与之对应,可以通过 HTTP 中的 GET 方法得到资源,这是典型的 RESTful 风格。

5.2.5 ROA、SOA、REST 与 RPC

ROA 即 Resource Oriented Architecture,RESTful 架构风格的服务是围绕资源展开的,是典型的 ROA 架构(虽然“A”和“架构”存在重复,但说无妨),虽然 ROA 与 SOA 并不冲突,甚至把 ROA 看做 SOA 的一种也未尝不可,但由于 RPC 也是 SOA,比较久远一点点论文、博客或图书也常把 SOA 与 RPC 混在一起讨论,因此,RESTful 架构风格的服务通常被称之为 ROA 架构,很少提及 SOA 架构,以便更加显式的与 RPC 区分。

RPC 风格曾是 Web Service 的主流,最初是基于 XML-RPC 协议(一个远程过程调用(remote procedure call,RPC)的分布式计算协议),后来渐渐被 SOAP 协议(简单对象访问协议(Simple Object Access Protocol))取代;RPC 风格的服务,不仅可以用 HTTP,还可以用 TCP 或其他通信协议。但 RPC 风格的服务,受开发服务采用语言的束缚比较大,如 .NET 框架中,开发 web service 的传统方式是使用 WCF,基于 WCF 开发的服务即 RPC 风格的服务,使用该服务的客户端通常要用 C# 来实现,如果使用 python 或其他语言,很难实现可以直接与服务通信客户端;进入移动互联网时代后,RPC 风格的服务很难在移动终端使用,而 RESTful 风格的服务,由于可以直接以 json 或 xml 为载体承载数据,以 HTTP 方法为统一接口完成数据操作,客户端的开发不依赖于服务实现的技术,移动终端也可以轻松使用服务,这也加剧了 REST 取代 RPC 成为 web service 的主导。

5.3 认证机制

由于 RESTful 风格的服务是无状态的,认证机制尤为重要。例如上文提到的员工工资,这应该是一个隐私资源,只有员工本人或其他少数有权限的人有资格看到,如果不通过权限认证机制对资源做一层限制,那么所有资源都以公开方式暴露出来,这是不合理的,也是很危险的。

认证机制解决的问题是,确定访问资源的用户是谁;权限机制解决的问题是,确定用户是否被许可使用、修改、删除或创建资源。权限机制通常与服务的业务逻辑绑定,因此权限机制需要在每个系统内部定制,而认证机制基本上是通用的,常用的认证机制包括 session auth(即通过用户名密码登录),basic authtoken authOAuth,服务开发中常用的认证机制为后三者。

OAuth(开放授权)是一个开放的授权标准,允许用户让第三方应用访问该用户在某一web服务上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。

OAuth 允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的第三方系统(例如,视频编辑网站)在特定的时段(例如,接下来的 2 小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样,OAuth 让用户可以授权第三方网站访问他们存储在另外服务提供者的某些特定信息,而非所有内容。

正是由于 OAuth 的严谨性和安全性,现在 OAuth 已成为 RESTful 架构风格中最常用的认证机制,和 RESTful 架构风格一起,成为企业级服务的标配。

5.4 简单 RESTFul 请求

  1. 在新建一个类

    package com.ice.controller;
    
    import org.springframework.stereotype.Controller;
    
    @Controller
    public class RestFulController {
          
          
    }
    
  2. 在Spring MVC 中可以使用 @PathVariable 注解,让方法参数的值对应绑定到一个 URI 模板变量上

    package com.ice.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    public class RestFulController {
          
          
    
        @RequestMapping("/commit/{p1}/{p2}")
        public String index(@PathVariable int p1, @PathVariable int p2, Model model) {
          
          
            int result = p1+p2;
            //Spring MVC会自动实例化一个Model对象用于向视图中传值
            model.addAttribute("msg", "结果:"+result);
            //返回视图位置
            return "hello";
        }
        
    }
    
  3. 结果

在这里插入图片描述

  1. 使用路径变量的好处

    • 使路径变得更加简洁;

    • 获得参数更加方便,框架会自动进行类型转换。

    • 通过路径变量的类型可以约束访问参数,如果类型不一样,则访问不到对应的请求方法,如这里访问是的路径是 /commit/1/a,则路径与方法不匹配,而不会是参数转换失败。

      在这里插入图片描述

5.5 使用method属性指定请求类型

用于约束请求的类型,可以收窄请求范围。指定请求谓词的类型如GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE等

  1. 增加一个方法

    @RequestMapping(value = "/hello", method = RequestMethod.POST)
    public String index2(Model model) {
          
          
        model.addAttribute("msg", "hello!");
        return "hello";
    }
    
  2. 我们使用浏览器地址栏进行访问默认是Get请求,会报错405:

在这里插入图片描述

  1. 如果将POST修改为GET则正常了

    @RequestMapping(value="/hello", method = RequestMethod.GET)
    public String index2(Model model) {
          
          
        model.addAttribute("msg", "hello!");
        return "hello";
    }
    

    在这里插入图片描述

所有的地址栏请求默认都会是 HTTP GET 类型的。

方法级别的注解变体有如下几个:组合注解

@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping

@GetMapping 是一个组合注解,平时使用的会比较多!

它所扮演的是 @RequestMapping(method =RequestMethod.GET) 的一个快捷方式。

6. Spring MVC 转发与重定向

@Controller
public class ResultSpringMVC2 {
    
    
   @RequestMapping("/rsm2/t1")
   public String test1(){
    
    
       //转发
       return "test";
  }

   @RequestMapping("/rsm2/t2")
   public String test2(){
    
    
       //重定向
       return "redirect:/index.jsp";
       //return "redirect:hello.do"; //hello.do为另一个请求/
  }
}

7. 数据处理

7.1 处理提交数据

7.1.1 提交的域名称和处理方法的参数名一致

提交数据:http://localhost:8080/test?name=ice

处理方法:

@RequestMapping("/test")
public String test(String name, Model model) {
    
    
    //Spring MVC会自动实例化一个Model对象用于向视图中传值
    model.addAttribute("msg", name);
    //返回视图位置
    return "hello";
}

7.1.2 提交的域名称和处理方法的参数名不一致

提交数据:http://localhost:8080/test?username=ice

处理方法:

@RequestMapping("/test")
public String test(@RequestParam("username") String name, Model model) {
    
    
    //Spring MVC会自动实例化一个Model对象用于向视图中传值
    model.addAttribute("msg", name);
    //返回视图位置
    return "hello";
}

7.1.3 提交的是一个对象

要求提交的表单域和对象的属性名一致 , 参数使用对象即可

  • 实体类

    package com.ice.pojo;
    
    import java.util.StringJoiner;
    
    
    public class User {
          
          
        private int id;
        private String name;
        private int age;
    
        public User() {
          
          
        }
    
        public User(int id, String name, int age) {
          
          
            this.id = id;
            this.name = name;
            this.age = age;
        }
    
        public int getId() {
          
          
            return id;
        }
    
        public void setId(int id) {
          
          
            this.id = id;
        }
    
        public String getName() {
          
          
            return name;
        }
    
        public void setName(String name) {
          
          
            this.name = name;
        }
    
        public int getAge() {
          
          
            return age;
        }
    
        public void setAge(int age) {
          
          
            this.age = age;
        }
    
        @Override
        public String toString() {
          
          
            return new StringJoiner(", ", User.class.getSimpleName() + "[", "]")
                    .add("id=" + id)
                    .add("name='" + name + "'")
                    .add("age=" + age)
                    .toString();
        }
    }
    
  • 提交数据:http://localhost:8080/test?name=ice&id=1&age=18

  • 处理方法

    @RequestMapping("/test")
    public String test(User user, Model model) {
          
          
        //Spring MVC会自动实例化一个Model对象用于向视图中传值
        model.addAttribute("msg", user);
        //返回视图位置
        return "hello";
    }
    

说明:如果使用对象的话,前端传递的参数名和对象名必须一致,否则就是null。

7.2 数据显示到前端

第一种 : 通过ModelAndView

我们前面一直都是如此,就不过多解释

public class ControllerTest1 implements Controller {
    
    

   public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
    
    
       //返回一个模型视图对象
       ModelAndView mv = new ModelAndView();
       mv.addObject("msg","ControllerTest1");
       mv.setViewName("test");
       return mv;
  }
}

第二种 : 通过ModelMap

@RequestMapping("/hello")
public String hello(@RequestParam("username") String name, ModelMap model){
    
    
   //封装要显示到视图中的数据
   //相当于req.setAttribute("name",name);
   model.addAttribute("name",name);
   System.out.println(name);
   return "hello";
}

第三种 : 通过Model

@RequestMapping("/ct2/hello")
public String hello(@RequestParam("username") String name, Model model){
    
    
   //封装要显示到视图中的数据
   //相当于req.setAttribute("name",name);
   model.addAttribute("msg",name);
   System.out.println(name);
   return "test";
}

Model 只有寥寥几个方法只适合用于储存数据,简化了新手对于Model对象的操作和理解;

ModelMap 继承了 LinkedMap ,除了实现了自身的一些方法,同样的继承 LinkedMap 的方法和特性;

ModelAndView 可以在储存数据的同时,可以进行设置返回的逻辑视图,进行控制展示层的跳转。

8. 乱码问题

不得不说,乱码问题是在我们开发中十分常见的问题,也是让我们程序猿比较头大的问题!

以前乱码问题通过过滤器解决 , 而SpringMVC给我们提供了一个过滤器 , 可以在web.xml中配置,修改了xml文件需要重启服务器!

<filter>
    <filter-name>encoding</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>
</filter>
<filter-mapping>
    <filter-name>encoding</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

但是我们发现 , 有些极端情况下,这个过滤器对get的支持不好。

处理方法 :

  1. 修改tomcat配置文件 :设置编码!

    <Connector URIEncoding="utf-8" port="8080" protocol="HTTP/1.1"
              connectionTimeout="20000"
              redirectPort="8443" />
    
  2. 自定义过滤器

    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletRequestWrapper;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.UnsupportedEncodingException;
    import java.util.Map;
    
    /**
    * 解决get和post请求 全部乱码的过滤器
    */
    public class GenericEncodingFilter implements Filter {
          
          
    
       @Override
       public void destroy() {
          
          
      }
    
       @Override
       public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
          
          
           //处理response的字符编码
           HttpServletResponse myResponse=(HttpServletResponse) response;
           myResponse.setContentType("text/html;charset=UTF-8");
    
           // 转型为与协议相关对象
           HttpServletRequest httpServletRequest = (HttpServletRequest) request;
           // 对request包装增强
           HttpServletRequest myrequest = new MyRequest(httpServletRequest);
           chain.doFilter(myrequest, response);
      }
    
       @Override
       public void init(FilterConfig filterConfig) throws ServletException {
          
          
      }
    
    }
    
    //自定义request对象,HttpServletRequest的包装类
    class MyRequest extends HttpServletRequestWrapper {
          
          
    
       private HttpServletRequest request;
       //是否编码的标记
       private boolean hasEncode;
       //定义一个可以传入HttpServletRequest对象的构造函数,以便对其进行装饰
       public MyRequest(HttpServletRequest request) {
          
          
           super(request);// super必须写
           this.request = request;
      }
    
       // 对需要增强方法 进行覆盖
       @Override
       public Map getParameterMap() {
          
          
           // 先获得请求方式
           String method = request.getMethod();
           if (method.equalsIgnoreCase("post")) {
          
          
               // post请求
               try {
          
          
                   // 处理post乱码
                   request.setCharacterEncoding("utf-8");
                   return request.getParameterMap();
              } catch (UnsupportedEncodingException e) {
          
          
                   e.printStackTrace();
              }
          } else if (method.equalsIgnoreCase("get")) {
          
          
               // get请求
               Map<String, String[]> parameterMap = request.getParameterMap();
               if (!hasEncode) {
          
           // 确保get手动编码逻辑只运行一次
                   for (String parameterName : parameterMap.keySet()) {
          
          
                       String[] values = parameterMap.get(parameterName);
                       if (values != null) {
          
          
                           for (int i = 0; i < values.length; i++) {
          
          
                               try {
          
          
                                   // 处理get乱码
                                   values[i] = new String(values[i]
                                          .getBytes("ISO-8859-1"), "utf-8");
                              } catch (UnsupportedEncodingException e) {
          
          
                                   e.printStackTrace();
                              }
                          }
                      }
                  }
                   hasEncode = true;
              }
               return parameterMap;
          }
           return super.getParameterMap();
      }
    
       //取一个值
       @Override
       public String getParameter(String name) {
          
          
           Map<String, String[]> parameterMap = getParameterMap();
           String[] values = parameterMap.get(name);
           if (values == null) {
          
          
               return null;
          }
           return values[0]; // 取回参数的第一个值
      }
    
       //取所有值
       @Override
       public String[] getParameterValues(String name) {
          
          
           Map<String, String[]> parameterMap = getParameterMap();
           String[] values = parameterMap.get(name);
           return values;
      }
    }
    

    这个也是在网上找的一些大神写的,一般情况下,SpringMVC默认的乱码处理就已经能够很好的解决了!

    然后在web.xml中配置这个过滤器即可!

    乱码问题,需要平时多注意,在尽可能能设置编码的地方,都设置为统一编码 UTF-8!

9. 拦截器

9.1 概述

Spring MVC 的处理器拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。开发者可以自己定义一些拦截器来实现特定的功能。

过滤器与拦截器的区别:拦截器是AOP思想的具体应用。

过滤器

  • servlet 规范中的一部分,任何 java web工程都可以使用
  • url-pattern 中配置了 /* 之后,可以对所有要访问的资源进行拦截

拦截器

  • 拦截器是 SpringMVC 框架自己的,只有使用了 Spring MVC 框架的工程才能使用
  • 拦截器只会拦截访问的控制器方法, 如果访问的是 jsp/html/css/image/js 是不会进行拦截的

9.2 自定义拦截器

想要自定义拦截器,必须实现 HandlerInterceptor 接口。

  1. 新建一个 project, 添加web支持

  2. 配置 web.xml 和 applicationContext.xml 文件

  3. 编写一个拦截器

    package com.ice.interceptor;
    
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    public class MyInterceptor implements HandlerInterceptor {
          
          
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
          
          
            System.out.println("====处理前===");
            return true;
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
          
          
            System.out.println("====处理后===");
        }
    }
    
  4. 在 spring mvc 的配置文件中配置拦截器

    <?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 http://www.springframework.org/schema/context/spring-context.xsd
                               http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
        <!-- 自动扫描包,让指定包下的注解生效,由IOC容器统一管理 -->
        <context:component-scan base-package="com.ice.controller"/>
        <!-- 让Spring MVC不处理静态资源 -->
        <mvc:default-servlet-handler/>
        <!-- 支持mvc注解驱动 -->
        <mvc:annotation-driven/>
    
        <mvc:interceptors>
            <mvc:interceptor>
                <mvc:mapping path="/**"/>
                <bean class="com.ice.interceptor.MyInterceptor"/>
            </mvc:interceptor>
        </mvc:interceptors>
    
        <!-- 视图解析器 -->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
              id="internalResourceViewResolver">
            <!-- 前缀 -->
            <property name="prefix" value="/WEB-INF/jsp/"/>
            <!-- 后缀 -->
            <property name="suffix" value=".jsp"/>
        </bean>
    
    </beans>
    
  5. 编写一个Controller,接收请求

    package com.ice.controller;
    
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class TestController {
          
          
    
        @GetMapping("/test")
        public String test() {
          
          
            System.out.println("TestController ==》 test() 执行了");
            return "OK";
        }
    }
    
  6. 启动 Tomcat 测试

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/dreaming_coder/article/details/113699510
今日推荐