• 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地址又不会发生变化,如果用户此时刷新的话,会造成表单的重复提交!

    如图:

    1582426388975.png

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

    1582426834381.png

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

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


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

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

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

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

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

    1582423857191.png

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

    1602834.png

    优点:

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

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

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

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

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

    1418974.png

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

    141218974.png

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

    查询所有员工

    给 a 连接添加请求

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

    1582515423963.png

    编写处理请求的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文件夹,专门存放公共页面;

    1582515752025.png

    1582516253635.png

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

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

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

    1582516434389.png

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

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

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

    解决:

    1582516728210.png

    去修改对应的请求:

    1582516826458.png

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

    <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(),这表示我们可以在配置文件中修改它默认的日期格式!

    1582518307053.png

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

    1582518991100.png

    重启,修改信息测试OK!

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

    HiddenHttpMethodFilter

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

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

    307787692.png

    但在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
    

    1582522309320.png

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

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

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

    定制错误页面

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

    1582520485801.png

    注销功能

    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;

  • 相关阅读:
    Visual C#核心编程之泛型
    Visual C#核心编程之枚举器
    标准的非托管资源的销毁模式
    Visual C#核心编程之LINQ
    Visual C#核心编程之数组和集合
    ACCPSQL第四章上机六
    Visual C#2008核心编程之类型
    一月一代码 3月 kmp 领悟代码
    [转] 技巧 如何统一设置 windows live writer 的 图片大小
    understanding the linux virtual memory management 图序
  • 原文地址:https://www.cnblogs.com/rainszj/p/12730736.html
Copyright © 2020-2023  润新知