SpringMVC------开发之路

1. SpringMVC 简介

SpringMVC 是一种基于Java 实现 MVC 设计模式的请求驱动类型的轻量级 Web 框架,它和Struts2都属于表现层的框架,属于Spring FrameWork 的后续产品, Spring MVC 分离了控制器, 模型对象, 过滤器以及处理程序对象的角色, 这种分离让它们更容易进行定制。

SpringMVC 通过一套简单注解, 让一个简单的java类称为处理请求的控制器, 而无须实现任何接口。同事它还支持RESTful 编程风格的请求。

2. SpringMVC架构

2.1 执行流程

1.用户发送请求至前端控制器DispatcherServlet
2.DispatcherServlet收到请求调用HandlerMapping处理器映射器。
3.处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
4.DispatcherServlet通过HandlerAdapter处理器适配器调用处理器
5.执行处理器(Controller,也叫后端控制器)。
6.Controller执行完成返回ModelAndView
7.HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet
8.DispatcherServlet将ModelAndView传给ViewReslover视图解析器
9.ViewReslover解析后返回具体View
10.DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)。
11.DispatcherServlet响应用户

 2.2 组件说明

以下组件通常使用框架提供实现:

DispatcherServlet:前端控制器 用户请求到达前端控制器,它就相当于mvc模式中的c,dispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet的存在降低了组件之间的耦合性。

HandlerMapping:处理器映射器 HandlerMapping负责根据用户请求url找到Handler即处理器,springmvc提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。

Handler:处理器 Handler 是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理。 由于Handler涉及到具体的用户业务请求,所以一般情况需要程序员根据业务需求开发Handler。

HandlAdapter:处理器适配器 通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。适配器最终都可以使用usb接口连接

ViewResolver:视图解析器 View Resolver负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。

View:视图 springmvc框架提供了很多的View视图类型的支持,包括:jstlView、freemarkerView、pdfView等。我们最常用的视图就是jsp。 一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面。

在springmvc的各个组件中,处理器映射器、处理器适配器、视图解析器称为springmvc的三大组件。 需要用户开发的组件有handler、view 因为框架已经默认加载这些组件了,所以我们不需要做任何配置,就可以使用这些组件了。

3. SpringMVC 入门

3.1 创建一个Web 工程, 导入依赖

<!-- spring-webmvc依赖中传递了spring的核心依赖 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.20</version>
    </dependency>

    <!-- tomcat中自带servlet和jsp的jar包,这里根据需求导入 -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
    </dependency>

3.2 在webapp目录下创建login.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<a href="/user/login">登录</a>
</body>
</html>

3.3 在main目录下创建 java 目录,然后创建UserController

@Controller
@RequestMapping("/user")//一级请求路径
public class UserController {
    @RequestMapping("/login")//二级请求路径
    public ModelAndView login(){
        System.out.println("login..");
        //创建ModelAndView对象
        ModelAndView modelAndView = new ModelAndView();
        //保存数据
        modelAndView.addObject("name","张三");
        //设置跳转的路径
        modelAndView.setViewName("/WEB-INF/jsp/show.jsp");
        return modelAndView;
    }

}

3.4 在\webapp\WEEB-INF目录下创建爱你show.jsp

这里如果EL表达式的数据无法显示时,需要设置忽略:isELIgnored="false"

<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    欢迎:${name}
</body>
</html>

3.5 web.xml放在web-apps标签下

<!-- 配置前端控制器,否则访问404 -->
<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <!-- 加载配置文件 -->
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:springmvc.xml</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

3.6 在main目录下创建resources目录,然后创建springmvc.xml

    <!-- springmvc三大组件 -->
    <mvc:annotation-driven></mvc:annotation-driven>
    <!-- 放行静态资源:html,css,js... -->
    <mvc:default-servlet-handler></mvc:default-servlet-handler>
    <!-- 扫描对应包下的注解 -->
    <context:component-scan base-package="包名"></context:component-scan>

3.7 访问http://localhost:8080/login.jsp

4. 视图解析器

4.1 在springmvc中配置

        <!-- 配置spingmvc组件 -->
        <mvc:annotation-driven/>
        <!-- 扫描包下注解 -->
        <context:component-scan base-package="包名"></context:component-scan>
        <!-- 视图解析器 -->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
                <!-- 设置前缀 -->
                <property name="prefix" value="/WEB-INF/jsp"></property>
                <!-- 设置后缀 -->
                <property name="suffix" value=".jsp"></property>
        </bean>

