学静思语
Published on 2025-02-15 / 0 Visits
0
0

SpringMVC框架

SpringMVC框架

一、SpringMVC介绍

1. 官方文档

  • 地址:spring-framework-5.3.8/docs/reference/html/web.html#spring-web

2. SpringMVC 基本介绍

2.1 SpringMVC特点和概述

  • SpringMVC 从易用性、效率上 比曾经流行的Struts2更好
  • SpringMVC 是Web层的框架【SpringMVC接管了Web层的组件,比如控制器、视图、视图解析器,返回给用户的数据格式,同时支持MVC的开发模式/开发架构】
  • SpringMVC通过注解,让pojo(普通的Java类)成为控制器,不需要继承类或者实现接口
  • SpringMVC采用低耦合的组件设计方式,具有更好的扩展和灵活性
  • 支持REST格式的URL请求风格
  • SpringMVC 是基于Spring的,也就是SpringMVC是在Spring基础上的。SpringMVC的核心包spring-webmvc-xx.jar和spring-web-xx.jar

2.2 梳理Spring、SpringMVC和SpringBoot的关系

  • SpringMVC只是Spring处理WEB层请求的一个模块/组件,SpringMVC 的基石是Servlet
  • SpringBoot 是为了简化开发者的使用,推出的封神框架(约定优于配置,简化了Spring的配置流程),SpringBoot包含很多组件/框架,Spring就是最核心的内容之一,也包含SpringMVC
  • 它们的关系大概是:SpringBoot > Spring > SpringMVC

3. 快速入门

3.1 需求说明

  • 需求说明:完成一个最基本的测试案例,登录案例

3.2 代码实现

3.2.1 Controller包
package com.leon.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * ClassName:UserController
 * Package:com.leon.controller
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/4/13 8:42
 * @Version: 1.0
 */
@Controller
public class UserServlet {


   /*
   *  1.login() 方法用于响应用户的登录请求,其被RequestMapping注解标注
   *  2.RequestMapping注解可以将一个方法变成可以访问的方法,通过指定的URL进行请求
   *    value属性值表示请求的url,也可以理解为在web.xml文件中配置的<url-pattern></url-pattern>
   *  3.注意这个注解可以在类上进行标注,也可以给类进行value赋值,这时就需要加上类的URL
   *  4.即当用户在浏览器输入 http://localhost:8080/web工程路径/{类的URL}/login
   *  5.return "login_ok" 表示返回结果给视图解析器(InternalResourceViewResolver)
   *    视图解析器会根据配置,来决定跳转到哪个界面
   *   <!--配置SpringMVC的视图解析器-->
   * <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
   *    <!--配置前缀,是要访问的包-->
   *     <property name="prefix" value="/WEB-INF/pages/"></property>
   *     <!--配置后缀,是要访问的文件类型 -->
   *    <property name="suffix" value=".jsp"></property>
   * </bean>
   *  根据上面的配置,return "login_ok",将会被转发到/WEB-INF/pages/login_ok.jsp
   * */
   @RequestMapping(value = "/login")
   public String login(){
      System.out.println("登录成功~~~~~");
      return "login_ok";
   }
}
3.2.2 ioc文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <!--配置要扫描的包路径-->
    <context:component-scan base-package="com.leon.controller"/>

    <!--配置SpringMVC的视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--配置前缀,是要访问的包-->
        <property name="prefix" value="/WEB-INF/pages/"></property>
        <!--配置后缀,是要访问的文件类型 -->
        <property name="suffix" value=".jsp"></property>
    </bean>
</beans>
3.2.3 web.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
        <!--配置前端控制器/中央处理器/分发控制
            1.用户的请求会经过它的处理器
        -->
        <servlet>
            <servlet-name>springDispatcherServlet</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <!--配置ioc容器文件-->
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:applicationcontext_mvc.xml</param-value>
            </init-param>
            <!--配置加载顺序-->
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>springDispatcherServlet</servlet-name>
            <!--
                1.这里配置的url-pattern是 / ,表示用户的请求都经过 DispatcherServlet
                2.这样配置也支持rest 风格的url请求
            -->
            <url-pattern>/</url-pattern>
        </servlet-mapping>
</web-app>
3.2.4 jsp文件
<%--
  Created by IntelliJ IDEA.
  User: ZGJ
  Author: leon --> ZGJ
  Version: 1.0
  Date: 2024/4/13
  Time: 8:39
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h1>登录</h1>
    <form action="login" method="post">

        u:<input type="text" name="user"><br>
        p:<input type="password" name="pwd"><br>

        <input type="submit" value="登录">

    </form>

</body>
</html>
======================================================================================
<%--
  Created by IntelliJ IDEA.
  User: ZGJ
  Author: leon --> ZGJ
  Version: 1.0
  Date: 2024/4/13
  Time: 9:12
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1>登录成功~~~</h1>

</body>
</html>

3.3 注意事项和使用细节

  • UserServlet 需要注解成Controller,称之为handler处理器

  • RequestMapping注解在指定url时,这个value属性名可以省略

  • 关于SpringMVC 的DispatcherServlet的init-param中的contextConfigLocation配置,如果不在web.xml指定applicationcontext_mvc.xml,默认会在/WEB-INF/springDispatcherServle-servlet.xml找这个配置文件

    image-20240413103559061

4. SpringMVC执行流程

SpringMVC执行流程图

二、SpringMVC的使用

1. @RequestMapping 注解

1.1 基本使用

  • @RequestMapping 注解可以指定控制器/处理器的某个方法的请求的url,RequestMapping:请求映射
1.1.1 代码
/*
   *  1.login() 方法用于响应用户的登录请求,其被RequestMapping注解标注
   *  2.RequestMapping注解可以将一个方法变成可以访问的方法,通过指定的URL进行请求
   *    value属性值表示请求的url,也可以理解为在web.xml文件中配置的<url-pattern></url-pattern>
   *  3.注意这个注解可以在类上进行标注,也可以给类进行value赋值,这时就需要加上类的URL
   *  4.即当用户在浏览器输入 http://localhost:8080/web工程路径/{类的URL}/login
   *  5.return "login_ok" 表示返回结果给视图解析器(InternalResourceViewResolver)
   *    视图解析器会根据配置,来决定跳转到哪个界面
   *   <!--配置SpringMVC的视图解析器-->
   * <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
   *    <!--配置前缀,是要访问的包-->
   *     <property name="prefix" value="/WEB-INF/pages/"></property>
   *     <!--配置后缀,是要访问的文件类型 -->
   *    <property name="suffix" value=".jsp"></property>
   * </bean>
   *  根据上面的配置,return "login_ok",将会被转发到/WEB-INF/pages/login_ok.jsp
   * */
   @RequestMapping(value = "/login")
   public String login(){
      System.out.println("登录成功~~~~~");
      return "login_ok";
   }

1.2 @RequestMapping 其它使用方式

1.2.1 @RequestMappping 可以修饰方法和类
  • 说明
  • @RequestMapping 注解可以修饰方法,还可以修饰类当同时修饰类和方法时,请求的url就是类和方法的value组合,格式为:/类请求值/方法请求值
1.2.1.1 代码
package com.leon.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * ClassName:UserHandler
 * Package:com.leon.controller
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/4/13 13:34
 * @Version: 1.0
 */
@RequestMapping("/user")
@Controller
public class UserHandler {


      /*
      *  1. method=RequestMethod.POST: 表示请求buy目标方法必须是POST,
      *     如果不是POST请求方式的话则会报错
      *  2. RequestMethod 常用的四个选项POST,GET,PUT,DELETE
      *  3. SpringMVC 控制器默认支持GET和POST两种方式
      *  4. buy方法的请求URL:http://localhost:8080/springmvc/user/buy
      * */
      @RequestMapping(value = "/buy")
      public String buy(){
         System.out.println("购买商品~~~~");
         return "success";

      }

}
======================================================================================

<%--
  Created by IntelliJ IDEA.
  User: ZGJ
  Author: leon --> ZGJ
  Version: 1.0
  Date: 2024/4/13
  Time: 8:39
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h1>登录</h1>
    <form action="user/buy" method="post">

        u:<input type="text" name="user"><br>
        p:<input type="password" name="pwd"><br>

        <input type="submit" value="登录">

    </form>

</body>
</html>
1.2.2 @RequestMapping 可以指定请求方式
  • 说明
  • @RequestMapping还可以指定请求的方式(POST/GET/PUT/DELETE),请求的的方式要和指定的一样,否则会报错
  • SpringMVC 控制器默认支持GET和POST两种方式,也就是你不指定Method,可以接收GET和POST请求
  • 还可以直接使用@GetMapping,@PostMapping,@PutMapping,@DeleteMapping等这些注解,它们类似于 @RequestMapping(value = url , method = RequestMethod.请求方式)
1.2.2.1 代码
package com.leon.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * ClassName:UserHandler
 * Package:com.leon.controller
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/4/13 13:34
 * @Version: 1.0
 */
@RequestMapping("/user")
@Controller
public class UserHandler {


      /*
      *  1. method=RequestMethod.POST: 表示请求buy目标方法必须是POST,
      *     如果不是POST请求方式的话则会报错
      *  2. RequestMethod 常用的四个选项POST,GET,PUT,DELETE
      *  3. SpringMVC 控制器默认支持GET和POST两种方式
      *  4. buy方法的请求URL:http://localhost:8080/springmvc/user/buy
      * */
      @RequestMapping(value = "/buy",method = RequestMethod.POST)
      public String buy(){
         System.out.println("购买商品~~~~");
         return "success";

      }

      @GetMapping(value = "shopping")
      public String shopping(){

         System.out.println("逛商城~~~~");

         return "success";

      }
}
1.2.3 @RequestMapping可以指定params和headers支持简单表达式
  • 说明
  • params =“xxx”,表示请求必须包含名为xxx的请求参数
  • params != “xxx”,表示请求不能包含名为xxx的请求参数
  • params = “xxx != value”,表示请求包含名为xxx的请求参数,但不能为value
  • param = { “xxx = value”,“ttt”}:表示请求必须包含为xxx和ttt的两个请求参数,且xxx参数的值必须为value
  • 注意:方法中的形参名必须要和params的param一样才可以,否则形参值是null,也就是赋值失败
1.2.3.1 代码
 /*
      *  1. params="bookId" 表示请求该方法时,必须给一个bookId参数,只没有要求,请求时,所
      *     携带的参数值会赋值给方法中形参
      *  2. 注意:形参名要和params="bookId"中的请求参数名要一样否则获取不到
      *  3. params = "bookId=100" 表示请求该方法时,必须给一个bookId参数,且值必须是100
      *
      * */

      @RequestMapping(value = "/find",params = "bookId=100",method = RequestMethod.GET)
      public String search(String bookId){
         System.out.println("要买的书的id=="+bookId);

         return "success";
      }
1.2.4 @RequestMapping 支持Ant风格资源地址
  • 说明
  • ? : 配文件中的一个字符
  • * :匹配文件中的任意字符
  • ** :匹配多层路径
  • Ant风格的URL地址举例
    • /user/*/createUser:匹配 /user/aaa/createUser、/user/bbb/createUser等URL
    • /user/**/createUser:匹配/user/createUser、/user/aaa/bbb/createUser等URL
    • /user/createUser??:匹配/user/createUseraa 、/user/createUserbb等URL
1.2.4.1 代码
@RequestMapping(value = "message/**")
      public String im(){
          System.out.println("发送信息");
          return "success";
      }
======================================================================================

    <h1>Ant的风格使用</h1>
    <a href="user/message/aa">发送信息</a>
    <a href="user/message/aa/dd">发送信息</a>
1.2.5 RequestMapping 可配合@PathVariable 映射URL绑定占位符
  • 说明
  • @RequestMapping还可以配合@PathVariable映射URL绑定占位符
  • @PathVariable的value值要和占位符的名称一样,否则会报错
1.2.5.1 代码
 /*
      *     1.{userName} 表示一个占位符,名称为userName,在收到请求后会默认将Url中的值赋值
      *       请求方法的形参,但是需要使用@PathVariable这个注解,且它的value值必须和占位符的
      *       名称一样,否则会报错
      * */
      @RequestMapping(value = "/reg/{userName}/{userId}")
      public String register(@PathVariable("username")String name,
                             @PathVariable("userId")Integer  id){
          System.out.println("注册成功=用户:" + name + "id:" +id);
          return "success";
      }
======================================================================================

<h1>PathVariable的使用</h1>
    <a href="user/reg/lihua/200">发送信息</a>
1.2.6 注意事项和使用细节
  • 映射的URL,不能重复

2. Rest优雅的Url请求风格

