核心类、常用注解与RESTful

DispatcherServlet

DispatcherServlet全称org.springframework.web.servlet.DispatcherServlet。DispatcherServlet并不直接处理请求,它只负责根据请求的信息把请求转发给合适的处理器,然后由处理器来执行实际的处理过程并生成响应,它是Spring MVC框架的入口点,它将所有这些步骤组合在一起,使得开发者可以更轻松地构建Web应用程序并处理客户端请求。每个Spring MVC应用程序通常只有一个。

我们可以在web.xml里进行配置:

<servlet>
    <!-- 配置前端过滤器 -->
    <servlet-name>springmvc</servlet-name>
    <servlet-class>
      org.springframework.web.servlet.DispatcherServlet
     </servlet-class>
    <!-- 初始化时加载配置文件 -->
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:springCtx.xml</param-value>
    </init-param>
    <!-- 表示容器在启动时立即加载Servlet -->
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>/springmvc/*</url-pattern>
</servlet-mapping>

上面例子中所有以/springmvc开头的请求都会被名称为springmvc的DispatcherServlet处理。

另外,在上述代码中,有两个可选项<load-on-startup><init-param>

<load-on-startup>如果元素的值为1,则启动程序的时候会立刻加载Servlet;如果元素不存在,则在第一个请求时加载。

<init-param>存在并配置了路径,则会根据路径寻找配置文件,否则将导WEB-INF下寻找servletName-servlet.xml命名形式的文件。

ViewResolver

ViewResolver即视图解析器,我们可以在springCtx.xml文件中配置它。

回顾之前的springCtx.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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 配置处理器Handle,映射“/hello”请求 -->
    <bean name="/hello" 
            class="top.cairbin.test7.controller.TestController" />
    <!-- 处理器映射器,将处理器Handle的name作为url进行查找 -->
    <bean class=
    "org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" />
    <!-- 处理器适配器,配置对处理器中handleRequest()方法的调用-->
    <bean class=
    "org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" />
    <!-- 视图解析器 -->
    <bean class=
    "org.springframework.web.servlet.view.InternalResourceViewResolver">
    </bean>
</beans> 

再来看看之前的controller

package top.cairbin.test7.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

public class TestController implements Controller{
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)  {
         // 创建ModelAndView对象
        ModelAndView mav = new ModelAndView();
         // 向模型对象中添加数据
        mav.addObject("message", "Hello Spring");
         // 设置逻辑视图名
        mav.setViewName("/WEB-INF/views/hello.jsp");
         // 返回ModelAndView对象
        return mav;
    }
}

我们在控制器中每次都要指定jsp格式文件和/WEB-INF/views/路径是一件很头疼的事,所以可以修改视图解析器的配置来帮助我们操作。

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
     <!-- 设置前缀 -->
    <property name="prefix" value="/WEB-INF/views/" />
    <!-- 设置后缀 -->
    <property name="suffix" value=".jsp" />
</bean>

@Controller、@RequestParam与映射相关的注解

@Controller

在程序中我们可以使用@Controller来声明这是一个控制器用于依赖注入,而无需去实现Controller接口。

@RequestMapping

@RequestMapping注解可以很方便地让我们设置类或者方法对应的URL路径,只需要将它们写在类或者方法上面即可

例如

[...]

@Controller
@RequestMapping("/myController")
class MyController{
    [...]
}
[...]
@Controller
@RequestMapping("/myController")
class MyController{
    [...]

    @RequestMapping("/myFunc1")
    public MyType MyFunc1([...]){
        [...]
    }

    //还可以指定路径和请求类型(例如GET)
    @RequestMapping(path="/myFunc2", method=RequestMethod.GET)
    public MyType MyFunc2([...]){
        [...]
    }

    //还有URI模式,路径可随参数变化。
    //比如请求某些文章,都有各自ID,它们对应的方法都是这个,路径仅有id不一样,返回对应内容,就可以这样写,此时这里的param对应文章id。
    //还可以指定路径和请求类型(例如GET)
    @RequestMapping(path="/{param}}", method=RequestMethod.GET)
    public MyType MyFunc3(@PathVariable ParamType param, [...]){
        doForParam(param);
        [...]
    }
}

需要注意@RequestMapping有两种属性可以指定映射路径,分别是valuepath

value被指定会覆盖默认值,path也一样,二者也都能匹配占位符。实际上path就是value的别名,没有区别。

另外使用占位符的URL路径需要对参数添加注解@PathVariable

组合注解

为了更方便地指定Http请求方法类型,Spring也为我们定义了一些组合注解。

  • @GetMapping匹配Get请求
  • @PostMapping匹配Post请求
  • @PutMapping匹配Put请求
  • @DeleteMapping匹配Delete请求
  • @PatchMapping匹配Patch请求

至于这些请求是干什么用的,分别对应什么场景,因为并不属于主要内容,此处不过多描述,请查阅HTTP协议文档。(尽管如此,你必须对这些方法及HTTP语义有一定了解)

@RequestParam

@RequestParam注解为请求指定参数,用法如下。

这是Controller里的一个方法

@RequestMapping("/hello")
public ModelAndView showMessage(@RequestParam(value = "name", required = false, defaultValue = "Spring") String name) {

    ModelAndView mv = new ModelAndView("hello");//指定视图
     
    mv.addObject("message", message);
    mv.addObject("name", name);
    return mv;
}

实践

test7一样,创建webapp类型的maven项目top.cairbin.test8。(如果不会请返回上一节)

pom.xml里引入依赖文件。

<!-- 添加servlet依赖 -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <scope>provided</scope>
    <version>3.1.0</version>
</dependency>

<!-- 添加spring依赖 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>5.3.23</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.3.23</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.23</version>
</dependency>

web.xml里进行配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns="http://java.sun.com/xml/ns/javaee"
          xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
          http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    id="WebApp_ID" version="3.0">
    <servlet>
        <!-- 配置前端过滤器 -->
        <servlet-name>springmvc</servlet-name>
        <servlet-class>
              org.springframework.web.servlet.DispatcherServlet
         </servlet-class>
        <!-- 初始化时加载配置文件 -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springCtx.xml</param-value>
        </init-param>
        <!-- 表示容器在启动时立即加载Servlet -->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern> 
    </servlet-mapping>
</web-app>

src/main/resources里创建springCtx.xml配置文件

这里与test7就不一样了,我们指定jsp文件的前后缀,并利用自动扫描来帮助我们完成控制器以及其他类的注入。

<?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 
  http://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="top.cairbin.test8" />
    <bean id="viewResolver" class=
    "org.springframework.web.servlet.view.InternalResourceViewResolver">
         <!-- 设置前缀 -->
         <property name="prefix" value="/WEB-INF/views/" />
         <!-- 设置后缀 -->
         <property name="suffix" value=".jsp" />
    </bean>    
</beans>

请注意自动扫描的路径与你项目是否一致。另外,因为分层设计,我们在之后肯定不会仅在controller层进行依赖注入,在其他层也会,我们创建的包名都是top.cairbin.test8.xxx,也就是说都是top.cairbin.test8的子包,所以这里指定top.cairbin.test8进行扫描即可。

在WEB-INF文件夹下创建views文件夹,并在views下创建hello.jsp文件

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<%@ page isELIgnored="false"%>
<!DOCTYPE html>
<html>

<head>
    <title>hello</title>
</head>

<body>
    <h1>Hello, ${name}!</h1>
</body>

</html>

接下来创建top.cairbin.test8.controller包,包下创建类MyController,并将控制器映射到/hello路径下,将sayHello方法映射到/hello/sayHello/{name}路径,其中{name}是一个占位符,随用户提供的参数而定,让hello.jsp模板渲染,并显示hello,后面跟这个参数的值。

package top.cairbin.test8.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

@Controller
@RequestMapping("/hello")
public class MyController {
    
    @GetMapping(path="/sayHello/{name}")
    public ModelAndView sayHello(
            @PathVariable String name) throws Exception {
        //指定视图
        ModelAndView mv = new ModelAndView("hello");
        //向视图中添加所要展示或使用的内容,将在页面中使用
        mv.addObject("name", name);
        return mv;
    }    
}

我们来运行一下,右键项目->Run as...->Run on server

选择你的Tomcat服务器并运行,这里有问题同样返回上一节。

浏览器访问http://localhost:8080/test8/hello/sayHello/CairBin

我们来看看在URL里不用占位符的方式传参是怎么样的。
MyController里添加一个方法

@GetMapping(path="/sayHello2")
public ModelAndView sayHello2(
        String name) throws Exception {
    //指定视图
    ModelAndView mv = new ModelAndView("hello");
    //向视图中添加所要展示或使用的内容,将在页面中使用
    mv.addObject("name", name);
    return mv;
}

在浏览器访问http://localhost:8080/test8/hello/sayHello2

我们发现并没有任何数据。

换这个URL试试http://localhost:8080/test8/hello/sayHello2?name=cairbin

amazing,竟然有了。实际上这对应两种不同的风格。
你可能迷茫了,我到底以什么方式传递参数,接下来将具体说明。

RESTful与RPC风格

请容许我从网上找一张图片

学过网络的都知道,TCP/IP模型的应用层实际上对应OSI的应用层、表示层和会话层。

对于我们WEB使用的HTTP协议来讲,它是应用层协议。

但是在上古年代,人们还在为网络世界里不同机器之间的通信方式而头疼,而最基础的方式是通过socket编程来实现的,不过这个难度有些大,人们迫切需要一个对新手友好且简单的方式。

于是,1984年,有位大佬叫 Bruce Jay Nelson,发表了一篇论文叫做 Implementing Remote Procedure Calls,定义了一种标准叫RPC。

RPC希望客户端可以像调用本地接口一样来调用服务端,RPC模式分为三层,RPCRuntime 负责最底层的网络传输,Stub 处理客户端和服务端约定好的语法、语义的封装和解封装,这些调用远程的细节都被这两层搞定了,用户和服务器这层就只要负责处理业务逻辑,调用本地 Stub 就可以调用远程。

也就是说RPC可以基于TCP/UDP传输层协议传输,也可以使用它们的上层协议HTTP。也正如此HTTP更关注服务提供方,对于客户端怎么调用是不关心的;而对于RPC还要满足更多的东西,也就是需要客户端接口和服务端保持一致(客户端可以像调用本地接口一样来调用服务端)。

这种API接口的设计风格就被称为RPC风格。

而对于REST,中文描述表述性状态传递,实际上主要关注资源定位,REST是把服务端方法写好,客户端不想知道方法,只想获取资源,这与HTTP语义天然一致。

所以从设计上看,RPC是面向方法的,REST是面向资源的。

总的来说,RPC通常是服务器和服务器之间的通信,比如和中间件的通信,MQ、分布式缓存、分布式数据库等等。而REST通常是面向客户端的(一般是浏览器)。使用场景不一样。

因此你在设计Web的时候两种接口风格实际上都会用到,而对于浏览器来讲,我们为方便实现客户端与服务器的缓存等功能,还是尽量用RESTful风格比较好。

但是RESTful,它毕竟只是一种风格,或者说是一种建议,即使你不按照它来设计或者说“接口不够RESTful”也符合规范,但是我们为了尽量靠拢HTTP语义,还是尽量去这样做。

接下来我们对比两种风格差异,分别以CRUD为例。

RPC

http://127.0.0.1/article/query?id=1  查询文章(GET)

http://127.0.0.1/article/add         新增文章(POST)

http://127.0.0.1/article/update      更新文章(POST)

http://127.0.0.1/article/delete?id=1 删除文章(GET或POST)

RESTful

http://127.0.0.1/article/1  查询文章(GET)

http://127.0.0.1/article    新增文章(POST)

http://127.0.0.1/article    更新文章(PUT)

http://127.0.0.1/article/1  删除文章(DELETE)
最后修改:2024 年 04 月 01 日
如果觉得我的文章对你有用,请随意赞赏