4.2 修改 UserController

@Controller
@RequestMapping("/user")//一级请求路径
public class UserController {
    @RequestMapping("/login")//二级请求路径
    public ModelAndView login(){
        System.out.println("login..");
        //创建ModelAndView对象
        ModelAndView modelAndView = new ModelAndView();
        //保存数据
        modelAndView.addObject("name","张三");
        //设置跳转的路径
        //modelAndView.setViewName("/WEB-INF/jsp/show.jsp");
        modelAndView.setViewName("/show");
        return modelAndView;
    }
}

5. 用户请求参数接收

5.1 基本数据类型形式收参

/*
测试 URL:
   http://localhost:8080/param/test2?id=10&username=张三&gender=0&birth=2023-01-09
   URL 参数中的时间方式是 前端提交数据的日期格式,yyyy-MM-dd HH:mm:ss
   但是 Spring MVC 支持的时间格式 yyyy/MM/dd HH:mm:ss, 前端提交的时间日期形式不满足 Spring MVC 需求
   @DateTimeFormat 注解来转换时间格式,提供给 Spring MVC 识别
 */
@RequestMapping("/test2")
public String testParam2(Integer id,
                         String username, 
                         Boolean gender,
                         @DateTimeFormat(pattern = "yyyy-MM-dd") Date birth) {
    /*
    展示用户请求参数数据内容
     */
    System.out.println("ID : " + id);
    System.out.println("Username : " + username);
    System.out.println("Gender : " + gender);
    System.out.println("Birth : " + birth);
    return "hello";
}

5.2 实体类收参

/*
测试 URL:
   http://localhost:8080/param/test3?id=10&username=张三&gender=0&birth=2023-01-09&hobby=唱&hobby=跳&hobby=rap&hobby=篮球
   Spring MVC 可以自动 JavaBean 对象实例化过程,同时根据用户请求参数给予成员变量赋值操作(IOC DI)
   日期问题依然存在 birth=2023-01-09 无法直接转换成 JavaBean 对象中 对应  java.util.Date 日期类型
   【解决问题】
       修改 JavaBean 实体类
       private @DateTimeFormat(pattern = "yyyy-MM-dd") Date birth;
   【数据结果】
       User(id=10, username=张三, gender=false, birth=Mon Jan 09 00:00:00 CST 2023, hobby=[唱, 跳, rap, 篮球])
 按照原本的 Servlet 形式
    service(HttpServletRequest req, HttpServletResponse resp) {
        Map<String, Object[]> map = req.getParameterMap();
        User user = new User();
        BeanUtils.populate(user, map); // 有异常
        // user 对象赋值完毕
    }
    以上过程现在由 Spring MVC 完成。
 */
@RequestMapping("/test3")
public String testParam3(User user) {
    System.out.println(user);
    return "hello";
}

5.3 数组收参

/*
测试 URL:
     http://localhost:8080/param/test4?hobby=唱&hobby=跳&hobby=rap&hobby=篮球&hobby=打游戏
     【注意】
        方法中的参数名称/形式参数列表中的数组参数名称,必须和用户请求 URL 参数名称一致。
        主要处理多选问题,多选操作提交数据在后台语言中基本上都是数组形式(Java PHP)
     数据结果:
        [唱, 跳, rap, 篮球, 打游戏]
 */
@RequestMapping("/test4")
public String testParam4(String[] hobby) {
    System.out.println(Arrays.toString(hobby));
    return "hello";
}

5.4 路径收参

/*
 测试 URL:
     http://localhost:8080/param/test5/20
     test5/{id} 表示 URL 请求资源名称之后 / 第一个参数是对应 id 数据
     id=10
     testParam5(Integer id);
        id = null
     【原因】 Spring MVC 尚未匹配识别参数,@RequestMapping("/test5/{id}") 告知了数据规则
     但是 Spring MVC 不知道 id 对应的数据给予哪一个变量
     【解决方法】
        testParam5(@PathVariable("id") Integer id);
        方法参数列表中,使用 @PathVariable 注解告知 Spring MVC @RequestMapping("/test5/{id}") 中
        声明路径参数 id 对应的变量是哪一个
        /test5/{id} ==> Integer id
 */
@RequestMapping("/test5/{id}")
public String testParam5(@PathVariable("id") Integer id) {
    System.out.println("ID : " + id);
    return "hello";
}
/*
 测试 URL:
     http://localhost:8080/param/test6/20/张三
 */