2.1 基本介绍

  • REST:即Representational State Transfer。(资源)表现层状态状态转化。是目前流行的请求方式,它结构清晰,很多网站采用
  • HTTP协议中,四个表示操作方式的动词:GET、POST 、PUT、 DELETE ,它们分别对应四种基本操作:GET用来获取资源,PSOT用来新建资源,PUT用来更新资源,DELETE用来删除资源
  • 实例
  • 传统方式
    • getBook?id=1 GET
    • delete?id=1 GET
    • update POST
    • update POST
  • 说明:传统的Url是通过参数来说明crud的类型,rest是通过get/post/put/delete来说明crud的类型
  • REST的核心过滤器
  • 当前浏览器只支持post/get请求,因此为了得到put/delete的请求方式需要使用Spring提供的HiddenHttpMethodFilter过滤器进行转换
  • HiddenHttpMethodFilter:浏览器form表单只支持GET与POST请求,而DELETE、PUT等Method并不支持,Spring添加了一个过滤器,可以将这些请求转换成为标准的HTTP方式,使得支持GET、POST、PUT、DELETE请求
  • HiddenHttpMethodFilter能对POST请求方式进行转换,因此我们需要特别注意这一点
  • 过滤器需要再web.xml中配置

2.2 代码实现

2.2.1 Controller
package com.leon.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

/**
 * ClassName:BookHandler
 * Package:com.leon.controller
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/4/14 13:53
 * @Version: 1.0
 */
@RequestMapping("/book")
@Controller
public class BookHandler {

    @RequestMapping(value = "/get/{id}",method = RequestMethod.GET)
    public String getBook(@PathVariable("id") Integer id) {
        System.out.println("要查询的书的id = " + id);
        return "success";
    }

    @PostMapping(value = "/post")
    public String addBook( String name) {
        System.out.println("要添加的书的name = " + name);
        return "success";
    }

    @PutMapping(value = "/put")
    public String updateBook( String name) {
        System.out.println("要修改的书的name = " + name);
        //return "success";
        return "redirect:/book/success";//这个是重定向,会被解析成/springmvc/book/success
    }

    @DeleteMapping(value = "/delete/{id}")
    public String deleteBook(@PathVariable("id") Integer  id) {
        System.out.println("要删除的书的id = " + id);
        //return "success"; 这样会报错,因为浏览器解析不了DElETE请求
        /*
        *  1.  redirect:/book/success 重定向
        *  2.  这个会被视图解析器解析成:/springmvc/book/success 然后返回给浏览器
        * */
        return "redirect:/book/success";//这个是重定向,会被解析成/springmvc/book/success
    }

    @RequestMapping("/success")
    public String success(){

        return "success";
    }

}
2.2.2 web.xml
 <!--配置HiddenHttpMethodFilter
        1.用与将POST请求转换为PUT、DELETE

    -->
    <filter>
        <filter-name>hiddenHttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>hiddenHttpMethodFilter</filter-name>
        <!--
            1. /* 表示所有请求都需要经过HiddenHttpMethodFilter过滤器
        -->
        <url-pattern>/*</url-pattern>
    </filter-mapping>
2.2.3 ioc文件:applicationcontext_ioc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://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">

    <!--配置要扫描的包路径-->
    <context:component-scan base-package="com.leon.controller"/>

    <!--配置SpringMVC的视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--配置前缀,是要访问的包-->
        <property name="prefix" value="/WEB-INF/pages/"></property>
        <!--配置后缀,是要访问的文件类型 -->
        <property name="suffix" value=".jsp"></property>
    </bean>

    <!--加入两个常规配置-->
    <!--配置支持SpringMVC的高级功能,比如JSR303校验,映射动态请求-->
    <mvc:annotation-driven></mvc:annotation-driven>
    <!--配置将SpringMVC不能处理的请求,交给Tomcat处理,比如css,js-->
    <mvc:default-servlet-handler/>

</beans>
2.2.4 页面
<%--
  Created by IntelliJ IDEA.
  User: ZGJ
  Author: leon --> ZGJ
  Version: 1.0
  Date: 2024/4/14
  Time: 13:48
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<script type="text/javascript" src="script/jquery-3.7.1.min.js"></script>
<script type="text/javascript">
        $(document).ready(function () {
            //获取id=deleteBook的a标签
            $("#deleteBook").click(function (){
                // 获取href的属性值
                var href = this.href;
                // 设置action属性
                $("#hiddenFrom").attr("action",href);
                // 给隐藏的input进行赋值
                $("#hiddenFrom :hidden").val("DELETE");
                // 提交数据
                $("#hiddenFrom").submit();
                // 取消默认行为
                return false;
            })
        });

</script>
<body>

    <h1>rest风格</h1>

    <h2>rest风格查询一本书[get]</h2>
    <a href="book/get/100">查询一本书</a>

    <h2>rest风格添加一本书[post]</h2>
    <form action="book/post" method="post">
        <input type="text" name="name"><br>
        <input type="submit" value="提交">
    </form>

    <h2>rest风格删除一本书[delete]</h2>
    <a href="book/delete/100" id="deleteBook">删除一本书</a>
    <form action="" method="post" id="hiddenFrom">
        <input type="hidden" name="_method">
    </form>


    <h2>rest风格修改一本书[put]</h2>
    <form action="book/put" method="post">
        <input type="text" name="name"><br>
        <input type="hidden" name="_method" value="PUT">
        <input type="submit" value="提交">
    </form>

</body>
</html>

2.3注意事项和使用细节

  • HiddenHttpMethodFilter,在将post转成DElETE/PUT请求时,是按_method参数名来读取的,然后再根据对应的值修改成DElETE/PUT请求
  • 如果web项目是运行在Tomcat8及以上,会发现被过滤成DElETE和PUT请求,到达控制器时能顺利执行,但是返回时请求转发(forward)会报405的错误提示:消息JSP只允许GET、POST或HEAD
  • 解决方式1:使用Tomcat7
  • 解决方式2:将请求转发(forward)改为请求重定向(redirect):重定向到一个Handler,由Handler转发到页面

3. SpringMVC 映射请求数据

3.1 RequestParam获取参数值

3.1.1 代码
/*
   * 1. @RequestParam 表示会接收提交的参数,它有value和required的属性
   * 2. value = "name" 表示提交参数的参数名称为name
   * 3. required = "false" 表示参数可以没有,默认是true,表示必须要有这个参数,如果没有则会报错
   * 4. @RequestParam(value = "name",required = false) 表示请求的参数名可以和形参名称不一样也能获取到请求参数的值,
   *     但是请求参数要和value的名称一样否则会赋值失败
   * */
   @RequestMapping(value = "/test1")
   public String test01(@RequestParam(value = "name",required = false) String userName){

      System.out.println("用户名称==" + userName);

      return "success";
   }

3.2 获取Http请求消息头

3.2.1 代码
 /*
   * 1. 如果想获取请求头的信息可以使用RequestHeader注解
   * 2. value属性值表示你要获取请求头的什么部分的信息,然后赋值给形参
   * */
   @RequestMapping(value = "/test2")
   public String test02(@RequestHeader("Accept-Encoding") String ae,
                        @RequestHeader("Host") String host){

      System.out.println("Accept-Encoding ===>" + ae);
      System.out.println("Host ===>" + host);

         return "success";
   }

3.3 获取JavaBean象

3.3.1 代码
 /*
   *  将提交过来的数据封装成JavaBean对象
   * 1. 方法的形参用对应的类型(JavaBean对象)来指定即可,SpringMVC会自动完成封装
   * 2. 如果使用自动完成封装,要求提交的数据的参数名和对象的属性名保持一致,否则会赋值失败,而对象的属性值则是默认值
   * 3. 如果属性是对象,这里仍然是通过属性名.属性名,例如Master中的【pet】 ,即提交的数据参数名是:pet.id pet.name
   *    这就是级联操作
   * 4. 如果提交的数据的参数名和对象的属性名不匹配,则对象的属性就是默认值
   * */
   @RequestMapping(value = "/test3")
   public String test03(Master master){

      System.out.println(master);

      return "success";
   }

3.4 使用Servlet API

3.4.1 说明
  • 开发中,我们可能需要使用到原生的Servlet API
  • 使用Servlet API ,需要引入servlet-api.jar包
3.4.2 代码
/*
   *  1. 如果想要使用Servlet的API直接在形参列表创建对应的对象
   * */
   @RequestMapping(value = "/test4")
   public String test04(HttpServletRequest request,
                        HttpServletResponse response){

      String name = request.getParameter("name");
      String id = request.getParameter("id");

      System.out.println("name==" + name);

      System.out.println("id==" + id);

      return "success";
   }
3.4.3 使用注意事项
  • 除了HttpServletRequest,HttpServletResponse还可以有其它对象,也可以用这样的形式获取例如:HttpSession,java.security.Principal,InputStream,OutputStream,Reader,Writer
  • 其中一些对象也可以通过HttpServletRequest,HttpServletResponse对象获取,比如Session对象,既可以通过参数传入,也可以通过request.getSession()获取,效果一样,推荐使用参数形式传入,更加简单明了

4. 数据模型

4.1 模型数据处理-数据放入request域中

4.1.1 默认机制放入到request域中
/*
   *  1. SpringMVC 会自动将提交过来的数据封装成JavaBean对象,然后再放入到request域中
   *     并且在request域中的key的值是形参对应的类型(类名)名称首字母小写,value则是对象
   *  2. 也可以理解成SpringMVC会自动的将Model模型,放入到request域中,其key值是其类型(类名)首字母小写
   *  3. 在方法中修改master对象的属性值是会影响到request域中的数据的,因它们公用这一个引用
   * */
   @RequestMapping(value = "/test5")
   public String test05(Master master){



      return "request_view";
   }
4.1.2 通过HttpServletRequest放入request域
/*
   *  1. SpringMVC 会自动将提交过来的数据封装成JavaBean对象,然后再放入到request域中
   *     并且在request域中的key的值是形参对应的类型名称首字母小写,value则是对象
   *  2. 也可以理解成SpringMVC会自动的将Model模型,放入到request域中,其key值是其类型首字母小写
   *  3. 在方法中修改master对象的属性值是会影响到request域中的数据的,因它们公用这一个引用
   * */
   @RequestMapping(value = "/test5")
   public String test05(Master master,HttpServletRequest request){

      try {
         //设置编码
         request.setCharacterEncoding("UTF-8");
      } catch (UnsupportedEncodingException e) {
         throw new RuntimeException(e);
      }
      //保存数据到request中
      request.setAttribute("address","天津");

      //这里修改是会影响到request域中的数据的
      master.setName("丽丽");
      return "request_view";
   }
4.1.3 通过请求的方法参数Map 放入到request域中
/*
    *  1. SpringMVC 会自动将提交过来的数据封装成JavaBean对象,然后再放入到request域中
    *     并且在request域中的key的值是形参对应的类型名称首字母小写,value则是对象
    *  2. 也可以理解成SpringMVC会自动的将Model模型,放入到request域中,其key值是其类型首字母小写
    *  3. 在方法中修改master对象的属性值是会影响到request域中的数据的,因它们公用这一个引用
    * */
   @RequestMapping(value = "/test6")
   public String test06(Master master, Map<String,Object> map){

      //添加数据进map集合中
      /*
      *  原理分析
      *  1. 首先SpringMVC会将封装好的Master对象放入到map集合中
      *  2. 然后再将Map集合中的数据进行遍历,查看收否数据
      *  3. 如果有数据,则会将该数据放入到request域中,如果没有则不进行数据存放
      *  4. 存放方式:map的key对应request域的key,map的value对应request域的value
      * */
      map.put("address","yunnan");
      //如果这样操作则会将request域中的master置空
      //map.put("master",null);

      return "request_view";
   }
4.1.4 通过返回ModelAndView放入到request域中
4.1.4.1 代码
@RequestMapping(value = "/test7")
   public ModelAndView test07(Master master){

      /*
      * 原理分析
      *  1. SpringMVC会将返回的ModelAndView进行扫描判断是否有数据
      *  2. 如果有数据则将数据存放进Request域中,如果没有则不存放
      *  3. 存放的格式是ModelAndView中的attributeName对应Request的key,
      *     attributeValue对应的是request的Value
      *  4. 在默认情况下,如果你的方法返回的是String,其底层最后也是封装成一个ModelAndView对象
      * */

      //创建一个ModelAndView对象
      ModelAndView modelAndView = new ModelAndView();

      //保存数据
      modelAndView.addObject("address","beijing");

      //设置视图,你要跳转的视图
      modelAndView.setViewName("request_view");


      return modelAndView;
   }
