SpringBoot:Demo

Posted rainszj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot:Demo相关的知识,希望对你有一定的参考价值。

跟着狂神的SpringBoot网课做了一个小demo,功能有:登录、注销、拦截器、Restful CRUD、错误页面。不过完善了下put、delete的请求方式。

Demo

准备工作

使用Lombok插件

pom.xml

<!--web依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--测试-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
  <exclusions>
    <exclusion>
      <groupId>org.junit.vintage</groupId>
      <artifactId>junit-vintage-engine</artifactId>
    </exclusion>
  </exclusions>
</dependency>

<!--thymeleaf依赖-->
<dependency>
  <groupId>org.thymeleaf</groupId>
  <artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
  <groupId>org.thymeleaf.extras</groupId>
  <artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
<!--lombok依赖-->
<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>1.18.10</version>
</dependency>

pojo层

package com.rainszj.pojo;

import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

// 员工表
@Data
@NoArgsConstructor
public class Employee {

    private Integer id;
    private String lastName;
    private String email;
    /**
     * 0:女  1: 男
     */
    private Integer gender;
    private Date birth;

    private Department department;

    public Employee(Integer id, String lastName, String email, Integer gender, Department department) {
        this.id = id;
        this.lastName = lastName;
        this.email = email;
        this.gender = gender;
        // 日期自动设置
        this.birth = new Date();
        this.department = department;
    }
}
package com.rainszj.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

// 部门表
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Department {
    private Integer id;
    private String departmentName;
}

dao层

此demo没有使用数据库,使用Map模拟数据库

package com.rainszj.dao;

import com.rainszj.pojo.Department;
import org.springframework.stereotype.Repository;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

@Repository
public class DepartmentDao {

    // 模拟数据库中的数据
    private static Map<Integer, Department> departments = null;

    static {
        // 创建一个部门表
        departments = new HashMap<Integer, Department>();

        departments.put(101, new Department(101, "研发部"));
        departments.put(102, new Department(102, "运营部"));
        departments.put(103, new Department(103, "项目部"));
        departments.put(104, new Department(104, "运维部"));
        departments.put(105, new Department(105, "测试部"));
    }

    // 获取所有的部门
    public Collection<Department> getDeparments() {
        return departments.values();
    }

    // 根据 id 获取部门
    public Department getDeparmentById(Integer id) {
        return departments.get(id);
    }
}
package com.rainszj.dao;

import com.rainszj.pojo.Department;
import com.rainszj.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

@Repository
public class EmployeeDao {

    @Autowired
    private DepartmentDao departmentDao;
    // 模拟数据库中的数据
    private static Map<Integer, Employee> employees = null;

    static {
        // 创建一个员工表
        employees = new HashMap<Integer, Employee>();

        employees.put(1001, new Employee(1001, "白展堂", "1315480331@qq.com", 1, new Department(101, "研发部")));
        employees.put(1002, new Employee(1002, "佟湘玉", "1315480331@qq.com", 0, new Department(102, "运营部")));
        employees.put(1003, new Employee(1003, "吕轻侯", "1315480331@qq.com", 1, new Department(103, "项目部")));
        employees.put(1004, new Employee(1004, "李大嘴", "1315480331@qq.com", 1, new Department(104, "运维部")));
        employees.put(1005, new Employee(1005, "郭芙蓉", "1315480331@qq.com", 0, new Department(105, "测试部")));
    }

    // 主键自增
    private static Integer initId = 1006;

    /**
     * 添加一个员工
     *
     * @param employee
     */
    public void addEmployee(Employee employee) {
        if (employee.getId() == null) {
            employee.setId(initId++);
        }
        // ???
        employee.setDepartment(departmentDao.getDeparmentById(employee.getDepartment().getId()));

        employees.put(employee.getId(), employee);
    }

    /**
     * 查询全部员工信息
     */
    public Collection<Employee> getAllEmployee() {
        return employees.values();
    }