@RequestMapping("/test6/{id}/{username}")
public String testParam6(@PathVariable("id") Integer id, @PathVariable("username") String username) {
    System.out.println("ID : " + id);
    System.out.println("Username : " + username);
    return "hello";
}

5.5 SpringMVC 编码集过滤器配置

<!--
设置一个编码集过滤器 使用的是 Spring MVC 中的  CharacterEncodingFilter
设置初始化参数 encoding=utf-8 使用 XML filter 参数形式
Spring MVC 编码集过滤器会完成以下内容操作
    request.setCharacterEncoding("utf-8");
-->
<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>
    <!-- 整个 Web Application 中所有的资源都要进行编码集过滤操作 -->
    <url-pattern>/*</url-pattern>
</filter-mapping>

6. 转发和重定向

6.1 转发

转发操作:
    1. 用户有且只请求一次
    2. 转发过程中所有服务器内部资源都可以得到用户的请求参数内容
    3. 转发操作是服务器行为
    4. 转发操作只能是服务器内部资源
    5. 转发操作用户请求 URL 不变
    【核心】
        用户请求数据存储于 HttpServletRequest 对象。
            Attribute 属性进行数据的增删改查
            void setAttribute(String name, Object value);
            void removeAttribute(String name);
            Object getAttribute(String name);
    【转发操作代码】
        HttpServletRequest
            request.getRequestDispatcher("location")
                    .forword(HttpServletRequest, HttpServletResponse)

// 转发操作测试 Controller
@Controller
@RequestMapping("/forward")
public class ForwardController {
    // 测试 URL:http://localhost:8080/forward/test1
    @RequestMapping("/test1")
    public String testForward1() {
        System.out.println("转发操作 test1");
        return "forward:/hello.jsp";
    }
    // 测试 URL: http://localhost:8080/forward/test2
    @RequestMapping("/test2")
    public String testForward2() {
        System.out.println("转发操作 test2");
        return "forward:/view/hello2.jsp";
        //return "forward:view/hello2.jsp";
    }
    //测试 URL:http://localhost:8080/forward/test3
    @RequestMapping("/test3")
    public String testForward3() {
        System.out.println("转发操作 test3");
        return "forward:test1";
        return "forward:/forward/test1";
    }
}

6.2 重定向

1. 用户请求多次,至少2次。
    2. 重定向操作,后期资源无法获取到用户之前的请求参数
    3. 重定向操作是浏览器行为
    4. 重定向可以访问服务器内部资源,也可以访问服务器外部资源
    5. 重定向操作用户请求 URL 改变
    【重定向操作代码】
        HttpServletResponse
            response.sendRedirect("location");

@Controller
@RequestMapping("/redirect")
public class RedirectController {
    //测试 URL:http://localhost:8080/redirect/test1
    @RequestMapping("/test1")
    public String test1() {
        System.out.println("重定向 test1");
        return "redirect:/hello.jsp";
    }
    //测试 URL:http://localhost:8080/redirect/test2
    @RequestMapping("/test2")
    public String test2() {
        System.out.println("重定向 test2");
        return "redirect:https://www.baidu.com";
    }
    //测试 URL: http://localhost:8080/redirect/test3
    @RequestMapping("/test3")
    public String test3() {
        System.out.println("重定向 test3");
        //return "redirect:test1";
        return "redirect:/redirect/test1";
    }
}

转发操作:适用于查询,可以利用request域对象传递数据

重定向:适用于更新,删除操作,避免多次操作的情况,同事利用sessiony域对象传递数据

7. 传值

将controller的数据在页面中展示 1.通过model

@Controller
@RequestMapping("/data")
public class DataController {
     //测试 URL:http://localhost:8080/data/test1
    @RequestMapping("/test1")
    public String test1(HttpServletRequest request, HttpSession session) {
        request.setAttribute("msg", "你最帅");
        session.setAttribute("msg", "你等着~~~");
        return "data";
    }
    //测试 URLhttp://localhost:8080/data/test2
    @RequestMapping("/test2")
    public String test2(HttpServletRequest request, HttpSession session) {
        User user = new User();
        user.setId(10);
        user.setUsername("张三");
        user.setGender(false);
        user.setBirth(new Date());
        // Request 域对象
        request.setAttribute("user", user);
        User user1 = new User();
        user1.setId(20);
        user1.setUsername("李四");
        user1.setGender(false);
        user1.setBirth(new Date());
        // HttpSession 域对象
        session.setAttribute("user", user1);
        User user2 = new User();
        user2.setId(30);
        user2.setUsername("王五");
        user2.setGender(false);
        user2.setBirth(new Date());
        // ServletContext / Application 域对象
        request.getServletContext().setAttribute("user", user2);
        return "data2";
    }
}

data.jsp

<%--
  Created by IntelliJ IDEA.
  User: Anonymous
  Date: 2023/1/9
  Time: 15:59
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title></title>
</head>
<body>
<h1>Request 域对象数据 : ${requestScope.msg}</h1>
<h1>Session 域对象数据 : ${sessionScope.msg}</h1>
</body>
</html>

data2.jsp

<%--
  Created by IntelliJ IDEA.
  User: Anonymous
  Date: 2023/1/9
  Time: 15:59
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title></title>
</head>
<body>
<h1>ID : ${requestScope.user.id}</h1>
<h1>Username : ${requestScope.user.username}</h1>
<h1>Gender : ${requestScope.user.gender}</h1>
<h1>Birth : ${requestScope.user.birth}</h1>
<hr>
<h1>ID : ${user.id}</h1>
<h1>Username : ${user.username}</h1>
<h1>Gender : ${user.gender}</h1>
<h1>Birth : ${user.birth}</h1>
</body>
</html>

2. 通过ModelAndView

@RequestMapping("/test4")
public ModelAndView test4() {
    //实例化 ModelAndView 对象
    ModelAndView mv = new ModelAndView();
    //等价于原本的 return "data2";设置 View 目标资源名称,方便转发操作
    mv.setViewName("data2");
    User user = new User();
    user.setId(10);
    user.setUsername("张三");
    user.setGender(false);
    user.setBirth(new Date());
    //添加数据到 ModelAndView 对象中,当前存储的数据会同步【复制】 Request 对象中
    //等价于 ==> request.setAttribute("user", user);
    mv.addObject("user", user);

    // 返回 ModelAndView 对象,交给 SpringMVC 视图解析操作
    return mv;
}

3.  sessionAttributes

@SessionAttributes({"userRoleId", "userRoleName"})

告知 SpringMVC 当前 Controller 中的 userRoleId 和 userRoleName 是存储在 Session 域对象中,可以直接通过Model 对象和 ModelAndView 对象操作

SessionStatus status;
status.setComplete();

通过 SessionStatus 可以删除Session 中存储的数据内容

/*
 测试使用 URL
     http://localhost:8080/data/test5
 */