4.1.4.2 注意事项
  • 从本质看,请求响应的方法return “xx” ,是返回了一个字符串,其实其本质是返回了一个ModelAndView对象,只是默认被封装起来了
  • ModelAndView既可以包含Model数据包,也可以包含视图信息
  • ModelAndView对象的addObject方法可以添加key-val数据,默认在Request域中
  • ModelAndView对象的setViewName方法可以设置视图名称

4.2 模型数据处理-数据放入Session域中

4.2.1 代码
 @RequestMapping(value = "/test8")
   public String test08(Master master,HttpSession session){

      //将数据放入Session域中
      session.setAttribute("master",master);
      //设置数据
      session.setAttribute("address","jiangxi");

      return "request_view";
   }

4.3 模型数据-@ModelAttribute

4.3.1 基本说明
  • 开发中,有时需要使用某个前置方法(比如prepareXxx(),方法名由程序员定义)给目标方法准备一个模型对象
  • @ModelAttribute注解可以实现这样的需求
  • 在某个方法上,增加了@ModelAttribute属性后,调用这个类的任何一个方法时,都会先调用这个方法
4.3.2 代码
/*
   *  1. 当Handler的方法被标识@ModelAttribute,就视为一个前置方法,
   *     每当这个类中的其他方法被执行前都会执行这个方法
   *  2. 这个方法类似于AOP中的前置通知
   * */
   @ModelAttribute
   public void prepareModel(){

      System.out.println("===================================prepareModel===================================");

   }
@ModelAttribute最佳实践
  • 修改用户信息(就是经典的使用这种机制的应用)流程如下:
  • 在修改前,在前置方法中从数据库查询出这个用户
  • 在修改方法(目标方法)中,可以使用前置方法从数据库查询的用户
  • 如果表单中对用户的某个属性修改了,则以新的数据为准,如果没有修改,则以数据库的信息为准,比如,用户的某个属性不能修改,就保持原来的值

5. 视图和视图解析器

5.1 基本介绍

  • 在SpringMVC中的目标方法最终返回都是一个视图(有各种视图)
  • 返回的视图都会由一个视图解析器来处理(视图解析器有很多种)

5.2 代码

5.2.1 Handler(Controller)
package com.leon.viewresolver;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * ClassName:GoodsHandler
 * Package:com.leon.viewresolver
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/4/16 16:08
 * @Version: 1.0
 */
@RequestMapping(value = "/goods")
@Controller
public class GoodsHandler {

    @RequestMapping(value = "/test1")
    public String test01(){

        System.out.println("GoodsHandler===test01()");

        //返回自定以视图,这里需要是自定义视图的Bean的id
        return "goodsView";
    }

}
5.2.2 view视图
package com.leon.viewresolver;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.view.AbstractView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;


/**
 * ClassName:GoodsView
 * Package:com.leon.viewresolver
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/4/16 16:10
 * @Version: 1.0
 */
@Component
public class GoodsView extends AbstractView {


    @Override
    protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

        System.out.println("自定义的视图······");

        //  跳转页面
        request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request,response);

    }
}
5.2.3 xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://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">

    <!--配置要扫描的包路径-->
    <context:component-scan base-package="com.leon"/>

    <!--配置SpringMVC的视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--配置前缀,是要访问的包-->
        <property name="prefix" value="/WEB-INF/pages/"></property>
        <!--配置后缀,是要访问的文件类型 -->
        <property name="suffix" value=".jsp"></property>
    </bean>

    <!--加入两个常规配置-->
    <!--配置支持SpringMVC的高级功能,比如JSR303校验,映射动态请求-->
    <mvc:annotation-driven></mvc:annotation-driven>
    <!--配置将SpringMVC不能处理的请求,交给Tomcat处理,比如css,js-->
    <mvc:default-servlet-handler/>

    <!--配置自定视图解析器
        1. 配置自定义视图解析器BeanNameViewResolver
        2. BeanNameViewResolver可以去解析我们自定义的视图
        3. 配置 order属性,表示视图解析器执行的顺序,值越小,优先级越高,
           order属性的默认值是最低优先级,值为Integer.MAX_VALUE

    -->
    <bean class="org.springframework.web.servlet.view.BeanNameViewResolver" >
            <property name="order" value="99"/>
    </bean>

</beans>
5.2.4 自定义视图步骤
  • 自定义视图:创建一个View的Bean,该Bean需要继承自AbstractView,并实现renderMergedOutputModel方法
  • 并把自定义View加入到IOC容器中
  • 自定义视图的视图处理器,使用BeanNameViewResolver,这个视图处理器也需要配置到IOC容器
  • BeanNameViewResolver的调用优先级需要设置一下,设置order比Integer.MAX_VALUE小的值,以确保其在InternalResourceViewResolver之前被调用
5.2.5 自定义视图解析器执行流程
  • SpringMVC调用目标方法,返回自定义View在IOC容器中的id
  • SpringMVC调用BeanNameViewResolver解析视图:从IOC容器中获取,自定义的View的对象(通过目标返回的id值对应的Bean)
  • SpringMVC调用自定义视图的renderMergedOutputModel方法渲染视图
  • 如果在SpringMVC调用目标方法,返回自定义View在IOC容器中的id,该View不存在,则仍然按照默认的视图处理机制处理
  • 注意:如果默认的视图解析器优先级更高,则不管成功或者失败都不会执行自定义的视图解析器

6. 请求转发或者重定向

6.1 代码

@RequestMapping(value = "/test2")
    public String test02(){

        /*
        * 解析
        *   1. forward关键字,表示请求转发 redirect关键字,表示重定向
        *   2. 注意重定向不能访问WEB-INF,否则会报错
        *   3. return "forward:/WEB-INF/pages/success.jsp" 会被解析成这个URL:
        *      http://localhost:8080/springmvc/WEB-INF/pages/success.jsp
        *   4. redirect:/login.jsp 会被解析成这个URL:http:/localhost:8080/springmvc/login.jsp
        *   5. redirect:/login.jsp 这个可能会和之前学习的路径有冲突,之前学的没有错,是因为SpringMVC将/login.jsp 解析成
        *      /springmvc/login.jsp返回给浏览器
        * */
        //请求转发
        //return "forward:/WEB-INF/pages/success.jsp";
        //会报错,访问不了WEB-INF
        //return "redirect:/WEB-INF/pages/success.jsp";
        //重定向
        return "redirect:/login.jsp";
    }

7. 数据格式化

7.1 基本介绍

  • 在我们提交数据(比如表单时)SpringMVC怎样对提交的数据进行装换和处理的?

  • 基本数据类型可以和字符串之间自动完成装换,比如:

    • SpringMVC 上下文中内建了很多装换器,可能完成大多数Java类型的转换工作。【相互转换,这里只列出部分】

      | 转换 | 装换类 |
      | :—————————————: | :—————————-: |
      | java.lang.Boolean —> java.lang.String | ObjectToStringConverter |
      | java.lang.Character —> java.lang.Number | CharacterToNumberFactory |
      | java.lang.Character —> java.lang.String | ObjectToStringConverter |
      | java.lang.Enum —> java.lang.String | EnumToStringConverter |
      | java.lang.Number —> java.lang.Character | NumberToCharacterConverter |
      | java.lang.Number—> java.lang.Number | NumberToNumberConverterFactory |
      | java.lang.Number—> java.lang.String | ObjectToStringConverter |
      | java.lang.String—> java.lang.Boolean | StringToBooleanConverter |
      | java.lang.String—> java.lang.Character | StringToCharacterConverter |
      | java.lang.String—> java.lang.Enum | StringToEnumConverterFactory |
      | java.lang.String—> java.lang.Number | StringToNumberConverterFactory |
      | java.lang.String—> java.lang.Locale | StringToLocalConverter |
      | java.lang.String—> java.lang.Properties | StringToPropertiesConverter |
      | java.lang.String—> java.lang.UUID | StringToUUIDConverter |
      | java.lang.Locale—> java.lang.String | ObjectToStringConverter |
      | java.lang.Properties—> java.lang.String | PropertiesToStringConverter |
      | java.lang.UUID—> java.lang.String | ObjectToStringConverter |

7.2 基本数据类型和字符串自定转换

7.2.1 Controller
package com.leon.dataconverter;

import com.leon.dataconverter.entity.Monster;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Map;

/**
 * ClassName:MonsterHandler
 * Package:com.leon.dataconverter
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/4/26 9:43
 * @Version: 1.0
 */
/*
 * @Scope(value = "prototype") 表示每次请求MonsterHandler时都会生成一个新的对象
 * */
@RequestMapping(value = "monsterConversion")
@Controller
@Scope(value = "prototype")
public class MonsterHandler {

    @RequestMapping(value = "/add")
    //显示添加monster的界面
    public String addMonsterUI(Map<String, Object> map) {

        /*
         *  解读
         *     1. 这里的表单,我们使用SpringMVC的标签来完成
         *     2.SpringMVC 表单标签在显示之前必须在request域中有一个bean,该bean的属性要和表单标签的path对应
         *       request中的key对应form标签的modelAttribute属性值,例如monster。
         *       解读:意思是,如果在request域中存放Monster对象,Monster对象在request域中对应的key值,要和modelAttribute
         *            的属性值一样,也就是保持对应,并且其属性名称要和form标签中的path属性值一样
         * */
        map.put("monster", new Monster());

        /*
         * 1. 如果跳转的页面使用了SpringMVC的form标签,就需要准备一个对象,放入request域中,这个对象的key值monster对应的
         *    是SpringMVC表单标签的modelAttribute="monster"
         * */
        return "dataconverter/monster_addUI";
    }

    @RequestMapping(value = "/save")
    public String saveMonster(Monster monster) {

        System.out.println("==================Monster================== :"+monster);

        return "dataconverter/add_success";
    }


}
7.2.2 entity
package com.leon.dataconverter.entity;

import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.NumberFormat;

import java.util.Date;

/**
 * ClassName:Monster
 * Package:com.leon.dataconverter.entity
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/4/26 9:22
 * @Version: 1.0
 */
public class Monster {

    private String id;

    private String name;

    private Integer age ;

    private String email ;

    //特殊格式的装换
    /*
     *   1.使用NumberFormat注解进行格式转换,pattern值是格式要求,如果没有满足要求就会报错
     *
     * */
    @NumberFormat(pattern = "##,###.##")
    private float salary ;

    //特殊格式的装换
    /*
    *   1.使用DateTimeFormat注解进行格式转换,pattern值是格式要求,如果没有满足要求就会报错
    *
    * */
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date birthday;

    public Monster() {
    }

    public Monster(String id, String name, Integer age, String email, float salary, Date birthday) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.email = email;
        this.salary = salary;
        this.birthday = birthday;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Float getSalary() {
        return salary;
    }

    public void setSalary(float salary) {
        this.salary = salary;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "Monster{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", email='" + email + '\'' +
                ", salary=" + salary +
                ", birthday='" + birthday + '\'' +
                '}';
    }
}
7.2.3 前端主页面
<%@ taglib prefix="from" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%--
  Created by IntelliJ IDEA.
  User: ZGJ
  Author: leon --> ZGJ
  Version: 1.0
  Date: 2024/4/26
  Time: 9:31
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <%--
        为了方便回显这里使用,SpringMVC的标签来完成
            1. SpringMVC 表单标签在显示之前必须在request域中有一个bean,该bean的属性要和表单标签的path对应
            2. SpringMVC 的 form:form 标签的action 属性值中的第一个 / 代表WEB应用的根目录
    --%>
    <h3>添加妖怪</h3>
    <from:form action="save" method="post" modelAttribute="monster">
        妖怪名字:<form:input path="name"/><br><br>
        妖怪年龄:<from:input path="age"/><br><br>
        妖怪生日:<from:input path="birthday"/><br><br>
        妖怪工资:<from:input path="salary"/><br><br>
        电子邮件:<from:input path="email"/><br><br>
        <input type="submit" value="添加妖怪">
    </from:form>
</body>
</html>
7.2.4 测试图片

image-20240426102823832

image-20240426102919217