    /**
     * 根据 ID 查询员工
     */
    public Employee getEmployeeById(Integer id) {
        return employees.get(id);
    }


    /**
     * 根据 ID删除员工
     */
    public void removeEmployeeById(Integer id) {
        employees.remove(id);
    }

}

BootStrap 后台模板下载地址

  • css、js等放在 static 文件夹下

  • html 放在 templates 文件夹下

登录+拦截器

templates下的页面只能通过Controller跳转实现,而META-INF/resources、resources、static、public下的页面是能直接被外界访问的

我们在登录的表单提交地址上写一个Controller:/user/login

<form class="form-signin" action="dashboard.html" th:action="@{/user/login}" method="post">
//这里面的所有表单标签都需要加上一个name属性
</form>

去写对应的Controller

package com.rainszj.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.annotation.SessionScope;
import org.thymeleaf.util.StringUtils;

import javax.servlet.http.HttpSession;

@Controller
public class LoginController {

    /**
     * 登录
     */
    @RequestMapping("/user/login")
    public String login(@RequestParam("username") String username,
                        @RequestParam("password") String password,
                        Model model, HttpSession session) {

        if (!StringUtils.isEmpty(username) && "123".equals(password)) {
						// 登录成功
            session.setAttribute("loginUser", username);
          	// 这里使用重定向,防止表单重复提交
            return "redirect:/main.html"; 
        } else {	
          	// 登录失败
            model.addAttribute("msg", "用户名或密码错误");
            return "index";
        }

    }
}

这里说一下为什么不能使用转发,而是用重定向:

因为如果是转发的话,提交完表单(即使表单提交方式为post)后,再经由Controller转发,也会将表单中的参数携带过去,而此时的URL地址又不会发生变化,如果用户此时刷新的话,会造成表单的重复提交!

如图:

技术图片

而重定向不同,重定向地址栏会发生变化。当表单提交到Controller后,经由Controller重定向后,表单的参数不会显示再地址栏,因为重定向是两次请求,可以防止表单的重复提交!

技术图片

而重定向不同,重定向地址栏会发生变化,经由Controller重定向后,不会将表单的参数携带过去,可以防止表单的重复提交!

其实问题的本质还是转发和重定向的区别。


设置重定向后的视图:在MyMvcConfigaddViewControllers()添加

// 跳转到 dashboard?
registry.addViewController("/main.html").setViewName("dashboard");

页面存在缓存,所以我们需要禁用模板引擎的缓存

#禁用模板缓存
spring.thymeleaf.cache=false

模板引擎修改后,想要实时生效!页面修改完毕后,IDEA小技巧 : Ctrl + F9? 重新编译!

技术图片

OK ,测试登录成功!如果模板出现500错误,参考处理连接:

https://blog.csdn.net/fengzyf/article/details/83341479


下面该是拦截器了!

如果不设置拦截器的话,用户可以不用登录进行身份验证,直接跳到后台!

package com.rainszj.config;

import org.springframework.web.servlet.HandlerInterceptor;

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

/**
 * 自定义拦截器
 */
public class LoginHandlerInterceptor implements HandlerInterceptor {

    /**
     * return true:放行,false:不放行
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        Object loginUser = request.getSession().getAttribute("loginUser");

        if (loginUser == null) {
            // 登录失败
            request.setAttribute("msg", "没有权限,请先登录");
            request.getRequestDispatcher("/index.html").forward(request, response);

            return false;
        } else {
            // 登录成功
            return true;
        }
    }
}

然后将拦截器注册到我们的SpringMVC配置类(MyMvcConfig)当中!

/**
 * 添加拦截器
 */
@Override
public void addInterceptors(InterceptorRegistry registry) {
				//注册拦截器,及拦截请求和要剔除哪些请求!
        //我们还需要过滤静态资源文件,否则样式显示不出来
    registry.addInterceptor(new LoginHandlerInterceptor())
      			// 对所有请求的所有路径进去拦截
            .addPathPatterns("/**")
      			// 放行哪些路径
            .excludePathPatterns("/index.html", "/", "/user/login", "/css/*", "/js/*", "/img/*");

}

我们可以在后台的导航栏显示用户的登录信息

th:text="${session.loginUser}"
或者
[[${session.loginUser}]]

下面进行正式的增删改查,也就是Restful 风格的CRUD

Restful CRUD

Restful架构

概念:REST指的是一组架构约束条件和原则,如果一个架构符合REST的约束条件和原则,就称之为RESTful架构。

restful不是一个专门的技术,他是一个规范。规范就是写写代码给类命名,给属性命名,创建包结构 等等都最好按一定的规则来进行。这样的话以后代码看起来更规范,更容易理解。好比以前写增删改查请求的路径。

技术图片

优点:

  • 可以方便的实现程序的前后台代码的分离

  • resutful要求每个请求都是无状态的

  • 可以使请求的路径更规范

要求 : 我们需要使用 Restful风格实现我们的crud操作!

普通CRUD和Restful风格的CRUD对别,明显的区别在URL地址栏上!

技术图片

看看一些具体的要求,就是我们小实验的架构;

技术图片

我们先来实现第一个功能:

查询所有员工

给 a 连接添加请求

<a class="nav-link" href="#" th:href="@{/emps}">员工管理</a>

技术图片

编写处理请求的Controller

@Controller
public class EmployeeController {

    @Autowired
    EmployeeDao employeeDao;

    //查询所有员工,返回列表页面
    @GetMapping("/emps")
    public String list(Model model){
        Collection<Employee> employees = employeeDao.getAll();
        //将结果放在请求中
        model.addAttribute("emps",employees);
        return "emp/list";
    }
}

ok,跳转没有问题!我们只需将数据渲染进去即可。

但是发现一个问题,侧边栏和顶部都相同,我们是不是应该将它抽取出来呢?

Thymeleaf 公共页面元素抽取

1.抽取公共片段 th:fragment? 定义模板名

2.引入公共片段? th:insert? 插入模板名

我们来抽取一下,使用list列表做演示!我们要抽取头部导航栏和侧边栏

为了重用更清晰,我们建立一个commons文件夹,专门存放公共页面;

技术图片

技术图片

然后我们在list页面中去引入,可以删掉原来的

模板名:会使用thymeleaf的前后缀配置规则进行解析

引入方式: ~{模板::标签名}

技术图片

除了使用replace插入,还可以使用insert替换,或者include包含,三种方式会有一些小区别,可以见名知义;

我们使用replace替换,可以解决div多余的问题,可以查看thymeleaf的文档学习

还有一个问题:侧边栏激活的问题,它总是激活第一个;按理来说,这应该是动态的才对!

解决:

技术图片

去修改对应的请求:

技术图片

现在我们来遍历我们的员工信息!顺便美化一些页面,增加添加,修改,删除的按钮

<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
    <h2><a class="btn btn-sm btn-success" th:href="@{/emp}">添加员工</a></h2>
    <div class="table-responsive">
        <table class="table table-striped table-sm">
            <thead>
            <tr>
                <th>ID</th>
                <th>姓名</th>
                <th>邮箱</th>
                <th>性别</th>
                <th>生日</th>
                <th>部门</th>
                <th>操作</th>
            </tr>
            </thead>

            <tbody>
            <tr th:each="emp:${emps}">
                <td th:text="${emp.id}"></td>
                <td th:text="${emp.lastName}"></td>
                <td th:text="${emp.email}"></td>
                <td th:text="${emp.gender == 0 ? ‘女‘ : ‘男‘}"></td>
                <!--使用时间格式化工具,查阅thymeleaf文档得知-->
                <td th:text="${#dates.format(emp.birth, ‘yyyy-MM-dd HH:mm:ss‘)}"></td>
                <td th:text="${emp.department.departmentName}"></td>
                <td>
                    <a class="btn btn-sm btn-primary">修改</a>
                    <a class="btn btn-sm btn-danger">删除</a>
                </td>
            </tr>
            </tbody>