@RequestMapping("/test5")
public String test5(Model model) {
    /*
    Spring MVC 根据
        @SessionAttributes({"userRoleId", "userRoleName"})
        明确以上的数据名称是存储在 Session 对象中

    【注意】
        Model 添加数据,一定会给 Request 对象复制一份对应的数据
     */
    model.addAttribute("userRoleId", 1);
    model.addAttribute("userRoleName", "管理员");

    return "data2";
}

/*
 测试使用 URL
     http://localhost:8080/data/test6
 */
@RequestMapping("/test6")
public ModelAndView test6() {
    ModelAndView mv = new ModelAndView();
    /*
    Spring MVC 根据
        @SessionAttributes({"userRoleId", "userRoleName"})
        明确以上的数据名称是存储在 Session 对象中
    使用的是 ModelAndView 对象,因为 @SessionAttributes 对应的数据
    都是存储在 Session 对象中
    【注意】
        ModelAndView 添加数据,一定会给 Request 对象复制一份对应的数据
     */
    mv.addObject("userRoleId", 1);
    mv.addObject("userRoleName", "管理员");
    mv.setViewName("forward:/data2.jsp");
    return mv;
}
/*
测试使用 URL
    http://localhost:8080/data/test7
 */
@RequestMapping("/test7")
public String test7(SessionStatus status) {
    /*
    清除 Session 对象中存储的数据内容,无法清除 Request 中存储的数据内容
     */
    status.setComplete();
    return "data2";
}

8. 静态资源处理

项目中不能直接访问html,css,js,图片等静态资源(下面写在springmvc的配置文件)

<!--
Spring MVC 注册 Tomcat 默认 Servlet 程序,使用 Tomcat defaultServlet 处理
静态资源内容
-->
<mvc:default-servlet-handler/>

9.  Json 数据接收和反馈

注意:设置返回结果为JSON格式,SpringMVC 视图解析器不再工作

9.1 导入依赖