7.3 特殊数据类型和字符串间的装换

  • 特殊格式的转换需要使用注解,这里使用了【@NumberFormat(pattern = “##,###.##“)】和【@DateTimeFormat(pattern = “yyyy-MM-dd”)】等
  • 这些注解需要标注到要转换的JavaBean的属性上面,pattern的属性值是要转换的格式,如果没有按照指定的格式传输数据会报错
  • 格式转换的注解只负责格式的转换,并不负责验证不要搞混了
  • 注意:被标注的属性是被最终转换成的类型,所以要转换的类型要和注解所转换的类型匹配,否则会失效
7.3.1 代码同基本数据类型的转换一样,只不过是在指定的属性上添加了注解
//特殊格式的装换
    /*
     *   1.使用NumberFormat注解进行格式转换,pattern值是格式要求,如果没有满足要求就会报错
     *
     * */
    @NumberFormat(pattern = "##,###.##")
    private float salary ;

    //特殊格式的装换
    /*
    *   1.使用DateTimeFormat注解进行格式转换,pattern值是格式要求,如果没有满足要求就会报错
    *
    * */
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date birthday;
7.3.1 测试图片

image-20240426105815499

image-20240426105849728

8. 验证以及国际化

3.1 概述

  • 对于输入的数据(比如表单数据),进行必要的验证,并给出相应的提示信息

  • 对于验证表单数据,SpringMVC提供了很多实用的注解,这些注解由JSR 303验证框架提供

  • JSR 303 验证框架

  • JSR 303 是Java为Bean数据合法性校验提供的标准框架,它已经包含在JavaEE中

  • JSR 303 通过在Bean属性上标注类似于@NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对Bean进行验证

  • JSR 303 提供的基本验证注解有:

    | 注解 | 功能说明 |
    | ————————- | ———————————————————— |
    | @Null | 被注解的元素必须为null |
    | @NotNull | 被注释的元素必须不为null |
    | @AssertTrue | 被注释的元素必须为true |
    | @AssertFalse | 被注释的元素必须为false |
    | @Min(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
    | @Max(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
    | @DecimalMin(value) | 被注释的元素必须是一个数字(小数),其值必须大于等于指定的最小值 |
    | @DecimalMin(value) | 被注释的元素必须是一个数字(小数),其值必须小于等于指定的最大值 |
    | @Size(max,min) | 被注释的元素的大小必须在指定的范围内 |
    | @Digits(integer,fraction) | 被注释的元素必须是一个数字,其值必须在可接受的范围内 |
    | @Past | 被注释的元素必须是一个过去的日期 |
    | @Future | 被注释的元素必须是一个将来的日期 |
    | @Pattern(value) | 被注释的元素必须符合指定的正则表达式 |

  • Hibernate Validator扩展注解

  • Hibernate Validator 和Hibernate没有关系,只是JSR 303 实现的一个扩展

  • Hibernate Validator 是JSR 303 的一个参考实现,除了支持所有的标注校验注解外,它还支持以下的扩展注解

    | 注解 | 功能说明 |
    | ——— | ———————————————————— |
    | @Email | 被注释的元素必须是电子邮箱地址 |
    | @Length | 被注释的字符串的大小必须在指定的范围内(也就是长度) |
    | @NotEmpty | 被注释的字符串必须是非空(还可以修饰其他的元素,详细看NotEmpty类) |
    | @Range | 被注释的元素必须在合适的范围内 |

3.2 数据验证

  • 需要在目标JavaBean的属性上标注验证的注解,然后在处理器的目标方法的形参列表中,在目标JavaBean的前面标注一个@Valid,Springmvc底层会自动处理和验证
  • 每一个验证注解都有自己的一个message()属性,这个是用来发生错误时的提示信息,它们有默认的值,可以自己定义的错误验证信息
  • 注解之间可以搭配使用

3.3 国际化

  • 需要在IOC配置文件中配置一个bean,这个类的名字是:org.springframework.context.support.ResourceBundleMessageSource还需要配置一个basename属性,属性值是你配置在src下的properties文件的名称,一般定义为:i18nxxxx.properties

  • 特别注意配置文件中的属性命名要求,验证类.request域中的key值.属性名称,例如:NotEmpty.monster.name,注意要是Unicode编码格式,完整的样式是

    #用户名不能为空
    NotEmpty.monster.name= \u7528\u6237\u540d\u4e0d\u80fd\u4e3a\u7a7a
    

3.4 代码

/*
    * 1. @Valid Monster monster 表示对monster接收的数据进行校验
    * 2. Errors errors 表示如果校验出现错误,将校验的错误信息保存errors
    * 3. Map<String,Object> map 表示如果校验出现错误,将校验的错误信息保存map同时保存monster对象
    * 4. 校验发生的时机:在Springmvc底层,反射调用目标方法时,会接收到Http请求的数据,然后根据注解来进行验证,
    *    在验证过程中,如果出现了错误,就把错误信息填充errors和map。
    * */
   @RequestMapping(value = "/validate")
   public String validateMonster(@Valid Monster monster , Errors errors,Map<String, Object> map) {

      System.out.println("-------Monster:" + monster);

      System.out.println("==============================map==============================");

      //遍历集合
      for (Map.Entry<String, Object> entry : map.entrySet()) {

         System.out.println(entry.getKey() + ":" + entry.getValue());

      }

      System.out.println("==============================Error==============================");

      //获取所有的错误信息
      List<ObjectError> allErrors = errors.getAllErrors();

      //遍历错误信息
      for (ObjectError allError : allErrors) {

         System.out.println(allError);

      }

      return "dataconverter/monster_addUI";
   }
======================================================================================

 <!--配置国际化错误信息的资源处理bean-->
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">

        <!--
            配置国际化文件名字
            1. 如果这样配置,表示messageSource去src/i18nxxxx.properties去读取错误信息
        -->
        <property name="basename" value="i18n"/>

    </bean>
======================================================================================
#用户名不能为空
NotEmpty.monster.name= \u7528\u6237\u540d\u4e0d\u80fd\u4e3a\u7a7a
#请输入正确的范围
Range.monster.age= \u8bf7\u8f93\u5165\u6b63\u786e\u7684\u8303\u56f4
#请输入正确的生日格式
typeMismatch.monster.birthday= \u8bf7\u8f93\u5165\u6b63\u786e\u7684\u751f\u65e5\u683c\u5f0f
#请输入正确的薪资
typeMismatch.monster.salary= \u8bf7\u8f93\u5165\u6b63\u786e\u7684\u85aa\u8d44

3.5 数据验证和国际化细节

  • 在需要验证的JavaBean/pojo的字段加上相应的验证注解

  • 目标方法上,在JavaBean/pojo类型的参数前,添加@Valid注解,告知Springmvc该bean是需要验证的

  • 在@Valid 注解之后,添加一个Errors或BindingResult类型的参数,可以获取到错误信息

  • 需要使用标签来显示错误消息,这个标签,需要写在 标签内生效

  • 错误消息的国际化文件i18n.properties,中文需要是Unicode编码,使用工具转码

  • 格式:验证规则.表单modelAttribute值.属性名=消息

  • NotEmpty.monster.name=\u7528\u6237\u540d\u4e0d\u80fd\u4e3a\u7a7a

  • typeMismatch.monster.birthday= \u8bf7\u8f93\u5165\u6b63\u786e\u7684\u751f\u65e5\u683c\u5f0f

  • 注解@NotNull和@NotEmpty的区别说明

  • 查看源码可以知道:@NotEmpty {Asserts that the annotated string, collection, map or array is not null or empty.}

  • 查看源码可以知道:@NotNull {The annotated element must not be null. Accepts any type.}

  • 有的时候验证注解会组合使用,因为它们功能单一,组合起来效果更好

3.6 数据类型转换校验核心类

  • SpringMVC通过反射机制对目标方法进行解析,将请求消息绑定到处理方法的入参中。数据绑定的核心部件是DataBinder,运行机制如下

    DataBinder工作机制

3.7 取消属性绑定

3.7.1 说明
  • 在默认情况下,表单提交的数据会和pojo类型的JavaBean属性绑定,如果程序员在开发中,希望取消某个属性绑定,也就是说,不希望接收到某个表单属性的值,则可以通过@InitBinder注解取消绑定
  • 编写一个方法,使用@InitBinder标识的该方法,可以对WebDataBinder对象进行初始化
  • WebDataBinder是DataBinder的子类,用于完成由表单字段到JavaBean属性的绑定
  • @InitBinder方法不能有返回值,它必须声明为void
  • @InitBinder方法的参数通常是WebDataBinder
3.7.2 代码
//取消绑定Monster的name表单提交的值给monster.name属性
   @InitBinder
   public void initBinder(WebDataBinder binder) {
      /*
      * 1. 方法上需要标注@InitBinder springmvc底层会初始化这个方法的形参WebDataBinder
      * 2. 调用WebDataBinder.setDisallowedFields("name")表示取消指定属性的绑定
      *     即:当表单提交字段name时,就不在把接收到的name值,填充到model数据monster的name属性
      * 3. 机制:Springmvc在底层通过反射调用目标方法时。接收到Http请求的参数和值,使用反射+注解技术
      *     取消对指定属性的填充
      * 4. setDisallowedFields支持可变参数,可以填写多个字段
      * 5. 如果我们取消某个属性绑定,验证就没有意义了,应当把验证的注解去掉
      * */

      binder.setDisallowedFields("name");



   }
3.7.3 注意事项和细节说明
  • setDisallowedFields() 是可变形参,可以指定多个字段
  • 当将一个字段/属性,设置为disallowed,就不在接收表单提交的值,那么这字段/属性的值,就是该对象默认的值(具体看程序员定义时指定)
  • 一般来说,如果不接收表单字段提交数据,则该对象字段的验证也就没有意义了可以注销掉

9.中文乱码处理

9.1 自定义中文乱码过滤器

9.1.1 代码
package com.leon.filter;


import javax.servlet.*;
import java.io.IOException;

/**
 * ClassName:MyCharacterFilter
 * Package:com.leon.filter
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/4/27 10:28
 * @Version: 1.0
 */
public class MyCharacterFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //设置编码
        servletRequest.setCharacterEncoding("UTF-8");
        //放行
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {

    }
}
======================================================================================

  <!--
        配置处理中文乱码的过滤器
           1.拦截所有请求,处理编码,提醒:如果是比较重要的过滤器,要配置在web.xml前面
    -->
    <filter>
        <filter-name>MyCharacterFilter</filter-name>
        <filter-class>com.leon.filter.MyCharacterFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>MyCharacterFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>1

9.2 Spring提供的过滤器处理中文

9.2.1 代码
<!--配置Spring提供的过滤器,解决中文乱码-->
    <filter>
        <filter-name>CharacterEncodingFilter</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>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

11. 处理json和HttpMessageConverter< T>

11.1 处理JSON-@ResponseBody

  • 需要在目标方法上标注注解@ResponseBody,表示这个方法返回的数据是以json的格式返回的
  • 底层通过反射+io等来实现的,要注意这个注解只是将方法返回的数据以json格式返回,而不是把请求过来的数据封装成JavaBean或者json数据
  • 返回的数据可以是集合
  • @ResponseBody可以修饰Handler类,@Controller和@ResponseBody可以组合成@RestController,这个是注解包含这个两个注解的功能
11.1.1 代码
package com.leon.json;

import com.leon.json.entity.Dog;
import com.leon.json.entity.User;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * ClassName:JsonHandler
 * Package:com.leon.json
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/4/27 13:50
 * @Version: 1.0
 */
@RequestMapping(value = "/json")
//@Controller
//@ResponseBody
@RestController
public class JsonHandler {


    /*
    * 1. @ResponseBody注解表示返回的数据是以json的格式
    *
    * */
    @RequestMapping(value = "/dog")
    //@ResponseBody
    public Dog jsonDog(){
        Dog dog = new Dog();

        dog.setName("大黄");
        dog.setAddress("小明家");
        return  dog ;

    }
}
======================================================================================
<%--
  Created by IntelliJ IDEA.
  User: ZGJ
  Author: leon --> ZGJ
  Version: 1.0
  Date: 2024/4/27
  Time: 13:45
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<script type="application/javascript" src="script/jquery-3.7.1.min.js"></script>
<script type="application/javascript">
    $(document).ready(function () {
        $("#requestJson").click(function () {

            // 获取href元素值
            var url = this.href;

            // 为了每次都是新的请求,这里返回时间数据
            var data = new Date();

            $.ajax({
                url:url,
                type:"POST",
                data:data,
                success:function (data) {
                    console.log(data);
                },
                dataType:"json"
            })

            // 取消默认行为
            return false ;

        });

        $("#submitJson").click(function () {

            //创建一个Url
            let url ="/springmvc/json/user";
            //获取用户
            let name = $("#name").val();
            //获取年龄
            let age = $("#age").val();

            let data =JSON.stringify( {name:name,age:age});

            $.ajax({
                url:url,
                type: "POST",
                data:data,
                success:function (data) {
                    console.log(data);
                },
                // 设置返回的数据格式
                contentType:"application/json;charset=utf-8"

            });

        });
    })
</script>
<body>
<h1>请求一个json数据</h1>
<a href="<%=request.getContextPath()%>/json/dog" id="requestJson">点击获取JSON数据</a>

<h1>发出一个json数据</h1>
   u:<input type="text" id="name" name="username"><br><br>
   a:<input type="text" id="age" name="age"><br><br>
    <button id="submitJson">提交</button>
</body>
</html>

11.2 处理JSON-@RequestBody

  • 需要标注在目标方法的形参中,要被封装成的目标bean形参之前,表示将请求过来的json数据封装成指定的JavaBean
  • 底层是通过反射+io等实现的,需要注意这个注解只是将请求过来的数据封装成指定的对象类型,而不是把请求过来的数据封装成json格式数据。
  • 封装的数据可以是集合
  • 注意:被封装的对象的属性要和json中的key值对应,否则会封装失败
11.2.1 代码
package com.leon.json;

import com.leon.json.entity.Dog;
import com.leon.json.entity.User;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * ClassName:JsonHandler
 * Package:com.leon.json
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/4/27 13:50
 * @Version: 1.0
 */
@RequestMapping(value = "/json")
//@Controller
//@ResponseBody
@RestController
public class JsonHandler {


    /*
    * 1. @ResponseBody注解表示返回的数据是以json的格式
    *
    * */
    @RequestMapping(value = "/dog")
    //@ResponseBody
    public Dog jsonDog(){
        Dog dog = new Dog();

        dog.setName("大黄");
        dog.setAddress("小明家");
        return  dog ;

    }

    /*
    * 1.@RequestBody表示将请求的过来的json格式的数据封装成一个UserJavaBean
    * */
    @RequestMapping(value = "/user")
    //@ResponseBody
    public List<User> jsonUser(@RequestBody List<User> users){

        System.out.println("user:===>" + users);

        return users;
    }
}
======================================================================================
<%--
  Created by IntelliJ IDEA.
  User: ZGJ
  Author: leon --> ZGJ
  Version: 1.0
  Date: 2024/4/27
  Time: 13:45
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<script type="application/javascript" src="script/jquery-3.7.1.min.js"></script>
<script type="application/javascript">
    $(document).ready(function () {
        $("#requestJson").click(function () {

            // 获取href元素值
            var url = this.href;

            // 为了每次都是新的请求,这里返回时间数据
            var data = new Date();

            $.ajax({
                url:url,
                type:"POST",
                data:data,
                success:function (data) {
                    console.log(data);
                },
                dataType:"json"
            })

            // 取消默认行为
            return false ;

        });

        $("#submitJson").click(function () {

            //创建一个Url
            let url ="/springmvc/json/user";
            //获取用户
            let name = $("#name").val();
            //获取年龄
            let age = $("#age").val();

            let data =JSON.stringify( {name:name,age:age});

            $.ajax({
                url:url,
                type: "POST",
                data:data,
                success:function (data) {
                    console.log(data);
                },
                // 设置返回的数据格式
                contentType:"application/json;charset=utf-8"

            });

        });
    })
</script>
<body>
<h1>请求一个json数据</h1>
<a href="<%=request.getContextPath()%>/json/dog" id="requestJson">点击获取JSON数据</a>

<h1>发出一个json数据</h1>
   u:<input type="text" id="name" name="username"><br><br>
   a:<input type="text" id="age" name="age"><br><br>
    <button id="submitJson">提交</button>
</body>
</html>

11.3 HttpMessageConverter机制介绍

11.3.1 基本说明
  • SpringMVC处理JSON底层实现是依靠HttpMessageConverter< T>进行转换的

  • 工作机制图

    HttpMessageConverter机制流程图

  • 处理JSON-底层实现(HttpMessageConverter< T>)

  • 使用HttpMessageConverter< T>将请求信息转化并绑定到处理方法的入参中,或将响应结果转为对应类型的响应信息,Spring提供了两种途径

    • 使用@ResponseBody/@RequestBody对目标方法进行标注
    • 使用HttpEntity< T>/ResponseEntity< T>作为目标方法的入参或返回值
  • 当控制器处理方法使用到@RequestBody/@ResponseBody或HttpEntity< T>/ResponseEntity< T>时,Spring首先根据请求头或者响应头的Accept属性选择匹配的HttpMessageConverter,进而根据参数类型或者泛型类型的过滤得到匹配的HttpMessageConverter,若找不到可用的HttpMessageConverter将报错

12.文件的下载

12.1 基本说明

  • 需要把目标方法的返回值设置成ResponseEntity< T>,这样是告诉SpringMVC你返回的是一个文件
  • 创建ResponseEntity类需要三个参数分别是:① Http的响应头Headers ②Http响应状态 ③下载的文件数据
  • 方法的形参可以设置一个HttpSession方便获取文件的绝对路径
  • 建议将泛型设置成byte类型,因为可以读成二进制文件,更方便

12.2 代码

@RequestMapping(value = "downFile")
    public ResponseEntity<byte[]> downFile(HttpSession session){

        //构建一个ResponseEntity 对象 需要: 1. Http的响应头Headers 2.Http响应状态 3. 下载的文件数据

        //获取要下载的文件的绝对路径
        InputStream resourceAsStream = session.getServletContext().getResourceAsStream("/img/background.jpg");

        try {
            //创建一个byte数组,resourceAsStream.available()表示获取文件的大小
            byte[] bytes = new byte[resourceAsStream.available()];

            //读取资源
           resourceAsStream.read(bytes);

            //创建返回的HttpStatus
            HttpStatus ok = HttpStatus.OK;

            //创建需要的Headers
            HttpHeaders headers = new HttpHeaders();
            //设置如何处理响应的内容
            headers.add("Content-Disposition",
                    "attachment;filename=background.jpg");
            //创建ResponseEntity
            /*
            * public ResponseEntity(@Nullable T body,
            * @Nullable MultiValueMap<String, String> headers, HttpStatus status) {} 需要的构造器参数
             * */
            ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes,headers,ok);

            //返回数据
            return responseEntity;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }


    }

13.文件的上传

13.1 基本介绍

  • SpringMVC为文件上传提供了直接的支持,这种支持是通过即插即用的MultipartResolver实现的,Spring用Jakarta Commons FileUpload技术实现了一个MultipartResolver实现类:CommonsMultipartResolver
  • SpringMVC上下文中默认没有装配MultipartResolver,因此默认情况下不能处理文件的上传工作,如果想使用Spring的文件上传功能,需要在上下文中配置MultipartResolver

13.2 代码

package com.leon.fileUpload;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;

/**
 * ClassName:FileUploadHandler
 * Package:com.leon.fileUpload
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/3 19:53
 * @Version: 1.0
 */
@Controller
@RequestMapping(value = "/file")
public class FileUploadHandler {


    @RequestMapping(value = "/upload")
    public String handleFileUpload(MultipartFile file,
                                   HttpServletRequest request) {

        //获取文件名
        String filename = file.getOriginalFilename();

        System.out.println("你上传的文件名称" + filename);


        //获取保存的绝对路径
        String path = request.getServletContext().getRealPath("/img/" + filename);


        //创建文件
        File saveToFile = new File(path);

        //将上传的文件,转存到saveToFile
        try {
            file.transferTo(saveToFile);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }


        return "success";
    }

}
======================================================================================
<%--
  Created by IntelliJ IDEA.
  User: ZGJ
  Author: leon --> ZGJ
  Version: 1.0
  Date: 2024/5/3
  Time: 19:46
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>文件上传</title>
</head>
<body>
    <h1>文件上传演示</h1>
    <form action="<%=request.getContextPath()%>/file/upload" method="post" enctype="multipart/form-data">
       文件介绍:<input type="text" name="introduce"><br>
       选择文件:<input type="file" name="file"><br>
        <input type="submit" value="提交">

    </form>






</body>
</html>

=====================================================================================
<!--配置SpringMVC的文件上传解析器
        1. 这个bean的id是通过MultipartResolver这个接口来获取的,所以id名必须设置成multipartResolver
    -->
    <bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver"/>

13.3 注意事项

  • 在配置CommonsMultipartResolver时要注意一个问题,因为SpringMVC是通过MultipartResolver接口来获取CommonsMultipartResolver的,所以id的名称必须是multipartResolver否则会报错
  • 在Controller类中的文件上传方法中,MultipartFile 这个形参是一定要的,它的实现接口子类封装了文件上传的方法,这个形参的名称要和请求参数的file的类型的名称一样,否则会上传失败,也可以使用RequestParam注解来指定
  • 注意在form表单中要设置为POST请求,因为POST请求对数据大小理论上没有限制,数据格式也要设置成enctype=“multipart/form-data”

14. 自定义拦截器

14.1 自定义单个拦截器

14.1.1 说明
  • SpringMVC也可以使用拦截器对请求进行拦截处理,用户可以自定义拦截器来实现特定的功能
  • 自定义的拦截器必须实现HandlerIntercepior接口
  • 自定义拦截器的三个方法
  • preHandler():这个方法在业务处理器处理请求之前被调用,在该方法中对用户请求request进行处理
  • psotHandler():这个方法在目标方法处理完请求之后执行
  • afterCompletion():这个方法在完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作
14.1.2 自定义拦截器流程分析

自定义拦截器执行流程

  • 自定义拦截器执行流程说明
  • 如果preHandle 方法,返回false,则不再执行目标方法,可以在此指定返回页面
  • postHandle在目标方法被执行后执行,可以在方法中访问到目标方法返回的ModelAndView对象
  • 若preHandle返回true,则afterCompletion 方法,在渲染视图之后被执行
  • 若preHandle返回false,则afterCompletion 方法,不会被嗲用
  • 配置拦截器时,可以指定该拦截器对那些请求生效,哪些请求不生效
14.1.3 代码
package com.leon.interceptor;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * ClassName:FurnHandler
 * Package:com.leon.interceptor
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/3 21:23
 * @Version: 1.0
 */
@Controller
@RequestMapping(value = "/furn")
public class FurnHandler {

    @RequestMapping(value = "/hi")
    public String hi() {

        System.out.println("====================FurnHandler[hi()]===================");

        return "success";
    }

    @RequestMapping(value = "/hello")
    public String holle() {

        System.out.println("====================FurnHandler[holle()]===================");

        return "success";
    }

}
======================================================================================
package com.leon.interceptor;

import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

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

/**
 * ClassName:MyInterceptor01
 * Package:com.leon.interceptor
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/3 21:10
 * @Version: 1.0
 */
@Controller
public class MyInterceptor01 implements HandlerInterceptor {

    /*
    * 1. 该方法在执行目标方法之前被执行
    * 2. 如果该方法返回为false,则不会在执行目标方法
    * 3. 该方法可以获取到request,Response,目标Handler
    * 4. 这里可以根据业务要求跳转到指定的页面
    * */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        System.out.println("MyInterceptor01 ==================preHandle()==================");

        return true;
    }

    /*
    * 1. 该方法在执行目标方法之后被执行
    * 2. 该目标方法可以获取到目标方法返回的ModelAndView对象,也可以获取到目标Handler
    * */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("MyInterceptor01 ==================postHandle()==================");
    }

    /*
    * 1.该方法在执行完DispatcherServlet的render方法之后被执行
    **/
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("MyInterceptor01 ==================afterCompletion()==================");
    }
}
======================================================================================
<!--配置拦截器-->
    <mvc:interceptors>

        <!--
            1. 第一种配置方式:直接使用ref,引用对应的拦截器
            2. 这种方式会拦截所有的目标方法
        -->
        <!--<ref bean="myInterceptor01"/>-->


        <!--
            1.第二种配置方式
              mvc:mapping path="/hi" 指定要拦截的路径
              通过ref来指定自定义拦截器
        -->
        <!--<mvc:interceptor>-->
        <!--    <mvc:mapping path="/furn/hi"/>-->
        <!--    <ref bean="myInterceptor01"/>-->
        <!--</mvc:interceptor>-->

        <!--
            1.第二种配置方式:可以使用通配符来进行路径拦截
                mvc:mapping path="/furn/h*" 表示要拦截的路径
                mvc:exclude-mapping path="/furn/hi" 表示要排除哪个路径,可以使用通配符
                通过ref来指定自定义拦截器
        -->
        <mvc:interceptor>
            <mvc:mapping path="/furn/h*"/>
            <mvc:exclude-mapping path="/furn/h?"/>
            <ref bean="myInterceptor01"/>
        </mvc:interceptor>
    </mvc:interceptors>
