Spring Boot Web 开发@Controller @RestController 使用教程

在 Spring Boot 中,@Controller 注解是专门用于处理 Http 请求处理的,是以 MVC 为核心的设计思想的控制层。@RestController 则是 @Controller 的衍生注解。

本项目源码下载

1 Spring Boot Controller

1.1 原理

Spring Boot 本身就 Spring MVC 的简化版本。是在 Spring MVC 的基础上实现了自动配置,简化了开发人员开发过程。

Spring MVC 是通过一个叫 DispatcherServlet 前端控制器的来拦截请求的。而在 Spring Boot 中 使用自动配置把 DispatcherServlet 前端控制器自动配置到框架中。

例如,我们来解析 /users 这个请求

/users请求流程

  1. DispatcherServlet 前端控制器拦截请求 /users
  2. servlet 决定使用哪个 handler 处理
  3. Spring 检测哪个控制器匹配 /users,Spring 从 @RquestMapping 中查找出需要的信息
  4. Spring 找到正确的 Controller 方法后,开始执行 Controller 方法
  5. 返回 users 对象列表
  6. 根据与客户端交互需要返回 Json 或者 Xml 格式

1.2 相关注解

在 Spring Boot 中使用到 @Controller 及相关的注解如下,主要分为三个层面进行,请求前,处理中,返回。

应用场景 注解 注解说明
处理请求 @Controller 处理 Http 请求
处理请求 @RestController @Controller 的衍生注解
路由请求 @RequestMapping 路由请求 可以设置各种操作方法
路由请求 @GetMapping GET 方法的路由
路由请求 @PostMapping POST 方法的路由
路由请求 @PutMapping PUT 方法的路由
路由请求 @DeleteMapping DELETE 方法的路由
请求参数 @PathVariable 处理请求 url 路径中的参数 /user/
请求参数 @RequestParam 处理问号后面的参数
请求参数 @RequestBody 请求参数以 json 格式提交
返回参数 @ResponseBody 返回 json 格式

注意以上注解需要强调的是 @RestController 是 @Controller 的子集。

@GetMapping、@PostMapping、@PutMapping、@DeleteMapping 是 @RequestMapping 的子集。所以实际上我们只需要掌握 @Controller 和 @RequestMapping 就可以了。

1.3 @Controller 与 @RestController 区别

@Controller 包括了 @RestController。@RestController 是 Spring4 后新加的注解,从 RestController 类源码可以看出 @RestController 是 @Controller 和 @ResponseBody 两个注解的结合体。

@Controller=@RestController+@ResponseBody
如下 @RestController 的源码可以看出他们的关系

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
    @AliasFor(annotation = Controller.class)
    String value() default "";
}

1.4 @Controller 与 @RestController 应用场景

  • @Controller 一般应用在有返回界面的应用场景下.

    例如,管理后台使用了 thymeleaf 作为模板开发,需要从后台直接返回 Model 对象到前台,那么这时候就需要使用 @Controller 来注解。

  • @RestController 如果只是接口,那么就用 RestController 来注解.

    例如前端页面全部使用了 Html、Jquery 来开发,通过 Ajax 请求服务端接口,那么接口就使用 @RestController 统一注解。

1.5 @RequestMapping 说明

首先我们来看看 @RequestMapping 的源码, 我在上面加了注释

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
    String name() default "";
<span class="hljs-comment">//指定请求的实际地址</span>
<span class="hljs-meta">@AliasFor("path")</span>
String[] value() <span class="hljs-keyword">default</span> {};
<span class="hljs-meta">@AliasFor("value")</span>
String[] path() <span class="hljs-keyword">default</span> {};
<span class="hljs-comment">//指定请求的method类型, GET、POST、PUT、DELETE等</span>
RequestMethod[] method() <span class="hljs-keyword">default</span> {};
<span class="hljs-comment">//指定request中必须包含某些参数值是,才让该方法处理。</span>
String[] params() <span class="hljs-keyword">default</span> {};
<span class="hljs-comment">//指定request中必须包含某些指定的header值,才能让该方法处理请求。</span>
String[] headers() <span class="hljs-keyword">default</span> {};
<span class="hljs-comment">//指定处理请求的提交内容类型(Content-Type),例如application/json, text/html;</span>
String[] consumes() <span class="hljs-keyword">default</span> {};
<span class="hljs-comment">//指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回;</span>
String[] produces() <span class="hljs-keyword">default</span> {};

}

示例说明:

示例 说明
@RequestMapping("/index") 默认为 GET 方法的路由 /index
@RequestMapping(value="/index",method = RequestMethod.GET) 同上面一条
@RequestMapping(value="/add",method = RequestMethod.POST) 路由为 /add 的 POST 请求
@RequestMapping(value="/add",method = RequestMethod.POST),consumes="application/json" 路由为 /add 的 POST 请求,但仅仅处理 application/json 的请求
@RequestMapping(value="/add",method = RequestMethod.POST),produces="application/json" 路由为 /add 的 POST 请求,强调返回为 JSON 格式
@RequestMapping(value="/add",method = RequestMethod.POST),params="myParam=xyz" 路由为 /add 的 POST 请求,但仅仅处理头部包括 myParam=xyz 的请求
@RequestMapping(value="/add",method = RequestMethod.POST),headers="Referer=http://www.xyz.com/" 路由为 /add 的 POST 请求,但仅仅处理 来源为 www.xyz.com 的请求

2 @Controller 和 @RestController 示例

本章节,将对两个注解配合其他注解编写一系列示例,为了演示 @Controller 返回对应页面功能,我们在示例中引入了 thymeleaf 模板。具体在 pom.xml 中有说明。

编号 路由 Http 方法 方法说明
1 /user/index GET 获取用户列表并返回列表页面
1 /user/add GET 用户新增页面
1 /user/save POST 新增用户的 api
1 /user/edit GET 用户编辑的页面
1 /user/update POST 编辑用户的 api
1 /user/del GET 删除用户页面
1 /user/deleted POST 删除用户页面的 api

2.1 新建 Spring Boot 项目

  1. File > New > Project,如下图选择 Spring Initializr 然后点击 【Next】下一步
  2. 填写 GroupId(包名)、Artifact(项目名) 即可。点击 下一步
    groupId=com.fishpro
    artifactId=restcontroller
  3. 选择依赖 Spring Web Starter 前面打钩, 在模板列中勾选 thymeleaf
  4. 项目名设置为 spring-boot-study-restcontroller.

2.2 依赖 Pom.xml 配置

本项目引入了 web 和 thymeleaf ,具体一人如下代码:

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-test<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">scope</span>&gt;</span>test<span class="hljs-tag">&lt;/<span class="hljs-name">scope</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span>

并把配置文件重命名为 application.yml ,修改默认测试端口

server:
  port: 8087

2.3 基于 @Controller 的示例代码

本代码实例中新增了如下页面,注意 templates 表示 /resources/tempalates/controller 表示 com.fishpro.restcontroller.controller 包。

  • controller/UserController.java 控制层 - 用户类
  • domain/UserDO.java 用户控实体类
  • templates/user/index.html 视图 - 用户列表页面
  • templates/user/add.html 视图 - 新增用户页面
  • templates/user/edit.html 视图 - 编辑用户页面

2.3.1 返回用户列表信息 /user/index

本项目源码下载

首先构件一个虚拟的用户数据

  /**
     * 模拟一组数据
     * */
    private List<UserDO> getData(){
        List<UserDO> list=new ArrayList<>();
    UserDO userDO=<span class="hljs-keyword">new</span> <span class="hljs-title class_">UserDO</span>();
    userDO.setUserId(<span class="hljs-number">1</span>);
    userDO.setUserName(<span class="hljs-string">"admin"</span>);
    list.add(userDO);

    userDO=<span class="hljs-keyword">new</span> <span class="hljs-title class_">UserDO</span>();
    userDO.setUserId(<span class="hljs-number">2</span>);
    userDO.setUserName(<span class="hljs-string">"heike"</span>);
    list.add(userDO);

    userDO=<span class="hljs-keyword">new</span> <span class="hljs-title class_">UserDO</span>();
    userDO.setUserId(<span class="hljs-number">3</span>);
    userDO.setUserName(<span class="hljs-string">"tom"</span>);
    list.add(userDO);

    userDO=<span class="hljs-keyword">new</span> <span class="hljs-title class_">UserDO</span>();
    userDO.setUserId(<span class="hljs-number">4</span>);
    userDO.setUserName(<span class="hljs-string">"mac"</span>);
    list.add(userDO);

    <span class="hljs-keyword">return</span>  list;
}

1 创建一个用户列表页面 /user/index

UserController 增加方法如下图所示,使用 @RequestMapping 注解,注意 @RequestMapping(method = RequestMethod.GET,value = "/index") 等于 @RequestMapping(value = "/index") 也等于 @RequestMapping("/index") 也等于 @GetMapping("/index")

 /**
     * GET  返回用户列表信息
     * */
    @RequestMapping(method = RequestMethod.GET,value = "/index")
    public String index(Model model){
        List<UserDO> list =getData();
        model.addAttribute("list",list);// 返回 用户 list
        return "user/index";
    }

2 创建 templates/index.html 对应上面的路由 /user/index

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div class="title">