        </table>
    </div>
</main>

添加员工

1. 将添加员工信息改为超链接

<!--添加员工按钮-->
<a class="btn btn-sm btn-success" href="emp" th:href="@{/emp}">添加员工</a>

2. 编写对应的controller

/**
?* 跳转到添加员工页面
?*/
@GetMapping("/emp")
public String toAddPage(Model model) {

? ? Collection<Department> depts = departmentDao.getDeparments();
? ? model.addAttribute("depts", depts);
? ? return "emp/add";
}

3. 修改前端页面

<form class="form-horizontal" method="post">
    <div class="form-group">
        <label for="lastName" class="col-sm-2 control-label">姓名</label>
        <div class="col-sm-10">
            <input type="text" class="form-control" id="lastName" name="lastName" placeholder="姓名">
        </div>
    </div>

    <div class="form-group">
        <label for="email" class="col-sm-2 control-label">邮箱</label>
        <div class="col-sm-10">
            <input type="text" name="email" class="form-control" id="email" placeholder="1315480331@qq.com">
        </div>
    </div>

    <div class="form-group">
        <label class="col-sm-2 control-label">性别</label> <br/>
        <div class="col-sm-8 control-label">
            <input type="radio" name="gender" value="1">
            <label class="form-check-label">男</label>
            &nbsp;&nbsp;
            <input type="radio" name="gender" value="0">
            <label class="form-check-label">女</label>
        </div>
    </div>

    <div class="form-group">
        <label for="birth" class="col-sm-2 control-label">生日</label>
        <div class="col-sm-10">
            <input type="text" name="birth" class="form-control" id="birth" placeholder="2020-1-1">
        </div>
    </div>

    <div class="form-group">
        <label class="col-sm-2 control-label">部门</label>
        <div class="col-sm-10">
            <!--由于我们 Controller 接受的是一个 Employee对象,这里传递的 department.id -->
            <select class="form-control" name="department.id">
                <option value="" selected disabled>--请选择--</option>
                <option th:each="dept:${depts}" th:value="${dept.id}" th:text="${dept.departmentName}"></option>
            </select>
        </div>
    </div>


    <div class="form-group">
        <div class="col-sm-offset-2 col-sm-10">
            <button type="submit" class="btn btn-default">提交</button>
        </div>
    </div>
</form>

先测试是否能够跳转和页面的展示情况

1. 设置表单的提交地址

<form class="form-horizontal" th:action="@{/emp}" method="post">

2. 编写对应的Controller

/**
 * 添加员工页面
 * 接收前端传递的参数,自动封装成为对象
 * [要求前端传递的参数名,和属性名一致]
 */
@PostMapping("/emp")
public String addEmp(Employee employee) {
  System.out.println("[addEmp]=>" + employee);

  employeeDao.addEmployee(employee);
  return "redirect:/emps";
}

原理探究 : ThymeleafViewResolver

@Override
protected View createView(final String viewName, final Locale locale) throws Exception {
    // First possible call to check "viewNames": before processing redirects and forwards
    if (!this.alwaysProcessRedirectAndForward && !canHandle(viewName, locale)) {
        vrlogger.trace("[THYMELEAF] View "{}" cannot be handled by ThymeleafViewResolver. Passing on to the next resolver in the chain.", viewName);
        return null;
    }
    // Process redirects (HTTP redirects)
    if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
        vrlogger.trace("[THYMELEAF] View "{}" is a redirect, and will not be handled directly by ThymeleafViewResolver.", viewName);
        final String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length(), viewName.length());
        final RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
        return (View) getApplicationContext().getAutowireCapableBeanFactory().initializeBean(view, viewName);
    }
    // Process forwards (to JSP resources)
    if (viewName.startsWith(FORWARD_URL_PREFIX)) {
        // The "forward:" prefix will actually create a Servlet/JSP view, and that‘s precisely its aim per the Spring
        // documentation. See http://docs.spring.io/spring-framework/docs/4.2.4.RELEASE/spring-framework-reference/html/mvc.html#mvc-redirecting-forward-prefix
        vrlogger.trace("[THYMELEAF] View "{}" is a forward, and will not be handled directly by ThymeleafViewResolver.", viewName);
        final String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length(), viewName.length());
        return new InternalResourceView(forwardUrl);
    }
    // Second possible call to check "viewNames": after processing redirects and forwards
    if (this.alwaysProcessRedirectAndForward && !canHandle(viewName, locale)) {
        vrlogger.trace("[THYMELEAF] View "{}" cannot be handled by ThymeleafViewResolver. Passing on to the next resolver in the chain.", viewName);
        return null;
    }
    vrlogger.trace("[THYMELEAF] View {} will be handled by ThymeleafViewResolver and a " +
                    "{} instance will be created for it", viewName, getViewClass().getSimpleName());
    return loadView(viewName, locale);
}