14.1.4 注意事项和细节
  • 默认配置是所有的目标方法都进行拦截,也可以指定拦截目标方法,比如只是拦截hi

    <!--
              1.第二种配置方式
                mvc:mapping path="/hi" 指定要拦截的路径
                通过ref来指定自定义拦截器
          -->
          <mvc:interceptor>
              <mvc:mapping path="/furn/hi"/>
              <ref bean="myInterceptor01"/>
          </mvc:interceptor>
    
  • mvc:mapping 支持通配符,同时可以指定不对哪些目标方法进行拦截

    <!--
              1.第二种配置方式:可以使用通配符来进行路径拦截
                  mvc:mapping path="/furn/h*" 表示要拦截的路径
                  mvc:exclude-mapping path="/furn/hi" 表示要排除哪个路径,可以使用通配符
                  通过ref来指定自定义拦截器
          -->
          <mvc:interceptor>
              <mvc:mapping path="/furn/h*"/>
              <mvc:exclude-mapping path="/furn/h?"/>
              <ref bean="myInterceptor01"/>
          </mvc:interceptor>
    
  • 拦截器需要配置才生效,不配置是不生效的

  • 如果preHandle()方法返回了false,就不会执行目标方法(前提是目标方法被拦截了),程序员可以在这个方法中根据业务指定需要跳转的页面