以springmvc默认支持的json格式处理数据的形式采用的依赖是jackson,需要导入jackson

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.8</version>
</dependency>

9.2@ResponseBody

自动将返回的数据结果转换为对应的JSON格式数据(或RestCpntroller=controller+responsebody)

@Controller
@RequestMapping("/json")
public class JsonController {
    //测试使用 URL: http://localhost:8080/json/test1
    @RequestMapping("/test1")
    @ResponseBody
    public User test1() {
        User user = new User(1, "张三", false, new Date(), new String[]{"篮球", "足球", "乒乓球"});
        /*
        @ResponseBody 默认当前 User 对象会转换为 JSON 格式数据,使用的第三方工具是 Jackson
        前端收到数据内容情况
        {
            "id": 1,
            "username": "张三",
            "gender": false,
            "birth": 1673321204313,
            "hobby": [
                "篮球",
                "足球",
                "乒乓球"
            ]
        }
         */
        return user;
    }
    测试使用 URL: http://localhost:8080/json/test2
    @RequestMapping("/test2")
    @ResponseBody
    public Map<String, Object> test2() {
        HashMap<String, Object> map = new HashMap<>(5);
        map.put("id", 1);
        map.put("username", "杰哥是个梗");
        map.put("gender", false);
        map.put("birth", "2001-01-01");
        map.put("hobby", new String[]{"篮球", "足球", "乒乓球"});
        return map;
    }
    测试使用 URL: http://localhost:8080/json/test3
    @RequestMapping("/test3")
    @ResponseBody
    public List<User> test3() {
        List<User> list = new ArrayList<>();
        for (int i = 0; i < 2; i++) {
            list.add(new User(i + 1, "杰哥是个梗" + i, false
                    , new Date(), new String[]{"篮球", "足球", "乒乓球"}));
        }
        return list;
    }
}

9.3 返回值为字符串

/*
value = "/test4" 当前资源名称为 /test4 对应的 URL
    http://localhost:8080/json2/test4
produces 防止出现中文乱码问题,同时约束当前返回类型为 可视化文本形式,纯字符串
 */
@RequestMapping(value = "/test4", produces = "text/html;charset=utf-8")
public String test4() {
    return "字符串";
}

9.4 @RequestBody

告知当前数据为 JSON 数据,并自可以自动完成映射关系

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>测试 HTML</title>
    <script src="/js/jquery-2.1.0.min.js"></script>
</head>
<body>
<button style="font-size: 32px" id="btn" onclick="sendJson()">Ajax 数据请求</button>
<script>
    var user = {id:1, username:'天下第一帅', gender:false, birth:'2001-11-11', hobby:['台球', '棒球', '橄榄球']};
    var userJson = JSON.stringify(user);
    function sendJson() {
        $.ajax({
            // url: "http://localhost:8080/json2/test6",
            url: "http://localhost:8080/json2/test5",
            method: "post",
            data: userJson,
            contentType: "application/json",
            success: function (ret) {
                alert(ret);
            }
        })
    }
</script>
</body>
</html
@RequestMapping(value = "/test5", produces = "text/html;charset=utf-8")
/*
@RequestBody User user
    Spring MVC 使用 Jackson 工具将 JSON 数据自动转换为 User 对象

    HttpServletResponse response;
    response.setContextType("text/html;charset=utf-8");
    response.getWriter().append("OK");
 */
public String test5(@RequestBody User user) {
    System.out.println(user);
    return "OK";
}

@RequestMapping(value = "/test6", produces = "text/html;charset=utf-8")
/*
@RequestBody User user
    Spring MVC 使用 Jackson 工具将 JSON 数据自动转换为 Map 双边队列
    HttpServletResponse response;
    response.setContextType("text/html;charset=utf-8");
    response.getWriter().append("OK");
 */
public String test5(@RequestBody Map<String, Object> map) {
    System.out.println(map);
    return "OK";
}

9.5 jackson常用注解

日期格式操作(将美国时间改为东八区时间)

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date birth;

修改JSON格式当前数据的名称别名

@JsonProperty("stu_id") // {id:1} ==> {stu_id:1}
private Integer id;

指定字段不参与JSON格式数据的生成

@JsonIgnore
private String address;

对应数据如果为null,则不参与JSON数据生成

@JsonInclude(value = JsonInclude.Include.NON_EMPTY)
private List<String> hobby;

对应数据如果为null或者数据的容量为0,则不参与数据生成