从源码中得知:如果是转发和重定向的话,不会走视图解析器,会走到我们的请求中!

在使用SpringBoot的时候,前端填写数据,注意时间格式问题!

SpringMVC会将页面提交的值转换为指定的类型,默认日期是按照 / 的方式提交 ; 比如将2019/01/01 转换为一个date对象。

那思考一个问题?我们能不能修改这个默认的格式呢?

我们去看WebMvcAutoConfiguration;找到一个日期格式化的方法 mvcConversionService(),我们可以看一下

@Bean
@Override
public FormattingConversionService mvcConversionService() {
   WebConversionService conversionService = new WebConversionService(this.mvcProperties.getDateFormat());
   addFormatters(conversionService);
   return conversionService;
}

点进:getDateFormat(),这表示我们可以在配置文件中修改它默认的日期格式!

技术图片

application.properties

# 自定义日期格式化
spring.mvc.date-format=yyyy-MM-dd

现在它可以支持 - 格式了,但是又不能支持 / 格式了。

员工修改功能

我们要实现员工修改功能,需要实现两步;

1. 点击修改按钮,去到编辑页面,我们可以直接使用添加员工的页面实现

2. 显示原数据,修改完毕后跳回列表页面!

我们去实现一下:首先修改跳转链接的位置;

<a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.id}">编辑</a>

编写对应的Controller

 /**
  * 跳转到修改页面
  */
@GetMapping("/emp/{id}")
 public String toUpdatePage(@PathVariable("id") Integer id, Model model) {
		Employee employee = employeeDao.getEmployeeById(id);
		model.addAttribute("emp", employee);
		Collection<Department> depts = departmentDao.getDeparments();
    model.addAttribute("depts", depts);
   
		return "emp/update";
}

我们需要在这里将add页面复制一份,改为update页面;需要修改页面,将我们后台查询数据回显

<form class="form-horizontal" method="post">
    <!--隐藏域, 提交要修改的 id-->
    <input type="hidden" name="id" th:value="${emp.id}">

    <div class="form-group">
        <label for="lastName" class="col-sm-2 control-label">姓名</label>
        <div class="col-sm-10">
            <input type="text" th:value="${emp.lastName}" class="form-control" id="lastName" name="lastName"
                   placeholder="姓名">
        </div>
    </div>

    <div class="form-group">
        <label for="email" class="col-sm-2 control-label">邮箱</label>
        <div class="col-sm-10">
            <input type="text" th:value="${emp.email}" name="email" class="form-control" id="email"
                   placeholder="1315480331@qq.com">
        </div>
    </div>

    <div class="form-group">
        <label class="col-sm-2 control-label">性别</label> <br/>
        <div class="col-sm-8 control-label">
            <input type="radio" th:checked="${emp.gender == 1}" name="gender" value="1">
            <label class="form-check-label">男</label>
            &nbsp;&nbsp;
            <input type="radio" th:checked="${emp.gender == 0}" name="gender" value="0">
            <label class="form-check-label">女</label>
        </div>
    </div>

    <div class="form-group">
        <label for="birth" class="col-sm-2 control-label">生日</label>
        <div class="col-sm-10">
            <input type="text" th:value="${#dates.format(emp.birth, ‘yyyy-MM-dd HH:mm‘)}" name="birth" class="form-control" id="birth" placeholder="2020-1-1">
        </div>
    </div>

    <div class="form-group">
        <label class="col-sm-2 control-label">部门</label>
        <div class="col-sm-10">
            <!--由于我们 Controller 接受的是一个 Employee对象,这里传递的 department.id -->
            <select class="form-control" name="department.id">
                <option value="" selected disabled>--请选择--</option>
                <option th:each="dept:${depts}" th:value="${dept.id}" th:text="${dept.departmentName}" th:selected="${dept.id == emp.department.id}"></option>
            </select>
        </div>
    </div>

    <div class="form-group">
        <div class="col-sm-offset-2 col-sm-10">
            <button type="submit" class="btn btn-default">修改</button>
        </div>
    </div>