14.2 自定义多个拦截器

14.2.1 自定义多个拦截器执行流程

14.2.2 代码
package com.leon.interceptor;

import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

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

/**
 * ClassName:MyInterceptor01
 * Package:com.leon.interceptor
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/3 21:10
 * @Version: 1.0
 */
@Controller
public class MyInterceptor01 implements HandlerInterceptor {

    /*
    * 1. 该方法在执行目标方法之前被执行
    * 2. 如果该方法返回为false,则不会在执行目标方法
    * 3. 该方法可以获取到request,Response,目标Handler
    * 4. 这里可以根据业务要求跳转到指定的页面
    * */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        System.out.println("MyInterceptor01 ==================preHandle()[01]==================");

        return true;
    }

    /*
    * 1. 该方法在执行目标方法之后被执行
    * 2. 该目标方法可以获取到目标方法返回的ModelAndView对象,也可以获取到目标Handler
    * */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("MyInterceptor01 ==================postHandle()[01]==================");
    }

    /*
    * 1.该方法在执行完DispatcherServlet的render方法之后被执行
    **/
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("MyInterceptor01 ==================afterCompletion()[01]==================");
    }
}

======================================================================================

package com.leon.interceptor;

import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

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

/**
 * ClassName:MyInterceptor01
 * Package:com.leon.interceptor
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/3 21:10
 * @Version: 1.0
 */
@Controller
public class MyInterceptor02 implements HandlerInterceptor {

    /*
    * 1. 该方法在执行目标方法之前被执行
    * 2. 如果该方法返回为false,则不会在执行目标方法
    * 3. 该方法可以获取到request,Response,目标Handler
    * 4. 这里可以根据业务要求跳转到指定的页面
    * */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        System.out.println("MyInterceptor02 ==================preHandle()[02]==================");

        return true;
    }

    /*
    * 1. 该方法在执行目标方法之后被执行
    * 2. 该目标方法可以获取到目标方法返回的ModelAndView对象,也可以获取到目标Handler
    * */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("MyInterceptor02 ==================postHandle()[02]==================");
    }

    /*
    * 1.该方法在执行完DispatcherServlet的render方法之后被执行
    **/
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("MyInterceptor02 ==================afterCompletion()[02]==================");
    }
}

======================================================================================
package com.leon.interceptor;

import com.leon.json.entity.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * ClassName:FurnHandler
 * Package:com.leon.interceptor
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/3 21:23
 * @Version: 1.0
 */
@Controller
@RequestMapping(value = "/furn")
public class FurnHandler {

    @RequestMapping(value = "/hi")
    public String hi() {

        System.out.println("====================FurnHandler[hi()]===================");

        return "success";
    }

    @RequestMapping(value = "/hello")
    public String holle(User user) {

        System.out.println("====================FurnHandler[holle()]===================");

        return "success";
    }

}


======================================================================================

<!--配置拦截器-->
    <mvc:interceptors>

        <!--
            1. 第一种配置方式:直接使用ref,引用对应的拦截器
            2. 这种方式会拦截所有的目标方法
        -->
        <!--<ref bean="myInterceptor01"/>-->


        <!--
            1.第二种配置方式
              mvc:mapping path="/hi" 指定要拦截的路径
              通过ref来指定自定义拦截器
        -->
        <!--<mvc:interceptor>-->
        <!--    <mvc:mapping path="/furn/hi"/>-->
        <!--    <ref bean="myInterceptor01"/>-->
        <!--</mvc:interceptor>-->

        <!--
            1.第二种配置方式:可以使用通配符来进行路径拦截
                mvc:mapping path="/furn/h*" 表示要拦截的路径
                mvc:exclude-mapping path="/furn/hi" 表示要排除哪个路径,可以使用通配符
                通过ref来指定自定义拦截器
        -->
        <mvc:interceptor>
            <mvc:mapping path="/furn/h*"/>
            <mvc:exclude-mapping path="/furn/h?"/>
            <ref bean="myInterceptor01"/>
        </mvc:interceptor>

        <!--
            配置多个拦截器
        -->
        <mvc:interceptor>
            <mvc:mapping path="/furn/hello"/>
            <ref bean="myInterceptor02"/>
        </mvc:interceptor>
    </mvc:interceptors>
14.2.3 注意事项和使用细节
  • 如果第1个拦截器的preHandle()返回false,后面不执行
  • 如果第2个拦截器的preHandle()返回false,就直接执行第1个拦截器的afterCompletion()方法,如果拦截器更多,规则类似。
  • 说明:前面说的规则,都是目标方法被拦截的

15. 异常处理

15.1 基本介绍

  • SpringMVC 通过HandlerExceptionResolver处理程序的异常,包括Handler 映射、数据绑定以及目标方法执行时发生的异常
  • 主要处理Handler中用@ExceptionHandler注解标注的定义的方法
  • ExceptionHandlerMethodResolver内部若找不到@ExceptionHandler注解的话,会找@ControllerAdvice类的@ExceptionHandler注解方法吗,这样就相当于一个全局异常处理器

15.2 局部异常

15.2.1 代码
package com.leon.exception;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;

/**
 * ClassName:MyExceptionHandler
 * Package:com.leon.exception
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/4 15:21
 * @Version: 1.0
 */
@Controller
@RequestMapping(value = "/exception")
public class MyExceptionHandler {


    /*
    * 1. localException()用于处理局部异常的方法
    * 2. 这里只处理了ArithmeticException和NullPointerException异常
    * 3. 形参是Exception,SpringMVC会自动的传入生成的异常给Exception,通过Exception形参可以获取到
    *    异常信息
    *
    * */
    @ExceptionHandler({ArithmeticException.class, NullPointerException.class})
    public String localException(Exception e, HttpServletRequest request) {

        System.out.println("异常信息是: " + e.getMessage());

        //将信息保存到request域中
        request.setAttribute("reason", e.getMessage());


        return "forward:/error.jsp";
    }



    @RequestMapping(value ="/test01")
    public String test01(int num){

        int i = 9 / num;

        return "success";
    }



}
15.2.1 注意事项和细节
  • 如果不确定是什么异常可以直接使用Exception类

15.3 全局异常

15.3.1 代码
package com.leon.exception;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import javax.servlet.http.HttpServletRequest;

/**
 * ClassName:MyGlobalException
 * Package:com.leon.exception
 * Description:
 *     1. 被注解@ControllerAdvice标注以后,这个就是一个全局异常类
 * @Author: leon-->ZGJ
 * @Create: 2024/5/4 15:48
 * @Version: 1.0
 */
@ControllerAdvice
public class MyGlobalException {


    /*
    * 1. 全局异常就是不管是哪个Handler抛出异常,都可以捕获
    * 2. 如果不确定会发生什么异常,可以直接写一个Exception异常类,这样所有的异常都会处理
    *
    * */
    @ExceptionHandler({NumberFormatException.class, ClassCastException.class})
    public String globalException(Exception e, HttpServletRequest request){
        System.out.println("全局异常信息是: " + e.getMessage());

        //将信息保存到request域中
        request.setAttribute("reason", e.getMessage());


        return "forward:/error.jsp";
    }




}
15.3.2 注意实现和细节
  • 局部异常的优先级高于全局异常
  • 如果不确定是什么异常可以直接使用Exception类

15.4 自定义异常

15.4.1 代码
package com.leon.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

/**
 * ClassName:AgeException
 * Package:com.leon.exception
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/4 16:07
 * @Version: 1.0
 */
@ResponseStatus(reason = "年龄需要1-120之间",value =HttpStatus.BAD_REQUEST)
public class AgeException extends RuntimeException{

    public AgeException() {
    }

    public AgeException(String message) {
        super(message);
    }
}
======================================================================================
@RequestMapping(value = "/test02")
    public String test02() {

        throw new AgeException("年龄必须在1-120之间======");
    }
15.4.2 注意事项和细节
  • @ResponseStatus(reason = “年龄需要1-120之间”,value =HttpStatus.BAD_REQUEST),这里的reason的属性值是提供给Tomcat使用的,如果想要在SpringMVC中获取到信息,需要通过构造器(或其他的方式)传入异常信息

15.5 SimpleMappingExceptionResolver

15.5.1 基本说明
  • 如果希望对所有异常进行统一处理,可以使用SimpleMappingExceptionResolver
  • 它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常,需要在IOC容器中配置
  • 注意:在IOC容器中配置的视图名是返回给视图解析器解析的
15.5.2 代码
<!--配置一个统一异常处理器-->
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <property name="exceptionMappings">
            <props>
                <prop key="java.lang.ArrayIndexOutOfBoundsException">forward:/arrError.jsp</prop>
                <prop key="java.lang.Exception">forward:/allError.jsp</prop>
            </props>
        </property>

    </bean>

15.6 处理异常优先级

  • 局部异常 > 全局异常 > SimpleMappingExceptionResolver > Tomcat 异常机制

三、手动实现SpringMVC的部分功能

1. controller包

package com.leon.controller;

import com.leon.pojo.Monster;
import com.leon.service.MonsterService;
import com.leon.springmvc_v1.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;

/**
 * ClassName:MonsterController
 * Package:com.leon.controller
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/4/21 21:13
 * @Version: 1.0
 */
@Controller
@RequestMapping(value = "/monster")
public class MonsterController {

    @Autowired
    private MonsterService monsterService;

    @Autowired
    private OrderController orderController;

    //为了方便使用HttpServletRequest和HttpServletResponse两个形参
    @RequestMapping(value = "/list")
    public void monsterList(HttpServletRequest request,
                            HttpServletResponse response
                           ){


        List<Monster> monsterList = monsterService.monsterList();

        for (Monster monster : monsterList) {
            System.out.println(monster);
        }

        //设置文件类型和字符编码
        response.setContentType("text/html;charset=utf-8");
        //获取输出流
        PrintWriter writer = null;
        try {
            writer = response.getWriter();
        //    输出信息
            writer.write("<h1>妖怪信息列表</h1>");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            //刷新数据
            writer.flush();
            //关闭资源
            writer.close();
        }

    }

    @RequestMapping(value = "/select")
    public void monsterSelect(HttpServletRequest request,
                            HttpServletResponse response,
                               String username) {
        System.out.println("username===> " + username);

        //设置文件类型和字符编码
        response.setContentType("text/html;charset=utf-8");
        //获取输出流
        PrintWriter writer = null;
        try {
            writer = response.getWriter();
            //    输出信息
            writer.write("<h1>妖怪信息列表</h1>");

            List<Monster> monsterList = monsterService.selectMonsterByName(username);

            StringBuilder stringBuilder = new StringBuilder();

            stringBuilder.append("<table border='1px' width='400px' style='border-collapse: collapse'>");

            for (Monster monster : monsterList) {

                stringBuilder.append("<tr>\n" +
                        "            <td>"+monster.getId()+"</td>\n" +
                        "            <td>"+monster.getName()+"</td>\n" +
                        "            <td>"+monster.getSkill()+"</td>\n" +
                        "            <td>"+monster.getAge()+"</td>\n" +
                        "        </tr>");

            }

            stringBuilder.append("</table>");

            //遍历
            writer.write(stringBuilder.toString());


        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            //刷新数据
            writer.flush();
            //关闭资源
            writer.close();
        }


    }

    @RequestMapping(value = "/login")
    public String monsterLogin( HttpServletRequest request,String username) {

        request.setAttribute("name",username);

        System.out.println(username);

        boolean flag  = monsterService.monsterLogin(username);

        if(flag){

            return "forward:/login_ok.jsp";

        }

        return "forward:/login_error.jsp";

    }