@JsonInclude(value = JsonInclude.Include.NON_NULL)
private String name;

修改数据的打包方式

// 引入下方的类
@JsonSerialize(using = MySerializer.class)
private Double salary = 10000.124;

public class MySerializer extends JsonSerializer<Double> {
    @Override
    public void serialize(Double value,
                          JsonGenerator gen,
                          SerializerProvider serializers) throws IOException {
        /*
        Double value 对应的就是 private Double salary = 10000.126;
        10000.126; ==> 10000.13
         */
        String s = BigDecimal.valueOf(value).setScale(2, RoundingMode.HALF_UP).toString();
        gen.writeNumber(s);
    }
}

补充:另外一种json数据接收和返回的工具 FastJson

//导入依赖
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.47</version>
</dependency>

//在springmvc中配置
<!-- 注册 MVC 注解驱动,提供注解方式配置 MVC 项目模型 -->
<mvc:annotation-driven>
    <!-- 引入 FastJson 支持 JSON 格式解析操作,同时引入 FastJson 特征的注解操作 -->
    <mvc:message-converters>
        <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
            <property name="supportedMediaTypes">
                <list>
                    <value>application/json</value>
                </list>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

//FastJosn注解
-------------JSON 格式信息名称修改
@JSONField(name = "teacher_id")
private Integer id;
-------------JSON 日期数据格式约束
@JSONField(format = "yyyy-MM-dd")
private Date birth;
-------------JSON 数据忽略当前成员变量
@JSONField(serialize = false)
private String address;
-------------Json中为null时忽略
@JSONField(serialzeFeatures = SerializerFeature.WriteMapNullValue)
private List<String> hobby;
-------------JSON 格式数据转换规则自定义
@JSONField(serializeUsing = MySerializer2.class)
private Double salary = 10000.124;
public class MySerializer2 implements ObjectSerializer {
    @Override
    public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException {
        System.out.println();
        System.out.println("object : " + object);
        System.out.println("fieldName : " + fieldName);
        System.out.println("fieldType : " + fieldType);
        System.out.println();
        Double ret = (Double) object;
        String s = BigDecimal.valueOf(ret).setScale(2, RoundingMode.HALF_UP).toString();
        String msg = s + "元";
        serializer.write(msg);
    }
}

10.  异常解析器

controller调用service,service调用dao,异常都是向上抛出的,最终有DispatcherServlet找异常处理器进行异常处理。

 在controller中添加相关方法进行测试

@RequestMapping("testException")
public void testException()throws UserException{
    //String str = null;
    //str.length();
    //int i = 1/0;
    throw new UserException("用户模块发生异常");
}

手动创建自定义异常类

/**
 * 自定义异常
 */
public class UserException extends Exception{
    public UserException(String message) {
        super(message);
    }
}

创建异常处理器

/**
 * 全局异常处理类
 */
public class MyExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request,
                                         HttpServletResponse response,
                                         Object handler,
                                         Exception ex) {
        System.out.println("当发生任何异常时,会执行MyExceptionResolver");
        ModelAndView modelAndView = new ModelAndView();
        //判断不同的异常可以跳转到不同的页面
        if(ex instanceof NullPointerException){
            //发生空指针异常时,ex.getMessage()在jsp页面上可能不会显示任何内容,这里可以指定内容
            modelAndView.addObject("msg","空指针异常");
        }else if(ex instanceof ArithmeticException){
            modelAndView.addObject("msg", ex.getMessage());
        }else if(ex instanceof UserException){
            modelAndView.addObject("msg", ex.getMessage());
        }else{
            modelAndView.addObject("msg", ex.getMessage());
        }
        //跳转页面,如果配置了视图解析器需注意路径的编写
        modelAndView.setViewName("/error.jsp");
        return modelAndView;
    }
}

在springmvc.xml中配置

<!-- 配置全局异常处理 -->
<bean class="com.qf.resolver.MyExceptionResolver"></bean>

在指定位置编写error.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
当前异常信息:${msg}
</body>
</html>

测试

访问测试:http://localhost:8080/user/testException

11. 拦截器

概念:SpringMVC框架中的拦截器用于对处理器进行预处理和后处理的技术

可以定义拦截器链,连接器链就是将拦截器按着一定的顺序结成一条链,在访问被拦截的方法时,拦截器链中的拦截器会按着定义的顺序执行。

过滤器是基于函数回调,拦截器是基于java的反射机制(动态代理)实现的