</div>
<div th:each="user :${list}">
用户ID:<span th:text="${user.userId}"></span>
用户名:<span th:text="${user.userName}"></span>
</div>
</body>
</html>

3 在浏览器中查看

右键 RestControllerApplication > Run 在浏览器输入 http://localhost:8087/user/index

用户ID:1 用户名:admin
用户ID:2 用户名:heike
用户ID:3 用户名:tom
用户ID:4 用户名:mac

2.3.3 新增用户页面 /user/add

本项目源码下载

在 UserController 中增加 add 路由 /user/add

UserController 增加方法如下图所示,使用 @RequestMapping 注解,并创建了两个路由

  • /user/add 对应前端页面
  • /user/save 对应前端页面提交按钮的接口
 /**
     * GET 返回 add 页面
     * @GetMapping("/add") = @RequestMapping(method = RequestMethod.GET,value = "/add")
     * */
    @GetMapping("/add")
    public String add(){
        return "user/add";
    }
    /**
     * POST 新增用户 api
     * @return 返回 map 对象
     * */
    @RequestMapping(method = RequestMethod.POST,value = "/save")
    @ResponseBody
    public Object save(UserDO user){
        List<UserDO> list= getData();
        list.add(user);// 模拟向列表中增加数据
        Map<String,Object> map=new HashMap<>();
        if(null==user){
        map.put(<span class="hljs-string">"status"</span>,<span class="hljs-number">3</span>);
        map.put(<span class="hljs-string">"message"</span>,<span class="hljs-string">"没有传任何对象"</span>);
        <span class="hljs-keyword">return</span>  map;
    }
    map.put(<span class="hljs-string">"status"</span>,<span class="hljs-number">0</span>);
    map.put(<span class="hljs-string">"data"</span>,user);
    <span class="hljs-keyword">return</span>  map;
}

创建 templates/add.htm 文件对应路由 /user/add

在对应的 templates/add.html 中增加代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户新增页面</title>
    <script src="https://cdn.bootcss.com/jquery/1.11.3/jquery.js"></script>
</head>
<body>
<form id="form1">
    <div> <input name="userId" id="userId"  placeholder="请输入 userid"></div>
    <div> <input name="userName" id="userName"   placeholder="请输入 username"></div>
    <div> <input  type="button" value="新增" id="btnSave"/></div>
</form>
<script>
    $(function () {
    $(<span class="hljs-string">"#btnSave"</span>).<span class="hljs-title function_">click</span>(<span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) {
        $.<span class="hljs-title function_">ajax</span>({
            <span class="hljs-attr">cache</span>: <span class="hljs-literal">true</span>,
            <span class="hljs-attr">type</span>: <span class="hljs-string">"POST"</span>,
            <span class="hljs-attr">url</span>: <span class="hljs-string">"/user/save"</span>,
            <span class="hljs-attr">data</span>:$(<span class="hljs-string">'#form1'</span>).<span class="hljs-title function_">serialize</span>(),
            <span class="hljs-attr">dataType</span>:<span class="hljs-string">"json"</span>,
            <span class="hljs-attr">async</span>: <span class="hljs-literal">false</span>,
            <span class="hljs-attr">error</span>: <span class="hljs-keyword">function</span> (<span class="hljs-params">request</span>) {
                <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">"Connection error"</span>);
            },
            <span class="hljs-attr">success</span>: <span class="hljs-keyword">function</span> (<span class="hljs-params">data</span>) {
                <span class="hljs-keyword">if</span> (data.<span class="hljs-property">code</span> == <span class="hljs-number">0</span>) {
                    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">"成功"</span>);
                } <span class="hljs-keyword">else</span> {
                    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">"失败"</span>);
                }

            }
        });
    });

});

</script>
</body>
</html>

在浏览器中测试效果
右键 RestControllerApplication > Run 在浏览器输入 http://localhost:8087/user/add

2.3.3 编辑用户页面 /user/edit

这部分代码原理同 2.3.2 代码原理 通过构件一个编辑页面,点击编辑页面的【保存】提交到后端的 api 中。

本示例中创建了 2 个接口 1 个文件

  • /user/edit 对应前端页面
  • /user/update 对应前端页面提交按钮的接口
  • /templates/user/edit.html
 /**
     * GET 返回编辑页面
     * @GetMapping("/edit/{id}") = @RequestMapping(method = RequestMethod.GET,value = "/edit/{id}")
     * @PathVariable("id") 表示路由中的动态参数部分
     * @param  id 表示要编辑的用户 id
     * @param model 表示将要输出到页面的 Model 对象
     * @return 返回到 user/edit 页面
     * */
    @GetMapping("/edit/{id}")
    public String edit(@PathVariable("id") String id, Model model){
        UserDO user =new UserDO();
        user.setUserId(3);
        user.setUserName("fishpro");
        model.addAttribute("user",user);
        return "user/edit";
    }