    @RequestMapping(value = "/json")
    @ResponseBody
    public List<Monster> monsterJson(){

        List<Monster> monsterList = monsterService.monsterList();
        System.out.println("monsterJson ==> ");

        return monsterList;

    }

}
======================================================================================

package com.leon.controller;

import com.leon.springmvc_v1.annotation.Controller;
import com.leon.springmvc_v1.annotation.RequestMapping;



import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * ClassName:OrderController
 * Package:com.leon.controller
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/4/22 16:13
 * @Version: 1.0
 */
@Controller
@RequestMapping(value = "/order")
public class OrderController {

    @RequestMapping(value = "/list")
    public void orderList(HttpServletRequest request, HttpServletResponse response){

        //设置文件类型和字符编码
        response.setContentType("text/html;charset=utf-8");
        //获取输出流
        PrintWriter writer = null;
        try {
            writer = response.getWriter();
            //    输出信息
            writer.write("<h1>订单信息列表</h1>");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            //刷新数据
            writer.flush();
            //关闭资源
            writer.close();
        }

    }

    @RequestMapping(value = "/add")
    public void orderAdd(HttpServletRequest request, HttpServletResponse response){

        //设置文件类型和字符编码
        response.setContentType("text/html;charset=utf-8");
        //获取输出流
        PrintWriter writer = null;
        try {
            writer = response.getWriter();
            //    输出信息
            writer.write("<h1>添加订单·····</h1>");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            //刷新数据
            writer.flush();
            //关闭资源
            writer.close();
        }

    }


}

2. pojo包

package com.leon.pojo;

/**
 * ClassName:Monster
 * Package:com.leon.pojo
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/4/22 17:01
 * @Version: 1.0
 */
public class Monster {

    private String id ;

    private String name ;

    private String skill ;

    private Integer age ;


    public Monster() {
    }