过滤器是Servlet规范的一部分,任何框架都可以使用过滤器技术,拦截器是springmvc框架独有的

过滤器配置了/*,可以拦截任何资源,拦截器只会对控制中的方法进行拦截。

过滤器和拦截器都是AOP思想的一种实现方式,想要自定义拦截器,需要实现HandlerInterceptor接口

-------在controller中添加相关方法进行测试

//测试拦截器
@RequestMapping("testInterceptor1")
public void testInterceptor1(){
    System.out.println("testInterceptor1");
}
@RequestMapping("testInterceptor2")
public void testInterceptor2(){
    System.out.println("testInterceptor2");
}

-------创建自定义拦截器

//自定义拦截器
public class MyInterceptor implements HandlerInterceptor {
    /**
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     * 访问 web层中的方法前执行,返回值为 true时,则继续向下执行controller中的方法,false表示不放行
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle");
        //preHandle 方法一般用于判断访问路径是否满足要求
        return true;
    }
    /**
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     * 调用完 controller中的方法后执行
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle");
    }
    /**
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     * 最后执行
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion");
    }
}

在springmvc.xml

        <!-- 配置拦截器 -->
        <mvc:interceptors>
                <!-- mvc:interceptor该标签可以写多个 -->
                <mvc:interceptor>
                        <!-- 配置要拦截的路径 -->
<!--                        <mvc:mapping path="/user/testInterceptor1"/>-->
                        <!-- 拦截所有,或指定拦截某一个路径下的子路径,例如:path=/user/** -->
                        <!-- 如果只拦截html页面,可以写成:path="*.html" -->
                        <mvc:mapping path="/**"/>
                        <!-- 还可以指定哪些路径不拦截,不能单独使用该标签 -->
                        <mvc:exclude-mapping path="/user/testInterceptor1"/>

                        <!-- 配置拦截器的bean -->
                        <bean class="com.qf.interceptor.MyInterceptor"></bean>
                </mvc:interceptor>

        </mvc:interceptors>

测试

访问测试:http://localhost:8080/user/testInterceptor1

11.  文件上传

SpringMVC框架提供了MultipartFile对象,该对象表示上传的文件,要求变量名称必须和表单file标签的 name属性名称相同。

11.1 在pom.xml中导入依赖

<!-- spring-webmvc依赖中传递了spring的核心依赖 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.20</version>
</dependency>

<!-- tomcat中自带servlet和jsp的jar包,这里根据需求导入 -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
</dependency>

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.3</version>
</dependency>

<!-- 文件上传 -->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>

</dependencies>

11.2 在springmvc.xml中配置文件解析器, id 名称必须:multipartResolver

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

        <!-- 配置spingmvc组件 -->
        <mvc:annotation-driven/>

        <!-- 扫描包下注解 -->
        <context:component-scan base-package="com.qf"></context:component-scan>

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

        <!-- 设置所有静态资源不被拦截 -->
        <mvc:default-servlet-handler/>

        <!-- 配置文件上传解析器对象,id的值必须是:"multipartResolver"  -->
        <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
                <!-- 上传单个文件大小限制(单位:byte) -> x 1024 -> KB -> x 1024 -> M -->
                <!--<property name="maxUploadSizePerFile" value="104857600"></property>-->
                <!-- 上传整个请求大小限制(byte) -->
                <!--<property name="maxUploadSize" value="104857600"></property>-->
        </bean>

</beans>

11.3 创建upload.html页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <!-- 上传表单:method="post",enctype="multipart/form-data" -->
    <form action="/user/upload" method="post" enctype="multipart/form-data">
        姓名*:<input type="text" name="name" required="required"> <br>
        健康码,行程码截图*:<input type="file" name="img" required="required"><br>
        <input type="submit" value="保存">
    </form>
</body>
</html>

11.4 实体类

@Data
public class User implements Serializable {
    private Integer id;
    private String name;
    //该属性主要用于存储上传的图片的名称
    private String imgUrl;
}

11.5 创建Controller

@RestController
@RequestMapping("/user")
public class UserController {
    /**
     * 文件上传
     * MultipartFile 参数名 要和
     *      表单中<input type="file" name="img" required="required"> name属性的属性值相同
     *
     * @param img
     * @return
     */
    @RequestMapping("/upload")
    public String upload(MultipartFile img, User user){
        //获取上传图片的文件名
        String filename = img.getOriginalFilename();
        System.out.println(filename + "--" + user);
        //设置要上传的路径,我们是解压一个Tomcat作为一个模拟图片(对象存储)服务器来使用
        String uploadPath = "C:\\Users\\72995\\Desktop\\apache-tomcat-9.0.33\\webapps\\upload\\";
        //为了保证上传文件的名称的唯一性,使用UUID作为前缀
        String uploadFileName = UUID.randomUUID().toString()
                    .replace("-","") + filename;
        //创建文件对象
        File file = new File(uploadPath,uploadFileName);
        try {
            //上传
            img.transferTo(file);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //后续操作,把当前表单中的数据插入导数据库
        //以Tomcat作为服务器为例
        //String imgUrl = "http://localhost:8081/upload/"+uploadFileName;
        //user.setImgUrl(imgUrl);
        //调用插入用户的方法,伪代码演示
        //userService.addUser(user);
        return "success";
    }

}