</form>

数据回显OK,我们继续完成数据修改问题!

修改表单提交的地址:

<form class="form-horizontal" th:action="@{/emp}" method="post">

编写对应的Controller

/**
 * 修改员工
 */
@PutMapping("/emp")
public String updateEmp(Employee employee) {

    employeeDao.addEmployee(employee);
    return "redirect:/emps";
}

这里我们需要注意一点:修改时,使用隐藏域提交该员工的id

<input name="id" type="hidden" class="form-control" th:value="${emp.id}">

技术图片

重启,修改信息测试OK!

在做Restful的删除操作之前,我们先来看一个类!,这个类可以将HTTP方法进行转换

HiddenHttpMethodFilter

当作删除操作的时候,不再通过超链接提交,通过form表单提交,为了将请求方法从post转成 DELETE,否则可能会走到修改请求或者是删除请求,具体走那个看这两个请求定义的先后顺序了!

spring 针对这个问题为我们一个解决访问,我们只需要在web.xml中配置一个转换请求方式的过滤器

技术图片

但在SpringBoot中已经我们自动装配了!

mvc的自动配置类中,有个hiddenHttpMethodFilter

@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = true)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
? ?return new OrderedHiddenHttpMethodFilter();
}

点进OrderedHiddenHttpMethodFilter类,发现他继承了HiddenHttpMethodFilter

/**
 * 由于浏览器目前仅支持GET和POST,无法使用其他方法发送请求。
 * 但该过滤器可以将发布的方法参数装换为相应的HTTP方法。
 * 例如由Prototype库(JS的一个库)提供的,使用带有附加隐藏表单字段(_method),的普通POST方法来传递"真实" HTTP方法。
 * 该过滤器读取该参数,隐藏域的name="_method",value仅允许使用delete、put、patch的HTTP方法
 * 注意:如果是多部分POST请求,此过滤器需要在多部分处理之后运行,
 * 因为它本身需要检查POST主体参数。
 * 因此,通常,放置一个Spring的MultipartFilter}在您的web.xml过滤器链中的此HiddenHttpMethodFilter之前。
 */
public class HiddenHttpMethodFilter extends OncePerRequestFilter {
	// 允许的方法
   private static final List<String> ALLOWED_METHODS =
         Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(),
               HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));

   /** 默认的方法的参数 */
   public static final String DEFAULT_METHOD_PARAM = "_method";

   private String methodParam = DEFAULT_METHOD_PARAM;


   /**
    * Set the parameter name to look for HTTP methods.
    * @see #DEFAULT_METHOD_PARAM
    */
   public void setMethodParam(String methodParam) {
      Assert.hasText(methodParam, "‘methodParam‘ must not be empty");
      this.methodParam = methodParam;
   }

  // 实际上将post方法装换成相应HTTP方法的操作
   @Override
   protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
         throws ServletException, IOException {

      HttpServletRequest requestToUse = request;
			// 表单的method必须为post
      if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
        // 获取_method的value
         String paramValue = request.getParameter(this.methodParam);
         if (StringUtils.hasLength(paramValue)) {
           // 转换成大写
            String method = paramValue.toUpperCase(Locale.ENGLISH);
            if (ALLOWED_METHODS.contains(method)) {
              // HTTP方法转换完成
               requestToUse = new HttpMethodRequestWrapper(request, method);
            }
         }
      }
			// 放行
      filterChain.doFilter(requestToUse, response);
   }


   /**
    * Http方法请求包装
    */
   private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {

      private final String method;

      public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
         super(request);
         this.method = method;
      }

      @Override
      public String getMethod() {
         return this.method;
      }
   }

}