    public Monster(String id, String name, String skill, Integer age) {
        this.id = id;
        this.name = name;
        this.skill = skill;
        this.age = age;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSkill() {
        return skill;
    }

    public void setSkill(String skill) {
        this.skill = skill;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Monster{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", skill='" + skill + '\'' +
                ", age=" + age +
                '}';
    }
}

3. service

package com.leon.service;

import com.leon.pojo.Monster;

import java.util.List;

/**
 * ClassName:MonsterService
 * Package:com.leon.service
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/4/22 10:20
 * @Version: 1.0
 */
public interface MonsterService {

    public List<Monster> monsterList();

    public List<Monster> selectMonsterByName(String name);

    public boolean monsterLogin(String name);

}
======================================================================================
package com.leon.service.impl;

import com.leon.pojo.Monster;
import com.leon.service.MonsterService;
import com.leon.springmvc_v1.annotation.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * ClassName:MonsterServiceImpl
 * Package:com.leon.service.impl
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/4/22 10:20
 * @Version: 1.0
 */
@Service(value = "monsterServiceImpl")
public class MonsterServiceImpl implements MonsterService {
    @Override
    public List<Monster> monsterList() {

        List<Monster> monsters = new ArrayList<>();

        monsters.add(new Monster("100", "牛魔王", "芭蕉扇", 400));
        monsters.add(new Monster("200", "白骨精", "魅惑", 200));

        return monsters;
    }

    @Override
    public List<Monster> selectMonsterByName(String name) {

        //类似数据库
        List<Monster> monsters = new ArrayList<>();

        monsters.add(new Monster("100", "牛魔王", "芭蕉扇", 400));
        monsters.add(new Monster("200", "白骨精", "魅惑", 200));
        monsters.add(new Monster("300", "黄袍怪", "吹风", 200));
        monsters.add(new Monster("400", "金角大王", "吐金珠", 200));
        monsters.add(new Monster("500", "豹妖", "大铁锤", 200));

        // 查询到的数据
        List<Monster> findMonster = new ArrayList<>();

        //查询
        for (Monster monster : monsters) {

            if (monster.getName().contains(name)) {
                findMonster.add(monster);
            }

        }


        return findMonster;
    }

    @Override
    public boolean monsterLogin(String name) {

        if("白骨精".equals(name)){
            return true ;
        }

        return false;
    }
}

4. springmvc 包

4.1 annotion 包

package com.leon.springmvc_v1.annotation;

import java.lang.annotation.*;

/**
 * ClassName:Autowired
 * Package:com.leon.springmvc.annotation
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/4/22 19:27
 * @Version: 1.0
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
}
======================================================================================
package com.leon.springmvc_v1.annotation;

import java.lang.annotation.*;

/**
 * ClassName:Controller
 * Package:com.leon.springmvc.annotation
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/4/21 21:01
 * @Version: 1.0
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {

    String value() default "";
}
======================================================================================
package com.leon.springmvc_v1.annotation;

import java.lang.annotation.*;

/**
 * ClassName:RequestMapping
 * Package:com.leon.springmvc.annotation
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/4/21 21:01
 * @Version: 1.0
 */

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {

    String value() default "";

}
======================================================================================
package com.leon.springmvc_v1.annotation;

import java.lang.annotation.*;

/**
 * ClassName:RequestParam
 * Package:com.leon.springmvc.annotation
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/4/22 20:34
 * @Version: 1.0
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {

    String value() default "";

}
======================================================================================
package com.leon.springmvc_v1.annotation;

import java.lang.annotation.*;

/**
 * ClassName:ResponseBody
 * Package:com.leon.springmvc.annotation
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/4/23 14:20
 * @Version: 1.0
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {
}
======================================================================================
package com.leon.springmvc_v1.annotation;

import java.lang.annotation.*;

/**
 * ClassName:Service
 * Package:com.leon.springmvc.annotation
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/4/22 13:13
 * @Version: 1.0
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {

    String value() default "";

}

4.2 context 包

package com.leon.springmvc_v1.context;

import com.leon.springmvc_v1.annotation.Autowired;
import com.leon.springmvc_v1.annotation.Controller;
import com.leon.springmvc_v1.annotation.Service;
import com.leon.springmvc_v1.utils.StringUtils;
import com.leon.springmvc_v1.xml.XMLParser;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * ClassName:WebAppliactionContext
 * Package:com.leon.springmvc.context
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/4/22 10:06
 * @Version: 1.0
 */
public class WebApplicationContext {

    //用于存放扫描到的类的全路径
    private Map<String, String> classFullPathMap = new HashMap<>();

    //要扫描的XMl
    private String xmlFileName;

    //用于存放对象
    private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>();

    public WebApplicationContext() {
    }

    public WebApplicationContext(String parameter) {

        //初始化xmlFileName
        xmlFileName = getFileName(parameter);

        //调用初始化方法
        init();

    }

    public void init() {

        List<String> componentScan = XMLParser.getComponentScan(xmlFileName);

        //判断是否有数据
        if (!componentScan.isEmpty()) {

            //遍历集合中的路径
            for (String packagePath : componentScan) {

                //扫描包
                getClassFilePath(packagePath);

            }

        }

        executeInjectionInstance();

        executeAutoWried();


    }


    //扫描指定包下的所有类文件
    public void getClassFilePath(String packagePath) {

        //获取类加载器获取绝对路径
        ClassLoader classLoader =
                WebApplicationContext.class.getClassLoader();

        //将路径中的"."替换成"/"
        String filePath = packagePath.replace(".", "/");

        //获取真实工作目录的绝对路径
        URL workAbsolutePath = classLoader.getResource(filePath);

        //获取目标目录
        File targetFile = new File(workAbsolutePath.getFile());

        //判断目标文件是否是一个目录
        if (targetFile.isDirectory()) {

            //获取目标目录下的所有子文件或者子目录
            File[] files = targetFile.listFiles();

            //循环遍历
            for (File file : files) {

                //判断是否是目录
                if (file.isDirectory()) {

                    //如果是则使用递归将子目录文件遍历出来
                    getClassFilePath(packagePath + "." + file.getName());

                } else {

                    //获取类名
                    String className = file.getName().split("\\.")[0];
                    //获取路径
                    String classFullPath = packagePath + "." + className;

                    //将路径保存
                    classFullPathMap.put(StringUtils.uncapitalize(className), classFullPath);

                }

            }


        }

    }

    //将classFullPathMap中被标注(Controller,Service)的类注入到IOC容器中
    public void executeInjectionInstance() {

        //判断classFullPathMap是否有数据
        if (classFullPathMap.isEmpty()) {

            return;

        }


        //遍历classFullPathMap
        for (String key : classFullPathMap.keySet()) {

            //获取类的全路径
            String classFullPath = classFullPathMap.get(key);

            try {
                //获取class类
                Class<?> beanClass = Class.forName(classFullPath);

                //判断是否有被标注
                if (beanClass.isAnnotationPresent(Controller.class)
                        || beanClass.isAnnotationPresent(Service.class)) {

                    //获取类名
                    String beanName = key;

                    //获取构造器
                    Constructor<?> declaredConstructor =
                            beanClass.getDeclaredConstructor();

                    //创建对象
                    Object instance = declaredConstructor.newInstance();

                    //判断是否是Controller注解
                    if (beanClass.isAnnotationPresent(Controller.class)) {

                        //获取注解对象
                        Controller controller =
                                beanClass.getDeclaredAnnotation(Controller.class);

                        //获取自己指定的名称
                        String assignName = controller.value();

                        //判断是否是默认值
                        if (!"".equals(assignName)) {

                            //将指定的名称赋值给beanName
                            beanName = assignName;

                        }

                        //将对象存放到IOC容器中
                        singletonObjects.put(beanName, instance);

                    }

                    //判断是否是Controller注解
                    if (beanClass.isAnnotationPresent(Service.class)) {

                        //获取注解对象
                        Service service =
                                beanClass.getDeclaredAnnotation(Service.class);

                        //获取自己指定的名称
                        String assignName = service.value();

                        //判断是否是默认值
                        if (!"".equals(assignName)) {

                            //将对象存放到IOC容器中
                            singletonObjects.put(assignName, instance);

                        } else {
                            //保存一个本身类名映射的对象
                            singletonObjects.put(beanName, instance);
                        }

                        //获取接口信息
                        Class<?>[] interfaces = beanClass.getInterfaces();

                        //遍历接口
                        for (Class<?> interfaceClass : interfaces) {

                            //获取接口名称
                            String interfaceClassSimpleName = interfaceClass.getSimpleName();


                            //保存对象
                            singletonObjects.put(StringUtils.uncapitalize(interfaceClassSimpleName), instance);

                        }

                    }


                }

            } catch (Exception e) {
                e.printStackTrace();
            }

        }


    }

    public ConcurrentHashMap<String, Object> getSingletonObjects() {
        return singletonObjects;
    }

    //这是一个解析构造器传值过来的字符串,解析成一个XMl文件
    public String getFileName(String parameter) {

        //将字符串进行分割
        String fileName = parameter.split(":")[1];

        return fileName;
    }

    public void executeAutoWried(){

        //判断容器中是否有数据
        if(singletonObjects.isEmpty()){

            return;

        }

        //遍历容器
        for (Map.Entry<String, Object> entry : singletonObjects.entrySet()) {

            //获取对象
            Object bean = entry.getValue();

            //获取所有字段
            Field[] fields = bean.getClass().getDeclaredFields();

            //遍历所有字段或者属性
            for (Field field : fields) {

                //判断是否被Autowired标注
                if(field.isAnnotationPresent(Autowired.class)){

                    //获取字段类型
                    Class<?> type = field.getType();

                    //获取字段类型名称
                    String typeSimpleName = type.getSimpleName();

                    //获取目标类型的bean对象
                    Object targetObject = singletonObjects.get(StringUtils.uncapitalize(typeSimpleName));

                    //判断是否为空
                    if(targetObject != null){

                        //允许强制访问
                        field.setAccessible(true);

                        try {

                            //进行赋值
                            field.set(bean,targetObject);

                        } catch (IllegalAccessException e) {
                            throw new RuntimeException(e);
                        }

                    }

                }

            }


        }



    }
}

4.3 mapping 包

package com.leon.springmvc_v1.mapping;

import java.lang.reflect.Method;

/**
 * ClassName:Handler
 * Package:com.leon.springmvc.mapping
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/4/22 13:53
 * @Version: 1.0
 */
public class Handler {

    //记录Url
    private String url;

    //记录对应的Controller
    private Object targetObject;

    //记录对应的方法
    private Method targetMethod;


    public Handler() {
    }

    public Handler(String url, Object targetObject, Method targetMethod) {
        this.url = url;
        this.targetObject = targetObject;
        this.targetMethod = targetMethod;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public Object getTargetObject() {
        return targetObject;
    }

    public void setTargetObject(Object targetObject) {
        this.targetObject = targetObject;
    }

    public Method getTargetMethod() {
        return targetMethod;
    }

    public void setTargetMethod(Method targetMethod) {
        this.targetMethod = targetMethod;
    }

    @Override
    public String toString() {
        return "Handler{" +
                "url='" + url + '\'' +
                ", targetObject=" + targetObject +
                ", targetMethod=" + targetMethod +
                '}';
    }

}
======================================================================================
package com.leon.springmvc_v1.mapping;

import com.leon.springmvc_v1.annotation.Controller;
import com.leon.springmvc_v1.annotation.RequestMapping;
import com.leon.springmvc_v1.context.WebApplicationContext;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * ClassName:HandlerMapping
 * Package:com.leon.springmvc.mapping
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/4/22 13:55
 * @Version: 1.0
 */
public class HandlerMapping {

    //用于存放映射关系的集合
    private ConcurrentHashMap<String,Handler> handlerMap = new ConcurrentHashMap<>();

    private WebApplicationContext ioc;

    public HandlerMapping(WebApplicationContext ioc) {

        //初始化IOC
        this.ioc = ioc ;

        //初始化handlerMap
        initHandlerMapping();


    }



    //初始化handlerMap
    public void initHandlerMapping(){

        //获取IOC
        ConcurrentHashMap<String, Object> singletonObjects = ioc.getSingletonObjects();

        //判断是否为空
        if(singletonObjects.isEmpty()){

            return;

        }

        //循环遍历map
        for (Map.Entry<String, Object> entry : singletonObjects.entrySet()) {

            //获取Value值
            Object entryValue = entry.getValue();

            //获取class对象
            Class<?> valueClass = entryValue.getClass();

            //判断是否有Controller注解
            if(valueClass.isAnnotationPresent(Controller.class)){

                //创建url
                String  classUrl = "";

                //判断是否有RequestMapping注解
                if(valueClass.isAnnotationPresent(RequestMapping.class)){

                    //创建RequestMapping对象
                    RequestMapping requestMapping =
                            valueClass.getDeclaredAnnotation(RequestMapping.class);

                    //获取指定的Url值
                    String assignUrl = requestMapping.value();

                    //判断是否指定了
                    if(!"".equals(assignUrl)){
                        classUrl = assignUrl;
                    }

                }

                //获取所有方法
                Method[] methods = valueClass.getMethods();

                //遍历methods
                for (Method method : methods) {


                    //判断是否有RequestMapping注解
                    if(method.isAnnotationPresent(RequestMapping.class)){

                        //创建RequestMapping对象
                        RequestMapping requestMapping =
                                method.getDeclaredAnnotation(RequestMapping.class);

                        //获取指定的Url
                        String assignUrl = requestMapping.value();

                        //判断是否为空
                        if(!"".equals(assignUrl)){

                            String requestUrl = classUrl + assignUrl;

                            //将映射信息存放到handlerMap
                            handlerMap.put(requestUrl,new Handler(requestUrl,entryValue,method));
                        }



                    }


                }

            }

        }

    }


    //用于返回handler对象
    public Handler getHandler(HttpServletRequest request){

        //判断映射关系的集合是否有数据
        if(handlerMap.isEmpty()){
            return null;
        }

        //获取请求的Url
        String requestUrl = request.getRequestURI();

        //获取项目工程路径
        String contextPath = request.getContextPath();

        //循环遍历handlerMap
        for (Map.Entry<String, Handler> entry : handlerMap.entrySet()) {

            //获取的完整的Url
            String temp  = contextPath + entry.getKey();

            //判断key中是否包含Url
            if(temp.equals(requestUrl)){

                //返回handler
                return entry.getValue();
            }

        }

        return  null;
    }


}

4.4 servlet 包

package com.leon.springmvc_v1.servlet;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.leon.springmvc_v1.annotation.RequestParam;
import com.leon.springmvc_v1.annotation.ResponseBody;
import com.leon.springmvc_v1.context.WebApplicationContext;
import com.leon.springmvc_v1.mapping.Handler;
import com.leon.springmvc_v1.mapping.HandlerMapping;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.List;
import java.util.Map;

/**
 * ClassName:DispatcherServlet
 * Package:com.leon.springmvc.servlet
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/4/21 21:03
 * @Version: 1.0
 */
public class DispatcherServlet extends HttpServlet {

    //创建IOC容器对象
    private WebApplicationContext ioc;

    //创建映射器对象
    private HandlerMapping handlerMapping;

    @Override
    public void init(ServletConfig config) throws ServletException {

        //获取配置文件中的IOC文件名称
        String parameter = config.getInitParameter("contextConfigLocation");

        //初始化IOC
        ioc = new WebApplicationContext(parameter);

        //初始化HandlerMapping
        handlerMapping = new HandlerMapping(ioc);


    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        executeDispatcher(req, resp);

    }

    private void executeDispatcher(HttpServletRequest request, HttpServletResponse response) {

        //获取Handler对象
        Handler handler = handlerMapping.getHandler(request);

        //判断是否存在
        if (null == handler) {

            //设置404状态
            response.setStatus(404);

        } else {

            //获取方法对象
            Method targetMethod = handler.getTargetMethod();

            //获取方法的形参列表
            Class<?>[] parameterTypes = targetMethod.getParameterTypes();

            //设置编码格式
            try {
                request.setCharacterEncoding("utf-8");
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException(e);
            }
            //获取请求参数列表
            /*
             *  1. 这里Value是一个数组,是因为你请求的参数可能是一个多选框或者是一个复选框
             * */
            Map<String, String[]> parameterMap = request.getParameterMap();

            //创建一个记录参数位置的数组
            Object[] parameters = new Object[parameterTypes.length];

            //遍历形参列表
            for (int i = 0; i < parameters.length; i++) {

                //判断是否是指定类型的HttpServletRequest参数
                if (parameterTypes[i].isAssignableFrom(HttpServletRequest.class)) {

                    //赋值给记录数组指定位置
                    parameters[i] = request;

                }
                //判断是否是指定类型的HttpServletResponse参数
                if (parameterTypes[i].isAssignableFrom(HttpServletResponse.class)) {
                    //赋值给记录数组指定位置
                    parameters[i] = response;
                }

            }

            //通过获取请求参数给指定形参赋值
            //遍历parameterMap
            for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {

                //获取key值,也就是请求参数名称
                String requestParamName = entry.getKey();

                //获取Value值,也就是请求参数值,这里默认请求参数只有一个值
                String requestParamValue = entry.getValue()[0];

                //获取匹配的下标
                int index = getRequestParamParameterIndex(targetMethod, requestParamName);

                //判断是否存在
                if (index != -1) {

                    //对参数值数组进行赋值
                    parameters[index] = requestParamValue;

                } else {

                    //默认方式赋值
                    int paramIndex = getDefaultParamIndex(targetMethod, requestParamName);

                    //判断是否存在
                    if (paramIndex != -1) {

                        //对参数进行赋值
                        parameters[paramIndex] = requestParamValue;
                    }

                }

            }


            try {

                //执行方法
                Object invoke = targetMethod.invoke(handler.getTargetObject(), parameters);

                viewParser(invoke, targetMethod ,request, response);

            } catch (Exception e) {
                e.printStackTrace();
            }


        }


    }


    //用于返回方法中形参的指定下标
    public int getRequestParamParameterIndex(Method method, String parameterName) {

        //获取方法的形参
        Parameter[] parameters = method.getParameters();

        //遍历形参
        for (int i = 0; i < parameters.length; i++) {

            //判断是否有RequestParam注解
            if (parameters[i].isAnnotationPresent(RequestParam.class)) {

                //获取RequestParam的Value值
                RequestParam param =
                        parameters[i].getDeclaredAnnotation(RequestParam.class);

                //获取Value值
                String paramName = param.value();

                //判断是否为默认值
                if (!"".equals(paramName)) {

                    //判断是否相同
                    if (paramName.equals(parameterName)) {
                        return i;
                    }

                }


            }

        }


        return -1;
    }

    public int getDefaultParamIndex(Method method, String parameterName) {

        //获取参数列表
        Parameter[] parameters = method.getParameters();

        //循环遍历
        for (int i = 0; i < parameters.length; i++) {

            //获取参数的名称
            String paramName = parameters[i].getName();

            //判断是否相同
            if (paramName.equals(parameterName)) {

                //返回指定下标
                return i;
            }

        }


        return -1;

    }


    //视图解析器方法
    public void viewParser(Object returnResult,Method method, HttpServletRequest request, HttpServletResponse response) {

        try {
            //判断是否是字符串
            if (returnResult instanceof String) {

                // 进行强转
                String result = (String) returnResult;

                //进行判断操作
                if (result.contains(":")) {

                    //进行截取
                    String[] requestModeAndPath = result.split(":");

                    //获取请求方式
                    String mode = requestModeAndPath[0];
                    //获取请求路径
                    String path = requestModeAndPath[1];

                    //判断是否是请求转发
                    if ("forward".equals(mode)) {

                        //请求转发
                        request.getRequestDispatcher(path).forward(request, response);
                    }
                    //判断是否是重定向
                    if ("redirect".equals(mode)) {

                        //重定向
                        response.sendRedirect(request.getContextPath() + path);

                    }

                } else {

                    //默认请求转发
                    request.getRequestDispatcher(result).forward(request, response);
                }

            }
            //判断是否是List集合
            if(returnResult instanceof List){

                //判断该方法是否被ResponseBody所标注
                if(method.isAnnotationPresent(ResponseBody.class)){

                    //获取转换对象
                    ObjectMapper objectMapper = new ObjectMapper();
                    //进行转换
                    String json = objectMapper.writeValueAsString(returnResult);

                    //设置编码格式
                    response.setContentType("text/html;charset=UTF-8");

                    //获取输出流
                    PrintWriter writer = response.getWriter();

                    //输出JSON格式字符串
                    writer.write(json);

                    //进行刷新
                    writer.flush();

                    //关闭资源
                    writer.close();

                }

            }

        } catch (ServletException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }


}

4.5 utils 包

package com.leon.springmvc_v1.utils;

/**
 * ClassName:StringUtils
 * Package:com.leon.springmvc.utils
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/4/22 9:51
 * @Version: 1.0
 */
public class StringUtils {


    public static String uncapitalize(String convertTarget){

        //获取第一个字符
        String target = convertTarget.charAt(0) + "";
        //转换成首字母小写
        convertTarget = convertTarget.replace(target,target.toLowerCase());

        return convertTarget;
    }

}

4.6 xml 包

package com.leon.springmvc_v1.xml;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;


/**
 * ClassName:XMLParser
 * Package:com.leon.springmvc.xml
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/4/21 17:50
 * @Version: 1.0
 */
public class XMLParser {

    //用于存放要扫描的包
    private static List<String> packegePath = new ArrayList<>();

    public static List<String> getComponentScan(String fileName){
        //创建一个解析器
        SAXReader reader = new SAXReader();
        //获取真实的工作目录的绝对路径,并获取输入流
        InputStream resourceAsStream = XMLParser.class.getClassLoader().getResourceAsStream(fileName);
        try {
            //读取document树
            Document document = reader.read(resourceAsStream);
            //获取根元素
            Element rootElement = document.getRootElement();
            //获取指定元素
            List<Element> elements = rootElement.elements("componentScan");
            //循环遍历
            for (Element element : elements) {
                //获取属性值
                String value = element.attributeValue("base-package");
                //将属性值放入到集合中
                packegePath.add(value);
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return packegePath;
    }

}

5. 配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<beans>
    <componentScan base-package="com.leon.controller"></componentScan>
    <componentScan base-package="com.leon.service"></componentScan>
</beans>

6. 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>
  <!--配置前端分发器-->
  <servlet>
    <servlet-name>DispatcherServlet</servlet-name>
    <servlet-class>com.leon.springmvc_v1.servlet.DispatcherServlet</servlet-class>
    <!--配置获取IOC文件属性-->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <!--配置加载顺序-->
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>DispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
<!--配置前端控制器-->
<!--  <servlet>-->
<!--    <servlet-name>DispatcherServlet</servlet-name>-->
<!--    <servlet-class>com.leon.springmvc_V2.servlet.DispatcherServlet</servlet-class>-->
<!--    <init-param>-->
<!--      <param-name>contextConfigLocation</param-name>-->
<!--      <param-value>classpath:springmvc.xml</param-value>-->
<!--    </init-param>-->
<!--    <load-on-startup>1</load-on-startup>-->
<!--  </servlet>-->
<!--  <servlet-mapping>-->
<!--    <servlet-name>DispatcherServlet</servlet-name>-->
<!--    <url-pattern>/</url-pattern>-->
<!--  </servlet-mapping>-->
<!--  -->
</web-app>

Comment