web.xml

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>

  <!-- 过滤器 -->
  <filter>
    <filter-name>encodingFilter</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>

    <init-param>
      <param-name>forceRequestEncoding</param-name>
      <param-value>true</param-value>
    </init-param>

    <!--    <init-param>-->
    <!--      <param-name>forceResponseEncoding</param-name>-->
    <!--      <param-value>true</param-value>-->
    <!--    </init-param>-->
  </filter>

  <filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>


  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springmvc.xml</param-value>
    </init-param>

  </servlet>

  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

</web-app>

测试:

访问:http://localhost:8080/upload.html

12. 验证码

作用:防止暴力攻击,服务器资源访问限制,保障服务器资源安全和稳定

12.1 Google 提供的验证码工具

导入依赖

<!-- Kaptcha -->
<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>2.3.2</version>
    <exclusions>
        <exclusion>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
        </exclusion>
    </exclusions>
</dependency>

在web.xml文件中配置Servlet资源

<servlet>
    <servlet-name>cap</servlet-name>
    <servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>
    <!--
    关注验证码的核心参数
    1. 验证码字符个数
    2. 验证码可以使用字符范围
    3. 验证码 Session 存储
    -->
    <!-- 验证码字符个数 -->
    <init-param>
        <param-name>kaptcha.textproducer.char.length</param-name>
        <param-value>4</param-value>
    </init-param>
    <!-- 验证码可以使用字符范围 -->
    <init-param>
        <param-name>kaptcha.textproducer.char.string</param-name>
        <param-value>abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789</param-value>
    </init-param>
    <!-- 验证码 Session 存储 -->
    <init-param>
        <!--
        KaptchaServlet 执行
        session.setAttribute("captcha", "验证码");
         -->
        <param-name>kaptcha.session.key</param-name>
        <param-value>captcha</param-value>
    </init-param>
    <!-- 验证码边框取消 -->
    <init-param>
        <param-name>kaptcha.border</param-name>
        <param-value>no</param-value>
    </init-param>
    <!-- 验证码背景颜色渐变 -->
    <init-param>
        <param-name>kaptcha.background.clear.to</param-name>
        <param-value>211,229,237</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>cap</servlet-name>
    <!-- 当前使用 Google 验证码生成 Servlet 资源名称,和验证码工具类一致 -->
    <url-pattern>/captcha</url-pattern>
</servlet-mapping>

12.2 验证码和刷新Handler

@Controller
// 要求和 web.xml 注册的 验证码 Servlet 程序资源名称一致
@RequestMapping("/captcha")
public class CaptchaController {

    /**
     * 验证码后台校验方法
     *
     * @param captcha 前端提交的 验证码信息
     * @param session Session 对象需要 Spring 自动注入
     * @return 成功返回 index,失败返回到 error
     */
    @RequestMapping("/check")
    public String check(String captcha, HttpSession session) {
        return captcha.equalsIgnoreCase((String) session.getAttribute("captcha"))
                ? "index" : "error";
    }
}

前端使用:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/captcha/check">
    <!-- 直接申请验证码注册的 Servlet 程序,img 标签会自动展示验证码图片 -->
    <img id="captcha_img" src="/captcha" style="width: 200px" onclick="refresh()">
    <input type="text" name="captcha"> <br>
    <input type="submit" value="验证">
</form>

<script>
    function refresh() {
        var img = document.getElementById('captcha_img');
        // img src 申请验证码对应的 Servlet 程序,提交参数是一个时间
        img.src = "/captcha?" + new Date().getTime();
    }
</script>
</body>
</html>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值