删除员工

解决思路:

借鉴出处

通过JS获取删除员工的a标签的href的值,将其赋值给form表单的action,form表单中通过隐藏域提交请求的方法,由JS来提交表单。

我们还需要保证两件事:

  • form的请求必须是POST

  • form中必须带一个_method的参数,代表要把POST请求转换为什么请求。

Come on!

编写删除的a链接

<a class="btn btn-sm btn-danger" th:href="@{/emp/} + ${emp.id}" id="deleteEmp">删除</a>

声明一个form,用来提交delete请求:

<form action="" id="toDelete" method="post">
? ? <input type="hidden" name="_method" value="delete">
</form>

需要给删除超链接增加单击事件,而且还要阻止原来的超链接事件:

<script>
    $(function () {
        // 当删除按钮点击时
        $("[id=‘deleteEmp‘]").click(
            function () {
                // 获取删除的超链接
                let href = $(this).attr("href");
                // 获取form表单
                let form = $("#toDelete");
                // 把form的action属性设置成href
                form.attr("action", href);
                // 提交表单
                form.submit();
                // 阻止原来的单击事件
                return false;
            }
        );
    });

</script>

编写Controller

/**
?* 删除用户
?*/
@DeleteMapping("/emp/{id}")
public String deleteEmp(@PathVariable("id") Integer id) {

? ? employeeDao.removeEmployeeById(id);
? ? return "redirect:/emps";
}

SpringBoot默认是开启的,如果我们关闭,就会405报错!

spring.mvc.hiddenmethod.filter.enabled=false

技术图片

做完删除操作之后,想起来修改操作put也应该这样做!哈哈

只需要在 update.html 的 form表单中加上隐藏域即可

<input type="hidden" name="_method" value="put">

定制错误页面

我们只需要在模板目录下添加一个error文件夹,文件夹中存放我们相应的错误页面,比如404.html? 或者 4xx.html 等等,SpringBoot就会帮我们自动使用了!

技术图片

注销功能

a链接

<a class="nav-link" href="#" th:href="@{/user/loginOut}">Sign out</a>

对应的Controller

/**
 * 注销
 */
@GetMapping("/user/logout")
public String logout(HttpSession session) {
    // 将 Session 删除
    session.invalidate();
  
    return "redirect:/index.html";
}

学到这里,SpringBoot的基本开发就以及没有问题了,我们后面去学习一下SpringBoot如何操作数据库以及配置Mybatis;

以上是关于SpringBoot:Demo的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot启动报错“Consider defining a bean of type ‘xxx.mapper.UserMapper‘ in your configuration.“(代码片段

12mmaction2 行为识别商用级别X3D复现 demo实现 检测自己的视频 Expanding Architecturesfor Efficient Video Recognition(代码片段

[异常解决] Keil安装好nRF51822开发环境,运行DEMO报错:Error:“GPIOTE_CONFIG_NUM_OF_LOW_POWER_ENVENTS” is undefined(代码片段

全栈编程系列SpringBoot整合Shiro(含KickoutSessionControlFilter并发在线人数控制以及不生效问题配置启动异常No SecurityManager...)(代码片段

面试常用的代码片段

SpringBoot中表单提交报错“Content type ‘application/x-www-form-urlencoded;charset=UTF-8‘ not supported“(代码片段