<span class="hljs-comment">/**
 * POST 修改用户api
 * <span class="hljs-doctag">@RequestBody</span> 表示参数使用 json 对象传输
 * <span class="hljs-doctag">@return</span> 返回 map对象
 * */</span>
<span class="hljs-meta">@PostMapping("/update")</span>
<span class="hljs-meta">@ResponseBody</span>
<span class="hljs-keyword">public</span> Object <span class="hljs-title function_">update</span><span class="hljs-params">(<span class="hljs-meta">@RequestBody</span> UserDO user)</span>{
    Map&lt;String,Object&gt; map=<span class="hljs-keyword">new</span> <span class="hljs-title class_">HashMap</span>&lt;&gt;();
    <span class="hljs-keyword">if</span>(<span class="hljs-literal">null</span>==user){

        map.put(<span class="hljs-string">"status"</span>,<span class="hljs-number">3</span>);
        map.put(<span class="hljs-string">"message"</span>,<span class="hljs-string">"没有传任何对象"</span>);
        <span class="hljs-keyword">return</span>  map;
    }
    <span class="hljs-comment">//更新逻辑</span>
    map.put(<span class="hljs-string">"status"</span>,<span class="hljs-number">0</span>);
    <span class="hljs-keyword">return</span>  map;
}

edit.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户编辑页面</title>
    <script src="https://cdn.bootcss.com/jquery/1.11.3/jquery.js"></script>
</head>
<body>
<form id="form1">
    <div> <input name="userId" id="userId"  placeholder="请输入 userid" th:value=${user.userId}> </div>
    <div> <input name="userName" id="userName"   placeholder="请输入 username" th:value=${user.userName}></div>
    <div> <input  type="button" value="新增" id="btnSave"/></div>
</form>
<script>
    $(function () {
    $(<span class="hljs-string">"#btnSave"</span>).<span class="hljs-title function_">click</span>(<span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) {
        $.<span class="hljs-title function_">ajax</span>({
            <span class="hljs-attr">cache</span>: <span class="hljs-literal">true</span>,
            <span class="hljs-attr">type</span>: <span class="hljs-string">"POST"</span>,
            <span class="hljs-attr">url</span>: <span class="hljs-string">"/user/update"</span>,
            <span class="hljs-attr">data</span>:$(<span class="hljs-string">'#form1'</span>).<span class="hljs-title function_">serialize</span>(),
            <span class="hljs-attr">dataType</span>:<span class="hljs-string">"json"</span>,
            <span class="hljs-attr">async</span>: <span class="hljs-literal">false</span>,
            <span class="hljs-attr">error</span>: <span class="hljs-keyword">function</span> (<span class="hljs-params">request</span>) {
                <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">"Connection error"</span>);
            },
            <span class="hljs-attr">success</span>: <span class="hljs-keyword">function</span> (<span class="hljs-params">data</span>) {
                <span class="hljs-keyword">if</span> (data.<span class="hljs-property">code</span> == <span class="hljs-number">0</span>) {
                    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">"成功"</span>);
                } <span class="hljs-keyword">else</span> {
                    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">"失败"</span>);
                }
            }
        });
    });

});

</script>
</body>
</html>

本项目源码下载

2.3.3 删除用户 /user/delete

本项目源码下载

/**
     * POST 修改用户 api 
     * @return 返回 map 对象
     * */
    @PostMapping("/delete/{id}")
    @ResponseBody
    public Object delete(@PathVariable("id") Integer id){
        List<UserDO> list= getData();
        UserDO userDO=null;
        for (UserDO user:list
                ) {
            if(id.equals(user.getUserId().toString())){
                // 删除用户
                userDO=user;
                break;
            }
        }
    Map&lt;String,Object&gt; map=<span class="hljs-keyword">new</span> <span class="hljs-title class_">HashMap</span>&lt;&gt;();
    map.put(<span class="hljs-string">"status"</span>,<span class="hljs-number">0</span>);
    map.put(<span class="hljs-string">"data"</span>,userDO);
    <span class="hljs-keyword">return</span>  map;
}

2.4 基于 @RestController 的示例代码

本章节中是基于 2.3 版本中的接口,其实是一样的功能,详细见下面的代码,为了完成代码功能示例,我们新建了 UserRestController.java,不同的是 我们给类加了 @RestController 修饰符。

@RestController
@RequestMapping("user2")
public class UserRestController {
    // 从 UserController 搬过来代码即可
}

本项目源码下载


关联阅读:

Spring Boot Thymeleaf 模板引擎的使用

Spring Boot RESTful api

Spring Boot Log 日志使用教程

Spring Boot 全局异常处理